[go: nahoru, domu]

blob: 18a5c2038f7bac6537858aae52a4a4eb061cbfe5 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/crostini/crostini_sshfs.h"
#include "build/build_config.h"
#include <memory>
#include "base/base64.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "chrome/browser/ash/crostini/crostini_manager.h"
#include "chrome/browser/ash/crostini/crostini_test_helper.h"
#include "chrome/browser/ash/crostini/crostini_util.h"
#include "chrome/browser/ash/file_manager/volume_manager.h"
#include "chrome/browser/ash/file_manager/volume_manager_factory.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/ash/components/dbus/chunneld/chunneld_client.h"
#include "chromeos/ash/components/dbus/cicerone/cicerone_client.h"
#include "chromeos/ash/components/dbus/concierge/concierge_client.h"
#include "chromeos/ash/components/dbus/cros_disks/cros_disks_client.h"
#include "chromeos/ash/components/dbus/seneschal/seneschal_client.h"
#include "chromeos/ash/components/dbus/vm_applications/apps.pb.h"
#include "chromeos/ash/components/disks/disk_mount_manager.h"
#include "chromeos/ash/components/disks/mock_disk_mount_manager.h"
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
#include "content/public/browser/browser_context.h"
#include "content/public/test/browser_task_environment.h"
#include "storage/browser/file_system/external_mount_points.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::ash::disks::DiskMountManager;
using testing::_;
namespace {
const char* kCrostiniMetricMountResultBackground =
"Crostini.Sshfs.Mount.Result.Background";
const char* kCrostiniMetricMountResultUserVisible =
"Crostini.Sshfs.Mount.Result.UserVisible";
const char* kCrostiniMetricMountTimeTaken = "Crostini.Sshfs.Mount.TimeTaken";
const char* kCrostiniMetricUnmount = "Crostini.Sshfs.Unmount.Result";
const char* kCrostiniMetricUnmountTimeTaken =
"Crostini.Sshfs.Unmount.TimeTaken";
// Creates a new VolumeManager for tests.
// By default, VolumeManager KeyedService is null for testing.
std::unique_ptr<KeyedService> BuildVolumeManager(
content::BrowserContext* context) {
return std::make_unique<file_manager::VolumeManager>(
Profile::FromBrowserContext(context),
nullptr /* drive_integration_service */,
nullptr /* power_manager_client */, DiskMountManager::GetInstance(),
nullptr /* file_system_provider_service */,
file_manager::VolumeManager::GetMtpStorageInfoCallback());
}
} // namespace
namespace crostini {
class CrostiniSshfsHelperTest : public testing::Test {
public:
CrostiniSshfsHelperTest() {
ash::ChunneldClient::InitializeFake();
ash::CiceroneClient::InitializeFake();
ash::ConciergeClient::InitializeFake();
ash::SeneschalClient::InitializeFake();
profile_ = std::make_unique<TestingProfile>();
crostini_test_helper_ =
std::make_unique<CrostiniTestHelper>(profile_.get());
// DiskMountManager::InitializeForTesting takes ownership and works with
// a raw pointer, hence the new with no matching delete.
disk_manager_ = new ash::disks::MockDiskMountManager;
crostini_sshfs_ = std::make_unique<CrostiniSshfs>(profile_.get());
file_manager::VolumeManagerFactory::GetInstance()->SetTestingFactory(
profile_.get(), base::BindRepeating(&BuildVolumeManager));
DiskMountManager::InitializeForTesting(disk_manager_);
std::string known_hosts = base::Base64Encode("[hostname]:2222 pubkey");
std::string identity = base::Base64Encode("privkey");
}
CrostiniSshfsHelperTest(const CrostiniSshfsHelperTest&) = delete;
CrostiniSshfsHelperTest& operator=(const CrostiniSshfsHelperTest&) = delete;
~CrostiniSshfsHelperTest() override {
storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
kMountName);
file_manager::VolumeManagerFactory::GetInstance()->SetTestingFactory(
profile_.get(), BrowserContextKeyedServiceFactory::TestingFactory{});
DiskMountManager::Shutdown();
crostini_sshfs_.reset();
crostini_test_helper_.reset();
profile_.reset();
ash::SeneschalClient::Shutdown();
ash::ConciergeClient::Shutdown();
ash::CiceroneClient::Shutdown();
ash::ChunneldClient::Shutdown();
}
TestingProfile* profile() { return profile_.get(); }
base::test::TaskEnvironment* task_environment() { return &task_environment_; }
void SetUp() override {}
void TearDown() override {}
protected:
void NotifyMountEvent(
const std::string& source_path,
const std::string& source_format,
const std::string& mount_label,
const std::vector<std::string>& mount_options,
ash::MountType type,
ash::MountAccessMode access_mode,
ash::disks::DiskMountManager::MountPathCallback callback) {
auto event = DiskMountManager::MountEvent::MOUNTING;
auto code = ash::MountError::kSuccess;
DiskMountManager::MountPoint info{"sftp://3:1234",
"/media/fuse/" + kMountName,
ash::MountType::kNetworkStorage};
disk_manager_->NotifyMountEvent(event, code, info);
std::move(callback).Run(code, info);
}
void ExpectMountCalls(int n) {
EXPECT_CALL(*disk_manager_, MountPath("sftp://3:1234", "", kMountName,
default_mount_options_,
ash::MountType::kNetworkStorage,
ash::MountAccessMode::kReadWrite, _))
.Times(n)
.WillRepeatedly(
Invoke(this, &CrostiniSshfsHelperTest::NotifyMountEvent));
}
void SetContainerRunning(guest_os::GuestId container) {
auto* manager = CrostiniManager::GetForProfile(profile());
ContainerInfo info(container.container_name, "username", "homedir",
"1.2.3.4", 1234);
manager->AddRunningVmForTesting(container.vm_name, 3);
manager->AddRunningContainerForTesting(container.vm_name, info);
}
content::BrowserTaskEnvironment task_environment_;
raw_ptr<ash::disks::MockDiskMountManager, DanglingUntriaged> disk_manager_;
std::unique_ptr<TestingProfile> profile_;
std::unique_ptr<CrostiniTestHelper> crostini_test_helper_;
const std::string kMountName = "crostini_test_termina_penguin";
std::vector<std::string> default_mount_options_;
std::unique_ptr<file_manager::VolumeManager> volume_manager_;
std::unique_ptr<CrostiniSshfs> crostini_sshfs_;
raw_ptr<CrostiniManager> crostini_manager_;
base::HistogramTester histogram_tester{};
};
// TODO(https://crbug.com/1491372): UAF and other failures in MSAN, ASAN
#if BUILDFLAG(IS_LINUX) && \
(defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER))
#define MAYBE_MountDiskMountsDisk DISABLED_MountDiskMountsDisk
#else
#define MAYBE_MountDiskMountsDisk MountDiskMountsDisk
#endif
TEST_F(CrostiniSshfsHelperTest, MAYBE_MountDiskMountsDisk) {
SetContainerRunning(DefaultContainerId());
ExpectMountCalls(1);
bool result = false;
crostini_sshfs_->MountCrostiniFiles(
DefaultContainerId(),
base::BindLambdaForTesting([&result](bool res) { result = res; }), true);
task_environment_.RunUntilIdle();
EXPECT_TRUE(result);
base::FilePath path;
EXPECT_TRUE(
storage::ExternalMountPoints::GetSystemInstance()->GetRegisteredPath(
kMountName, &path));
EXPECT_EQ(base::FilePath("/media/fuse/" + kMountName), path);
histogram_tester.ExpectUniqueSample(
kCrostiniMetricMountResultBackground,
CrostiniSshfs::CrostiniSshfsResult::kSuccess, 1);
histogram_tester.ExpectTotalCount(kCrostiniMetricMountTimeTaken, 1);
}
TEST_F(CrostiniSshfsHelperTest, FailsIfContainerNotRunning) {
bool result = false;
EXPECT_CALL(*disk_manager_, MountPath).Times(0);
crostini_sshfs_->MountCrostiniFiles(
DefaultContainerId(),
base::BindLambdaForTesting([&result](bool res) { result = res; }), false);
task_environment_.RunUntilIdle();
EXPECT_FALSE(result);
histogram_tester.ExpectUniqueSample(
kCrostiniMetricMountResultUserVisible,
CrostiniSshfs::CrostiniSshfsResult::kContainerNotRunning, 1);
histogram_tester.ExpectTotalCount(kCrostiniMetricMountTimeTaken, 1);
}
// TODO(https://crbug.com/1491372): UAF and other failures in MSAN, ASAN
#if BUILDFLAG(IS_LINUX) && \
(defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER))
#define MAYBE_OnlyDefaultContainerSupported \
DISABLED_OnlyDefaultContainerSupported
#else
#define MAYBE_OnlyDefaultContainerSupported OnlyDefaultContainerSupported
#endif
TEST_F(CrostiniSshfsHelperTest, MAYBE_OnlyDefaultContainerSupported) {
auto not_default =
guest_os::GuestId(kCrostiniDefaultVmType, "vm_name", "container_name");
SetContainerRunning(not_default);
EXPECT_CALL(*disk_manager_, MountPath).Times(0);
bool result = false;
crostini_sshfs_->MountCrostiniFiles(
not_default,
base::BindLambdaForTesting([&result](bool res) { result = res; }), false);
task_environment_.RunUntilIdle();
EXPECT_FALSE(result);
histogram_tester.ExpectUniqueSample(
kCrostiniMetricMountResultUserVisible,
CrostiniSshfs::CrostiniSshfsResult::kNotDefaultContainer, 1);
histogram_tester.ExpectTotalCount(kCrostiniMetricMountTimeTaken, 1);
}
TEST_F(CrostiniSshfsHelperTest, RecordBackgroundMetricIfBackground) {
auto not_default =
guest_os::GuestId(kCrostiniDefaultVmType, "vm_name", "container_name");
SetContainerRunning(not_default);
EXPECT_CALL(*disk_manager_, MountPath).Times(0);
bool result = false;
crostini_sshfs_->MountCrostiniFiles(not_default, base::DoNothing(), true);
task_environment_.RunUntilIdle();
EXPECT_FALSE(result);
histogram_tester.ExpectUniqueSample(
kCrostiniMetricMountResultBackground,
CrostiniSshfs::CrostiniSshfsResult::kNotDefaultContainer, 1);
histogram_tester.ExpectTotalCount(kCrostiniMetricMountResultUserVisible, 0);
}
// TODO(https://crbug.com/1491372): UAF and other failures in MSAN, ASAN
#if BUILDFLAG(IS_LINUX) && \
(defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER))
#define MAYBE_MultipleCallsAreQueuedAndOnlyMountOnce \
DISABLED_MultipleCallsAreQueuedAndOnlyMountOnce
#else
#define MAYBE_MultipleCallsAreQueuedAndOnlyMountOnce \
MultipleCallsAreQueuedAndOnlyMountOnce
#endif
TEST_F(CrostiniSshfsHelperTest, MAYBE_MultipleCallsAreQueuedAndOnlyMountOnce) {
SetContainerRunning(DefaultContainerId());
ExpectMountCalls(1);
int successes = 0;
crostini_sshfs_->MountCrostiniFiles(
DefaultContainerId(),
base::BindLambdaForTesting(
[&successes](bool result) { successes += result; }),
false);
crostini_sshfs_->MountCrostiniFiles(
DefaultContainerId(),
base::BindLambdaForTesting(
[&successes](bool result) { successes += result; }),
false);
task_environment_.RunUntilIdle();
EXPECT_EQ(successes, 2);
base::FilePath path;
EXPECT_TRUE(
storage::ExternalMountPoints::GetSystemInstance()->GetRegisteredPath(
kMountName, &path));
EXPECT_EQ(base::FilePath("/media/fuse/" + kMountName), path);
histogram_tester.ExpectUniqueSample(
kCrostiniMetricMountResultUserVisible,
CrostiniSshfs::CrostiniSshfsResult::kSuccess, 2);
histogram_tester.ExpectTotalCount(kCrostiniMetricMountTimeTaken, 2);
}
// TODO(https://crbug.com/1491372): UAF and other failures in MSAN, ASAN
#if BUILDFLAG(IS_LINUX) && \
(defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER))
#define MAYBE_CanRemountAfterUnmount DISABLED_CanRemountAfterUnmount
#else
#define MAYBE_CanRemountAfterUnmount CanRemountAfterUnmount
#endif
TEST_F(CrostiniSshfsHelperTest, MAYBE_CanRemountAfterUnmount) {
SetContainerRunning(DefaultContainerId());
ExpectMountCalls(2);
EXPECT_CALL(*disk_manager_, UnmountPath)
.WillOnce(testing::Invoke(
[this](const std::string& mount_path,
DiskMountManager::UnmountPathCallback callback) {
EXPECT_EQ(mount_path, "/media/fuse/" + kMountName);
std::move(callback).Run(ash::MountError::kSuccess);
}));
crostini_sshfs_->MountCrostiniFiles(
DefaultContainerId(),
base::BindLambdaForTesting([](bool res) { EXPECT_TRUE(res); }), false);
task_environment_.RunUntilIdle();
crostini_sshfs_->UnmountCrostiniFiles(
DefaultContainerId(),
base::BindLambdaForTesting([](bool res) { EXPECT_TRUE(res); }));
task_environment_.RunUntilIdle();
crostini_sshfs_->MountCrostiniFiles(
DefaultContainerId(),
base::BindLambdaForTesting([](bool res) { EXPECT_TRUE(res); }), false);
task_environment_.RunUntilIdle();
base::FilePath path;
EXPECT_TRUE(
storage::ExternalMountPoints::GetSystemInstance()->GetRegisteredPath(
kMountName, &path));
EXPECT_EQ(base::FilePath("/media/fuse/" + kMountName), path);
histogram_tester.ExpectUniqueSample(
kCrostiniMetricMountResultUserVisible,
CrostiniSshfs::CrostiniSshfsResult::kSuccess, 2);
histogram_tester.ExpectTotalCount(kCrostiniMetricMountTimeTaken, 2);
histogram_tester.ExpectUniqueSample(kCrostiniMetricUnmount, true, 1);
histogram_tester.ExpectTotalCount(kCrostiniMetricUnmountTimeTaken, 1);
}
// TODO(https://crbug.com/1491372): UAF and other failures in MSAN, ASAN
#if BUILDFLAG(IS_LINUX) && \
(defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER))
#define MAYBE_ContainerShutdownClearsMountStatus \
DISABLED_ContainerShutdownClearsMountStatus
#else
#define MAYBE_ContainerShutdownClearsMountStatus \
ContainerShutdownClearsMountStatus
#endif
TEST_F(CrostiniSshfsHelperTest, MAYBE_ContainerShutdownClearsMountStatus) {
SetContainerRunning(DefaultContainerId());
ExpectMountCalls(2);
crostini_sshfs_->MountCrostiniFiles(
DefaultContainerId(),
base::BindLambdaForTesting([](bool res) { EXPECT_TRUE(res); }), false);
task_environment_.RunUntilIdle();
crostini_sshfs_->OnContainerShutdown(DefaultContainerId());
task_environment_.RunUntilIdle();
crostini_sshfs_->MountCrostiniFiles(
DefaultContainerId(),
base::BindLambdaForTesting([](bool res) { EXPECT_TRUE(res); }), true);
task_environment_.RunUntilIdle();
base::FilePath path;
EXPECT_TRUE(
storage::ExternalMountPoints::GetSystemInstance()->GetRegisteredPath(
kMountName, &path));
EXPECT_EQ(base::FilePath("/media/fuse/" + kMountName), path);
}
} // namespace crostini