Loading cmds/installd/InstalldNativeService.cpp +126 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ #include <errno.h> #include <fts.h> #include <inttypes.h> #include <linux/fsverity.h> #include <stdio.h> #include <stdlib.h> #include <string.h> Loading Loading @@ -51,6 +52,7 @@ #include <android-base/unique_fd.h> #include <cutils/ashmem.h> #include <cutils/fs.h> #include <cutils/misc.h> #include <cutils/properties.h> #include <cutils/sched_policy.h> #include <linux/quota.h> Loading Loading @@ -84,6 +86,8 @@ using android::base::ParseUint; using android::base::Split; using android::base::StringPrintf; using android::base::unique_fd; using android::os::ParcelFileDescriptor; using std::endl; namespace android { Loading Loading @@ -229,6 +233,14 @@ binder::Status checkArgumentFileName(const std::string& path) { return ok(); } binder::Status checkUidInAppRange(int32_t appUid) { if (FIRST_APPLICATION_UID <= appUid && appUid <= LAST_APPLICATION_UID) { return ok(); } return exception(binder::Status::EX_ILLEGAL_ARGUMENT, StringPrintf("UID %d is outside of the range", appUid)); } #define ENFORCE_UID(uid) { \ binder::Status status = checkUid((uid)); \ if (!status.isOk()) { \ Loading Loading @@ -283,6 +295,14 @@ binder::Status checkArgumentFileName(const std::string& path) { } \ } #define CHECK_ARGUMENT_UID_IN_APP_RANGE(uid) \ { \ binder::Status status = checkUidInAppRange((uid)); \ if (!status.isOk()) { \ return status; \ } \ } #ifdef GRANULAR_LOCKS /** Loading Loading @@ -383,6 +403,33 @@ using PackageLockGuard = std::lock_guard<PackageLock>; } // namespace binder::Status InstalldNativeService::FsveritySetupAuthToken::authenticate( const ParcelFileDescriptor& authFd, int32_t appUid, int32_t userId) { int open_flags = fcntl(authFd.get(), F_GETFL); if (open_flags < 0) { return exception(binder::Status::EX_SERVICE_SPECIFIC, "fcntl failed"); } if ((open_flags & O_ACCMODE) != O_WRONLY && (open_flags & O_ACCMODE) != O_RDWR) { return exception(binder::Status::EX_SECURITY, "Received FD with unexpected open flag"); } if (fstat(authFd.get(), &this->mStatFromAuthFd) < 0) { return exception(binder::Status::EX_SERVICE_SPECIFIC, "fstat failed"); } if (!S_ISREG(this->mStatFromAuthFd.st_mode)) { return exception(binder::Status::EX_SECURITY, "Not a regular file"); } // Don't accept a file owned by a different app. uid_t uid = multiuser_get_uid(userId, appUid); if (this->mStatFromAuthFd.st_uid != uid) { return exception(binder::Status::EX_SERVICE_SPECIFIC, "File not owned by appUid"); } return ok(); } bool InstalldNativeService::FsveritySetupAuthToken::isSameStat(const struct stat& st) const { return memcmp(&st, &mStatFromAuthFd, sizeof(st)) == 0; } status_t InstalldNativeService::start() { IPCThreadState::self()->disableBackgroundScheduling(true); status_t ret = BinderService<InstalldNativeService>::publish(); Loading Loading @@ -3857,5 +3904,84 @@ binder::Status InstalldNativeService::getOdexVisibility( return *_aidl_return == -1 ? error() : ok(); } // Creates an auth token to be used in enableFsverity. This token is really to store a proof that // the caller can write to a file, represented by the authFd. Effectively, system_server as the // attacker-in-the-middle cannot enable fs-verity on arbitrary app files. If the FD is not writable, // return null. // // appUid and userId are passed for additional ownership check, such that one app can not be // authenticated for another app's file. These parameters are assumed trusted for this purpose of // consistency check. // // Notably, creating the token allows us to manage the writable FD easily during enableFsverity. // Since enabling fs-verity to a file requires no outstanding writable FD, passing the authFd to the // server allows the server to hold the only reference (as long as the client app doesn't). binder::Status InstalldNativeService::createFsveritySetupAuthToken( const ParcelFileDescriptor& authFd, int32_t appUid, int32_t userId, sp<IFsveritySetupAuthToken>* _aidl_return) { CHECK_ARGUMENT_UID_IN_APP_RANGE(appUid); ENFORCE_VALID_USER(userId); auto token = sp<FsveritySetupAuthToken>::make(); binder::Status status = token->authenticate(authFd, appUid, userId); if (!status.isOk()) { return status; } *_aidl_return = token; return ok(); } // Enables fs-verity for filePath, which must be an absolute path and the same inode as in the auth // token previously returned from createFsveritySetupAuthToken, and owned by the app uid. As // installd is more privileged than its client / system server, we attempt to limit what a // (compromised) client can do. // // The reason for this app request to go through installd is to avoid exposing a risky area (PKCS#7 // signature verification) in the kernel to the app as an attack surface (it can't be system server // because it can't override DAC and manipulate app files). Note that we should be able to drop // these hops and simply the app calls the ioctl, once all upgrading devices run with a kernel // without fs-verity built-in signature (https://r.android.com/2650402). binder::Status InstalldNativeService::enableFsverity(const sp<IFsveritySetupAuthToken>& authToken, const std::string& filePath, const std::string& packageName, int32_t* _aidl_return) { ENFORCE_UID(AID_SYSTEM); CHECK_ARGUMENT_PATH(filePath); CHECK_ARGUMENT_PACKAGE_NAME(packageName); LOCK_PACKAGE(); if (authToken == nullptr) { return exception(binder::Status::EX_ILLEGAL_ARGUMENT, "Received a null auth token"); } // Authenticate to check the targeting file is the same inode as the authFd. sp<IBinder> authTokenBinder = IInterface::asBinder(authToken)->localBinder(); if (authTokenBinder == nullptr) { return exception(binder::Status::EX_SECURITY, "Received a non-local auth token"); } auto authTokenInstance = sp<FsveritySetupAuthToken>::cast(authTokenBinder); unique_fd rfd(open(filePath.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW)); struct stat stFromPath; if (fstat(rfd.get(), &stFromPath) < 0) { *_aidl_return = errno; return ok(); } if (!authTokenInstance->isSameStat(stFromPath)) { LOG(DEBUG) << "FD authentication failed"; *_aidl_return = EPERM; return ok(); } fsverity_enable_arg arg = {}; arg.version = 1; arg.hash_algorithm = FS_VERITY_HASH_ALG_SHA256; arg.block_size = 4096; if (ioctl(rfd.get(), FS_IOC_ENABLE_VERITY, &arg) < 0) { *_aidl_return = errno; } else { *_aidl_return = 0; } return ok(); } } // namespace installd } // namespace android cmds/installd/InstalldNativeService.h +26 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ #define COMMANDS_H_ #include <inttypes.h> #include <sys/stat.h> #include <unistd.h> #include <shared_mutex> Loading @@ -35,8 +36,26 @@ namespace android { namespace installd { using IFsveritySetupAuthToken = android::os::IInstalld::IFsveritySetupAuthToken; class InstalldNativeService : public BinderService<InstalldNativeService>, public os::BnInstalld { public: class FsveritySetupAuthToken : public os::IInstalld::BnFsveritySetupAuthToken { public: FsveritySetupAuthToken() : mStatFromAuthFd() {} binder::Status authenticate(const android::os::ParcelFileDescriptor& authFd, int32_t appUid, int32_t userId); bool isSameStat(const struct stat& st) const; private: // Not copyable or movable FsveritySetupAuthToken(const FsveritySetupAuthToken&) = delete; FsveritySetupAuthToken& operator=(const FsveritySetupAuthToken&) = delete; struct stat mStatFromAuthFd; }; static status_t start(); static char const* getServiceName() { return "installd"; } virtual status_t dump(int fd, const Vector<String16> &args) override; Loading Loading @@ -192,6 +211,13 @@ public: const std::optional<std::string>& outputPath, int32_t* _aidl_return); binder::Status createFsveritySetupAuthToken(const android::os::ParcelFileDescriptor& authFd, int32_t appUid, int32_t userId, android::sp<IFsveritySetupAuthToken>* _aidl_return); binder::Status enableFsverity(const android::sp<IFsveritySetupAuthToken>& authToken, const std::string& filePath, const std::string& packageName, int32_t* _aidl_return); private: std::recursive_mutex mLock; std::unordered_map<userid_t, std::weak_ptr<std::shared_mutex>> mUserIdLock; Loading cmds/installd/binder/android/os/IInstalld.aidl +16 −0 Original line number Diff line number Diff line Loading @@ -134,6 +134,22 @@ interface IInstalld { int getOdexVisibility(@utf8InCpp String packageName, @utf8InCpp String apkPath, @utf8InCpp String instructionSet, @nullable @utf8InCpp String outputPath); interface IFsveritySetupAuthToken { // Using an interface here is an easy way to create and maintain an IBinder object across // the processes. When installd creates this binder object, it stores the file stat // privately for later authentication, and only returns the reference to the caller process. // Once the binder object has no reference count, it gets destructed automatically // (alternatively, installd can maintain an internal mapping, but it is more error prone // because the app may crash and not finish the fs-verity setup, keeping the memory unused // forever). // // We don't necessarily need a method here, so it's left blank intentionally. } IFsveritySetupAuthToken createFsveritySetupAuthToken(in ParcelFileDescriptor authFd, int appUid, int userId); int enableFsverity(in IFsveritySetupAuthToken authToken, @utf8InCpp String filePath, @utf8InCpp String packageName); const int FLAG_STORAGE_DE = 0x1; const int FLAG_STORAGE_CE = 0x2; const int FLAG_STORAGE_EXTERNAL = 0x4; Loading cmds/installd/tests/installd_service_test.cpp +101 −0 Original line number Diff line number Diff line Loading @@ -42,9 +42,12 @@ #include "binder_test_utils.h" #include "dexopt.h" #include "globals.h" #include "unique_file.h" #include "utils.h" using android::base::StringPrintf; using android::base::unique_fd; using android::os::ParcelFileDescriptor; using std::filesystem::is_empty; namespace android { Loading Loading @@ -136,6 +139,16 @@ static int create(const std::string& path, uid_t owner, gid_t group, mode_t mode return fd; } static void create_with_content(const std::string& path, uid_t owner, gid_t group, mode_t mode, const std::string& content) { int fd = ::open(path.c_str(), O_RDWR | O_CREAT, mode); EXPECT_NE(fd, -1); EXPECT_TRUE(android::base::WriteStringToFd(content, fd)); EXPECT_EQ(::fchown(fd, owner, group), 0); EXPECT_EQ(::fchmod(fd, mode), 0); close(fd); } static void touch(const std::string& path, uid_t owner, gid_t group, mode_t mode) { EXPECT_EQ(::close(create(path.c_str(), owner, group, mode)), 0); } Loading Loading @@ -527,6 +540,94 @@ TEST_F(ServiceTest, GetAppSizeWrongSizes) { externalStorageAppId, ceDataInodes, codePaths, &externalStorageSize)); } class FsverityTest : public ServiceTest { protected: binder::Status createFsveritySetupAuthToken(const std::string& path, int open_mode, sp<IFsveritySetupAuthToken>* _aidl_return) { unique_fd ufd(open(path.c_str(), open_mode)); EXPECT_GE(ufd.get(), 0) << "open failed: " << strerror(errno); ParcelFileDescriptor rfd(std::move(ufd)); return service->createFsveritySetupAuthToken(std::move(rfd), kTestAppId, kTestUserId, _aidl_return); } }; TEST_F(FsverityTest, enableFsverity) { const std::string path = kTestPath + "/foo"; create_with_content(path, kTestAppUid, kTestAppUid, 0600, "content"); UniqueFile raii(/*fd=*/-1, path, [](const std::string& path) { unlink(path.c_str()); }); // Expect to fs-verity setup to succeed sp<IFsveritySetupAuthToken> authToken; binder::Status status = createFsveritySetupAuthToken(path, O_RDWR, &authToken); EXPECT_TRUE(status.isOk()); EXPECT_TRUE(authToken != nullptr); // Verity auth token works to enable fs-verity int32_t errno_local; status = service->enableFsverity(authToken, path, "fake.package.name", &errno_local); EXPECT_TRUE(status.isOk()); EXPECT_EQ(errno_local, 0); } TEST_F(FsverityTest, enableFsverity_nullAuthToken) { const std::string path = kTestPath + "/foo"; create_with_content(path, kTestAppUid, kTestAppUid, 0600, "content"); UniqueFile raii(/*fd=*/-1, path, [](const std::string& path) { unlink(path.c_str()); }); // Verity null auth token fails sp<IFsveritySetupAuthToken> authToken; int32_t errno_local; binder::Status status = service->enableFsverity(authToken, path, "fake.package.name", &errno_local); EXPECT_FALSE(status.isOk()); } TEST_F(FsverityTest, enableFsverity_differentFile) { const std::string path = kTestPath + "/foo"; create_with_content(path, kTestAppUid, kTestAppUid, 0600, "content"); UniqueFile raii(/*fd=*/-1, path, [](const std::string& path) { unlink(path.c_str()); }); // Expect to fs-verity setup to succeed sp<IFsveritySetupAuthToken> authToken; binder::Status status = createFsveritySetupAuthToken(path, O_RDWR, &authToken); EXPECT_TRUE(status.isOk()); EXPECT_TRUE(authToken != nullptr); // Verity auth token does not work for a different file const std::string anotherPath = kTestPath + "/bar"; ASSERT_TRUE(android::base::WriteStringToFile("content", anotherPath)); UniqueFile raii2(/*fd=*/-1, anotherPath, [](const std::string& path) { unlink(path.c_str()); }); int32_t errno_local; status = service->enableFsverity(authToken, anotherPath, "fake.package.name", &errno_local); EXPECT_TRUE(status.isOk()); EXPECT_NE(errno_local, 0); } TEST_F(FsverityTest, createFsveritySetupAuthToken_ReadonlyFdDoesNotAuthenticate) { const std::string path = kTestPath + "/foo"; create_with_content(path, kTestAppUid, kTestAppUid, 0600, "content"); UniqueFile raii(/*fd=*/-1, path, [](const std::string& path) { unlink(path.c_str()); }); // Expect the fs-verity setup to fail sp<IFsveritySetupAuthToken> authToken; binder::Status status = createFsveritySetupAuthToken(path, O_RDONLY, &authToken); EXPECT_FALSE(status.isOk()); } TEST_F(FsverityTest, createFsveritySetupAuthToken_UnownedFile) { const std::string path = kTestPath + "/foo"; // Simulate world-writable file owned by another app create_with_content(path, kTestAppUid + 1, kTestAppUid + 1, 0666, "content"); UniqueFile raii(/*fd=*/-1, path, [](const std::string& path) { unlink(path.c_str()); }); // Expect the fs-verity setup to fail sp<IFsveritySetupAuthToken> authToken; binder::Status status = createFsveritySetupAuthToken(path, O_RDWR, &authToken); EXPECT_FALSE(status.isOk()); } static bool mkdirs(const std::string& path, mode_t mode) { struct stat sb; if (stat(path.c_str(), &sb) != -1 && S_ISDIR(sb.st_mode)) { Loading Loading
cmds/installd/InstalldNativeService.cpp +126 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ #include <errno.h> #include <fts.h> #include <inttypes.h> #include <linux/fsverity.h> #include <stdio.h> #include <stdlib.h> #include <string.h> Loading Loading @@ -51,6 +52,7 @@ #include <android-base/unique_fd.h> #include <cutils/ashmem.h> #include <cutils/fs.h> #include <cutils/misc.h> #include <cutils/properties.h> #include <cutils/sched_policy.h> #include <linux/quota.h> Loading Loading @@ -84,6 +86,8 @@ using android::base::ParseUint; using android::base::Split; using android::base::StringPrintf; using android::base::unique_fd; using android::os::ParcelFileDescriptor; using std::endl; namespace android { Loading Loading @@ -229,6 +233,14 @@ binder::Status checkArgumentFileName(const std::string& path) { return ok(); } binder::Status checkUidInAppRange(int32_t appUid) { if (FIRST_APPLICATION_UID <= appUid && appUid <= LAST_APPLICATION_UID) { return ok(); } return exception(binder::Status::EX_ILLEGAL_ARGUMENT, StringPrintf("UID %d is outside of the range", appUid)); } #define ENFORCE_UID(uid) { \ binder::Status status = checkUid((uid)); \ if (!status.isOk()) { \ Loading Loading @@ -283,6 +295,14 @@ binder::Status checkArgumentFileName(const std::string& path) { } \ } #define CHECK_ARGUMENT_UID_IN_APP_RANGE(uid) \ { \ binder::Status status = checkUidInAppRange((uid)); \ if (!status.isOk()) { \ return status; \ } \ } #ifdef GRANULAR_LOCKS /** Loading Loading @@ -383,6 +403,33 @@ using PackageLockGuard = std::lock_guard<PackageLock>; } // namespace binder::Status InstalldNativeService::FsveritySetupAuthToken::authenticate( const ParcelFileDescriptor& authFd, int32_t appUid, int32_t userId) { int open_flags = fcntl(authFd.get(), F_GETFL); if (open_flags < 0) { return exception(binder::Status::EX_SERVICE_SPECIFIC, "fcntl failed"); } if ((open_flags & O_ACCMODE) != O_WRONLY && (open_flags & O_ACCMODE) != O_RDWR) { return exception(binder::Status::EX_SECURITY, "Received FD with unexpected open flag"); } if (fstat(authFd.get(), &this->mStatFromAuthFd) < 0) { return exception(binder::Status::EX_SERVICE_SPECIFIC, "fstat failed"); } if (!S_ISREG(this->mStatFromAuthFd.st_mode)) { return exception(binder::Status::EX_SECURITY, "Not a regular file"); } // Don't accept a file owned by a different app. uid_t uid = multiuser_get_uid(userId, appUid); if (this->mStatFromAuthFd.st_uid != uid) { return exception(binder::Status::EX_SERVICE_SPECIFIC, "File not owned by appUid"); } return ok(); } bool InstalldNativeService::FsveritySetupAuthToken::isSameStat(const struct stat& st) const { return memcmp(&st, &mStatFromAuthFd, sizeof(st)) == 0; } status_t InstalldNativeService::start() { IPCThreadState::self()->disableBackgroundScheduling(true); status_t ret = BinderService<InstalldNativeService>::publish(); Loading Loading @@ -3857,5 +3904,84 @@ binder::Status InstalldNativeService::getOdexVisibility( return *_aidl_return == -1 ? error() : ok(); } // Creates an auth token to be used in enableFsverity. This token is really to store a proof that // the caller can write to a file, represented by the authFd. Effectively, system_server as the // attacker-in-the-middle cannot enable fs-verity on arbitrary app files. If the FD is not writable, // return null. // // appUid and userId are passed for additional ownership check, such that one app can not be // authenticated for another app's file. These parameters are assumed trusted for this purpose of // consistency check. // // Notably, creating the token allows us to manage the writable FD easily during enableFsverity. // Since enabling fs-verity to a file requires no outstanding writable FD, passing the authFd to the // server allows the server to hold the only reference (as long as the client app doesn't). binder::Status InstalldNativeService::createFsveritySetupAuthToken( const ParcelFileDescriptor& authFd, int32_t appUid, int32_t userId, sp<IFsveritySetupAuthToken>* _aidl_return) { CHECK_ARGUMENT_UID_IN_APP_RANGE(appUid); ENFORCE_VALID_USER(userId); auto token = sp<FsveritySetupAuthToken>::make(); binder::Status status = token->authenticate(authFd, appUid, userId); if (!status.isOk()) { return status; } *_aidl_return = token; return ok(); } // Enables fs-verity for filePath, which must be an absolute path and the same inode as in the auth // token previously returned from createFsveritySetupAuthToken, and owned by the app uid. As // installd is more privileged than its client / system server, we attempt to limit what a // (compromised) client can do. // // The reason for this app request to go through installd is to avoid exposing a risky area (PKCS#7 // signature verification) in the kernel to the app as an attack surface (it can't be system server // because it can't override DAC and manipulate app files). Note that we should be able to drop // these hops and simply the app calls the ioctl, once all upgrading devices run with a kernel // without fs-verity built-in signature (https://r.android.com/2650402). binder::Status InstalldNativeService::enableFsverity(const sp<IFsveritySetupAuthToken>& authToken, const std::string& filePath, const std::string& packageName, int32_t* _aidl_return) { ENFORCE_UID(AID_SYSTEM); CHECK_ARGUMENT_PATH(filePath); CHECK_ARGUMENT_PACKAGE_NAME(packageName); LOCK_PACKAGE(); if (authToken == nullptr) { return exception(binder::Status::EX_ILLEGAL_ARGUMENT, "Received a null auth token"); } // Authenticate to check the targeting file is the same inode as the authFd. sp<IBinder> authTokenBinder = IInterface::asBinder(authToken)->localBinder(); if (authTokenBinder == nullptr) { return exception(binder::Status::EX_SECURITY, "Received a non-local auth token"); } auto authTokenInstance = sp<FsveritySetupAuthToken>::cast(authTokenBinder); unique_fd rfd(open(filePath.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW)); struct stat stFromPath; if (fstat(rfd.get(), &stFromPath) < 0) { *_aidl_return = errno; return ok(); } if (!authTokenInstance->isSameStat(stFromPath)) { LOG(DEBUG) << "FD authentication failed"; *_aidl_return = EPERM; return ok(); } fsverity_enable_arg arg = {}; arg.version = 1; arg.hash_algorithm = FS_VERITY_HASH_ALG_SHA256; arg.block_size = 4096; if (ioctl(rfd.get(), FS_IOC_ENABLE_VERITY, &arg) < 0) { *_aidl_return = errno; } else { *_aidl_return = 0; } return ok(); } } // namespace installd } // namespace android
cmds/installd/InstalldNativeService.h +26 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ #define COMMANDS_H_ #include <inttypes.h> #include <sys/stat.h> #include <unistd.h> #include <shared_mutex> Loading @@ -35,8 +36,26 @@ namespace android { namespace installd { using IFsveritySetupAuthToken = android::os::IInstalld::IFsveritySetupAuthToken; class InstalldNativeService : public BinderService<InstalldNativeService>, public os::BnInstalld { public: class FsveritySetupAuthToken : public os::IInstalld::BnFsveritySetupAuthToken { public: FsveritySetupAuthToken() : mStatFromAuthFd() {} binder::Status authenticate(const android::os::ParcelFileDescriptor& authFd, int32_t appUid, int32_t userId); bool isSameStat(const struct stat& st) const; private: // Not copyable or movable FsveritySetupAuthToken(const FsveritySetupAuthToken&) = delete; FsveritySetupAuthToken& operator=(const FsveritySetupAuthToken&) = delete; struct stat mStatFromAuthFd; }; static status_t start(); static char const* getServiceName() { return "installd"; } virtual status_t dump(int fd, const Vector<String16> &args) override; Loading Loading @@ -192,6 +211,13 @@ public: const std::optional<std::string>& outputPath, int32_t* _aidl_return); binder::Status createFsveritySetupAuthToken(const android::os::ParcelFileDescriptor& authFd, int32_t appUid, int32_t userId, android::sp<IFsveritySetupAuthToken>* _aidl_return); binder::Status enableFsverity(const android::sp<IFsveritySetupAuthToken>& authToken, const std::string& filePath, const std::string& packageName, int32_t* _aidl_return); private: std::recursive_mutex mLock; std::unordered_map<userid_t, std::weak_ptr<std::shared_mutex>> mUserIdLock; Loading
cmds/installd/binder/android/os/IInstalld.aidl +16 −0 Original line number Diff line number Diff line Loading @@ -134,6 +134,22 @@ interface IInstalld { int getOdexVisibility(@utf8InCpp String packageName, @utf8InCpp String apkPath, @utf8InCpp String instructionSet, @nullable @utf8InCpp String outputPath); interface IFsveritySetupAuthToken { // Using an interface here is an easy way to create and maintain an IBinder object across // the processes. When installd creates this binder object, it stores the file stat // privately for later authentication, and only returns the reference to the caller process. // Once the binder object has no reference count, it gets destructed automatically // (alternatively, installd can maintain an internal mapping, but it is more error prone // because the app may crash and not finish the fs-verity setup, keeping the memory unused // forever). // // We don't necessarily need a method here, so it's left blank intentionally. } IFsveritySetupAuthToken createFsveritySetupAuthToken(in ParcelFileDescriptor authFd, int appUid, int userId); int enableFsverity(in IFsveritySetupAuthToken authToken, @utf8InCpp String filePath, @utf8InCpp String packageName); const int FLAG_STORAGE_DE = 0x1; const int FLAG_STORAGE_CE = 0x2; const int FLAG_STORAGE_EXTERNAL = 0x4; Loading
cmds/installd/tests/installd_service_test.cpp +101 −0 Original line number Diff line number Diff line Loading @@ -42,9 +42,12 @@ #include "binder_test_utils.h" #include "dexopt.h" #include "globals.h" #include "unique_file.h" #include "utils.h" using android::base::StringPrintf; using android::base::unique_fd; using android::os::ParcelFileDescriptor; using std::filesystem::is_empty; namespace android { Loading Loading @@ -136,6 +139,16 @@ static int create(const std::string& path, uid_t owner, gid_t group, mode_t mode return fd; } static void create_with_content(const std::string& path, uid_t owner, gid_t group, mode_t mode, const std::string& content) { int fd = ::open(path.c_str(), O_RDWR | O_CREAT, mode); EXPECT_NE(fd, -1); EXPECT_TRUE(android::base::WriteStringToFd(content, fd)); EXPECT_EQ(::fchown(fd, owner, group), 0); EXPECT_EQ(::fchmod(fd, mode), 0); close(fd); } static void touch(const std::string& path, uid_t owner, gid_t group, mode_t mode) { EXPECT_EQ(::close(create(path.c_str(), owner, group, mode)), 0); } Loading Loading @@ -527,6 +540,94 @@ TEST_F(ServiceTest, GetAppSizeWrongSizes) { externalStorageAppId, ceDataInodes, codePaths, &externalStorageSize)); } class FsverityTest : public ServiceTest { protected: binder::Status createFsveritySetupAuthToken(const std::string& path, int open_mode, sp<IFsveritySetupAuthToken>* _aidl_return) { unique_fd ufd(open(path.c_str(), open_mode)); EXPECT_GE(ufd.get(), 0) << "open failed: " << strerror(errno); ParcelFileDescriptor rfd(std::move(ufd)); return service->createFsveritySetupAuthToken(std::move(rfd), kTestAppId, kTestUserId, _aidl_return); } }; TEST_F(FsverityTest, enableFsverity) { const std::string path = kTestPath + "/foo"; create_with_content(path, kTestAppUid, kTestAppUid, 0600, "content"); UniqueFile raii(/*fd=*/-1, path, [](const std::string& path) { unlink(path.c_str()); }); // Expect to fs-verity setup to succeed sp<IFsveritySetupAuthToken> authToken; binder::Status status = createFsveritySetupAuthToken(path, O_RDWR, &authToken); EXPECT_TRUE(status.isOk()); EXPECT_TRUE(authToken != nullptr); // Verity auth token works to enable fs-verity int32_t errno_local; status = service->enableFsverity(authToken, path, "fake.package.name", &errno_local); EXPECT_TRUE(status.isOk()); EXPECT_EQ(errno_local, 0); } TEST_F(FsverityTest, enableFsverity_nullAuthToken) { const std::string path = kTestPath + "/foo"; create_with_content(path, kTestAppUid, kTestAppUid, 0600, "content"); UniqueFile raii(/*fd=*/-1, path, [](const std::string& path) { unlink(path.c_str()); }); // Verity null auth token fails sp<IFsveritySetupAuthToken> authToken; int32_t errno_local; binder::Status status = service->enableFsverity(authToken, path, "fake.package.name", &errno_local); EXPECT_FALSE(status.isOk()); } TEST_F(FsverityTest, enableFsverity_differentFile) { const std::string path = kTestPath + "/foo"; create_with_content(path, kTestAppUid, kTestAppUid, 0600, "content"); UniqueFile raii(/*fd=*/-1, path, [](const std::string& path) { unlink(path.c_str()); }); // Expect to fs-verity setup to succeed sp<IFsveritySetupAuthToken> authToken; binder::Status status = createFsveritySetupAuthToken(path, O_RDWR, &authToken); EXPECT_TRUE(status.isOk()); EXPECT_TRUE(authToken != nullptr); // Verity auth token does not work for a different file const std::string anotherPath = kTestPath + "/bar"; ASSERT_TRUE(android::base::WriteStringToFile("content", anotherPath)); UniqueFile raii2(/*fd=*/-1, anotherPath, [](const std::string& path) { unlink(path.c_str()); }); int32_t errno_local; status = service->enableFsverity(authToken, anotherPath, "fake.package.name", &errno_local); EXPECT_TRUE(status.isOk()); EXPECT_NE(errno_local, 0); } TEST_F(FsverityTest, createFsveritySetupAuthToken_ReadonlyFdDoesNotAuthenticate) { const std::string path = kTestPath + "/foo"; create_with_content(path, kTestAppUid, kTestAppUid, 0600, "content"); UniqueFile raii(/*fd=*/-1, path, [](const std::string& path) { unlink(path.c_str()); }); // Expect the fs-verity setup to fail sp<IFsveritySetupAuthToken> authToken; binder::Status status = createFsveritySetupAuthToken(path, O_RDONLY, &authToken); EXPECT_FALSE(status.isOk()); } TEST_F(FsverityTest, createFsveritySetupAuthToken_UnownedFile) { const std::string path = kTestPath + "/foo"; // Simulate world-writable file owned by another app create_with_content(path, kTestAppUid + 1, kTestAppUid + 1, 0666, "content"); UniqueFile raii(/*fd=*/-1, path, [](const std::string& path) { unlink(path.c_str()); }); // Expect the fs-verity setup to fail sp<IFsveritySetupAuthToken> authToken; binder::Status status = createFsveritySetupAuthToken(path, O_RDWR, &authToken); EXPECT_FALSE(status.isOk()); } static bool mkdirs(const std::string& path, mode_t mode) { struct stat sb; if (stat(path.c_str(), &sb) != -1 && S_ISDIR(sb.st_mode)) { Loading