Merge "Update installd to allow multiuser"
diff --git a/cmds/installd/Android.mk b/cmds/installd/Android.mk
index 8641c30..d7a9ef6 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_C_INCLUDES := \
-# $(call include-path-for, system-core)/cutils
+LOCAL_MODULE := libinstalld
+
+LOCAL_MODULE_TAGS := eng tests
+
+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 4d49c30..80ba1e9 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 @@
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 @@
{
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 @@
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 @@
{
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 @@
{
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 @@
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 @@
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 @@
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 @@
{
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 @@
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 @@
/* 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 @@
}
}
- 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 @@
}
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 @@
}
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 @@
// 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 @@
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 @@
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 d2b2f7f..e0d0f97 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 @@
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 77b58ec..cbca135 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_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 0000000..e53378d
--- /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 0000000..1128fce
--- /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 a5e4b5a..f37a6fb 100644
--- a/cmds/installd/utils.c
+++ b/cmds/installd/utils.c
@@ -16,24 +16,93 @@
#include "installd.h"
-int create_pkg_path(char path[PKG_PATH_MAX],
- const char *prefix,
- const char *pkgname,
- const char *postfix)
+int create_pkg_path_in_dir(char path[PKG_PATH_MAX],
+ const dir_rec_t* dir,
+ const char* pkgname,
+ const char* postfix)
{
- int len;
- const char *x;
+ const size_t postfix_len = strlen(postfix);
- len = strlen(pkgname);
- if (len > PKG_NAME_MAX) {
- return -1;
+ 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 *pkgname,
+ const char *postfix,
+ uid_t persona)
+{
+ 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);
}
- if ((len + strlen(prefix) + strlen(postfix)) >= PKG_PATH_MAX) {
+
+ const size_t prefix_len = android_data_dir.len + strlen(persona_prefix) + uid_len + 1 /*slash*/;
+ char prefix[prefix_len + 1];
+
+ 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;
}
- x = pkgname;
+ 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;
+ }
+ }
+
+ 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 @@
/* 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 @@
}
}
- sprintf(path, "%s%s%s", prefix, pkgname, postfix);
return 0;
}
@@ -171,3 +241,170 @@
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;
+}