diff options
author | 2011-03-31 13:16:12 -0700 | |
---|---|---|
committer | 2011-04-05 10:45:35 -0700 | |
commit | 86c9584559439504fc57ece2ccd9b6cbd568430c (patch) | |
tree | 8c9cc83b3a980394b7cbe23dfd3c44fcc189b6b4 | |
parent | 7f5a026d255fdcbd600a01b4abbd87eb0b528e37 (diff) |
Update installd to allow multiuser
* Add ability to select different personas to generate the path to be
created.
* Move hardcoded paths to read from init's set environment.
* Add unit tests for all the utility functions that build strings to
make sure they're correct.
* Fill in persona with "0" all the time now. Will be plumbed through in
later CL.
Change-Id: I0a7f6e3640cb6b052f8823080886ee79e90b679f
-rw-r--r-- | cmds/installd/Android.mk | 27 | ||||
-rw-r--r-- | cmds/installd/commands.c | 104 | ||||
-rw-r--r-- | cmds/installd/installd.c | 68 | ||||
-rw-r--r-- | cmds/installd/installd.h | 55 | ||||
-rw-r--r-- | cmds/installd/tests/Android.mk | 42 | ||||
-rw-r--r-- | cmds/installd/tests/installd_utils_test.cpp | 383 | ||||
-rw-r--r-- | cmds/installd/utils.c | 259 |
7 files changed, 850 insertions, 88 deletions
diff --git a/cmds/installd/Android.mk b/cmds/installd/Android.mk index 8641c3090a6c..d7a9ef67df05 100644 --- a/cmds/installd/Android.mk +++ b/cmds/installd/Android.mk @@ -1,13 +1,34 @@ ifneq ($(TARGET_SIMULATOR),true) LOCAL_PATH := $(call my-dir) + +common_src_files := \ + commands.c utils.c + +# +# Static library used in testing and executable +# + include $(CLEAR_VARS) LOCAL_SRC_FILES := \ - installd.c commands.c utils.c + $(common_src_files) + +LOCAL_MODULE := libinstalld + +LOCAL_MODULE_TAGS := eng tests -#LOCAL_C_INCLUDES := \ -# $(call include-path-for, system-core)/cutils +include $(BUILD_STATIC_LIBRARY) + +# +# Executable +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + installd.c \ + $(common_src_files) LOCAL_SHARED_LIBRARIES := \ libcutils diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c index 4d49c30501f5..80ba1e986dca 100644 --- a/cmds/installd/commands.c +++ b/cmds/installd/commands.c @@ -17,6 +17,13 @@ #include "installd.h" #include <diskusage/dirsize.h> +/* Directory records that are used in execution of commands. */ +dir_rec_t android_data_dir; +dir_rec_t android_asec_dir; +dir_rec_t android_app_dir; +dir_rec_t android_app_private_dir; +dir_rec_array_t android_system_dirs; + int install(const char *pkgname, uid_t uid, gid_t gid) { char pkgdir[PKG_PATH_MAX]; @@ -27,10 +34,15 @@ int install(const char *pkgname, uid_t uid, gid_t gid) return -1; } - if (create_pkg_path(pkgdir, PKG_DIR_PREFIX, pkgname, PKG_DIR_POSTFIX)) + if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, 0)) { + LOGE("cannot create package path\n"); return -1; - if (create_pkg_path(libdir, PKG_LIB_PREFIX, pkgname, PKG_LIB_POSTFIX)) + } + + if (create_pkg_path(libdir, pkgname, PKG_LIB_POSTFIX, 0)) { + LOGE("cannot create package lib path\n"); return -1; + } if (mkdir(pkgdir, 0751) < 0) { LOGE("cannot create dir '%s': %s\n", pkgdir, strerror(errno)); @@ -59,7 +71,7 @@ int uninstall(const char *pkgname) { char pkgdir[PKG_PATH_MAX]; - if (create_pkg_path(pkgdir, PKG_DIR_PREFIX, pkgname, PKG_DIR_POSTFIX)) + if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, 0)) return -1; /* delete contents AND directory, no exceptions */ @@ -71,9 +83,9 @@ int renamepkg(const char *oldpkgname, const char *newpkgname) char oldpkgdir[PKG_PATH_MAX]; char newpkgdir[PKG_PATH_MAX]; - if (create_pkg_path(oldpkgdir, PKG_DIR_PREFIX, oldpkgname, PKG_DIR_POSTFIX)) + if (create_pkg_path(oldpkgdir, oldpkgname, PKG_DIR_POSTFIX, 0)) return -1; - if (create_pkg_path(newpkgdir, PKG_DIR_PREFIX, newpkgname, PKG_DIR_POSTFIX)) + if (create_pkg_path(newpkgdir, newpkgname, PKG_DIR_POSTFIX, 0)) return -1; if (rename(oldpkgdir, newpkgdir) < 0) { @@ -87,7 +99,7 @@ int delete_user_data(const char *pkgname) { char pkgdir[PKG_PATH_MAX]; - if (create_pkg_path(pkgdir, PKG_DIR_PREFIX, pkgname, PKG_DIR_POSTFIX)) + if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, 0)) return -1; /* delete contents, excluding "lib", but not the directory itself */ @@ -98,7 +110,7 @@ int delete_cache(const char *pkgname) { char cachedir[PKG_PATH_MAX]; - if (create_pkg_path(cachedir, CACHE_DIR_PREFIX, pkgname, CACHE_DIR_POSTFIX)) + if (create_pkg_path(cachedir, pkgname, CACHE_DIR_POSTFIX, 0)) return -1; /* delete contents, not the directory, no exceptions */ @@ -108,10 +120,10 @@ int delete_cache(const char *pkgname) static int64_t disk_free() { struct statfs sfs; - if (statfs(PKG_DIR_PREFIX, &sfs) == 0) { + if (statfs(android_data_dir.path, &sfs) == 0) { return sfs.f_bavail * sfs.f_bsize; } else { - LOGE("Couldn't statfs " PKG_DIR_PREFIX ": %s\n", strerror(errno)); + LOGE("Couldn't statfs %s: %s\n", android_data_dir.path, strerror(errno)); return -1; } } @@ -137,9 +149,9 @@ int free_cache(int64_t free_size) LOGI("free_cache(%" PRId64 ") avail %" PRId64 "\n", free_size, avail); if (avail >= free_size) return 0; - d = opendir(PKG_DIR_PREFIX); + d = opendir(android_data_dir.path); if (d == NULL) { - LOGE("cannot open %s: %s\n", PKG_DIR_PREFIX, strerror(errno)); + LOGE("cannot open %s: %s\n", android_data_dir.path, strerror(errno)); return -1; } dfd = dirfd(d); @@ -172,43 +184,13 @@ int free_cache(int64_t free_size) return -1; } -/* used by move_dex, rm_dex, etc to ensure that the provided paths - * don't point anywhere other than at the APK_DIR_PREFIX - */ -static int is_valid_apk_path(const char *path) -{ - int len = strlen(APK_DIR_PREFIX); -int nosubdircheck = 0; - if (strncmp(path, APK_DIR_PREFIX, len)) { - len = strlen(PROTECTED_DIR_PREFIX); - if (strncmp(path, PROTECTED_DIR_PREFIX, len)) { - len = strlen(SDCARD_DIR_PREFIX); - if (strncmp(path, SDCARD_DIR_PREFIX, len)) { - LOGE("invalid apk path '%s' (bad prefix)\n", path); - return 0; - } else { - nosubdircheck = 1; - } - } - } - if ((nosubdircheck != 1) && strchr(path + len, '/')) { - LOGE("invalid apk path '%s' (subdir?)\n", path); - return 0; - } - if (path[len] == '.') { - LOGE("invalid apk path '%s' (trickery)\n", path); - return 0; - } - return 1; -} - int move_dex(const char *src, const char *dst) { char src_dex[PKG_PATH_MAX]; char dst_dex[PKG_PATH_MAX]; - if (!is_valid_apk_path(src)) return -1; - if (!is_valid_apk_path(dst)) return -1; + if (validate_apk_path(src)) return -1; + if (validate_apk_path(dst)) return -1; if (create_cache_path(src_dex, src)) return -1; if (create_cache_path(dst_dex, dst)) return -1; @@ -226,7 +208,7 @@ int rm_dex(const char *path) { char dex_path[PKG_PATH_MAX]; - if (!is_valid_apk_path(path)) return -1; + if (validate_apk_path(path)) return -1; if (create_cache_path(dex_path, path)) return -1; LOGI("unlink %s\n", dex_path); @@ -245,7 +227,7 @@ int protect(char *pkgname, gid_t gid) if (gid < AID_SYSTEM) return -1; - if (create_pkg_path(pkgpath, PROTECTED_DIR_PREFIX, pkgname, ".apk")) + if (create_pkg_path_in_dir(pkgpath, &android_app_private_dir, pkgname, ".apk")) return -1; if (stat(pkgpath, &s) < 0) return -1; @@ -280,8 +262,8 @@ int get_size(const char *pkgname, const char *apkpath, /* count the source apk as code -- but only if it's not * on the /system partition and its not on the sdcard. */ - if (strncmp(apkpath, "/system", 7) != 0 && - strncmp(apkpath, SDCARD_DIR_PREFIX, 7) != 0) { + if (validate_system_app_path(apkpath) && + strncmp(apkpath, android_asec_dir.path, android_asec_dir.len) != 0) { if (stat(apkpath, &s) == 0) { codesize += stat_size(&s); } @@ -300,7 +282,7 @@ int get_size(const char *pkgname, const char *apkpath, } } - if (create_pkg_path(path, PKG_DIR_PREFIX, pkgname, PKG_DIR_POSTFIX)) { + if (create_pkg_path(path, pkgname, PKG_DIR_POSTFIX, 0)) { goto done; } @@ -310,10 +292,10 @@ int get_size(const char *pkgname, const char *apkpath, } dfd = dirfd(d); - /* most stuff in the pkgdir is data, except for the "cache" - * directory and below, which is cache, and the "lib" directory - * and below, which is code... - */ + /* most stuff in the pkgdir is data, except for the "cache" + * directory and below, which is cache, and the "lib" directory + * and below, which is code... + */ while ((de = readdir(d))) { const char *name = de->d_name; @@ -544,15 +526,15 @@ fail: } int create_move_path(char path[PKG_PATH_MAX], - const char* prefix, const char* pkgname, - const char* leaf) + const char* leaf, + uid_t persona) { - if ((strlen(prefix) + strlen(pkgname) + strlen(leaf) + 1) >= PKG_PATH_MAX) { + if ((android_data_dir.len + strlen(pkgname) + strlen(leaf) + 1) >= PKG_PATH_MAX) { return -1; } - sprintf(path, "%s%s/%s", prefix, pkgname, leaf); + sprintf(path, "%s%s%s/%s", android_data_dir.path, PRIMARY_USER_PREFIX, pkgname, leaf); return 0; } @@ -720,8 +702,8 @@ int movefiles() // Skip -- source package no longer exists. } else { LOGV("Move file: %s (from %s to %s)\n", buf+bufp, srcpkg, dstpkg); - if (!create_move_path(srcpath, PKG_DIR_PREFIX, srcpkg, buf+bufp) && - !create_move_path(dstpath, PKG_DIR_PREFIX, dstpkg, buf+bufp)) { + if (!create_move_path(srcpath, srcpkg, buf+bufp, 0) && + !create_move_path(dstpath, dstpkg, buf+bufp, 0)) { movefileordir(srcpath, dstpath, strlen(dstpath)-strlen(buf+bufp), dstuid, dstgid, &s); @@ -750,8 +732,7 @@ int movefiles() UPDATE_COMMANDS_DIR_PREFIX, name, div); } if (srcpkg[0] != 0) { - if (!create_pkg_path(srcpath, PKG_DIR_PREFIX, srcpkg, - PKG_DIR_POSTFIX)) { + if (!create_pkg_path(srcpath, srcpkg, PKG_DIR_POSTFIX, 0)) { if (lstat(srcpath, &s) < 0) { // Package no longer exists -- skip. srcpkg[0] = 0; @@ -762,8 +743,7 @@ int movefiles() div, UPDATE_COMMANDS_DIR_PREFIX, name); } if (srcpkg[0] != 0) { - if (!create_pkg_path(dstpath, PKG_DIR_PREFIX, dstpkg, - PKG_DIR_POSTFIX)) { + if (!create_pkg_path(dstpath, dstpkg, PKG_DIR_POSTFIX, 0)) { if (lstat(dstpath, &s) == 0) { dstuid = s.st_uid; dstgid = s.st_gid; diff --git a/cmds/installd/installd.c b/cmds/installd/installd.c index d2b2f7f9fc60..e0d0f97b3094 100644 --- a/cmds/installd/installd.c +++ b/cmds/installd/installd.c @@ -21,7 +21,6 @@ #define TOKEN_MAX 8 /* max number of arguments in buffer */ #define REPLY_MAX 256 /* largest reply allowed */ - static int do_ping(char **arg, char reply[REPLY_MAX]) { return 0; @@ -235,12 +234,77 @@ done: return 0; } -int main(const int argc, const char *argv[]) { +/** + * Initialize all the global variables that are used elsewhere. Returns 0 upon + * success and -1 on error. + */ +void free_globals() { + size_t i; + + for (i = 0; i < android_system_dirs.count; i++) { + if (android_system_dirs.dirs[i].path != NULL) { + free(android_system_dirs.dirs[i].path); + } + } + + free(android_system_dirs.dirs); +} + +int initialize_globals() { + // Get the android data directory. + if (get_path_from_env(&android_data_dir, "ANDROID_DATA") < 0) { + return -1; + } + + // Get the android app directory. + if (copy_and_append(&android_app_dir, &android_data_dir, APP_SUBDIR) < 0) { + return -1; + } + + // Get the android protected app directory. + if (copy_and_append(&android_app_private_dir, &android_data_dir, PRIVATE_APP_SUBDIR) < 0) { + return -1; + } + + // Get the sd-card ASEC mount point. + if (get_path_from_env(&android_asec_dir, "ASEC_MOUNTPOINT") < 0) { + return -1; + } + + // Take note of the system and vendor directories. + android_system_dirs.count = 2; + + android_system_dirs.dirs = calloc(android_system_dirs.count, sizeof(dir_rec_t)); + if (android_system_dirs.dirs == NULL) { + LOGE("Couldn't allocate array for dirs; aborting\n"); + return -1; + } + + // system + if (get_path_from_env(&android_system_dirs.dirs[0], "ANDROID_ROOT") < 0) { + free_globals(); + return -1; + } + + // vendor + // TODO replace this with an environment variable (doesn't exist yet) + android_system_dirs.dirs[1].path = "/vendor/"; + android_system_dirs.dirs[1].len = strlen(android_system_dirs.dirs[1].path); + + return 0; +} + +int main(const int argc, const char *argv[]) { char buf[BUFFER_MAX]; struct sockaddr addr; socklen_t alen; int lsocket, s, count; + if (initialize_globals() < 0) { + LOGE("Could not initialize globals; exiting.\n"); + exit(1); + } + lsocket = android_get_control_socket(SOCKET_PATH); if (lsocket < 0) { LOGE("Failed to get socket from environment: %s\n", strerror(errno)); diff --git a/cmds/installd/installd.h b/cmds/installd/installd.h index 77b58ec10692..cbca135c3043 100644 --- a/cmds/installd/installd.h +++ b/cmds/installd/installd.h @@ -49,37 +49,60 @@ /* elements combined with a valid package name to form paths */ -#define PKG_DIR_PREFIX "/data/data/" +#define PRIMARY_USER_PREFIX "data/" +#define SECONDARY_USER_PREFIX "user/" + #define PKG_DIR_POSTFIX "" -#define PKG_LIB_PREFIX "/data/data/" #define PKG_LIB_POSTFIX "/lib" -#define CACHE_DIR_PREFIX "/data/data/" #define CACHE_DIR_POSTFIX "/cache" -#define APK_DIR_PREFIX "/data/app/" +#define APP_SUBDIR "app/" // sub-directory under ANDROID_DATA /* other handy constants */ -#define PROTECTED_DIR_PREFIX "/data/app-private/" -#define SDCARD_DIR_PREFIX getenv("ASEC_MOUNTPOINT") +#define PRIVATE_APP_SUBDIR "app-private/" // sub-directory under ANDROID_DATA -#define DALVIK_CACHE_PREFIX "/data/dalvik-cache/" -#define DALVIK_CACHE_POSTFIX "/classes.dex" +#define DALVIK_CACHE_PREFIX "/data/dalvik-cache/" +#define DALVIK_CACHE_POSTFIX "/classes.dex" #define UPDATE_COMMANDS_DIR_PREFIX "/system/etc/updatecmds/" #define PKG_NAME_MAX 128 /* largest allowed package name */ #define PKG_PATH_MAX 256 /* max size of any path we use */ +/* data structures */ + +typedef struct { + char* path; + size_t len; +} dir_rec_t; + +typedef struct { + size_t count; + dir_rec_t* dirs; +} dir_rec_array_t; + +extern dir_rec_t android_app_dir; +extern dir_rec_t android_app_private_dir; +extern dir_rec_t android_data_dir; +extern dir_rec_t android_asec_dir; +extern dir_rec_array_t android_system_dirs; /* util.c */ +int create_pkg_path_in_dir(char path[PKG_PATH_MAX], + const dir_rec_t* dir, + const char* pkgname, + const char* postfix); + int create_pkg_path(char path[PKG_PATH_MAX], - const char *prefix, const char *pkgname, - const char *postfix); + const char *postfix, + uid_t persona); + +int is_valid_package_name(const char* pkgname); int create_cache_path(char path[PKG_PATH_MAX], const char *src); @@ -89,6 +112,18 @@ int delete_dir_contents(const char *pathname, int delete_dir_contents_fd(int dfd, const char *name); +int validate_system_app_path(const char* path); + +int get_path_from_env(dir_rec_t* rec, const char* var); + +int get_path_from_string(dir_rec_t* rec, const char* path); + +int copy_and_append(dir_rec_t* dst, const dir_rec_t* src, const char* suffix); + +int validate_apk_path(const char *path); + +int append_and_increment(char** dst, const char* src, size_t* dst_size); + /* commands.c */ int install(const char *pkgname, uid_t uid, gid_t gid); diff --git a/cmds/installd/tests/Android.mk b/cmds/installd/tests/Android.mk new file mode 100644 index 000000000000..e53378d9f724 --- /dev/null +++ b/cmds/installd/tests/Android.mk @@ -0,0 +1,42 @@ +# Build the unit tests for installd +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +ifneq ($(TARGET_SIMULATOR),true) + +# Build the unit tests. +test_src_files := \ + installd_utils_test.cpp + +shared_libraries := \ + libutils \ + libcutils \ + libstlport + +static_libraries := \ + libinstalld \ + libdiskusage \ + libgtest \ + libgtest_main + +c_includes := \ + frameworks/base/cmds/installd \ + bionic \ + bionic/libstdc++/include \ + external/gtest/include \ + external/stlport/stlport + +module_tags := eng tests + +$(foreach file,$(test_src_files), \ + $(eval include $(CLEAR_VARS)) \ + $(eval LOCAL_SHARED_LIBRARIES := $(shared_libraries)) \ + $(eval LOCAL_STATIC_LIBRARIES := $(static_libraries)) \ + $(eval LOCAL_SRC_FILES := $(file)) \ + $(eval LOCAL_C_INCLUDES := $(c_includes)) \ + $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \ + $(eval LOCAL_MODULE_TAGS := $(module_tags)) \ + $(eval include $(BUILD_EXECUTABLE)) \ +) + +endif diff --git a/cmds/installd/tests/installd_utils_test.cpp b/cmds/installd/tests/installd_utils_test.cpp new file mode 100644 index 000000000000..1128fceca0af --- /dev/null +++ b/cmds/installd/tests/installd_utils_test.cpp @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> +#include <string.h> + +#define LOG_TAG "utils_test" +#include <utils/Log.h> + +#include <gtest/gtest.h> + +extern "C" { +#include "installd.h" +} + +#define TEST_DATA_DIR "/data/" +#define TEST_APP_DIR "/data/app/" +#define TEST_APP_PRIVATE_DIR "/data/app-private/" +#define TEST_ASEC_DIR "/mnt/asec/" + +#define TEST_SYSTEM_DIR1 "/system/app/" +#define TEST_SYSTEM_DIR2 "/vendor/app/" + +namespace android { + +class UtilsTest : public testing::Test { +protected: + virtual void SetUp() { + android_app_dir.path = TEST_APP_DIR; + android_app_dir.len = strlen(TEST_APP_DIR); + + android_app_private_dir.path = TEST_APP_PRIVATE_DIR; + android_app_private_dir.len = strlen(TEST_APP_PRIVATE_DIR); + + android_data_dir.path = TEST_DATA_DIR; + android_data_dir.len = strlen(TEST_DATA_DIR); + + android_asec_dir.path = TEST_ASEC_DIR; + android_asec_dir.len = strlen(TEST_ASEC_DIR); + + android_system_dirs.count = 2; + + android_system_dirs.dirs = (dir_rec_t*) calloc(android_system_dirs.count, sizeof(dir_rec_t)); + android_system_dirs.dirs[0].path = TEST_SYSTEM_DIR1; + android_system_dirs.dirs[0].len = strlen(TEST_SYSTEM_DIR1); + + android_system_dirs.dirs[1].path = TEST_SYSTEM_DIR2; + android_system_dirs.dirs[1].len = strlen(TEST_SYSTEM_DIR2); + } + + virtual void TearDown() { + free(android_system_dirs.dirs); + } +}; + +TEST_F(UtilsTest, IsValidApkPath_BadPrefix) { + // Bad prefixes directories + const char *badprefix1 = "/etc/passwd"; + EXPECT_EQ(-1, validate_apk_path(badprefix1)) + << badprefix1 << " should be allowed as a valid path"; + + const char *badprefix2 = "../.." TEST_APP_DIR "../../../blah"; + EXPECT_EQ(-1, validate_apk_path(badprefix2)) + << badprefix2 << " should be allowed as a valid path"; + + const char *badprefix3 = "init.rc"; + EXPECT_EQ(-1, validate_apk_path(badprefix3)) + << badprefix3 << " should be allowed as a valid path"; + + const char *badprefix4 = "/init.rc"; + EXPECT_EQ(-1, validate_apk_path(badprefix4)) + << badprefix4 << " should be allowed as a valid path"; +} + +TEST_F(UtilsTest, IsValidApkPath_Internal) { + // Internal directories + const char *internal1 = TEST_APP_DIR "example.apk"; + EXPECT_EQ(0, validate_apk_path(internal1)) + << internal1 << " should be allowed as a valid path"; + + const char *badint1 = TEST_APP_DIR "../example.apk"; + EXPECT_EQ(-1, validate_apk_path(badint1)) + << badint1 << " should be rejected as a invalid path"; + + const char *badint2 = TEST_APP_DIR "/../example.apk"; + EXPECT_EQ(-1, validate_apk_path(badint2)) + << badint2 << " should be rejected as a invalid path"; + + const char *badint3 = TEST_APP_DIR "example.com/pkg.apk"; + EXPECT_EQ(-1, validate_apk_path(badint3)) + << badint3 << " should be rejected as a invalid path"; +} + +TEST_F(UtilsTest, IsValidApkPath_Private) { + // Internal directories + const char *private1 = TEST_APP_PRIVATE_DIR "example.apk"; + EXPECT_EQ(0, validate_apk_path(private1)) + << private1 << " should be allowed as a valid path"; + + const char *badpriv1 = TEST_APP_PRIVATE_DIR "../example.apk"; + EXPECT_EQ(-1, validate_apk_path(badpriv1)) + << badpriv1 << " should be rejected as a invalid path"; + + const char *badpriv2 = TEST_APP_PRIVATE_DIR "/../example.apk"; + EXPECT_EQ(-1, validate_apk_path(badpriv2)) + << badpriv2 << " should be rejected as a invalid path"; + + const char *badpriv3 = TEST_APP_PRIVATE_DIR "example.com/pkg.apk"; + EXPECT_EQ(-1, validate_apk_path(badpriv3)) + << badpriv3 << " should be rejected as a invalid path"; +} + + +TEST_F(UtilsTest, IsValidApkPath_AsecGood1) { + const char *asec1 = TEST_ASEC_DIR "example.apk"; + EXPECT_EQ(0, validate_apk_path(asec1)) + << asec1 << " should be allowed as a valid path"; +} + +TEST_F(UtilsTest, IsValidApkPath_AsecGood2) { + const char *asec2 = TEST_ASEC_DIR "com.example.asec/pkg.apk"; + EXPECT_EQ(0, validate_apk_path(asec2)) + << asec2 << " should be allowed as a valid path"; +} + +TEST_F(UtilsTest, IsValidApkPath_EscapeFail) { + const char *badasec1 = TEST_ASEC_DIR "../example.apk"; + EXPECT_EQ(-1, validate_apk_path(badasec1)) + << badasec1 << " should be rejected as a invalid path"; +} + +TEST_F(UtilsTest, IsValidApkPath_DoubleSlashFail) { + const char *badasec2 = TEST_ASEC_DIR "com.example.asec//pkg.apk"; + EXPECT_EQ(-1, validate_apk_path(badasec2)) + << badasec2 << " should be rejected as a invalid path"; +} + +TEST_F(UtilsTest, IsValidApkPath_SubdirEscapeFail) { + const char *badasec3 = TEST_ASEC_DIR "com.example.asec/../../../pkg.apk"; + EXPECT_EQ(-1, validate_apk_path(badasec3)) + << badasec3 << " should be rejected as a invalid path"; +} + +TEST_F(UtilsTest, IsValidApkPath_SlashEscapeFail) { + const char *badasec4 = TEST_ASEC_DIR "/../example.apk"; + EXPECT_EQ(-1, validate_apk_path(badasec4)) + << badasec4 << " should be rejected as a invalid path"; +} + +TEST_F(UtilsTest, IsValidApkPath_CrazyDirFail) { + const char *badasec5 = TEST_ASEC_DIR ".//../.."; + EXPECT_EQ(-1, validate_apk_path(badasec5)) + << badasec5 << " should be rejected as a invalid path"; +} + +TEST_F(UtilsTest, IsValidApkPath_SubdirEscapeSingleFail) { + const char *badasec6 = TEST_ASEC_DIR "com.example.asec/../pkg.apk"; + EXPECT_EQ(-1, validate_apk_path(badasec6)) + << badasec6 << " should be rejected as a invalid path"; +} + +TEST_F(UtilsTest, IsValidApkPath_TwoSubdirFail) { + const char *badasec7 = TEST_ASEC_DIR "com.example.asec/subdir1/pkg.apk"; + EXPECT_EQ(-1, validate_apk_path(badasec7)) + << badasec7 << " should be rejected as a invalid path"; +} + +TEST_F(UtilsTest, CheckSystemApp_Dir1) { + const char *sysapp1 = TEST_SYSTEM_DIR1 "Voice.apk"; + EXPECT_EQ(0, validate_system_app_path(sysapp1)) + << sysapp1 << " should be allowed as a system path"; +} + +TEST_F(UtilsTest, CheckSystemApp_Dir2) { + const char *sysapp2 = TEST_SYSTEM_DIR2 "com.example.myapp.apk"; + EXPECT_EQ(0, validate_system_app_path(sysapp2)) + << sysapp2 << " should be allowed as a system path"; +} + +TEST_F(UtilsTest, CheckSystemApp_EscapeFail) { + const char *badapp1 = TEST_SYSTEM_DIR1 "../com.example.apk"; + EXPECT_EQ(-1, validate_system_app_path(badapp1)) + << badapp1 << " should be rejected not a system path"; +} + +TEST_F(UtilsTest, CheckSystemApp_DoubleEscapeFail) { + const char *badapp2 = TEST_SYSTEM_DIR2 "/../../com.example.apk"; + EXPECT_EQ(-1, validate_system_app_path(badapp2)) + << badapp2 << " should be rejected not a system path"; +} + +TEST_F(UtilsTest, CheckSystemApp_BadPathEscapeFail) { + const char *badapp3 = TEST_APP_DIR "/../../com.example.apk"; + EXPECT_EQ(-1, validate_system_app_path(badapp3)) + << badapp3 << " should be rejected not a system path"; +} + +TEST_F(UtilsTest, GetPathFromString_NullPathFail) { + dir_rec_t test1; + EXPECT_EQ(-1, get_path_from_string(&test1, NULL)) + << "Should not allow NULL as a path."; +} + +TEST_F(UtilsTest, GetPathFromString_EmptyPathFail) { + dir_rec_t test1; + EXPECT_EQ(-1, get_path_from_string(&test1, "")) + << "Should not allow empty paths."; +} + +TEST_F(UtilsTest, GetPathFromString_RelativePathFail) { + dir_rec_t test1; + EXPECT_EQ(-1, get_path_from_string(&test1, "mnt/asec")) + << "Should not allow relative paths."; +} + +TEST_F(UtilsTest, GetPathFromString_NonCanonical) { + dir_rec_t test1; + + EXPECT_EQ(0, get_path_from_string(&test1, "/mnt/asec")) + << "Should be able to canonicalize directory /mnt/asec"; + EXPECT_STREQ("/mnt/asec/", test1.path) + << "/mnt/asec should be canonicalized to /mnt/asec/"; + EXPECT_EQ(10, (ssize_t) test1.len) + << "path len should be equal to the length of /mnt/asec/ (10)"; + free(test1.path); +} + +TEST_F(UtilsTest, GetPathFromString_CanonicalPath) { + dir_rec_t test3; + EXPECT_EQ(0, get_path_from_string(&test3, "/data/app/")) + << "Should be able to canonicalize directory /data/app/"; + EXPECT_STREQ("/data/app/", test3.path) + << "/data/app/ should be canonicalized to /data/app/"; + EXPECT_EQ(10, (ssize_t) test3.len) + << "path len should be equal to the length of /data/app/ (10)"; + free(test3.path); +} + +TEST_F(UtilsTest, CreatePkgPath_LongPkgNameSuccess) { + char path[PKG_PATH_MAX]; + + // Create long packagename of "aaaaa..." + size_t pkgnameSize = PKG_NAME_MAX; + char pkgname[pkgnameSize + 1]; + memset(pkgname, 'a', pkgnameSize); + pkgname[pkgnameSize] = '\0'; + + EXPECT_EQ(0, create_pkg_path(path, pkgname, "", 0)) + << "Should successfully be able to create package name."; + + const char *prefix = TEST_DATA_DIR PRIMARY_USER_PREFIX; + size_t offset = strlen(prefix); + EXPECT_STREQ(pkgname, path + offset) + << "Package path should be a really long string of a's"; +} + +TEST_F(UtilsTest, CreatePkgPath_LongPkgNameFail) { + char path[PKG_PATH_MAX]; + + // Create long packagename of "aaaaa..." + size_t pkgnameSize = PKG_NAME_MAX + 1; + char pkgname[pkgnameSize + 1]; + memset(pkgname, 'a', pkgnameSize); + pkgname[pkgnameSize] = '\0'; + + EXPECT_EQ(-1, create_pkg_path(path, pkgname, "", 0)) + << "Should return error because package name is too long."; +} + +TEST_F(UtilsTest, CreatePkgPath_LongPostfixFail) { + char path[PKG_PATH_MAX]; + + // Create long packagename of "aaaaa..." + size_t postfixSize = PKG_PATH_MAX; + char postfix[postfixSize + 1]; + memset(postfix, 'a', postfixSize); + postfix[postfixSize] = '\0'; + + EXPECT_EQ(-1, create_pkg_path(path, "com.example.package", postfix, 0)) + << "Should return error because postfix is too long."; +} + +TEST_F(UtilsTest, CreatePkgPath_PrimaryUser) { + char path[PKG_PATH_MAX]; + + EXPECT_EQ(0, create_pkg_path(path, "com.example.package", "", 0)) + << "Should return error because postfix is too long."; + + EXPECT_STREQ(TEST_DATA_DIR PRIMARY_USER_PREFIX "com.example.package", path) + << "Package path should be in /data/data/"; +} + +TEST_F(UtilsTest, CreatePkgPath_SecondaryUser) { + char path[PKG_PATH_MAX]; + + EXPECT_EQ(0, create_pkg_path(path, "com.example.package", "", 1)) + << "Should successfully create package path."; + + EXPECT_STREQ(TEST_DATA_DIR SECONDARY_USER_PREFIX "1/com.example.package", path) + << "Package path should be in /data/user/"; +} + +TEST_F(UtilsTest, CreatePkgPathInDir_ProtectedDir) { + char path[PKG_PATH_MAX]; + + dir_rec_t dir; + dir.path = "/data/app-private/"; + dir.len = strlen(dir.path); + + EXPECT_EQ(0, create_pkg_path_in_dir(path, &dir, "com.example.package", ".apk")) + << "Should successfully create package path."; + + EXPECT_STREQ("/data/app-private/com.example.package.apk", path) + << "Package path should be in /data/app-private/"; +} + +TEST_F(UtilsTest, CopyAndAppend_Normal) { + //int copy_and_append(dir_rec_t* dst, dir_rec_t* src, char* suffix) + dir_rec_t dst; + dir_rec_t src; + + src.path = "/data/"; + src.len = strlen(src.path); + + EXPECT_EQ(0, copy_and_append(&dst, &src, "app/")) + << "Should return error because postfix is too long."; + + EXPECT_STREQ("/data/app/", dst.path) + << "Appended path should be correct"; + + EXPECT_EQ(10, (ssize_t) dst.len) + << "Appended path should be length of '/data/app/' (10)"; +} + +TEST_F(UtilsTest, AppendAndIncrement_Normal) { + size_t dst_size = 10; + char dst[dst_size]; + char *dstp = dst; + const char* src = "FOO"; + + EXPECT_EQ(0, append_and_increment(&dstp, src, &dst_size)) + << "String should append successfully"; + + EXPECT_STREQ("FOO", dst) + << "String should append correctly"; + + EXPECT_EQ(0, append_and_increment(&dstp, src, &dst_size)) + << "String should append successfully again"; + + EXPECT_STREQ("FOOFOO", dst) + << "String should append correctly again"; +} + +TEST_F(UtilsTest, AppendAndIncrement_TooBig) { + size_t dst_size = 5; + char dst[dst_size]; + char *dstp = dst; + const char* src = "FOO"; + + EXPECT_EQ(0, append_and_increment(&dstp, src, &dst_size)) + << "String should append successfully"; + + EXPECT_STREQ("FOO", dst) + << "String should append correctly"; + + EXPECT_EQ(-1, append_and_increment(&dstp, src, &dst_size)) + << "String should fail because it's too large to fit"; +} + +} diff --git a/cmds/installd/utils.c b/cmds/installd/utils.c index a5e4b5a1557d..f37a6fbae152 100644 --- a/cmds/installd/utils.c +++ b/cmds/installd/utils.c @@ -16,24 +16,93 @@ #include "installd.h" +int create_pkg_path_in_dir(char path[PKG_PATH_MAX], + const dir_rec_t* dir, + const char* pkgname, + const char* postfix) +{ + const size_t postfix_len = strlen(postfix); + + const size_t pkgname_len = strlen(pkgname); + if (pkgname_len > PKG_NAME_MAX) { + return -1; + } + + if (is_valid_package_name(pkgname) < 0) { + return -1; + } + + if ((pkgname_len + dir->len + postfix_len) >= PKG_PATH_MAX) { + return -1; + } + + char *dst = path; + size_t dst_size = PKG_PATH_MAX; + + if (append_and_increment(&dst, dir->path, &dst_size) < 0 + || append_and_increment(&dst, pkgname, &dst_size) < 0 + || append_and_increment(&dst, postfix, &dst_size) < 0) { + LOGE("Error building APK path"); + return -1; + } + + return 0; +} + +/** + * Create the package path name for a given package name with a postfix for + * a certain persona. Returns 0 on success, and -1 on failure. + */ int create_pkg_path(char path[PKG_PATH_MAX], - const char *prefix, const char *pkgname, - const char *postfix) + const char *postfix, + uid_t persona) { - int len; - const char *x; + size_t uid_len; + char* persona_prefix; + if (persona == 0) { + persona_prefix = PRIMARY_USER_PREFIX; + uid_len = 0; + } else { + persona_prefix = SECONDARY_USER_PREFIX; + uid_len = snprintf(NULL, 0, "%d", persona); + } + + const size_t prefix_len = android_data_dir.len + strlen(persona_prefix) + uid_len + 1 /*slash*/; + char prefix[prefix_len + 1]; - len = strlen(pkgname); - if (len > PKG_NAME_MAX) { + char *dst = prefix; + size_t dst_size = sizeof(prefix); + + if (append_and_increment(&dst, android_data_dir.path, &dst_size) < 0 + || append_and_increment(&dst, persona_prefix, &dst_size) < 0) { + LOGE("Error building prefix for APK path"); return -1; } - if ((len + strlen(prefix) + strlen(postfix)) >= PKG_PATH_MAX) { - return -1; + + if (persona != 0) { + int ret = snprintf(dst, dst_size, "%d/", persona); + if (ret < 0 || (size_t) ret != uid_len + 1) { + LOGW("Error appending UID to APK path"); + return -1; + } } - x = pkgname; + dir_rec_t dir; + dir.path = prefix; + dir.len = prefix_len; + + return create_pkg_path_in_dir(path, &dir, pkgname, postfix); +} + +/** + * Checks whether the package name is valid. Returns -1 on error and + * 0 on success. + */ +int is_valid_package_name(const char* pkgname) { + const char *x = pkgname; int alpha = -1; + while (*x) { if (isalnum(*x) || (*x == '_')) { /* alphanumeric or underscore are fine */ @@ -47,13 +116,15 @@ int create_pkg_path(char path[PKG_PATH_MAX], /* Suffix -X is fine to let versioning of packages. But whatever follows should be alphanumeric.*/ alpha = 1; - }else { + } else { /* anything not A-Z, a-z, 0-9, _, or . is invalid */ LOGE("invalid package name '%s'\n", pkgname); return -1; } + x++; } + if (alpha == 1) { // Skip current character x++; @@ -66,7 +137,6 @@ int create_pkg_path(char path[PKG_PATH_MAX], } } - sprintf(path, "%s%s%s", prefix, pkgname, postfix); return 0; } @@ -171,3 +241,170 @@ int delete_dir_contents_fd(int dfd, const char *name) closedir(d); return res; } + +/** + * Checks whether a path points to a system app (.apk file). Returns 0 + * if it is a system app or -1 if it is not. + */ +int validate_system_app_path(const char* path) { + size_t i; + + for (i = 0; i < android_system_dirs.count; i++) { + const size_t dir_len = android_system_dirs.dirs[i].len; + if (!strncmp(path, android_system_dirs.dirs[i].path, dir_len)) { + if (path[dir_len] == '.' || strchr(path + dir_len, '/') != NULL) { + LOGE("invalid system apk path '%s' (trickery)\n", path); + return -1; + } + return 0; + } + } + + return -1; +} + +/** + * Get the contents of a environment variable that contains a path. Caller + * owns the string that is inserted into the directory record. Returns + * 0 on success and -1 on error. + */ +int get_path_from_env(dir_rec_t* rec, const char* var) { + const char* path = getenv(var); + int ret = get_path_from_string(rec, path); + if (ret < 0) { + LOGW("Problem finding value for environment variable %s\n", var); + } + return ret; +} + +/** + * Puts the string into the record as a directory. Appends '/' to the end + * of all paths. Caller owns the string that is inserted into the directory + * record. A null value will result in an error. + * + * Returns 0 on success and -1 on error. + */ +int get_path_from_string(dir_rec_t* rec, const char* path) { + if (path == NULL) { + return -1; + } else { + const size_t path_len = strlen(path); + if (path_len <= 0) { + return -1; + } + + // Make sure path is absolute. + if (path[0] != '/') { + return -1; + } + + if (path[path_len - 1] == '/') { + // Path ends with a forward slash. Make our own copy. + + rec->path = strdup(path); + if (rec->path == NULL) { + return -1; + } + + rec->len = path_len; + } else { + // Path does not end with a slash. Generate a new string. + char *dst; + + // Add space for slash and terminating null. + size_t dst_size = path_len + 2; + + rec->path = malloc(dst_size); + if (rec->path == NULL) { + return -1; + } + + dst = rec->path; + + if (append_and_increment(&dst, path, &dst_size) < 0 + || append_and_increment(&dst, "/", &dst_size)) { + LOGE("Error canonicalizing path"); + return -1; + } + + rec->len = dst - rec->path; + } + } + return 0; +} + +int copy_and_append(dir_rec_t* dst, const dir_rec_t* src, const char* suffix) { + dst->len = src->len + strlen(suffix); + const size_t dstSize = dst->len + 1; + dst->path = (char*) malloc(dstSize); + + if (dst->path == NULL + || snprintf(dst->path, dstSize, "%s%s", src->path, suffix) + != (ssize_t) dst->len) { + LOGE("Could not allocate memory to hold appended path; aborting\n"); + return -1; + } + + return 0; +} + +/** + * Check whether path points to a valid path for an APK file. An ASEC + * directory is allowed to have one level of subdirectory names. Returns -1 + * when an invalid path is encountered and 0 when a valid path is encountered. + */ +int validate_apk_path(const char *path) +{ + int allowsubdir = 0; + char *subdir = NULL; + size_t dir_len; + size_t path_len; + + if (!strncmp(path, android_app_dir.path, android_app_dir.len)) { + dir_len = android_app_dir.len; + } else if (!strncmp(path, android_app_private_dir.path, android_app_private_dir.len)) { + dir_len = android_app_private_dir.len; + } else if (!strncmp(path, android_asec_dir.path, android_asec_dir.len)) { + dir_len = android_asec_dir.len; + allowsubdir = 1; + } else { + LOGE("invalid apk path '%s' (bad prefix)\n", path); + return -1; + } + + path_len = strlen(path); + + /* + * Only allow the path to have a subdirectory if it's been marked as being allowed. + */ + if ((subdir = strchr(path + dir_len, '/')) != NULL) { + ++subdir; + if (!allowsubdir + || (path_len > (size_t) (subdir - path) && (strchr(subdir, '/') != NULL))) { + LOGE("invalid apk path '%s' (subdir?)\n", path); + return -1; + } + } + + /* + * Directories can't have a period directly after the directory markers + * to prevent ".." + */ + if (path[dir_len] == '.' + || (subdir != NULL && ((*subdir == '.') || (strchr(subdir, '/') != NULL)))) { + LOGE("invalid apk path '%s' (trickery)\n", path); + return -1; + } + + return 0; +} + +int append_and_increment(char** dst, const char* src, size_t* dst_size) { + ssize_t ret = strlcpy(*dst, src, *dst_size); + if (ret < 0 || (size_t) ret >= *dst_size) { + return -1; + } + *dst += ret; + *dst_size -= ret; + return 0; +} |