blob: 901e9e370e06afbafc00cc0cf9a0115b02b214e8 [file] [log] [blame]
David 'Digit' Turner1f4d9522010-03-02 18:05:23 -08001/*
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 <unistd.h>
20#include <sys/stat.h>
David 'Digit' Turner5792ce72011-08-27 18:48:45 +020021#include <sys/mman.h>
David 'Digit' Turner1f4d9522010-03-02 18:05:23 -080022#include <private/android_filesystem_config.h>
23#include "package.h"
24
25/*
26 * WARNING WARNING WARNING WARNING
27 *
28 * The following code runs as root on production devices, before
29 * the run-as command has dropped the uid/gid. Hence be very
30 * conservative and keep in mind the following:
31 *
32 * - Performance does not matter here, clarity and safety of the code
33 * does however. Documentation is a must.
34 *
35 * - Avoid calling C library functions with complex implementations
36 * like malloc() and printf(). You want to depend on simple system
37 * calls instead, which behaviour is not going to be altered in
38 * unpredictible ways by environment variables or system properties.
39 *
40 * - Do not trust user input and/or the filesystem whenever possible.
41 *
42 */
43
44/* The file containing the list of installed packages on the system */
45#define PACKAGES_LIST_FILE "/data/system/packages.list"
46
David 'Digit' Turner1f4d9522010-03-02 18:05:23 -080047/* Copy 'srclen' string bytes from 'src' into buffer 'dst' of size 'dstlen'
48 * This function always zero-terminate the destination buffer unless
49 * 'dstlen' is 0, even in case of overflow.
Robert Craigfced3de2013-03-26 08:09:09 -040050 * Returns a pointer into the src string, leaving off where the copy
51 * has stopped. The copy will stop when dstlen, srclen or a null
52 * character on src has been reached.
David 'Digit' Turner1f4d9522010-03-02 18:05:23 -080053 */
Robert Craigfced3de2013-03-26 08:09:09 -040054static const char*
David 'Digit' Turner1f4d9522010-03-02 18:05:23 -080055string_copy(char* dst, size_t dstlen, const char* src, size_t srclen)
56{
57 const char* srcend = src + srclen;
58 const char* dstend = dst + dstlen;
59
60 if (dstlen == 0)
Robert Craigfced3de2013-03-26 08:09:09 -040061 return src;
David 'Digit' Turner1f4d9522010-03-02 18:05:23 -080062
63 dstend--; /* make room for terminating zero */
64
65 while (dst < dstend && src < srcend && *src != '\0')
66 *dst++ = *src++;
67
68 *dst = '\0'; /* zero-terminate result */
Robert Craigfced3de2013-03-26 08:09:09 -040069 return src;
David 'Digit' Turner1f4d9522010-03-02 18:05:23 -080070}
71
David 'Digit' Turner5792ce72011-08-27 18:48:45 +020072/* Open 'filename' and map it into our address-space.
73 * Returns buffer address, or NULL on error
74 * On exit, *filesize will be set to the file's size, or 0 on error
David 'Digit' Turner1f4d9522010-03-02 18:05:23 -080075 */
David 'Digit' Turner5792ce72011-08-27 18:48:45 +020076static void*
77map_file(const char* filename, size_t* filesize)
David 'Digit' Turner1f4d9522010-03-02 18:05:23 -080078{
David 'Digit' Turner5792ce72011-08-27 18:48:45 +020079 int fd, ret, old_errno;
80 struct stat st;
81 size_t length = 0;
82 void* address = NULL;
Nick Kralevich080427e2013-02-15 14:39:15 -080083 gid_t oldegid;
David 'Digit' Turner1f4d9522010-03-02 18:05:23 -080084
David 'Digit' Turner5792ce72011-08-27 18:48:45 +020085 *filesize = 0;
David 'Digit' Turner1f4d9522010-03-02 18:05:23 -080086
Nick Kralevich080427e2013-02-15 14:39:15 -080087 /*
88 * Temporarily switch effective GID to allow us to read
89 * the packages file
90 */
91
92 oldegid = getegid();
Alex Klyubin18860c52013-08-20 15:16:31 -070093 if (setegid(AID_PACKAGE_INFO) < 0) {
Nick Kralevich080427e2013-02-15 14:39:15 -080094 return NULL;
95 }
96
David 'Digit' Turner1f4d9522010-03-02 18:05:23 -080097 /* open the file for reading */
David 'Digit' Turner5792ce72011-08-27 18:48:45 +020098 fd = TEMP_FAILURE_RETRY(open(filename, O_RDONLY));
Nick Kralevich080427e2013-02-15 14:39:15 -080099 if (fd < 0) {
David 'Digit' Turner5792ce72011-08-27 18:48:45 +0200100 return NULL;
Nick Kralevich080427e2013-02-15 14:39:15 -0800101 }
102
103 /* restore back to our old egid */
104 if (setegid(oldegid) < 0) {
105 goto EXIT;
106 }
David 'Digit' Turner1f4d9522010-03-02 18:05:23 -0800107
David 'Digit' Turner5792ce72011-08-27 18:48:45 +0200108 /* get its size */
109 ret = TEMP_FAILURE_RETRY(fstat(fd, &st));
110 if (ret < 0)
111 goto EXIT;
David 'Digit' Turner1f4d9522010-03-02 18:05:23 -0800112
Nick Kralevich4ae77162012-02-09 11:22:33 -0800113 /* Ensure that the file is owned by the system user */
Jeff Sharkey977a9f32013-08-12 20:23:49 -0700114 if ((st.st_uid != AID_SYSTEM) || (st.st_gid != AID_PACKAGE_INFO)) {
Nick Kralevich4ae77162012-02-09 11:22:33 -0800115 goto EXIT;
116 }
117
118 /* Ensure that the file has sane permissions */
119 if ((st.st_mode & S_IWOTH) != 0) {
120 goto EXIT;
121 }
122
David 'Digit' Turner5792ce72011-08-27 18:48:45 +0200123 /* Ensure that the size is not ridiculously large */
124 length = (size_t)st.st_size;
125 if ((off_t)length != st.st_size) {
126 errno = ENOMEM;
127 goto EXIT;
128 }
129
130 /* Memory-map the file now */
131 address = TEMP_FAILURE_RETRY(mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0));
132 if (address == MAP_FAILED) {
133 address = NULL;
134 goto EXIT;
135 }
136
137 /* We're good, return size */
138 *filesize = length;
139
140EXIT:
David 'Digit' Turner1f4d9522010-03-02 18:05:23 -0800141 /* close the file, preserve old errno for better diagnostics */
142 old_errno = errno;
143 close(fd);
144 errno = old_errno;
145
David 'Digit' Turner5792ce72011-08-27 18:48:45 +0200146 return address;
147}
148
149/* unmap the file, but preserve errno */
150static void
151unmap_file(void* address, size_t size)
152{
153 int old_errno = errno;
154 TEMP_FAILURE_RETRY(munmap(address, size));
155 errno = old_errno;
David 'Digit' Turner1f4d9522010-03-02 18:05:23 -0800156}
157
158/* Check that a given directory:
159 * - exists
160 * - is owned by a given uid/gid
161 * - is a real directory, not a symlink
162 * - isn't readable or writable by others
163 *
164 * Return 0 on success, or -1 on error.
165 * errno is set to EINVAL in case of failed check.
166 */
167static int
168check_directory_ownership(const char* path, uid_t uid)
169{
170 int ret;
171 struct stat st;
172
173 do {
174 ret = lstat(path, &st);
175 } while (ret < 0 && errno == EINTR);
176
177 if (ret < 0)
178 return -1;
179
180 /* must be a real directory, not a symlink */
181 if (!S_ISDIR(st.st_mode))
182 goto BAD;
183
184 /* must be owned by specific uid/gid */
185 if (st.st_uid != uid || st.st_gid != uid)
186 goto BAD;
187
188 /* must not be readable or writable by others */
189 if ((st.st_mode & (S_IROTH|S_IWOTH)) != 0)
190 goto BAD;
191
192 /* everything ok */
193 return 0;
194
195BAD:
196 errno = EINVAL;
197 return -1;
198}
199
200/* This function is used to check the data directory path for safety.
201 * We check that every sub-directory is owned by the 'system' user
202 * and exists and is not a symlink. We also check that the full directory
203 * path is properly owned by the user ID.
204 *
205 * Return 0 on success, -1 on error.
206 */
207int
208check_data_path(const char* dataPath, uid_t uid)
209{
210 int nn;
211
212 /* the path should be absolute */
213 if (dataPath[0] != '/') {
214 errno = EINVAL;
215 return -1;
216 }
217
218 /* look for all sub-paths, we do that by finding
219 * directory separators in the input path and
220 * checking each sub-path independently
221 */
222 for (nn = 1; dataPath[nn] != '\0'; nn++)
223 {
224 char subpath[PATH_MAX];
225
226 /* skip non-separator characters */
227 if (dataPath[nn] != '/')
228 continue;
229
230 /* handle trailing separator case */
231 if (dataPath[nn+1] == '\0') {
232 break;
233 }
234
235 /* found a separator, check that dataPath is not too long. */
236 if (nn >= (int)(sizeof subpath)) {
237 errno = EINVAL;
238 return -1;
239 }
240
241 /* reject any '..' subpath */
242 if (nn >= 3 &&
243 dataPath[nn-3] == '/' &&
244 dataPath[nn-2] == '.' &&
245 dataPath[nn-1] == '.') {
246 errno = EINVAL;
247 return -1;
248 }
249
250 /* copy to 'subpath', then check ownership */
251 memcpy(subpath, dataPath, nn);
252 subpath[nn] = '\0';
253
254 if (check_directory_ownership(subpath, AID_SYSTEM) < 0)
255 return -1;
256 }
257
258 /* All sub-paths were checked, now verify that the full data
259 * directory is owned by the application uid
260 */
261 if (check_directory_ownership(dataPath, uid) < 0)
262 return -1;
263
264 /* all clear */
265 return 0;
266}
267
268/* Return TRUE iff a character is a space or tab */
269static inline int
270is_space(char c)
271{
272 return (c == ' ' || c == '\t');
273}
274
275/* Skip any space or tab character from 'p' until 'end' is reached.
276 * Return new position.
277 */
278static const char*
279skip_spaces(const char* p, const char* end)
280{
281 while (p < end && is_space(*p))
282 p++;
283
284 return p;
285}
286
287/* Skip any non-space and non-tab character from 'p' until 'end'.
288 * Return new position.
289 */
290static const char*
291skip_non_spaces(const char* p, const char* end)
292{
293 while (p < end && !is_space(*p))
294 p++;
295
296 return p;
297}
298
299/* Find the first occurence of 'ch' between 'p' and 'end'
300 * Return its position, or 'end' if none is found.
301 */
302static const char*
303find_first(const char* p, const char* end, char ch)
304{
305 while (p < end && *p != ch)
306 p++;
307
308 return p;
309}
310
311/* Check that the non-space string starting at 'p' and eventually
312 * ending at 'end' equals 'name'. Return new position (after name)
313 * on success, or NULL on failure.
314 *
315 * This function fails is 'name' is NULL, empty or contains any space.
316 */
317static const char*
318compare_name(const char* p, const char* end, const char* name)
319{
320 /* 'name' must not be NULL or empty */
321 if (name == NULL || name[0] == '\0' || p == end)
322 return NULL;
323
324 /* compare characters to those in 'name', excluding spaces */
325 while (*name) {
326 /* note, we don't check for *p == '\0' since
327 * it will be caught in the next conditional.
328 */
329 if (p >= end || is_space(*p))
330 goto BAD;
331
332 if (*p != *name)
333 goto BAD;
334
335 p++;
336 name++;
337 }
338
339 /* must be followed by end of line or space */
340 if (p < end && !is_space(*p))
341 goto BAD;
342
343 return p;
344
345BAD:
346 return NULL;
347}
348
349/* Parse one or more whitespace characters starting from '*pp'
350 * until 'end' is reached. Updates '*pp' on exit.
351 *
352 * Return 0 on success, -1 on failure.
353 */
354static int
355parse_spaces(const char** pp, const char* end)
356{
357 const char* p = *pp;
358
359 if (p >= end || !is_space(*p)) {
360 errno = EINVAL;
361 return -1;
362 }
363 p = skip_spaces(p, end);
364 *pp = p;
365 return 0;
366}
367
368/* Parse a positive decimal number starting from '*pp' until 'end'
369 * is reached. Adjust '*pp' on exit. Return decimal value or -1
370 * in case of error.
371 *
372 * If the value is larger than INT_MAX, -1 will be returned,
373 * and errno set to EOVERFLOW.
374 *
375 * If '*pp' does not start with a decimal digit, -1 is returned
376 * and errno set to EINVAL.
377 */
378static int
379parse_positive_decimal(const char** pp, const char* end)
380{
381 const char* p = *pp;
382 int value = 0;
383 int overflow = 0;
384
385 if (p >= end || *p < '0' || *p > '9') {
386 errno = EINVAL;
387 return -1;
388 }
389
390 while (p < end) {
391 int ch = *p;
392 unsigned d = (unsigned)(ch - '0');
393 int val2;
394
395 if (d >= 10U) /* d is unsigned, no lower bound check */
396 break;
397
398 val2 = value*10 + (int)d;
399 if (val2 < value)
400 overflow = 1;
401 value = val2;
402 p++;
403 }
404 *pp = p;
405
406 if (overflow) {
407 errno = EOVERFLOW;
408 value = -1;
409 }
410 return value;
411
412BAD:
413 *pp = p;
414 return -1;
415}
416
417/* Read the system's package database and extract information about
418 * 'pkgname'. Return 0 in case of success, or -1 in case of error.
419 *
420 * If the package is unknown, return -1 and set errno to ENOENT
421 * If the package database is corrupted, return -1 and set errno to EINVAL
422 */
423int
424get_package_info(const char* pkgName, PackageInfo *info)
425{
David 'Digit' Turner5792ce72011-08-27 18:48:45 +0200426 char* buffer;
427 size_t buffer_len;
David 'Digit' Turner1f4d9522010-03-02 18:05:23 -0800428 const char* p;
429 const char* buffer_end;
David 'Digit' Turner5792ce72011-08-27 18:48:45 +0200430 int result = -1;
David 'Digit' Turner1f4d9522010-03-02 18:05:23 -0800431
432 info->uid = 0;
433 info->isDebuggable = 0;
434 info->dataDir[0] = '\0';
Robert Craigfced3de2013-03-26 08:09:09 -0400435 info->seinfo[0] = '\0';
David 'Digit' Turner1f4d9522010-03-02 18:05:23 -0800436
David 'Digit' Turner5792ce72011-08-27 18:48:45 +0200437 buffer = map_file(PACKAGES_LIST_FILE, &buffer_len);
438 if (buffer == NULL)
David 'Digit' Turner1f4d9522010-03-02 18:05:23 -0800439 return -1;
440
441 p = buffer;
442 buffer_end = buffer + buffer_len;
443
444 /* expect the following format on each line of the control file:
445 *
Robert Craigfced3de2013-03-26 08:09:09 -0400446 * <pkgName> <uid> <debugFlag> <dataDir> <seinfo>
David 'Digit' Turner1f4d9522010-03-02 18:05:23 -0800447 *
448 * where:
449 * <pkgName> is the package's name
450 * <uid> is the application-specific user Id (decimal)
451 * <debugFlag> is 1 if the package is debuggable, or 0 otherwise
452 * <dataDir> is the path to the package's data directory (e.g. /data/data/com.example.foo)
Robert Craigfced3de2013-03-26 08:09:09 -0400453 * <seinfo> is the seinfo label associated with the package
David 'Digit' Turner1f4d9522010-03-02 18:05:23 -0800454 *
455 * The file is generated in com.android.server.PackageManagerService.Settings.writeLP()
456 */
457
458 while (p < buffer_end) {
459 /* find end of current line and start of next one */
460 const char* end = find_first(p, buffer_end, '\n');
461 const char* next = (end < buffer_end) ? end + 1 : buffer_end;
462 const char* q;
463 int uid, debugFlag;
464
465 /* first field is the package name */
466 p = compare_name(p, end, pkgName);
467 if (p == NULL)
468 goto NEXT_LINE;
469
470 /* skip spaces */
471 if (parse_spaces(&p, end) < 0)
472 goto BAD_FORMAT;
473
474 /* second field is the pid */
475 uid = parse_positive_decimal(&p, end);
476 if (uid < 0)
477 return -1;
478
479 info->uid = (uid_t) uid;
480
481 /* skip spaces */
482 if (parse_spaces(&p, end) < 0)
483 goto BAD_FORMAT;
484
485 /* third field is debug flag (0 or 1) */
486 debugFlag = parse_positive_decimal(&p, end);
487 switch (debugFlag) {
488 case 0:
489 info->isDebuggable = 0;
490 break;
491 case 1:
492 info->isDebuggable = 1;
493 break;
494 default:
495 goto BAD_FORMAT;
496 }
497
498 /* skip spaces */
499 if (parse_spaces(&p, end) < 0)
500 goto BAD_FORMAT;
501
502 /* fourth field is data directory path and must not contain
503 * spaces.
504 */
505 q = skip_non_spaces(p, end);
506 if (q == p)
507 goto BAD_FORMAT;
508
Robert Craigfced3de2013-03-26 08:09:09 -0400509 p = string_copy(info->dataDir, sizeof info->dataDir, p, q - p);
510
511 /* skip spaces */
512 if (parse_spaces(&p, end) < 0)
513 goto BAD_FORMAT;
514
515 /* fifth field is the seinfo string */
516 q = skip_non_spaces(p, end);
517 if (q == p)
518 goto BAD_FORMAT;
519
520 string_copy(info->seinfo, sizeof info->seinfo, p, q - p);
David 'Digit' Turner1f4d9522010-03-02 18:05:23 -0800521
522 /* Ignore the rest */
David 'Digit' Turner5792ce72011-08-27 18:48:45 +0200523 result = 0;
524 goto EXIT;
David 'Digit' Turner1f4d9522010-03-02 18:05:23 -0800525
526 NEXT_LINE:
527 p = next;
528 }
529
530 /* the package is unknown */
531 errno = ENOENT;
David 'Digit' Turner5792ce72011-08-27 18:48:45 +0200532 result = -1;
533 goto EXIT;
David 'Digit' Turner1f4d9522010-03-02 18:05:23 -0800534
535BAD_FORMAT:
536 errno = EINVAL;
David 'Digit' Turner5792ce72011-08-27 18:48:45 +0200537 result = -1;
538
539EXIT:
540 unmap_file(buffer, buffer_len);
541 return result;
David 'Digit' Turner1f4d9522010-03-02 18:05:23 -0800542}