blob: 616ed1f95574200116c5a3ac8f1f41d6717f8bb0 [file] [log] [blame]
// Copyright 2018 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/drivefs_helper.h"
#include <sys/mount.h>
#include <base/strings/string_util.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "cros-disks/fuse_mounter.h"
#include "cros-disks/mount_options.h"
#include "cros-disks/platform.h"
#include "cros-disks/uri.h"
namespace cros_disks {
namespace {
using testing::_;
using testing::DoAll;
using testing::EndsWith;
using testing::Eq;
using testing::HasSubstr;
using testing::Invoke;
using testing::Return;
using testing::SetArgPointee;
using testing::StrEq;
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 kSomeDirPath[] = "/foo/bar";
// Mock Platform implementation for testing.
class MockPlatform : public Platform {
public:
MockPlatform() {
ON_CALL(*this, GetUserAndGroupId(_, _, _))
.WillByDefault(Invoke(this, &MockPlatform::GetUserAndGroupIdImpl));
ON_CALL(*this, GetGroupId(_, _))
.WillByDefault(Invoke(this, &MockPlatform::GetGroupIdImpl));
ON_CALL(*this, GetRealPath(_, _))
.WillByDefault(Invoke(this, &MockPlatform::GetRealPathImpl));
ON_CALL(*this, IsDirectoryEmpty(_)).WillByDefault(Return(true));
ON_CALL(*this, DirectoryExists(_)).WillByDefault(Return(true));
ON_CALL(*this, PathExists(EndsWith("-seccomp.policy")))
.WillByDefault(Return(false));
}
MOCK_CONST_METHOD2(GetRealPath, bool(const std::string&, std::string*));
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,
bool(const std::string&,
const std::string&,
const std::string&,
MountOptions::Flags,
const std::string&));
MOCK_CONST_METHOD1(PathExists, bool(const std::string& path));
MOCK_CONST_METHOD1(DirectoryExists, bool(const std::string& path));
MOCK_CONST_METHOD1(IsDirectoryEmpty, bool(const std::string& path));
MOCK_CONST_METHOD1(CreateDirectory, 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 GetRealPathImpl(const std::string& path, std::string* real_path) const {
*real_path = "/foo/bar";
return true;
}
bool GetUserAndGroupIdImpl(const std::string& user,
uid_t* user_id,
gid_t* group_id) const {
if (user == FUSEHelper::kFilesUser) {
if (user_id)
*user_id = kFilesUID;
if (group_id)
*group_id = kFilesGID;
return true;
}
if (user == "fuse-drivefs") {
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 == FUSEHelper::kFilesGroup) {
if (group_id)
*group_id = kFilesAccessGID;
return true;
}
return false;
}
};
class TestDrivefsHelper : public DrivefsHelper {
public:
explicit TestDrivefsHelper(const Platform* platform)
: DrivefsHelper(platform) {
ON_CALL(*this, SetupDirectoryForFUSEAccess(_))
.WillByDefault(Invoke(
this,
&TestDrivefsHelper::ForwardSetupDirectoryForFUSEAccessToImpl));
}
MOCK_CONST_METHOD1(SetupDirectoryForFUSEAccess,
bool(const base::FilePath& dirpath));
private:
bool ForwardSetupDirectoryForFUSEAccessToImpl(const base::FilePath& path) {
return DrivefsHelper::SetupDirectoryForFUSEAccess(path);
}
};
class DrivefsHelperTest : public ::testing::Test {
public:
DrivefsHelperTest() : helper_(&platform_) {}
protected:
bool SetupDirectoryForFUSEAccess(const std::string& dir) {
return helper_.SetupDirectoryForFUSEAccess(base::FilePath(dir));
}
MockPlatform platform_;
TestDrivefsHelper helper_;
};
TEST_F(DrivefsHelperTest, CreateMounter) {
EXPECT_CALL(helper_, SetupDirectoryForFUSEAccess(base::FilePath("/foo/bar")))
.WillOnce(Return(true));
auto mounter = helper_.CreateMounter(
base::FilePath("/tmp/working_dir"), Uri::Parse("drivefs://id"),
base::FilePath("/media/fuse/drivefs/id"),
{"rw", "datadir=/foo//bar/./", "datadir=/ignored/second/datadir/value"});
ASSERT_TRUE(mounter);
EXPECT_EQ("drivefs", mounter->filesystem_type());
EXPECT_TRUE(mounter->source_path().empty());
EXPECT_EQ("/media/fuse/drivefs/id", mounter->target_path());
auto options_string = mounter->mount_options().ToString();
EXPECT_THAT(options_string, HasSubstr("datadir=/foo/bar"));
EXPECT_THAT(options_string, HasSubstr("identity=id"));
EXPECT_THAT(options_string, HasSubstr("rw"));
EXPECT_THAT(options_string, HasSubstr("uid=700"));
EXPECT_THAT(options_string, HasSubstr("gid=1501"));
}
TEST_F(DrivefsHelperTest, CreateMounter_CreateDataDir) {
EXPECT_CALL(platform_, DirectoryExists("/foo//bar/")).WillOnce(Return(false));
EXPECT_CALL(platform_, GetRealPath("/foo", _))
.WillOnce(DoAll(SetArgPointee<1>("/foo"), Return(true)));
EXPECT_CALL(helper_, SetupDirectoryForFUSEAccess(base::FilePath("/foo/bar")))
.WillOnce(Return(true));
auto mounter = helper_.CreateMounter(
base::FilePath("/tmp/working_dir"), Uri::Parse("drivefs://id"),
base::FilePath("/media/fuse/drivefs/id"),
{"rw", "datadir=/foo//bar/", "datadir=/ignored/second/datadir/value"});
ASSERT_TRUE(mounter);
EXPECT_EQ("drivefs", mounter->filesystem_type());
EXPECT_TRUE(mounter->source_path().empty());
EXPECT_EQ("/media/fuse/drivefs/id", mounter->target_path());
auto options_string = mounter->mount_options().ToString();
EXPECT_THAT(options_string, HasSubstr("datadir=/foo/bar"));
EXPECT_THAT(options_string, HasSubstr("identity=id"));
EXPECT_THAT(options_string, HasSubstr("rw"));
EXPECT_THAT(options_string, HasSubstr("uid=700"));
EXPECT_THAT(options_string, HasSubstr("gid=1501"));
}
TEST_F(DrivefsHelperTest, CreateMounter_GetUserAndGroupIdFails) {
EXPECT_CALL(platform_, GetUserAndGroupId(_, _, _)).WillOnce(Return(false));
EXPECT_CALL(helper_, SetupDirectoryForFUSEAccess(base::FilePath("/foo/bar")))
.Times(0);
EXPECT_FALSE(helper_.CreateMounter(
base::FilePath("/tmp/working_dir"), Uri::Parse("drivefs://id"),
base::FilePath("/media/fuse/drivefs/id"), {"rw", "datadir=/foo/bar"}));
}
TEST_F(DrivefsHelperTest, CreateMounter_GetAndGroupIdFails) {
EXPECT_CALL(platform_, GetGroupId(_, _)).WillOnce(Return(false));
EXPECT_CALL(helper_, SetupDirectoryForFUSEAccess(base::FilePath("/foo/bar")))
.Times(0);
EXPECT_FALSE(helper_.CreateMounter(
base::FilePath("/tmp/working_dir"), Uri::Parse("drivefs://id"),
base::FilePath("/media/fuse/drivefs/id"), {"rw", "datadir=/foo/bar"}));
}
TEST_F(DrivefsHelperTest, CreateMounter_GetRealPathFails_DirectoryExists) {
EXPECT_CALL(platform_, GetRealPath("/foo/bar", _)).WillOnce(Return(false));
EXPECT_CALL(helper_, SetupDirectoryForFUSEAccess(_)).Times(0);
EXPECT_FALSE(helper_.CreateMounter(
base::FilePath("/tmp/working_dir"), Uri::Parse("drivefs://id"),
base::FilePath("/media/fuse/drivefs/id"), {"rw", "datadir=/foo/bar"}));
}
TEST_F(DrivefsHelperTest, CreateMounter_GetRealPathFails_DirectoryDoesntExist) {
EXPECT_CALL(platform_, DirectoryExists("/foo/bar")).WillOnce(Return(false));
EXPECT_CALL(platform_, GetRealPath("/foo", _)).WillOnce(Return(false));
EXPECT_CALL(platform_, GetGroupId(_, _)).Times(0);
EXPECT_CALL(helper_, SetupDirectoryForFUSEAccess(_)).Times(0);
EXPECT_FALSE(helper_.CreateMounter(
base::FilePath("/tmp/working_dir"), Uri::Parse("drivefs://id"),
base::FilePath("/media/fuse/drivefs/id"), {"rw", "datadir=/foo/bar"}));
}
TEST_F(DrivefsHelperTest, CreateMounter_InvalidPath) {
EXPECT_CALL(helper_, SetupDirectoryForFUSEAccess(_)).Times(0);
for (const auto* path : {"relative/path", "/foo/../bar", ".", ".."}) {
EXPECT_FALSE(helper_.CreateMounter(base::FilePath("/tmp/working_dir"),
Uri::Parse("drivefs://id"),
base::FilePath("/media/fuse/drivefs/id"),
{"rw", "datadir=" + std::string(path)}));
}
}
TEST_F(DrivefsHelperTest, CreateMounter_NoDatadir) {
EXPECT_CALL(helper_, SetupDirectoryForFUSEAccess(_)).Times(0);
EXPECT_FALSE(helper_.CreateMounter(
base::FilePath("/tmp/working_dir"), Uri::Parse("drivefs://id"),
base::FilePath("/media/fuse/drivefs/id"), {"rw"}));
}
TEST_F(DrivefsHelperTest, CreateMounter_SetupDirectoryFails) {
EXPECT_CALL(helper_, SetupDirectoryForFUSEAccess(base::FilePath("/foo/bar")))
.WillOnce(Return(false));
EXPECT_FALSE(helper_.CreateMounter(
base::FilePath("/tmp/working_dir"), Uri::Parse("drivefs://id"),
base::FilePath("/media/fuse/drivefs/id"), {"rw", "datadir=/foo/bar"}));
}
// Verifies that SetupDirectoryForFUSEAccess crashes if path is unsafe.
TEST_F(DrivefsHelperTest, SetupDirectoryForFUSEAccess_UnsafePath) {
EXPECT_DEATH(SetupDirectoryForFUSEAccess("foo"), ".*");
EXPECT_DEATH(SetupDirectoryForFUSEAccess("../foo"), ".*");
EXPECT_DEATH(SetupDirectoryForFUSEAccess("/bar/../foo"), ".*");
EXPECT_DEATH(SetupDirectoryForFUSEAccess("/../foo"), ".*");
}
// Verifies that SetupDirectoryForFUSEAccess creates directory with
// correct access if there was no directory initially.
TEST_F(DrivefsHelperTest, SetupDirectoryForFUSEAccess_NoDir) {
EXPECT_CALL(platform_, DirectoryExists(kSomeDirPath)).WillOnce(Return(false));
EXPECT_CALL(platform_, CreateDirectory(kSomeDirPath)).WillOnce(Return(true));
EXPECT_CALL(platform_, SetPermissions(kSomeDirPath, 0770))
.WillOnce(Return(true));
EXPECT_CALL(platform_, SetOwnership(kSomeDirPath, kMountUID, kFilesAccessGID))
.WillOnce(Return(true));
EXPECT_TRUE(SetupDirectoryForFUSEAccess(kSomeDirPath));
}
// Verifies that SetupDirectoryForFUSEAccess fails if there was no
// directory initially and can't create one.
TEST_F(DrivefsHelperTest, SetupDirectoryForFUSEAccess_NoDir_CantCreate) {
EXPECT_CALL(platform_, DirectoryExists(kSomeDirPath)).WillOnce(Return(false));
EXPECT_CALL(platform_, CreateDirectory(kSomeDirPath)).WillOnce(Return(false));
EXPECT_CALL(platform_, SetPermissions(_, _)).Times(0);
EXPECT_CALL(platform_, SetOwnership(_, _, _)).Times(0);
EXPECT_FALSE(SetupDirectoryForFUSEAccess(kSomeDirPath));
}
// Verifies that SetupDirectoryForFUSEAccess fails if chmod fails.
TEST_F(DrivefsHelperTest, SetupDirectoryForFUSEAccess_NoDir_CantChmod) {
EXPECT_CALL(platform_, DirectoryExists(kSomeDirPath)).WillOnce(Return(false));
EXPECT_CALL(platform_, CreateDirectory(kSomeDirPath)).WillOnce(Return(true));
EXPECT_CALL(platform_, SetPermissions(kSomeDirPath, 0770));
EXPECT_FALSE(SetupDirectoryForFUSEAccess(kSomeDirPath));
}
// Verifies that SetupDirectoryForFUSEAccess fails if can't get attributes
// of an existing directory.
TEST_F(DrivefsHelperTest, SetupDirectoryForFUSEAccess_CantStat) {
EXPECT_CALL(platform_, DirectoryExists(kSomeDirPath)).WillOnce(Return(true));
EXPECT_CALL(platform_, GetOwnership(kSomeDirPath, _, _));
EXPECT_CALL(platform_, SetOwnership(_, _, _)).Times(0);
EXPECT_FALSE(SetupDirectoryForFUSEAccess(kSomeDirPath));
}
// Verifies that SetupDirectoryForFUSEAccess succeeds with shortcut
// if directory already has correct owner.
TEST_F(DrivefsHelperTest, SetupDirectoryForFUSEAccess_Owned) {
EXPECT_CALL(platform_, DirectoryExists(kSomeDirPath)).WillOnce(Return(true));
EXPECT_CALL(platform_, GetOwnership(kSomeDirPath, _, _))
.WillOnce(DoAll(SetArgPointee<1>(kMountUID),
SetArgPointee<2>(kFilesAccessGID), Return(true)));
EXPECT_CALL(platform_, SetOwnership(_, _, _)).Times(0);
EXPECT_TRUE(SetupDirectoryForFUSEAccess(kSomeDirPath));
}
// Verifies that SetupDirectoryForFUSEAccess refuses to chown an existing dir if
// it can't delete it.
TEST_F(DrivefsHelperTest,
SetupDirectoryForFUSEAccess_AlreadyExistsWithWrongOwnerAndCantDelete) {
EXPECT_CALL(platform_, DirectoryExists(kSomeDirPath)).WillOnce(Return(true));
EXPECT_CALL(platform_, GetOwnership(kSomeDirPath, _, _))
.WillOnce(DoAll(SetArgPointee<1>(kFilesUID),
SetArgPointee<2>(kFilesAccessGID), Return(true)));
EXPECT_CALL(platform_, RemoveEmptyDirectory(kSomeDirPath))
.WillOnce(Return(false));
EXPECT_CALL(platform_, SetOwnership(_, _, _)).Times(0);
EXPECT_FALSE(SetupDirectoryForFUSEAccess(kSomeDirPath));
}
// Verifies that SetupDirectoryForFUSEAccess deletes an empty existing dir with
// incorrent owners and then performs the usual no directory exists actions.
TEST_F(DrivefsHelperTest,
SetupDirectoryForFUSEAccess_AlreadyExistsWithWrongOwner) {
EXPECT_CALL(platform_, DirectoryExists(kSomeDirPath)).WillOnce(Return(true));
EXPECT_CALL(platform_, GetOwnership(kSomeDirPath, _, _))
.WillOnce(DoAll(SetArgPointee<1>(kFilesUID),
SetArgPointee<2>(kFilesAccessGID), Return(true)));
EXPECT_CALL(platform_, RemoveEmptyDirectory(kSomeDirPath))
.WillOnce(Return(true));
EXPECT_CALL(platform_, CreateDirectory(kSomeDirPath)).WillOnce(Return(true));
EXPECT_CALL(platform_, SetPermissions(kSomeDirPath, 0770))
.WillOnce(Return(true));
EXPECT_CALL(platform_, SetOwnership(kSomeDirPath, kMountUID, kFilesAccessGID))
.WillOnce(Return(true));
EXPECT_TRUE(SetupDirectoryForFUSEAccess(kSomeDirPath));
}
} // namespace
} // namespace cros_disks