| // Copyright 2019 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "cros-disks/fuse_mounter.h" |
| |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include <brillo/process_reaper.h> |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| #include "cros-disks/mount_options.h" |
| #include "cros-disks/platform.h" |
| #include "cros-disks/sandboxed_process.h" |
| |
| namespace cros_disks { |
| |
| namespace { |
| |
| using testing::_; |
| using testing::ElementsAre; |
| using testing::Invoke; |
| using testing::Return; |
| using testing::StartsWith; |
| |
| const uid_t kMountUID = 200; |
| const gid_t kMountGID = 201; |
| const uid_t kFilesUID = 700; |
| const uid_t kFilesGID = 701; |
| const uid_t kFilesAccessGID = 1501; |
| const char kMountUser[] = "fuse-fuse"; |
| const char kFUSEType[] = "fuse"; |
| const char kMountProgram[] = "dummy"; |
| const char kSomeSource[] = "/some/src"; |
| const char kMountDir[] = "/mnt"; |
| |
| // Mock Platform implementation for testing. |
| class MockFUSEPlatform : public Platform { |
| public: |
| MockFUSEPlatform() { |
| ON_CALL(*this, GetUserAndGroupId(_, _, _)) |
| .WillByDefault(Invoke(this, &MockFUSEPlatform::GetUserAndGroupIdImpl)); |
| ON_CALL(*this, GetGroupId(_, _)) |
| .WillByDefault(Invoke(this, &MockFUSEPlatform::GetGroupIdImpl)); |
| ON_CALL(*this, PathExists(_)).WillByDefault(Return(true)); |
| ON_CALL(*this, SetOwnership(_, _, _)).WillByDefault(Return(true)); |
| ON_CALL(*this, SetPermissions(_, _)).WillByDefault(Return(true)); |
| } |
| |
| MOCK_CONST_METHOD3(GetUserAndGroupId, |
| bool(const std::string&, uid_t* user_id, gid_t* group_id)); |
| MOCK_CONST_METHOD2(GetGroupId, bool(const std::string&, gid_t* group_id)); |
| MOCK_CONST_METHOD5(Mount, |
| MountErrorType(const std::string&, |
| const std::string&, |
| const std::string&, |
| uint64_t flags, |
| const std::string&)); |
| MOCK_CONST_METHOD2(Unmount, MountErrorType(const std::string&, int flags)); |
| MOCK_CONST_METHOD1(PathExists, bool(const std::string& path)); |
| MOCK_CONST_METHOD1(RemoveEmptyDirectory, bool(const std::string& path)); |
| MOCK_CONST_METHOD3(SetOwnership, |
| bool(const std::string& path, |
| uid_t user_id, |
| gid_t group_id)); |
| MOCK_CONST_METHOD3(GetOwnership, |
| bool(const std::string& path, |
| uid_t* user_id, |
| gid_t* group_id)); |
| MOCK_CONST_METHOD2(SetPermissions, |
| bool(const std::string& path, mode_t mode)); |
| |
| private: |
| bool GetUserAndGroupIdImpl(const std::string& user, |
| uid_t* user_id, |
| gid_t* group_id) const { |
| if (user == "chronos") { |
| if (user_id) |
| *user_id = kFilesUID; |
| if (group_id) |
| *group_id = kFilesGID; |
| return true; |
| } |
| if (user == kMountUser) { |
| if (user_id) |
| *user_id = kMountUID; |
| if (group_id) |
| *group_id = kMountGID; |
| return true; |
| } |
| return false; |
| } |
| |
| bool GetGroupIdImpl(const std::string& group, gid_t* group_id) const { |
| if (group == "chronos-access") { |
| if (group_id) |
| *group_id = kFilesAccessGID; |
| return true; |
| } |
| return false; |
| } |
| }; |
| |
| class MockSandboxedProcess : public SandboxedProcess { |
| public: |
| MockSandboxedProcess() = default; |
| MOCK_METHOD3(StartImpl, |
| pid_t(base::ScopedFD*, base::ScopedFD*, base::ScopedFD*)); |
| MOCK_METHOD0(WaitImpl, int()); |
| MOCK_METHOD1(WaitNonBlockingImpl, bool(int*)); |
| }; |
| |
| class FUSEMounterForTesting : public FUSEMounter { |
| public: |
| FUSEMounterForTesting(const Platform* platform, |
| brillo::ProcessReaper* process_reaper) |
| : FUSEMounter(kSomeSource, |
| kMountDir, |
| kFUSEType, |
| MountOptions(), |
| platform, |
| process_reaper, |
| kMountProgram, |
| kMountUser, |
| {}, |
| {}, |
| false) {} |
| |
| MOCK_CONST_METHOD1(InvokeMountTool, |
| int(const std::vector<std::string>& args)); |
| |
| private: |
| std::unique_ptr<SandboxedProcess> CreateSandboxedProcess() const override { |
| auto mock = std::make_unique<MockSandboxedProcess>(); |
| auto* raw_ptr = mock.get(); |
| ON_CALL(*mock, StartImpl(_, _, _)).WillByDefault(Return(123)); |
| ON_CALL(*mock, WaitImpl()).WillByDefault(Invoke([raw_ptr, this]() { |
| return InvokeMountTool(raw_ptr->arguments()); |
| })); |
| return mock; |
| } |
| }; |
| |
| } // namespace |
| |
| TEST(FUSEMounterTest, Sandboxing_Unprivileged) { |
| MockFUSEPlatform platform; |
| brillo::ProcessReaper process_reaper; |
| FUSEMounterForTesting mounter(&platform, &process_reaper); |
| EXPECT_CALL(mounter, InvokeMountTool(ElementsAre( |
| kMountProgram, "-o", MountOptions().ToString(), |
| kSomeSource, StartsWith("/dev/fd/")))) |
| .WillOnce(Return(0)); |
| |
| // Expectations for sandbox setup. |
| EXPECT_CALL(platform, Mount(kSomeSource, kMountDir, _, _, _)) |
| .WillOnce(Return(MOUNT_ERROR_NONE)); |
| |
| EXPECT_CALL(platform, PathExists(kMountProgram)).WillOnce(Return(true)); |
| EXPECT_CALL(platform, PathExists(kSomeSource)).WillOnce(Return(true)); |
| EXPECT_CALL(platform, SetOwnership(kSomeSource, getuid(), kMountGID)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform, SetPermissions(kSomeSource, |
| S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform, SetOwnership(kMountDir, _, _)).Times(0); |
| EXPECT_CALL(platform, SetPermissions(kMountDir, _)).Times(0); |
| EXPECT_CALL(platform, Unmount(_, _)).Times(0); |
| |
| EXPECT_EQ(MOUNT_ERROR_NONE, mounter.Mount()); |
| } |
| |
| TEST(FUSEMounterTest, AppFailed) { |
| MockFUSEPlatform platform; |
| brillo::ProcessReaper process_reaper; |
| FUSEMounterForTesting mounter(&platform, &process_reaper); |
| |
| EXPECT_CALL(platform, Mount(kSomeSource, kMountDir, _, _, _)) |
| .WillOnce(Return(MOUNT_ERROR_NONE)); |
| EXPECT_CALL(platform, Unmount(_, _)).WillOnce(Return(MOUNT_ERROR_NONE)); |
| EXPECT_CALL(platform, RemoveEmptyDirectory(_)).WillOnce(Return(true)); |
| EXPECT_CALL(mounter, InvokeMountTool(_)).WillOnce(Return(1)); |
| |
| EXPECT_EQ(MOUNT_ERROR_MOUNT_PROGRAM_FAILED, mounter.Mount()); |
| } |
| |
| } // namespace cros_disks |