summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Jeff Sharkey <jsharkey@android.com> 2017-04-03 16:41:02 -0600
committer Jeff Sharkey <jsharkey@android.com> 2017-04-03 16:41:08 -0600
commite12d5964a8d14abe7f2eb6e57469cbe7f7391a19 (patch)
treea9ed18ab9487d9c7a74adc8d0687dac049082253
parent623912183d8314595b37cdedc7c193a21c345bdb (diff)
Offer to "fixup" GIDs used for app data.
We recently started tracking cached app data using a per-app GID for the "cache" and "code_cache" directories and their contents. For upgraded devices, we ideally want to "fixup" the GIDs of any existing data while the device is still showing the boot animation, instead of blocking the user when they unlock the device. Since all the information we need is available in metadata, we can update GIDs before the user has unlocked data. We're pretty paranoid and we only pivot between the normal app GID and the cache GID; any other GID values are ignored. This "fixup" method can also be used in the future to ensure consistency of the files on disk. Also fix bug by always using "fts_path" instead of "fts_accpath" which is based on racy chdir(). Test: /data/nativetest/installd_service_test/installd_service_test Bug: 34201111, 35084485 Change-Id: Ia52694f3763cba09926082c08f0766477e03e39c
-rw-r--r--cmds/installd/InstalldNativeService.cpp108
-rw-r--r--cmds/installd/InstalldNativeService.h2
-rw-r--r--cmds/installd/binder/android/os/IInstalld.aidl2
-rw-r--r--cmds/installd/tests/Android.bp19
-rw-r--r--cmds/installd/tests/installd_service_test.cpp155
-rw-r--r--cmds/installd/utils.cpp6
-rw-r--r--cmds/installd/utils.h1
7 files changed, 290 insertions, 3 deletions
diff --git a/cmds/installd/InstalldNativeService.cpp b/cmds/installd/InstalldNativeService.cpp
index c604ca0336..20b960d218 100644
--- a/cmds/installd/InstalldNativeService.cpp
+++ b/cmds/installd/InstalldNativeService.cpp
@@ -88,6 +88,7 @@ static constexpr int FLAG_USE_QUOTA = 1 << 12;
static constexpr int FLAG_FREE_CACHE_V2 = 1 << 13;
static constexpr int FLAG_FREE_CACHE_V2_DEFY_QUOTA = 1 << 14;
static constexpr int FLAG_FREE_CACHE_NOOP = 1 << 15;
+static constexpr int FLAG_FORCE = 1 << 16;
namespace {
@@ -600,6 +601,113 @@ binder::Status InstalldNativeService::destroyAppData(const std::unique_ptr<std::
return res;
}
+static gid_t get_cache_gid(uid_t uid) {
+ int32_t gid = multiuser_get_cache_gid(multiuser_get_user_id(uid), multiuser_get_app_id(uid));
+ return (gid != -1) ? gid : uid;
+}
+
+binder::Status InstalldNativeService::fixupAppData(const std::unique_ptr<std::string>& uuid,
+ int32_t flags) {
+ ENFORCE_UID(AID_SYSTEM);
+ CHECK_ARGUMENT_UUID(uuid);
+ std::lock_guard<std::recursive_mutex> lock(mLock);
+
+ const char* uuid_ = uuid ? uuid->c_str() : nullptr;
+ for (auto user : get_known_users(uuid_)) {
+ ATRACE_BEGIN("fixup user");
+ FTS* fts;
+ FTSENT* p;
+ char *argv[] = {
+ (char*) create_data_user_ce_path(uuid_, user).c_str(),
+ (char*) create_data_user_de_path(uuid_, user).c_str(),
+ nullptr
+ };
+ if (!(fts = fts_open(argv, FTS_PHYSICAL | FTS_NOCHDIR | FTS_XDEV, NULL))) {
+ return error("Failed to fts_open");
+ }
+ while ((p = fts_read(fts)) != nullptr) {
+ if (p->fts_info == FTS_D && p->fts_level == 1) {
+ // Track down inodes of cache directories
+ uint64_t raw = 0;
+ ino_t inode_cache = 0;
+ ino_t inode_code_cache = 0;
+ if (getxattr(p->fts_path, kXattrInodeCache, &raw, sizeof(raw)) == sizeof(raw)) {
+ inode_cache = raw;
+ }
+ if (getxattr(p->fts_path, kXattrInodeCodeCache, &raw, sizeof(raw)) == sizeof(raw)) {
+ inode_code_cache = raw;
+ }
+
+ // Figure out expected GID of each child
+ FTSENT* child = fts_children(fts, 0);
+ while (child != nullptr) {
+ if ((child->fts_statp->st_ino == inode_cache)
+ || (child->fts_statp->st_ino == inode_code_cache)
+ || !strcmp(child->fts_name, "cache")
+ || !strcmp(child->fts_name, "code_cache")) {
+ child->fts_number = get_cache_gid(p->fts_statp->st_uid);
+ } else {
+ child->fts_number = p->fts_statp->st_uid;
+ }
+ child = child->fts_link;
+ }
+ } else if (p->fts_level >= 2) {
+ if (p->fts_level > 2) {
+ // Inherit GID from parent once we're deeper into tree
+ p->fts_number = p->fts_parent->fts_number;
+ }
+
+ uid_t uid = p->fts_parent->fts_statp->st_uid;
+ gid_t cache_gid = get_cache_gid(uid);
+ gid_t expected = p->fts_number;
+ gid_t actual = p->fts_statp->st_gid;
+ if (actual == expected) {
+#if FIXUP_DEBUG
+ LOG(DEBUG) << "Ignoring " << p->fts_path << " with expected GID " << expected;
+#endif
+ if (!(flags & FLAG_FORCE)) {
+ fts_set(fts, p, FTS_SKIP);
+ }
+ } else if ((actual == uid) || (actual == cache_gid)) {
+ // Only consider fixing up when current GID belongs to app
+ if (p->fts_info != FTS_D) {
+ LOG(INFO) << "Fixing " << p->fts_path << " with unexpected GID " << actual
+ << " instead of " << expected;
+ }
+ switch (p->fts_info) {
+ case FTS_DP:
+ // If we're moving towards cache GID, we need to set S_ISGID
+ if (expected == cache_gid) {
+ if (chmod(p->fts_path, 02771) != 0) {
+ PLOG(WARNING) << "Failed to chmod " << p->fts_path;
+ }
+ }
+ // Intentional fall through to also set GID
+ case FTS_F:
+ if (chown(p->fts_path, -1, expected) != 0) {
+ PLOG(WARNING) << "Failed to chown " << p->fts_path;
+ }
+ break;
+ case FTS_SL:
+ case FTS_SLNONE:
+ if (lchown(p->fts_path, -1, expected) != 0) {
+ PLOG(WARNING) << "Failed to chown " << p->fts_path;
+ }
+ break;
+ }
+ } else {
+ // Ignore all other GID transitions, since they're kinda shady
+ LOG(WARNING) << "Ignoring " << p->fts_path << " with unexpected GID " << actual
+ << " instead of " << expected;
+ }
+ }
+ }
+ fts_close(fts);
+ ATRACE_END();
+ }
+ return ok();
+}
+
binder::Status InstalldNativeService::moveCompleteApp(const std::unique_ptr<std::string>& fromUuid,
const std::unique_ptr<std::string>& toUuid, const std::string& packageName,
const std::string& dataAppName, int32_t appId, const std::string& seInfo,
diff --git a/cmds/installd/InstalldNativeService.h b/cmds/installd/InstalldNativeService.h
index 7ad86878ca..f5b7142a12 100644
--- a/cmds/installd/InstalldNativeService.h
+++ b/cmds/installd/InstalldNativeService.h
@@ -58,6 +58,8 @@ public:
binder::Status destroyAppData(const std::unique_ptr<std::string>& uuid,
const std::string& packageName, int32_t userId, int32_t flags, int64_t ceDataInode);
+ binder::Status fixupAppData(const std::unique_ptr<std::string>& uuid, int32_t flags);
+
binder::Status getAppSize(const std::unique_ptr<std::string>& uuid,
const std::vector<std::string>& packageNames, int32_t userId, int32_t flags,
int32_t appId, const std::vector<int64_t>& ceDataInodes,
diff --git a/cmds/installd/binder/android/os/IInstalld.aidl b/cmds/installd/binder/android/os/IInstalld.aidl
index 4195a0128c..03ff96e866 100644
--- a/cmds/installd/binder/android/os/IInstalld.aidl
+++ b/cmds/installd/binder/android/os/IInstalld.aidl
@@ -32,6 +32,8 @@ interface IInstalld {
void destroyAppData(@nullable @utf8InCpp String uuid, @utf8InCpp String packageName,
int userId, int flags, long ceDataInode);
+ void fixupAppData(@nullable @utf8InCpp String uuid, int flags);
+
long[] getAppSize(@nullable @utf8InCpp String uuid, in @utf8InCpp String[] packageNames,
int userId, int flags, int appId, in long[] ceDataInodes,
in @utf8InCpp String[] codePaths);
diff --git a/cmds/installd/tests/Android.bp b/cmds/installd/tests/Android.bp
index b5b080d263..630c1f3652 100644
--- a/cmds/installd/tests/Android.bp
+++ b/cmds/installd/tests/Android.bp
@@ -33,3 +33,22 @@ cc_test {
"libdiskusage",
],
}
+
+cc_test {
+ name: "installd_service_test",
+ clang: true,
+ srcs: ["installd_service_test.cpp"],
+ shared_libs: [
+ "libbase",
+ "libbinder",
+ "libcutils",
+ "liblog",
+ "liblogwrap",
+ "libselinux",
+ "libutils",
+ ],
+ static_libs: [
+ "libinstalld",
+ "libdiskusage",
+ ],
+}
diff --git a/cmds/installd/tests/installd_service_test.cpp b/cmds/installd/tests/installd_service_test.cpp
new file mode 100644
index 0000000000..4a1f333bae
--- /dev/null
+++ b/cmds/installd/tests/installd_service_test.cpp
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2017 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>
+#include <sys/statvfs.h>
+#include <sys/xattr.h>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <cutils/properties.h>
+#include <gtest/gtest.h>
+
+#include "InstalldNativeService.h"
+#include "globals.h"
+#include "utils.h"
+
+using android::base::StringPrintf;
+
+namespace android {
+namespace installd {
+
+constexpr const char* kTestUuid = "TEST";
+
+static constexpr int FLAG_FORCE = 1 << 16;
+
+int get_property(const char *key, char *value, const char *default_value) {
+ return property_get(key, value, default_value);
+}
+
+bool calculate_oat_file_path(char path[PKG_PATH_MAX] ATTRIBUTE_UNUSED,
+ const char *oat_dir ATTRIBUTE_UNUSED,
+ const char *apk_path ATTRIBUTE_UNUSED,
+ const char *instruction_set ATTRIBUTE_UNUSED) {
+ return false;
+}
+
+bool calculate_odex_file_path(char path[PKG_PATH_MAX] ATTRIBUTE_UNUSED,
+ const char *apk_path ATTRIBUTE_UNUSED,
+ const char *instruction_set ATTRIBUTE_UNUSED) {
+ return false;
+}
+
+bool create_cache_path(char path[PKG_PATH_MAX] ATTRIBUTE_UNUSED,
+ const char *src ATTRIBUTE_UNUSED,
+ const char *instruction_set ATTRIBUTE_UNUSED) {
+ return false;
+}
+
+static void mkdir(const char* path, uid_t owner, gid_t group, mode_t mode) {
+ const char* fullPath = StringPrintf("/data/local/tmp/user/0/%s", path).c_str();
+ ::mkdir(fullPath, mode);
+ ::chown(fullPath, owner, group);
+ ::chmod(fullPath, mode);
+}
+
+static void touch(const char* path, uid_t owner, gid_t group, mode_t mode) {
+ int fd = ::open(StringPrintf("/data/local/tmp/user/0/%s", path).c_str(),
+ O_RDWR | O_CREAT, mode);
+ ::fchown(fd, owner, group);
+ ::fchmod(fd, mode);
+ ::close(fd);
+}
+
+static int stat_gid(const char* path) {
+ struct stat buf;
+ ::stat(StringPrintf("/data/local/tmp/user/0/%s", path).c_str(), &buf);
+ return buf.st_gid;
+}
+
+static int stat_mode(const char* path) {
+ struct stat buf;
+ ::stat(StringPrintf("/data/local/tmp/user/0/%s", path).c_str(), &buf);
+ return buf.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO | S_ISGID);
+}
+
+class ServiceTest : public testing::Test {
+protected:
+ InstalldNativeService* service;
+ std::unique_ptr<std::string> testUuid;
+
+ virtual void SetUp() {
+ setenv("ANDROID_LOG_TAGS", "*:v", 1);
+ android::base::InitLogging(nullptr);
+
+ service = new InstalldNativeService();
+ testUuid = std::make_unique<std::string>();
+ *testUuid = std::string(kTestUuid);
+ system("mkdir -p /data/local/tmp/user/0");
+ }
+
+ virtual void TearDown() {
+ delete service;
+ system("rm -rf /data/local/tmp/user");
+ }
+};
+
+TEST_F(ServiceTest, FixupAppData_Upgrade) {
+ LOG(INFO) << "FixupAppData_Upgrade";
+
+ mkdir("com.example", 10000, 10000, 0700);
+ mkdir("com.example/normal", 10000, 10000, 0700);
+ mkdir("com.example/cache", 10000, 10000, 0700);
+ touch("com.example/cache/file", 10000, 10000, 0700);
+
+ service->fixupAppData(testUuid, 0);
+
+ EXPECT_EQ(10000, stat_gid("com.example/normal"));
+ EXPECT_EQ(20000, stat_gid("com.example/cache"));
+ EXPECT_EQ(20000, stat_gid("com.example/cache/file"));
+
+ EXPECT_EQ(0700, stat_mode("com.example/normal"));
+ EXPECT_EQ(02771, stat_mode("com.example/cache"));
+ EXPECT_EQ(0700, stat_mode("com.example/cache/file"));
+}
+
+TEST_F(ServiceTest, FixupAppData_Moved) {
+ LOG(INFO) << "FixupAppData_Moved";
+
+ mkdir("com.example", 10000, 10000, 0700);
+ mkdir("com.example/foo", 10000, 10000, 0700);
+ touch("com.example/foo/file", 10000, 20000, 0700);
+ mkdir("com.example/bar", 10000, 20000, 0700);
+ touch("com.example/bar/file", 10000, 20000, 0700);
+
+ service->fixupAppData(testUuid, 0);
+
+ EXPECT_EQ(10000, stat_gid("com.example/foo"));
+ EXPECT_EQ(20000, stat_gid("com.example/foo/file"));
+ EXPECT_EQ(10000, stat_gid("com.example/bar"));
+ EXPECT_EQ(10000, stat_gid("com.example/bar/file"));
+
+ service->fixupAppData(testUuid, FLAG_FORCE);
+
+ EXPECT_EQ(10000, stat_gid("com.example/foo"));
+ EXPECT_EQ(10000, stat_gid("com.example/foo/file"));
+ EXPECT_EQ(10000, stat_gid("com.example/bar"));
+ EXPECT_EQ(10000, stat_gid("com.example/bar/file"));
+}
+
+} // namespace installd
+} // namespace android
diff --git a/cmds/installd/utils.cpp b/cmds/installd/utils.cpp
index 24c0b45f45..c7920821c6 100644
--- a/cmds/installd/utils.cpp
+++ b/cmds/installd/utils.cpp
@@ -1047,18 +1047,18 @@ int prepare_app_cache_dir(const std::string& parent, const char* name, mode_t ta
while ((p = fts_read(fts)) != NULL) {
switch (p->fts_info) {
case FTS_DP:
- if (chmod(p->fts_accpath, target_mode) != 0) {
+ if (chmod(p->fts_path, target_mode) != 0) {
PLOG(WARNING) << "Failed to chmod " << p->fts_path;
}
// Intentional fall through to also set GID
case FTS_F:
- if (chown(p->fts_accpath, -1, gid) != 0) {
+ if (chown(p->fts_path, -1, gid) != 0) {
PLOG(WARNING) << "Failed to chown " << p->fts_path;
}
break;
case FTS_SL:
case FTS_SLNONE:
- if (lchown(p->fts_accpath, -1, gid) != 0) {
+ if (lchown(p->fts_path, -1, gid) != 0) {
PLOG(WARNING) << "Failed to chown " << p->fts_path;
}
break;
diff --git a/cmds/installd/utils.h b/cmds/installd/utils.h
index 7ebfea213c..dd94da9e31 100644
--- a/cmds/installd/utils.h
+++ b/cmds/installd/utils.h
@@ -31,6 +31,7 @@
#include <installd_constants.h>
#define MEASURE_DEBUG 0
+#define FIXUP_DEBUG 0
namespace android {
namespace installd {