blob: 5af3bcf11965ca212b9bb434f4020d5296215a48 [file] [log] [blame]
// 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 <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_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_METHOD0(Start, bool());
MOCK_METHOD0(Wait, int());
};
class FUSEMounterForTesting : public FUSEMounter {
public:
FUSEMounterForTesting(const Platform* platform, bool unprivileged)
: FUSEMounter(kSomeSource,
kMountDir,
kFUSEType,
MountOptions(),
platform,
kMountProgram,
kMountUser,
{},
{},
false,
unprivileged) {}
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, Start()).WillByDefault(Return(true));
ON_CALL(*mock, Wait()).WillByDefault(Invoke([raw_ptr, this]() {
return InvokeMountTool(raw_ptr->arguments());
}));
return mock;
}
};
} // namespace
TEST(FUSEMounterTest, Sandboxing) {
MockFUSEPlatform platform;
FUSEMounterForTesting mounter(&platform, false);
EXPECT_CALL(mounter, InvokeMountTool(ElementsAre(kMountProgram, "-o",
MountOptions().ToString(),
kSomeSource, kMountDir)))
.WillOnce(Return(0));
// Expectations for sandbox setup.
EXPECT_CALL(platform, Mount(_, _, _, _, _)).Times(0);
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, getuid(), kMountGID))
.WillOnce(Return(true));
EXPECT_CALL(platform, SetPermissions(kMountDir, S_IRWXU | S_IRWXG))
.WillOnce(Return(true));
EXPECT_EQ(MOUNT_ERROR_NONE, mounter.Mount());
}
TEST(FUSEMounterTest, Sandboxing_Unprivileged) {
MockFUSEPlatform platform;
FUSEMounterForTesting mounter(&platform, true);
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_EQ(MOUNT_ERROR_NONE, mounter.Mount());
}
TEST(FUSEMounterTest, Sandboxing_PermissionDenied) {
MockFUSEPlatform platform;
FUSEMounterForTesting mounter(&platform, false);
EXPECT_CALL(platform, SetOwnership(kMountDir, getuid(), kMountGID))
.WillOnce(Return(false));
EXPECT_CALL(mounter, InvokeMountTool(_)).Times(0);
EXPECT_EQ(MOUNT_ERROR_INSUFFICIENT_PERMISSIONS, mounter.Mount());
}
TEST(FUSEMounterTest, AppFailed) {
MockFUSEPlatform platform;
FUSEMounterForTesting mounter(&platform, false);
EXPECT_CALL(mounter, InvokeMountTool(_)).WillOnce(Return(1));
EXPECT_EQ(MOUNT_ERROR_MOUNT_PROGRAM_FAILED, mounter.Mount());
}
} // namespace cros_disks