Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9dc2e6bf authored by Treehugger Robot's avatar Treehugger Robot Committed by Automerger Merge Worker
Browse files

Merge "installd: allow enabling fs-verity to a given file" into main am: 81b651b9

parents 0f14f52f 81b651b9
Loading
Loading
Loading
Loading
+126 −0
Original line number Diff line number Diff line
@@ -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>
@@ -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>
@@ -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 {
@@ -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()) {                                   \
@@ -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

/**
@@ -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();
@@ -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
+26 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@
#define COMMANDS_H_

#include <inttypes.h>
#include <sys/stat.h>
#include <unistd.h>

#include <shared_mutex>
@@ -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;
@@ -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;
+16 −0
Original line number Diff line number Diff line
@@ -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;
+101 −0
Original line number Diff line number Diff line
@@ -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 {
@@ -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);
}
@@ -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)) {