From 63568b1430d741f40ca008391c854ef1cc880138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A5rten=20Kongstad?= Date: Fri, 31 Jan 2014 14:42:59 +0100 Subject: [PATCH] Runtime resource overlay, iteration 2 Support any number of overlay packages. Support any target package. UPDATED PACKAGE MATCHING ------------------------ In Runtime resource overlay, iteration 1, only a single overlay package was considered. Package matching was based on file paths: /vendor/overlay/system/framework-res.apk corresponded to /system/framework-res.apk. Introduce a more flexible matching scheme where any package is an overlay package if its manifest includes For security reasons, an overlay package must fulfill certain criteria to take effect: see below. THE IDMAP TOOL AND IDMAP FILES ------------------------------ Idmap files are created by the 'idmap' binary; idmap files must be present when loading packages. For the Android system, Zygote calls 'idmap' as part of the resource pre-loading. For application packages, 'idmap' is invoked via 'installd' during package installation (similar to 'dexopt'). UPDATED FLOW ------------ The following is an outline of the start-up sequences for the Android system and Android apps. Steps marked with '+' are introduced by this commit. Zygote initialization Initial AssetManager object created + idmap --scan creates idmaps for overlays targeting 'android', \ stores list of overlays in /data/resource-cache/overlays.list AssetManager caches framework-res.apk + AssetManager caches overlay packages listed in overlays.list Android boot New AssetManager's ResTable acquired AssetManager re-uses cached framework-res.apk + AssetManager re-uses cached 'android' overlays (if any) App boot ActivityThread prepares AssetManager to load app.apk + ActivityThread prepares AssetManager to load app overlays (if any) New AssetManager's ResTable acquired as per Android boot SECURITY -------- Overlay packages are required to be pre-loaded (in /vendor/overlay). These packages are trusted by definition. A future iteration of runtime resource overlay may add support for downloaded overlays, which would likely require target and overlay signatures match for the overlay to be trusted. LOOKUP PRIORITY --------------- During resource lookup, packages are sequentially queried to provide a best match, given the constraints of the current configuration. If any package provide a better match than what has been found so far, it replaces the previous match. The target package is always queried last. When loading a package with more than one overlay, the order in which the overlays are added become significant if several packages overlay the same resource. Had downloaded overlays been supported, the install time could have been used to determine the load order. Regardless, for pre-installed overlays, the install time is randomly determined by the order in which the Package Manager locates the packages during initial boot. To support a well-defined order, pre-installed overlay packages are expected to define an additional 'priority' attribute in their tags: Pre-installed overlays are loaded in order of their priority attributes, sorted in ascending order. Assigning the same priority to several overlays targeting the same base package leads to undefined behaviour. It is the responsibility of the vendor to avoid this. The following example shows the ResTable and PackageGroups after loading an application and two overlays. The resource lookup framework will query the packages in the order C, B, A. +------+------+- -+------+------+ | 0x01 | | ... | | 0x7f | +------+------+- -+------+------+ | | "android" Target package A | Pre-installed overlay B (priority 1) | Pre-installed overlay C (priority 2) Change-Id: If49c963149369b1957f7d2303b3dd27f669ed24e --- cmds/installd/commands.c | 128 ++++++++++++++++++++++++++++++++++++--- cmds/installd/installd.c | 7 +++ cmds/installd/installd.h | 4 ++ 3 files changed, 129 insertions(+), 10 deletions(-) diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c index 6fed798e1..e4f63e21f 100644 --- a/cmds/installd/commands.c +++ b/cmds/installd/commands.c @@ -618,14 +618,11 @@ static void run_dex2oat(int zip_fd, int oat_fd, const char* input_file_name, ALOGE("execl(%s) failed: %s\n", DEX2OAT_BIN, strerror(errno)); } -static int wait_dexopt(pid_t pid, const char* apk_path) +static int wait_child(pid_t pid) { int status; pid_t got_pid; - /* - * Wait for the optimization process to finish. - */ while (1) { got_pid = waitpid(pid, &status, 0); if (got_pid == -1 && errno == EINTR) { @@ -641,11 +638,8 @@ static int wait_dexopt(pid_t pid, const char* apk_path) } if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { - ALOGV("DexInv: --- END '%s' (success) ---\n", apk_path); return 0; } else { - ALOGW("DexInv: --- END '%s' --- status=0x%04x, process failed\n", - apk_path, status); return status; /* always nonzero */ } } @@ -747,9 +741,11 @@ int dexopt(const char *apk_path, uid_t uid, int is_public) } exit(68); /* only get here on exec failure */ } else { - res = wait_dexopt(pid, apk_path); - if (res != 0) { - ALOGE("dexopt in='%s' out='%s' res=%d\n", apk_path, out_path, res); + res = wait_child(pid); + if (res == 0) { + ALOGV("DexInv: --- END '%s' (success) ---\n", apk_path); + } else { + ALOGE("DexInv: --- END '%s' --- status=0x%04x, process failed\n", apk_path, res); goto fail; } } @@ -1103,3 +1099,115 @@ out: return rc; } + +static void run_idmap(const char *target_apk, const char *overlay_apk, int idmap_fd) +{ + static const char *IDMAP_BIN = "/system/bin/idmap"; + static const size_t MAX_INT_LEN = 32; + char idmap_str[MAX_INT_LEN]; + + snprintf(idmap_str, sizeof(idmap_str), "%d", idmap_fd); + + execl(IDMAP_BIN, IDMAP_BIN, "--fd", target_apk, overlay_apk, idmap_str, (char*)NULL); + ALOGE("execl(%s) failed: %s\n", IDMAP_BIN, strerror(errno)); +} + +// Transform string /a/b/c.apk to (prefix)/a@b@c.apk@(suffix) +// eg /a/b/c.apk to /data/resource-cache/a@b@c.apk@idmap +static int flatten_path(const char *prefix, const char *suffix, + const char *overlay_path, char *idmap_path, size_t N) +{ + if (overlay_path == NULL || idmap_path == NULL) { + return -1; + } + const size_t len_overlay_path = strlen(overlay_path); + // will access overlay_path + 1 further below; requires absolute path + if (len_overlay_path < 2 || *overlay_path != '/') { + return -1; + } + const size_t len_idmap_root = strlen(prefix); + const size_t len_suffix = strlen(suffix); + if (SIZE_MAX - len_idmap_root < len_overlay_path || + SIZE_MAX - (len_idmap_root + len_overlay_path) < len_suffix) { + // additions below would cause overflow + return -1; + } + if (N < len_idmap_root + len_overlay_path + len_suffix) { + return -1; + } + memset(idmap_path, 0, N); + snprintf(idmap_path, N, "%s%s%s", prefix, overlay_path + 1, suffix); + char *ch = idmap_path + len_idmap_root; + while (*ch != '\0') { + if (*ch == '/') { + *ch = '@'; + } + ++ch; + } + return 0; +} + +int idmap(const char *target_apk, const char *overlay_apk, uid_t uid) +{ + ALOGV("idmap target_apk=%s overlay_apk=%s uid=%d\n", target_apk, overlay_apk, uid); + + int idmap_fd = -1; + char idmap_path[PATH_MAX]; + + if (flatten_path(IDMAP_PREFIX, IDMAP_SUFFIX, overlay_apk, + idmap_path, sizeof(idmap_path)) == -1) { + ALOGE("idmap cannot generate idmap path for overlay %s\n", overlay_apk); + goto fail; + } + + unlink(idmap_path); + idmap_fd = open(idmap_path, O_RDWR | O_CREAT | O_EXCL, 0644); + if (idmap_fd < 0) { + ALOGE("idmap cannot open '%s' for output: %s\n", idmap_path, strerror(errno)); + goto fail; + } + if (fchown(idmap_fd, AID_SYSTEM, uid) < 0) { + ALOGE("idmap cannot chown '%s'\n", idmap_path); + goto fail; + } + if (fchmod(idmap_fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0) { + ALOGE("idmap cannot chmod '%s'\n", idmap_path); + goto fail; + } + + pid_t pid; + pid = fork(); + if (pid == 0) { + /* child -- drop privileges before continuing */ + if (setgid(uid) != 0) { + ALOGE("setgid(%d) failed during idmap\n", uid); + exit(1); + } + if (setuid(uid) != 0) { + ALOGE("setuid(%d) failed during idmap\n", uid); + exit(1); + } + if (flock(idmap_fd, LOCK_EX | LOCK_NB) != 0) { + ALOGE("flock(%s) failed during idmap: %s\n", idmap_path, strerror(errno)); + exit(1); + } + + run_idmap(target_apk, overlay_apk, idmap_fd); + exit(1); /* only if exec call to idmap failed */ + } else { + int status = wait_child(pid); + if (status != 0) { + ALOGE("idmap failed, status=0x%04x\n", status); + goto fail; + } + } + + close(idmap_fd); + return 0; +fail: + if (idmap_fd >= 0) { + close(idmap_fd); + unlink(idmap_path); + } + return -1; +} diff --git a/cmds/installd/installd.c b/cmds/installd/installd.c index 0c80dac3b..9c66f2d72 100644 --- a/cmds/installd/installd.c +++ b/cmds/installd/installd.c @@ -124,6 +124,11 @@ static int do_linklib(char **arg, char reply[REPLY_MAX]) return linklib(arg[0], arg[1], atoi(arg[2])); } +static int do_idmap(char **arg, char reply[REPLY_MAX]) +{ + return idmap(arg[0], arg[1], atoi(arg[2])); +} + struct cmdinfo { const char *name; unsigned numargs; @@ -147,6 +152,7 @@ struct cmdinfo cmds[] = { { "linklib", 3, do_linklib }, { "mkuserdata", 4, do_mk_user_data }, { "rmuser", 1, do_rm_user }, + { "idmap", 3, do_idmap }, }; static int readx(int s, void *_buf, int count) @@ -515,6 +521,7 @@ static void drop_privileges() { capdata[CAP_TO_INDEX(CAP_CHOWN)].permitted |= CAP_TO_MASK(CAP_CHOWN); capdata[CAP_TO_INDEX(CAP_SETUID)].permitted |= CAP_TO_MASK(CAP_SETUID); capdata[CAP_TO_INDEX(CAP_SETGID)].permitted |= CAP_TO_MASK(CAP_SETGID); + capdata[CAP_TO_INDEX(CAP_FOWNER)].permitted |= CAP_TO_MASK(CAP_FOWNER); capdata[0].effective = capdata[0].permitted; capdata[1].effective = capdata[1].permitted; diff --git a/cmds/installd/installd.h b/cmds/installd/installd.h index 9ca2f86ff..cab5cb7ee 100644 --- a/cmds/installd/installd.h +++ b/cmds/installd/installd.h @@ -75,6 +75,9 @@ #define UPDATE_COMMANDS_DIR_PREFIX "/system/etc/updatecmds/" +#define IDMAP_PREFIX "/data/resource-cache/" +#define IDMAP_SUFFIX "@idmap" + #define PKG_NAME_MAX 128 /* largest allowed package name */ #define PKG_PATH_MAX 256 /* max size of any path we use */ @@ -207,3 +210,4 @@ int free_cache(int64_t free_size); int dexopt(const char *apk_path, uid_t uid, int is_public); int movefiles(); int linklib(const char* target, const char* source, int userId); +int idmap(const char *target_path, const char *overlay_path, uid_t uid);