diff options
author | Elliott Hughes | 2016-07-11 15:53:11 -0500 |
---|---|---|
committer | Gerrit Code Review | 2016-07-11 15:53:11 -0500 |
commit | 743b0c5e357023f38672149bc6d7820819f081f6 (patch) | |
tree | b959425bd1a4056db89be6fa0b5490436a813fbf | |
parent | 82d2bcc21e4a9cc347c9c0598e12b2419561db0b (diff) | |
parent | 0c8bf5798f7beedcf7a1781501151d689b88fe98 (diff) | |
download | platform-system-core-743b0c5e357023f38672149bc6d7820819f081f6.tar.gz platform-system-core-743b0c5e357023f38672149bc6d7820819f081f6.tar.xz platform-system-core-743b0c5e357023f38672149bc6d7820819f081f6.zip |
Merge "Switch run-as to libpackagelistparser."
-rw-r--r-- | run-as/Android.mk | 12 | ||||
-rw-r--r-- | run-as/package.c | 556 | ||||
-rw-r--r-- | run-as/package.h | 44 | ||||
-rw-r--r-- | run-as/run-as.c | 224 | ||||
-rw-r--r-- | run-as/run-as.cpp | 246 |
5 files changed, 250 insertions, 832 deletions
diff --git a/run-as/Android.mk b/run-as/Android.mk index 3774acc8f..2e305d7fc 100644 --- a/run-as/Android.mk +++ b/run-as/Android.mk | |||
@@ -1,12 +1,8 @@ | |||
1 | LOCAL_PATH:= $(call my-dir) | 1 | LOCAL_PATH:= $(call my-dir) |
2 | include $(CLEAR_VARS) | ||
3 | |||
4 | LOCAL_SRC_FILES := run-as.c package.c | ||
5 | |||
6 | LOCAL_SHARED_LIBRARIES := libselinux | ||
7 | |||
8 | LOCAL_MODULE := run-as | ||
9 | 2 | ||
3 | include $(CLEAR_VARS) | ||
10 | LOCAL_CFLAGS := -Werror | 4 | LOCAL_CFLAGS := -Werror |
11 | 5 | LOCAL_MODULE := run-as | |
6 | LOCAL_SHARED_LIBRARIES := libselinux libpackagelistparser | ||
7 | LOCAL_SRC_FILES := run-as.cpp | ||
12 | include $(BUILD_EXECUTABLE) | 8 | include $(BUILD_EXECUTABLE) |
diff --git a/run-as/package.c b/run-as/package.c deleted file mode 100644 index aea89e56b..000000000 --- a/run-as/package.c +++ /dev/null | |||
@@ -1,556 +0,0 @@ | |||
1 | /* | ||
2 | ** | ||
3 | ** Copyright 2010, The Android Open Source Project | ||
4 | ** | ||
5 | ** Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | ** you may not use this file except in compliance with the License. | ||
7 | ** You may obtain a copy of the License at | ||
8 | ** | ||
9 | ** http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | ** | ||
11 | ** Unless required by applicable law or agreed to in writing, software | ||
12 | ** distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | ** See the License for the specific language governing permissions and | ||
15 | ** limitations under the License. | ||
16 | */ | ||
17 | #include <errno.h> | ||
18 | #include <fcntl.h> | ||
19 | #include <stdio.h> | ||
20 | #include <string.h> | ||
21 | #include <sys/mman.h> | ||
22 | #include <sys/stat.h> | ||
23 | #include <unistd.h> | ||
24 | |||
25 | #include <private/android_filesystem_config.h> | ||
26 | #include "package.h" | ||
27 | |||
28 | /* | ||
29 | * WARNING WARNING WARNING WARNING | ||
30 | * | ||
31 | * The following code runs as root on production devices, before | ||
32 | * the run-as command has dropped the uid/gid. Hence be very | ||
33 | * conservative and keep in mind the following: | ||
34 | * | ||
35 | * - Performance does not matter here, clarity and safety of the code | ||
36 | * does however. Documentation is a must. | ||
37 | * | ||
38 | * - Avoid calling C library functions with complex implementations | ||
39 | * like malloc() and printf(). You want to depend on simple system | ||
40 | * calls instead, which behaviour is not going to be altered in | ||
41 | * unpredictible ways by environment variables or system properties. | ||
42 | * | ||
43 | * - Do not trust user input and/or the filesystem whenever possible. | ||
44 | * | ||
45 | */ | ||
46 | |||
47 | /* The file containing the list of installed packages on the system */ | ||
48 | #define PACKAGES_LIST_FILE "/data/system/packages.list" | ||
49 | |||
50 | /* Copy 'srclen' string bytes from 'src' into buffer 'dst' of size 'dstlen' | ||
51 | * This function always zero-terminate the destination buffer unless | ||
52 | * 'dstlen' is 0, even in case of overflow. | ||
53 | * Returns a pointer into the src string, leaving off where the copy | ||
54 | * has stopped. The copy will stop when dstlen, srclen or a null | ||
55 | * character on src has been reached. | ||
56 | */ | ||
57 | static const char* | ||
58 | string_copy(char* dst, size_t dstlen, const char* src, size_t srclen) | ||
59 | { | ||
60 | const char* srcend = src + srclen; | ||
61 | const char* dstend = dst + dstlen; | ||
62 | |||
63 | if (dstlen == 0) | ||
64 | return src; | ||
65 | |||
66 | dstend--; /* make room for terminating zero */ | ||
67 | |||
68 | while (dst < dstend && src < srcend && *src != '\0') | ||
69 | *dst++ = *src++; | ||
70 | |||
71 | *dst = '\0'; /* zero-terminate result */ | ||
72 | return src; | ||
73 | } | ||
74 | |||
75 | /* Open 'filename' and map it into our address-space. | ||
76 | * Returns buffer address, or NULL on error | ||
77 | * On exit, *filesize will be set to the file's size, or 0 on error | ||
78 | */ | ||
79 | static void* | ||
80 | map_file(const char* filename, size_t* filesize) | ||
81 | { | ||
82 | int fd, ret, old_errno; | ||
83 | struct stat st; | ||
84 | size_t length = 0; | ||
85 | void* address = NULL; | ||
86 | gid_t oldegid; | ||
87 | |||
88 | *filesize = 0; | ||
89 | |||
90 | /* | ||
91 | * Temporarily switch effective GID to allow us to read | ||
92 | * the packages file | ||
93 | */ | ||
94 | |||
95 | oldegid = getegid(); | ||
96 | if (setegid(AID_PACKAGE_INFO) < 0) { | ||
97 | return NULL; | ||
98 | } | ||
99 | |||
100 | /* open the file for reading */ | ||
101 | fd = TEMP_FAILURE_RETRY(open(filename, O_RDONLY)); | ||
102 | if (fd < 0) { | ||
103 | return NULL; | ||
104 | } | ||
105 | |||
106 | /* restore back to our old egid */ | ||
107 | if (setegid(oldegid) < 0) { | ||
108 | goto EXIT; | ||
109 | } | ||
110 | |||
111 | /* get its size */ | ||
112 | ret = TEMP_FAILURE_RETRY(fstat(fd, &st)); | ||
113 | if (ret < 0) | ||
114 | goto EXIT; | ||
115 | |||
116 | /* Ensure that the file is owned by the system user */ | ||
117 | if ((st.st_uid != AID_SYSTEM) || (st.st_gid != AID_PACKAGE_INFO)) { | ||
118 | goto EXIT; | ||
119 | } | ||
120 | |||
121 | /* Ensure that the file has sane permissions */ | ||
122 | if ((st.st_mode & S_IWOTH) != 0) { | ||
123 | goto EXIT; | ||
124 | } | ||
125 | |||
126 | /* Ensure that the size is not ridiculously large */ | ||
127 | length = (size_t)st.st_size; | ||
128 | if ((off_t)length != st.st_size) { | ||
129 | errno = ENOMEM; | ||
130 | goto EXIT; | ||
131 | } | ||
132 | |||
133 | /* Memory-map the file now */ | ||
134 | do { | ||
135 | address = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0); | ||
136 | } while (address == MAP_FAILED && errno == EINTR); | ||
137 | if (address == MAP_FAILED) { | ||
138 | address = NULL; | ||
139 | goto EXIT; | ||
140 | } | ||
141 | |||
142 | /* We're good, return size */ | ||
143 | *filesize = length; | ||
144 | |||
145 | EXIT: | ||
146 | /* close the file, preserve old errno for better diagnostics */ | ||
147 | old_errno = errno; | ||
148 | close(fd); | ||
149 | errno = old_errno; | ||
150 | |||
151 | return address; | ||
152 | } | ||
153 | |||
154 | /* unmap the file, but preserve errno */ | ||
155 | static void | ||
156 | unmap_file(void* address, size_t size) | ||
157 | { | ||
158 | int old_errno = errno; | ||
159 | TEMP_FAILURE_RETRY(munmap(address, size)); | ||
160 | errno = old_errno; | ||
161 | } | ||
162 | |||
163 | /* Check that a given directory: | ||
164 | * - exists | ||
165 | * - is owned by a given uid/gid | ||
166 | * - is a real directory, not a symlink | ||
167 | * - isn't readable or writable by others | ||
168 | * | ||
169 | * Return 0 on success, or -1 on error. | ||
170 | * errno is set to EINVAL in case of failed check. | ||
171 | */ | ||
172 | static int | ||
173 | check_directory_ownership(const char* path, uid_t uid) | ||
174 | { | ||
175 | int ret; | ||
176 | struct stat st; | ||
177 | |||
178 | do { | ||
179 | ret = lstat(path, &st); | ||
180 | } while (ret < 0 && errno == EINTR); | ||
181 | |||
182 | if (ret < 0) | ||
183 | return -1; | ||
184 | |||
185 | /* must be a real directory, not a symlink */ | ||
186 | if (!S_ISDIR(st.st_mode)) | ||
187 | goto BAD; | ||
188 | |||
189 | /* must be owned by specific uid/gid */ | ||
190 | if (st.st_uid != uid || st.st_gid != uid) | ||
191 | goto BAD; | ||
192 | |||
193 | /* must not be readable or writable by others */ | ||
194 | if ((st.st_mode & (S_IROTH|S_IWOTH)) != 0) | ||
195 | goto BAD; | ||
196 | |||
197 | /* everything ok */ | ||
198 | return 0; | ||
199 | |||
200 | BAD: | ||
201 | errno = EINVAL; | ||
202 | return -1; | ||
203 | } | ||
204 | |||
205 | /* This function is used to check the data directory path for safety. | ||
206 | * We check that every sub-directory is owned by the 'system' user | ||
207 | * and exists and is not a symlink. We also check that the full directory | ||
208 | * path is properly owned by the user ID. | ||
209 | * | ||
210 | * Return 0 on success, -1 on error. | ||
211 | */ | ||
212 | int | ||
213 | check_data_path(const char* dataPath, uid_t uid) | ||
214 | { | ||
215 | int nn; | ||
216 | |||
217 | /* the path should be absolute */ | ||
218 | if (dataPath[0] != '/') { | ||
219 | errno = EINVAL; | ||
220 | return -1; | ||
221 | } | ||
222 | |||
223 | /* look for all sub-paths, we do that by finding | ||
224 | * directory separators in the input path and | ||
225 | * checking each sub-path independently | ||
226 | */ | ||
227 | for (nn = 1; dataPath[nn] != '\0'; nn++) | ||
228 | { | ||
229 | char subpath[PATH_MAX]; | ||
230 | |||
231 | /* skip non-separator characters */ | ||
232 | if (dataPath[nn] != '/') | ||
233 | continue; | ||
234 | |||
235 | /* handle trailing separator case */ | ||
236 | if (dataPath[nn+1] == '\0') { | ||
237 | break; | ||
238 | } | ||
239 | |||
240 | /* found a separator, check that dataPath is not too long. */ | ||
241 | if (nn >= (int)(sizeof subpath)) { | ||
242 | errno = EINVAL; | ||
243 | return -1; | ||
244 | } | ||
245 | |||
246 | /* reject any '..' subpath */ | ||
247 | if (nn >= 3 && | ||
248 | dataPath[nn-3] == '/' && | ||
249 | dataPath[nn-2] == '.' && | ||
250 | dataPath[nn-1] == '.') { | ||
251 | errno = EINVAL; | ||
252 | return -1; | ||
253 | } | ||
254 | |||
255 | /* copy to 'subpath', then check ownership */ | ||
256 | memcpy(subpath, dataPath, nn); | ||
257 | subpath[nn] = '\0'; | ||
258 | |||
259 | if (check_directory_ownership(subpath, AID_SYSTEM) < 0) | ||
260 | return -1; | ||
261 | } | ||
262 | |||
263 | /* All sub-paths were checked, now verify that the full data | ||
264 | * directory is owned by the application uid | ||
265 | */ | ||
266 | if (check_directory_ownership(dataPath, uid) < 0) | ||
267 | return -1; | ||
268 | |||
269 | /* all clear */ | ||
270 | return 0; | ||
271 | } | ||
272 | |||
273 | /* Return TRUE iff a character is a space or tab */ | ||
274 | static inline int | ||
275 | is_space(char c) | ||
276 | { | ||
277 | return (c == ' ' || c == '\t'); | ||
278 | } | ||
279 | |||
280 | /* Skip any space or tab character from 'p' until 'end' is reached. | ||
281 | * Return new position. | ||
282 | */ | ||
283 | static const char* | ||
284 | skip_spaces(const char* p, const char* end) | ||
285 | { | ||
286 | while (p < end && is_space(*p)) | ||
287 | p++; | ||
288 | |||
289 | return p; | ||
290 | } | ||
291 | |||
292 | /* Skip any non-space and non-tab character from 'p' until 'end'. | ||
293 | * Return new position. | ||
294 | */ | ||
295 | static const char* | ||
296 | skip_non_spaces(const char* p, const char* end) | ||
297 | { | ||
298 | while (p < end && !is_space(*p)) | ||
299 | p++; | ||
300 | |||
301 | return p; | ||
302 | } | ||
303 | |||
304 | /* Find the first occurence of 'ch' between 'p' and 'end' | ||
305 | * Return its position, or 'end' if none is found. | ||
306 | */ | ||
307 | static const char* | ||
308 | find_first(const char* p, const char* end, char ch) | ||
309 | { | ||
310 | while (p < end && *p != ch) | ||
311 | p++; | ||
312 | |||
313 | return p; | ||
314 | } | ||
315 | |||
316 | /* Check that the non-space string starting at 'p' and eventually | ||
317 | * ending at 'end' equals 'name'. Return new position (after name) | ||
318 | * on success, or NULL on failure. | ||
319 | * | ||
320 | * This function fails is 'name' is NULL, empty or contains any space. | ||
321 | */ | ||
322 | static const char* | ||
323 | compare_name(const char* p, const char* end, const char* name) | ||
324 | { | ||
325 | /* 'name' must not be NULL or empty */ | ||
326 | if (name == NULL || name[0] == '\0' || p == end) | ||
327 | return NULL; | ||
328 | |||
329 | /* compare characters to those in 'name', excluding spaces */ | ||
330 | while (*name) { | ||
331 | /* note, we don't check for *p == '\0' since | ||
332 | * it will be caught in the next conditional. | ||
333 | */ | ||
334 | if (p >= end || is_space(*p)) | ||
335 | goto BAD; | ||
336 | |||
337 | if (*p != *name) | ||
338 | goto BAD; | ||
339 | |||
340 | p++; | ||
341 | name++; | ||
342 | } | ||
343 | |||
344 | /* must be followed by end of line or space */ | ||
345 | if (p < end && !is_space(*p)) | ||
346 | goto BAD; | ||
347 | |||
348 | return p; | ||
349 | |||
350 | BAD: | ||
351 | return NULL; | ||
352 | } | ||
353 | |||
354 | /* Parse one or more whitespace characters starting from '*pp' | ||
355 | * until 'end' is reached. Updates '*pp' on exit. | ||
356 | * | ||
357 | * Return 0 on success, -1 on failure. | ||
358 | */ | ||
359 | static int | ||
360 | parse_spaces(const char** pp, const char* end) | ||
361 | { | ||
362 | const char* p = *pp; | ||
363 | |||
364 | if (p >= end || !is_space(*p)) { | ||
365 | errno = EINVAL; | ||
366 | return -1; | ||
367 | } | ||
368 | p = skip_spaces(p, end); | ||
369 | *pp = p; | ||
370 | return 0; | ||
371 | } | ||
372 | |||
373 | /* Parse a positive decimal number starting from '*pp' until 'end' | ||
374 | * is reached. Adjust '*pp' on exit. Return decimal value or -1 | ||
375 | * in case of error. | ||
376 | * | ||
377 | * If the value is larger than INT_MAX, -1 will be returned, | ||
378 | * and errno set to EOVERFLOW. | ||
379 | * | ||
380 | * If '*pp' does not start with a decimal digit, -1 is returned | ||
381 | * and errno set to EINVAL. | ||
382 | */ | ||
383 | static int | ||
384 | parse_positive_decimal(const char** pp, const char* end) | ||
385 | { | ||
386 | const char* p = *pp; | ||
387 | int value = 0; | ||
388 | int overflow = 0; | ||
389 | |||
390 | if (p >= end || *p < '0' || *p > '9') { | ||
391 | errno = EINVAL; | ||
392 | return -1; | ||
393 | } | ||
394 | |||
395 | while (p < end) { | ||
396 | int ch = *p; | ||
397 | unsigned d = (unsigned)(ch - '0'); | ||
398 | int val2; | ||
399 | |||
400 | if (d >= 10U) /* d is unsigned, no lower bound check */ | ||
401 | break; | ||
402 | |||
403 | val2 = value*10 + (int)d; | ||
404 | if (val2 < value) | ||
405 | overflow = 1; | ||
406 | value = val2; | ||
407 | p++; | ||
408 | } | ||
409 | *pp = p; | ||
410 | |||
411 | if (overflow) { | ||
412 | errno = EOVERFLOW; | ||
413 | value = -1; | ||
414 | } | ||
415 | return value; | ||
416 | } | ||
417 | |||
418 | /* Read the system's package database and extract information about | ||
419 | * 'pkgname'. Return 0 in case of success, or -1 in case of error. | ||
420 | * | ||
421 | * If the package is unknown, return -1 and set errno to ENOENT | ||
422 | * If the package database is corrupted, return -1 and set errno to EINVAL | ||
423 | */ | ||
424 | int | ||
425 | get_package_info(const char* pkgName, uid_t userId, PackageInfo *info) | ||
426 | { | ||
427 | char* buffer; | ||
428 | size_t buffer_len; | ||
429 | const char* p; | ||
430 | const char* buffer_end; | ||
431 | int result = -1; | ||
432 | |||
433 | info->uid = 0; | ||
434 | info->isDebuggable = 0; | ||
435 | info->dataDir[0] = '\0'; | ||
436 | info->seinfo[0] = '\0'; | ||
437 | |||
438 | buffer = map_file(PACKAGES_LIST_FILE, &buffer_len); | ||
439 | if (buffer == NULL) | ||
440 | return -1; | ||
441 | |||
442 | p = buffer; | ||
443 | buffer_end = buffer + buffer_len; | ||
444 | |||
445 | /* expect the following format on each line of the control file: | ||
446 | * | ||
447 | * <pkgName> <uid> <debugFlag> <dataDir> <seinfo> | ||
448 | * | ||
449 | * where: | ||
450 | * <pkgName> is the package's name | ||
451 | * <uid> is the application-specific user Id (decimal) | ||
452 | * <debugFlag> is 1 if the package is debuggable, or 0 otherwise | ||
453 | * <dataDir> is the path to the package's data directory (e.g. /data/data/com.example.foo) | ||
454 | * <seinfo> is the seinfo label associated with the package | ||
455 | * | ||
456 | * The file is generated in com.android.server.PackageManagerService.Settings.writeLP() | ||
457 | */ | ||
458 | |||
459 | while (p < buffer_end) { | ||
460 | /* find end of current line and start of next one */ | ||
461 | const char* end = find_first(p, buffer_end, '\n'); | ||
462 | const char* next = (end < buffer_end) ? end + 1 : buffer_end; | ||
463 | const char* q; | ||
464 | int uid, debugFlag; | ||
465 | |||
466 | /* first field is the package name */ | ||
467 | p = compare_name(p, end, pkgName); | ||
468 | if (p == NULL) | ||
469 | goto NEXT_LINE; | ||
470 | |||
471 | /* skip spaces */ | ||
472 | if (parse_spaces(&p, end) < 0) | ||
473 | goto BAD_FORMAT; | ||
474 | |||
475 | /* second field is the pid */ | ||
476 | uid = parse_positive_decimal(&p, end); | ||
477 | if (uid < 0) | ||
478 | return -1; | ||
479 | |||
480 | info->uid = (uid_t) uid; | ||
481 | |||
482 | /* skip spaces */ | ||
483 | if (parse_spaces(&p, end) < 0) | ||
484 | goto BAD_FORMAT; | ||
485 | |||
486 | /* third field is debug flag (0 or 1) */ | ||
487 | debugFlag = parse_positive_decimal(&p, end); | ||
488 | switch (debugFlag) { | ||
489 | case 0: | ||
490 | info->isDebuggable = 0; | ||
491 | break; | ||
492 | case 1: | ||
493 | info->isDebuggable = 1; | ||
494 | break; | ||
495 | default: | ||
496 | goto BAD_FORMAT; | ||
497 | } | ||
498 | |||
499 | /* skip spaces */ | ||
500 | if (parse_spaces(&p, end) < 0) | ||
501 | goto BAD_FORMAT; | ||
502 | |||
503 | /* fourth field is data directory path and must not contain | ||
504 | * spaces. | ||
505 | */ | ||
506 | q = skip_non_spaces(p, end); | ||
507 | if (q == p) | ||
508 | goto BAD_FORMAT; | ||
509 | |||
510 | /* If userId == 0 (i.e. user is device owner) we can use dataDir value | ||
511 | * from packages.list, otherwise compose data directory as | ||
512 | * /data/user/$uid/$packageId | ||
513 | */ | ||
514 | if (userId == 0) { | ||
515 | p = string_copy(info->dataDir, sizeof info->dataDir, p, q - p); | ||
516 | } else { | ||
517 | snprintf(info->dataDir, | ||
518 | sizeof info->dataDir, | ||
519 | "/data/user/%d/%s", | ||
520 | userId, | ||
521 | pkgName); | ||
522 | p = q; | ||
523 | } | ||
524 | |||
525 | /* skip spaces */ | ||
526 | if (parse_spaces(&p, end) < 0) | ||
527 | goto BAD_FORMAT; | ||
528 | |||
529 | /* fifth field is the seinfo string */ | ||
530 | q = skip_non_spaces(p, end); | ||
531 | if (q == p) | ||
532 | goto BAD_FORMAT; | ||
533 | |||
534 | string_copy(info->seinfo, sizeof info->seinfo, p, q - p); | ||
535 | |||
536 | /* Ignore the rest */ | ||
537 | result = 0; | ||
538 | goto EXIT; | ||
539 | |||
540 | NEXT_LINE: | ||
541 | p = next; | ||
542 | } | ||
543 | |||
544 | /* the package is unknown */ | ||
545 | errno = ENOENT; | ||
546 | result = -1; | ||
547 | goto EXIT; | ||
548 | |||
549 | BAD_FORMAT: | ||
550 | errno = EINVAL; | ||
551 | result = -1; | ||
552 | |||
553 | EXIT: | ||
554 | unmap_file(buffer, buffer_len); | ||
555 | return result; | ||
556 | } | ||
diff --git a/run-as/package.h b/run-as/package.h deleted file mode 100644 index eeb59137d..000000000 --- a/run-as/package.h +++ /dev/null | |||
@@ -1,44 +0,0 @@ | |||
1 | /* | ||
2 | ** | ||
3 | ** Copyright 2010, The Android Open Source Project | ||
4 | ** | ||
5 | ** Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | ** you may not use this file except in compliance with the License. | ||
7 | ** You may obtain a copy of the License at | ||
8 | ** | ||
9 | ** http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | ** | ||
11 | ** Unless required by applicable law or agreed to in writing, software | ||
12 | ** distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | ** See the License for the specific language governing permissions and | ||
15 | ** limitations under the License. | ||
16 | */ | ||
17 | #ifndef RUN_AS_PACKAGE_H | ||
18 | #define RUN_AS_PACKAGE_H | ||
19 | |||
20 | #include <limits.h> | ||
21 | #include <sys/types.h> | ||
22 | |||
23 | typedef enum { | ||
24 | PACKAGE_IS_DEBUGGABLE = 0, | ||
25 | PACKAGE_IS_NOT_DEBUGGABLE, | ||
26 | PACKAGE_IS_UNKNOWN, | ||
27 | } PackageStatus; | ||
28 | |||
29 | typedef struct { | ||
30 | uid_t uid; | ||
31 | char isDebuggable; | ||
32 | char dataDir[PATH_MAX]; | ||
33 | char seinfo[PATH_MAX]; | ||
34 | } PackageInfo; | ||
35 | |||
36 | /* see documentation in package.c for these functions */ | ||
37 | |||
38 | extern int get_package_info(const char* packageName, | ||
39 | uid_t userId, | ||
40 | PackageInfo* info); | ||
41 | |||
42 | extern int check_data_path(const char* dataDir, uid_t uid); | ||
43 | |||
44 | #endif /* RUN_AS_PACKAGE_H */ | ||
diff --git a/run-as/run-as.c b/run-as/run-as.c deleted file mode 100644 index f0fd2fe7c..000000000 --- a/run-as/run-as.c +++ /dev/null | |||
@@ -1,224 +0,0 @@ | |||
1 | /* | ||
2 | ** | ||
3 | ** Copyright 2010, The Android Open Source Project | ||
4 | ** | ||
5 | ** Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | ** you may not use this file except in compliance with the License. | ||
7 | ** You may obtain a copy of the License at | ||
8 | ** | ||
9 | ** http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | ** | ||
11 | ** Unless required by applicable law or agreed to in writing, software | ||
12 | ** distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | ** See the License for the specific language governing permissions and | ||
15 | ** limitations under the License. | ||
16 | */ | ||
17 | |||
18 | #define PROGNAME "run-as" | ||
19 | #define LOG_TAG PROGNAME | ||
20 | |||
21 | #include <dirent.h> | ||
22 | #include <errno.h> | ||
23 | #include <paths.h> | ||
24 | #include <pwd.h> | ||
25 | #include <stdarg.h> | ||
26 | #include <stdio.h> | ||
27 | #include <stdlib.h> | ||
28 | #include <string.h> | ||
29 | #include <sys/capability.h> | ||
30 | #include <sys/cdefs.h> | ||
31 | #include <sys/stat.h> | ||
32 | #include <sys/types.h> | ||
33 | #include <time.h> | ||
34 | #include <unistd.h> | ||
35 | |||
36 | #include <private/android_filesystem_config.h> | ||
37 | #include <selinux/android.h> | ||
38 | |||
39 | #include "package.h" | ||
40 | |||
41 | /* | ||
42 | * WARNING WARNING WARNING WARNING | ||
43 | * | ||
44 | * This program runs with CAP_SETUID and CAP_SETGID capabilities on Android | ||
45 | * production devices. Be very conservative when modifying it to avoid any | ||
46 | * serious security issue. Keep in mind the following: | ||
47 | * | ||
48 | * - This program should only run for the 'root' or 'shell' users | ||
49 | * | ||
50 | * - Avoid anything that is more complex than simple system calls | ||
51 | * until the uid/gid has been dropped to that of a normal user | ||
52 | * or you are sure to exit. | ||
53 | * | ||
54 | * This avoids depending on environment variables, system properties | ||
55 | * and other external factors that may affect the C library in | ||
56 | * unpredictable ways. | ||
57 | * | ||
58 | * - Do not trust user input and/or the filesystem whenever possible. | ||
59 | * | ||
60 | * Read README.TXT for more details. | ||
61 | * | ||
62 | * | ||
63 | * | ||
64 | * The purpose of this program is to run a command as a specific | ||
65 | * application user-id. Typical usage is: | ||
66 | * | ||
67 | * run-as <package-name> <command> <args> | ||
68 | * | ||
69 | * The 'run-as' binary is installed with CAP_SETUID and CAP_SETGID file | ||
70 | * capabilities, but will check the following: | ||
71 | * | ||
72 | * - that it is invoked from the 'shell' or 'root' user (abort otherwise) | ||
73 | * - that '<package-name>' is the name of an installed and debuggable package | ||
74 | * - that the package's data directory is well-formed (see package.c) | ||
75 | * | ||
76 | * If so, it will drop to the application's user id / group id, cd to the | ||
77 | * package's data directory, then run the command there. | ||
78 | * | ||
79 | * NOTE: In the future it might not be possible to cd to the package's data | ||
80 | * directory under that package's user id / group id, in which case this | ||
81 | * utility will need to be changed accordingly. | ||
82 | * | ||
83 | * This can be useful for a number of different things on production devices: | ||
84 | * | ||
85 | * - Allow application developers to look at their own applicative data | ||
86 | * during development. | ||
87 | * | ||
88 | * - Run the 'gdbserver' binary executable to allow native debugging | ||
89 | */ | ||
90 | |||
91 | __noreturn static void | ||
92 | panic(const char* format, ...) | ||
93 | { | ||
94 | va_list args; | ||
95 | int e = errno; | ||
96 | |||
97 | fprintf(stderr, "%s: ", PROGNAME); | ||
98 | va_start(args, format); | ||
99 | vfprintf(stderr, format, args); | ||
100 | va_end(args); | ||
101 | exit(e ? -e : 1); | ||
102 | } | ||
103 | |||
104 | static void | ||
105 | usage(void) | ||
106 | { | ||
107 | panic("Usage:\n " PROGNAME " <package-name> [--user <uid>] <command> [<args>]\n"); | ||
108 | } | ||
109 | |||
110 | int main(int argc, char **argv) | ||
111 | { | ||
112 | const char* pkgname; | ||
113 | uid_t myuid, uid, gid, userAppId = 0; | ||
114 | int commandArgvOfs = 2, userId = 0; | ||
115 | PackageInfo info; | ||
116 | struct __user_cap_header_struct capheader; | ||
117 | struct __user_cap_data_struct capdata[2]; | ||
118 | |||
119 | /* check arguments */ | ||
120 | if (argc < 2) { | ||
121 | usage(); | ||
122 | } | ||
123 | |||
124 | /* check userid of caller - must be 'shell' or 'root' */ | ||
125 | myuid = getuid(); | ||
126 | if (myuid != AID_SHELL && myuid != AID_ROOT) { | ||
127 | panic("only 'shell' or 'root' users can run this program\n"); | ||
128 | } | ||
129 | |||
130 | memset(&capheader, 0, sizeof(capheader)); | ||
131 | memset(&capdata, 0, sizeof(capdata)); | ||
132 | capheader.version = _LINUX_CAPABILITY_VERSION_3; | ||
133 | capdata[CAP_TO_INDEX(CAP_SETUID)].effective |= CAP_TO_MASK(CAP_SETUID); | ||
134 | capdata[CAP_TO_INDEX(CAP_SETGID)].effective |= CAP_TO_MASK(CAP_SETGID); | ||
135 | capdata[CAP_TO_INDEX(CAP_SETUID)].permitted |= CAP_TO_MASK(CAP_SETUID); | ||
136 | capdata[CAP_TO_INDEX(CAP_SETGID)].permitted |= CAP_TO_MASK(CAP_SETGID); | ||
137 | |||
138 | if (capset(&capheader, &capdata[0]) < 0) { | ||
139 | panic("Could not set capabilities: %s\n", strerror(errno)); | ||
140 | } | ||
141 | |||
142 | pkgname = argv[1]; | ||
143 | |||
144 | /* get user_id from command line if provided */ | ||
145 | if ((argc >= 4) && !strcmp(argv[2], "--user")) { | ||
146 | userId = atoi(argv[3]); | ||
147 | if (userId < 0) | ||
148 | panic("Negative user id %d is provided\n", userId); | ||
149 | commandArgvOfs += 2; | ||
150 | } | ||
151 | |||
152 | /* retrieve package information from system (does setegid) */ | ||
153 | if (get_package_info(pkgname, userId, &info) < 0) { | ||
154 | panic("Package '%s' is unknown\n", pkgname); | ||
155 | } | ||
156 | |||
157 | /* verify that user id is not too big. */ | ||
158 | if ((UID_MAX - info.uid) / AID_USER < (uid_t)userId) { | ||
159 | panic("User id %d is too big\n", userId); | ||
160 | } | ||
161 | |||
162 | /* calculate user app ID. */ | ||
163 | userAppId = (AID_USER * userId) + info.uid; | ||
164 | |||
165 | /* reject system packages */ | ||
166 | if (userAppId < AID_APP) { | ||
167 | panic("Package '%s' is not an application\n", pkgname); | ||
168 | } | ||
169 | |||
170 | /* reject any non-debuggable package */ | ||
171 | if (!info.isDebuggable) { | ||
172 | panic("Package '%s' is not debuggable\n", pkgname); | ||
173 | } | ||
174 | |||
175 | /* check that the data directory path is valid */ | ||
176 | if (check_data_path(info.dataDir, userAppId) < 0) { | ||
177 | panic("Package '%s' has corrupt installation\n", pkgname); | ||
178 | } | ||
179 | |||
180 | /* Ensure that we change all real/effective/saved IDs at the | ||
181 | * same time to avoid nasty surprises. | ||
182 | */ | ||
183 | uid = gid = userAppId; | ||
184 | if(setresgid(gid,gid,gid) || setresuid(uid,uid,uid)) { | ||
185 | panic("Permission denied\n"); | ||
186 | } | ||
187 | |||
188 | /* Required if caller has uid and gid all non-zero */ | ||
189 | memset(&capdata, 0, sizeof(capdata)); | ||
190 | if (capset(&capheader, &capdata[0]) < 0) { | ||
191 | panic("Could not clear all capabilities: %s\n", strerror(errno)); | ||
192 | } | ||
193 | |||
194 | if (selinux_android_setcontext(uid, 0, info.seinfo, pkgname) < 0) { | ||
195 | panic("Could not set SELinux security context: %s\n", strerror(errno)); | ||
196 | } | ||
197 | |||
198 | // cd into the data directory, and set $HOME correspondingly. | ||
199 | if (TEMP_FAILURE_RETRY(chdir(info.dataDir)) < 0) { | ||
200 | panic("Could not cd to package's data directory: %s\n", strerror(errno)); | ||
201 | } | ||
202 | setenv("HOME", info.dataDir, 1); | ||
203 | |||
204 | // Reset parts of the environment, like su would. | ||
205 | setenv("PATH", _PATH_DEFPATH, 1); | ||
206 | unsetenv("IFS"); | ||
207 | |||
208 | // Set the user-specific parts for this user. | ||
209 | struct passwd* pw = getpwuid(uid); | ||
210 | setenv("LOGNAME", pw->pw_name, 1); | ||
211 | setenv("SHELL", pw->pw_shell, 1); | ||
212 | setenv("USER", pw->pw_name, 1); | ||
213 | |||
214 | /* User specified command for exec. */ | ||
215 | if ((argc >= commandArgvOfs + 1) && | ||
216 | (execvp(argv[commandArgvOfs], argv+commandArgvOfs) < 0)) { | ||
217 | panic("exec failed for %s: %s\n", argv[commandArgvOfs], strerror(errno)); | ||
218 | } | ||
219 | |||
220 | /* Default exec shell. */ | ||
221 | execlp("/system/bin/sh", "sh", NULL); | ||
222 | |||
223 | panic("exec failed: %s\n", strerror(errno)); | ||
224 | } | ||
diff --git a/run-as/run-as.cpp b/run-as/run-as.cpp new file mode 100644 index 000000000..50b47b99c --- /dev/null +++ b/run-as/run-as.cpp | |||
@@ -0,0 +1,246 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2010 The Android Open Source Project | ||
3 | * | ||
4 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | * you may not use this file except in compliance with the License. | ||
6 | * You may obtain a copy of the License at | ||
7 | * | ||
8 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | * | ||
10 | * Unless required by applicable law or agreed to in writing, software | ||
11 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | * See the License for the specific language governing permissions and | ||
14 | * limitations under the License. | ||
15 | */ | ||
16 | |||
17 | #include <errno.h> | ||
18 | #include <error.h> | ||
19 | #include <paths.h> | ||
20 | #include <pwd.h> | ||
21 | #include <stdlib.h> | ||
22 | #include <string.h> | ||
23 | #include <sys/capability.h> | ||
24 | #include <sys/stat.h> | ||
25 | #include <sys/types.h> | ||
26 | #include <unistd.h> | ||
27 | |||
28 | #include <packagelistparser/packagelistparser.h> | ||
29 | #include <private/android_filesystem_config.h> | ||
30 | #include <selinux/android.h> | ||
31 | |||
32 | // The purpose of this program is to run a command as a specific | ||
33 | // application user-id. Typical usage is: | ||
34 | // | ||
35 | // run-as <package-name> <command> <args> | ||
36 | // | ||
37 | // The 'run-as' binary is installed with CAP_SETUID and CAP_SETGID file | ||
38 | // capabilities, but will check the following: | ||
39 | // | ||
40 | // - that it is invoked from the 'shell' or 'root' user (abort otherwise) | ||
41 | // - that '<package-name>' is the name of an installed and debuggable package | ||
42 | // - that the package's data directory is well-formed | ||
43 | // | ||
44 | // If so, it will drop to the application's user id / group id, cd to the | ||
45 | // package's data directory, then run the command there. | ||
46 | // | ||
47 | // This can be useful for a number of different things on production devices: | ||
48 | // | ||
49 | // - Allow application developers to look at their own application data | ||
50 | // during development. | ||
51 | // | ||
52 | // - Run the 'gdbserver' binary executable to allow native debugging | ||
53 | // | ||
54 | |||
55 | static bool packagelist_parse_callback(pkg_info* this_package, void* userdata) { | ||
56 | pkg_info* p = reinterpret_cast<pkg_info*>(userdata); | ||
57 | if (strcmp(p->name, this_package->name) == 0) { | ||
58 | *p = *this_package; | ||
59 | return false; // Stop searching. | ||
60 | } | ||
61 | packagelist_free(this_package); | ||
62 | return true; // Keep searching. | ||
63 | } | ||
64 | |||
65 | static bool check_directory(const char* path, uid_t uid) { | ||
66 | struct stat st; | ||
67 | if (TEMP_FAILURE_RETRY(lstat(path, &st)) == -1) return false; | ||
68 | |||
69 | // /data/user/0 is a known safe symlink. | ||
70 | if (strcmp("/data/user/0", path) == 0) return true; | ||
71 | |||
72 | // Must be a real directory, not a symlink. | ||
73 | if (!S_ISDIR(st.st_mode)) return false; | ||
74 | |||
75 | // Must be owned by specific uid/gid. | ||
76 | if (st.st_uid != uid || st.st_gid != uid) return false; | ||
77 | |||
78 | // Must not be readable or writable by others. | ||
79 | if ((st.st_mode & (S_IROTH|S_IWOTH)) != 0) return false; | ||
80 | |||
81 | return true; | ||
82 | } | ||
83 | |||
84 | // This function is used to check the data directory path for safety. | ||
85 | // We check that every sub-directory is owned by the 'system' user | ||
86 | // and exists and is not a symlink. We also check that the full directory | ||
87 | // path is properly owned by the user ID. | ||
88 | static bool check_data_path(const char* data_path, uid_t uid) { | ||
89 | // The path should be absolute. | ||
90 | if (data_path[0] != '/') return false; | ||
91 | |||
92 | // Look for all sub-paths, we do that by finding | ||
93 | // directory separators in the input path and | ||
94 | // checking each sub-path independently. | ||
95 | for (int nn = 1; data_path[nn] != '\0'; nn++) { | ||
96 | char subpath[PATH_MAX]; | ||
97 | |||
98 | /* skip non-separator characters */ | ||
99 | if (data_path[nn] != '/') continue; | ||
100 | |||
101 | /* handle trailing separator case */ | ||
102 | if (data_path[nn+1] == '\0') break; | ||
103 | |||
104 | /* found a separator, check that data_path is not too long. */ | ||
105 | if (nn >= (int)(sizeof subpath)) return false; | ||
106 | |||
107 | /* reject any '..' subpath */ | ||
108 | if (nn >= 3 && | ||
109 | data_path[nn-3] == '/' && | ||
110 | data_path[nn-2] == '.' && | ||
111 | data_path[nn-1] == '.') { | ||
112 | return false; | ||
113 | } | ||
114 | |||
115 | /* copy to 'subpath', then check ownership */ | ||
116 | memcpy(subpath, data_path, nn); | ||
117 | subpath[nn] = '\0'; | ||
118 | |||
119 | if (!check_directory(subpath, AID_SYSTEM)) return false; | ||
120 | } | ||
121 | |||
122 | // All sub-paths were checked, now verify that the full data | ||
123 | // directory is owned by the application uid. | ||
124 | return check_directory(data_path, uid); | ||
125 | } | ||
126 | |||
127 | int main(int argc, char* argv[]) { | ||
128 | // Check arguments. | ||
129 | if (argc < 2) { | ||
130 | error(1, 0, "usage: run-as <package-name> [--user <uid>] <command> [<args>]\n"); | ||
131 | } | ||
132 | |||
133 | // This program runs with CAP_SETUID and CAP_SETGID capabilities on Android | ||
134 | // production devices. Check user id of caller --- must be 'shell' or 'root'. | ||
135 | if (getuid() != AID_SHELL && getuid() != AID_ROOT) { | ||
136 | error(1, 0, "only 'shell' or 'root' users can run this program"); | ||
137 | } | ||
138 | |||
139 | __user_cap_header_struct capheader; | ||
140 | __user_cap_data_struct capdata[2]; | ||
141 | memset(&capheader, 0, sizeof(capheader)); | ||
142 | memset(&capdata, 0, sizeof(capdata)); | ||
143 | capheader.version = _LINUX_CAPABILITY_VERSION_3; | ||
144 | capdata[CAP_TO_INDEX(CAP_SETUID)].effective |= CAP_TO_MASK(CAP_SETUID); | ||
145 | capdata[CAP_TO_INDEX(CAP_SETGID)].effective |= CAP_TO_MASK(CAP_SETGID); | ||
146 | capdata[CAP_TO_INDEX(CAP_SETUID)].permitted |= CAP_TO_MASK(CAP_SETUID); | ||
147 | capdata[CAP_TO_INDEX(CAP_SETGID)].permitted |= CAP_TO_MASK(CAP_SETGID); | ||
148 | if (capset(&capheader, &capdata[0]) == -1) { | ||
149 | error(1, errno, "couldn't set capabilities"); | ||
150 | } | ||
151 | |||
152 | char* pkgname = argv[1]; | ||
153 | int cmd_argv_offset = 2; | ||
154 | |||
155 | // Get user_id from command line if provided. | ||
156 | int userId = 0; | ||
157 | if ((argc >= 4) && !strcmp(argv[2], "--user")) { | ||
158 | userId = atoi(argv[3]); | ||
159 | if (userId < 0) error(1, 0, "negative user id: %d", userId); | ||
160 | cmd_argv_offset += 2; | ||
161 | } | ||
162 | |||
163 | // Retrieve package information from system, switching egid so we can read the file. | ||
164 | gid_t old_egid = getegid(); | ||
165 | if (setegid(AID_PACKAGE_INFO) == -1) error(1, errno, "setegid(AID_PACKAGE_INFO) failed"); | ||
166 | pkg_info info; | ||
167 | memset(&info, 0, sizeof(info)); | ||
168 | info.name = pkgname; | ||
169 | if (!packagelist_parse(packagelist_parse_callback, &info)) { | ||
170 | error(1, errno, "packagelist_parse failed"); | ||
171 | } | ||
172 | if (info.uid == 0) { | ||
173 | error(1, 0, "unknown package: %s", pkgname); | ||
174 | } | ||
175 | if (setegid(old_egid) == -1) error(1, errno, "couldn't restore egid"); | ||
176 | |||
177 | // Verify that user id is not too big. | ||
178 | if ((UID_MAX - info.uid) / AID_USER < (uid_t)userId) { | ||
179 | error(1, 0, "user id too big: %d", userId); | ||
180 | } | ||
181 | |||
182 | // Calculate user app ID. | ||
183 | uid_t userAppId = (AID_USER * userId) + info.uid; | ||
184 | |||
185 | // Reject system packages. | ||
186 | if (userAppId < AID_APP) { | ||
187 | error(1, 0, "package not an application: %s", pkgname); | ||
188 | } | ||
189 | |||
190 | // Reject any non-debuggable package. | ||
191 | if (!info.debuggable) { | ||
192 | error(1, 0, "package not debuggable: %s", pkgname); | ||
193 | } | ||
194 | |||
195 | // Check that the data directory path is valid. | ||
196 | if (!check_data_path(info.data_dir, userAppId)) { | ||
197 | error(1, 0, "package has corrupt installation: %s", pkgname); | ||
198 | } | ||
199 | |||
200 | // Ensure that we change all real/effective/saved IDs at the | ||
201 | // same time to avoid nasty surprises. | ||
202 | uid_t uid = userAppId; | ||
203 | uid_t gid = userAppId; | ||
204 | if (setresgid(gid, gid, gid) == -1) { | ||
205 | error(1, errno, "setresgid failed"); | ||
206 | } | ||
207 | if (setresuid(uid, uid, uid) == -1) { | ||
208 | error(1, errno, "setresuid failed"); | ||
209 | } | ||
210 | |||
211 | // Required if caller has uid and gid all non-zero. | ||
212 | memset(&capdata, 0, sizeof(capdata)); | ||
213 | if (capset(&capheader, &capdata[0]) == -1) { | ||
214 | error(1, errno, "couldn't clear all capabilities"); | ||
215 | } | ||
216 | |||
217 | if (selinux_android_setcontext(uid, 0, info.seinfo, pkgname) < 0) { | ||
218 | error(1, errno, "couldn't set SELinux security context"); | ||
219 | } | ||
220 | |||
221 | // cd into the data directory, and set $HOME correspondingly. | ||
222 | if (TEMP_FAILURE_RETRY(chdir(info.data_dir)) == -1) { | ||
223 | error(1, errno, "couldn't chdir to package's data directory"); | ||
224 | } | ||
225 | setenv("HOME", info.data_dir, 1); | ||
226 | |||
227 | // Reset parts of the environment, like su would. | ||
228 | setenv("PATH", _PATH_DEFPATH, 1); | ||
229 | unsetenv("IFS"); | ||
230 | |||
231 | // Set the user-specific parts for this user. | ||
232 | passwd* pw = getpwuid(uid); | ||
233 | setenv("LOGNAME", pw->pw_name, 1); | ||
234 | setenv("SHELL", pw->pw_shell, 1); | ||
235 | setenv("USER", pw->pw_name, 1); | ||
236 | |||
237 | // User specified command for exec. | ||
238 | if ((argc >= cmd_argv_offset + 1) && | ||
239 | (execvp(argv[cmd_argv_offset], argv+cmd_argv_offset) == -1)) { | ||
240 | error(1, errno, "exec failed for %s", argv[cmd_argv_offset]); | ||
241 | } | ||
242 | |||
243 | // Default exec shell. | ||
244 | execlp(_PATH_BSHELL, "sh", NULL); | ||
245 | error(1, errno, "exec failed"); | ||
246 | } | ||