| // Copyright 2019 The Fuchsia 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 <fuchsia/device/llcpp/fidl.h> |
| #include <fuchsia/fshost/llcpp/fidl.h> |
| #include <lib/devmgr-integration-test/fixture.h> |
| #include <lib/driver-integration-test/fixture.h> |
| #include <lib/fdio/cpp/caller.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fdio/watcher.h> |
| #include <zircon/assert.h> |
| #include <zircon/device/block.h> |
| #include <zircon/hw/gpt.h> |
| |
| #include <string_view> |
| |
| #include <ramdevice-client/ramdisk.h> |
| #include <zxtest/zxtest.h> |
| |
| #include "block-device-interface.h" |
| #include "block-device-manager.h" |
| #include "block-watcher-test-data.h" |
| #include "encrypted-volume-interface.h" |
| |
| namespace devmgr { |
| namespace { |
| |
| using ::devmgr_integration_test::RecursiveWaitForFile; |
| using ::driver_integration_test::IsolatedDevmgr; |
| |
| class MockBlockDevice : public devmgr::BlockDeviceInterface { |
| public: |
| static const std::string& BaseTopologicalPath() { |
| static std::string* path = new std::string("/dev/mock_device/block"); |
| return *path; |
| } |
| |
| struct Options { |
| static Options Default() { return {}; } |
| |
| disk_format_t content_format = DISK_FORMAT_UNKNOWN; |
| std::string_view driver_path; |
| std::string topological_path = BaseTopologicalPath(); |
| std::string partition_name; |
| }; |
| |
| static Options GptOptions() { |
| return {.content_format = DISK_FORMAT_GPT, .driver_path = kGPTDriverPath}; |
| } |
| |
| static Options FvmOptions() { |
| return {.content_format = DISK_FORMAT_FVM, .driver_path = kFVMDriverPath}; |
| } |
| |
| static Options DurableOptions() { |
| return { |
| .topological_path = MockBlockDevice::BaseTopologicalPath() + |
| "/" GPT_DURABLE_NAME "-004/block/zxcrypt/unsealed/block", |
| }; |
| } |
| |
| MockBlockDevice(const Options& options = Options::Default()) : options_(options) {} |
| |
| disk_format_t content_format() const override { return options_.content_format; } |
| const std::string& topological_path() const override { return options_.topological_path; } |
| const std::string& partition_name() const override { return options_.partition_name; } |
| disk_format_t GetFormat() final { return format_; } |
| void SetFormat(disk_format_t format) final { format_ = format; } |
| zx_status_t GetInfo(fuchsia_hardware_block_BlockInfo* out_info) const override { |
| fuchsia_hardware_block_BlockInfo info = {}; |
| info.flags = 0; |
| info.block_size = 512; |
| info.block_count = 1024; |
| *out_info = info; |
| return ZX_OK; |
| } |
| const fuchsia_hardware_block_partition_GUID& GetTypeGuid() const override { |
| ADD_FAILURE("Test should not invoke function %s\n", __FUNCTION__); |
| static fuchsia_hardware_block_partition_GUID null_guid; |
| return null_guid; |
| } |
| zx_status_t AttachDriver(const std::string_view& driver) override { |
| EXPECT_EQ(driver, options_.driver_path); |
| EXPECT_FALSE(attached_); |
| attached_ = true; |
| return ZX_OK; |
| } |
| zx_status_t UnsealZxcrypt() override { |
| ADD_FAILURE("Test should not invoke function %s\n", __FUNCTION__); |
| return ZX_ERR_INTERNAL; |
| } |
| zx_status_t FormatZxcrypt() override { |
| ADD_FAILURE("Test should not invoke function %s\n", __FUNCTION__); |
| return ZX_ERR_INTERNAL; |
| } |
| bool ShouldCheckFilesystems() override { |
| ADD_FAILURE("Test should not invoke function %s\n", __FUNCTION__); |
| return false; |
| } |
| zx_status_t CheckFilesystem() override { |
| ADD_FAILURE("Test should not invoke function %s\n", __FUNCTION__); |
| return ZX_ERR_INTERNAL; |
| } |
| zx_status_t FormatFilesystem() override { |
| ADD_FAILURE("Test should not invoke function %s\n", __FUNCTION__); |
| return ZX_ERR_INTERNAL; |
| } |
| zx_status_t MountFilesystem() override { |
| ADD_FAILURE("Test should not invoke function %s\n", __FUNCTION__); |
| return ZX_ERR_INTERNAL; |
| } |
| zx::status<std::string> VeritySeal() override { |
| ADD_FAILURE("Test should not invoke function %s\n", __FUNCTION__); |
| return zx::error(ZX_ERR_INTERNAL); |
| } |
| zx_status_t OpenBlockVerityForVerifiedRead(std::string seal_hex) override { |
| ADD_FAILURE("Test should not invoke function %s\n", __FUNCTION__); |
| return ZX_ERR_INTERNAL; |
| } |
| bool ShouldAllowAuthoringFactory() override { |
| ADD_FAILURE("Test should not invoke function %s\n", __FUNCTION__); |
| return false; |
| } |
| |
| bool attached() const { return attached_; } |
| |
| private: |
| const Options options_; |
| disk_format_t format_ = DISK_FORMAT_UNKNOWN; |
| bool attached_ = false; |
| }; |
| |
| class BlockVerityDevice : public MockBlockDevice { |
| public: |
| static Options VerityOptions() { |
| return Options{.driver_path = kBlockVerityDriverPath, |
| .topological_path = BaseTopologicalPath() + "/factory-001/block", |
| .partition_name = "factory"}; |
| } |
| |
| BlockVerityDevice(bool allow_authoring, const Options& options = VerityOptions()) |
| : MockBlockDevice(options), allow_authoring_(allow_authoring) {} |
| const fuchsia_hardware_block_partition_GUID& GetTypeGuid() const final { |
| static fuchsia_hardware_block_partition_GUID guid = GPT_FACTORY_TYPE_GUID; |
| return guid; |
| } |
| bool ShouldAllowAuthoringFactory() override { return allow_authoring_; } |
| |
| private: |
| const bool allow_authoring_; |
| }; |
| |
| static constexpr char kFakeSeal[] = |
| "0000000000000000000000000000000000000000000000000000000000000000"; |
| |
| class SealedBlockVerityDevice : public BlockVerityDevice { |
| public: |
| SealedBlockVerityDevice() : BlockVerityDevice(/*allow_authoring=*/false) {} |
| |
| zx::status<std::string> VeritySeal() final { return zx::ok(std::string(kFakeSeal)); } |
| zx_status_t OpenBlockVerityForVerifiedRead(std::string seal_hex) final { |
| EXPECT_STR_EQ(kFakeSeal, seal_hex); |
| opened_ = true; |
| return ZX_OK; |
| } |
| bool opened() const { return opened_; } |
| |
| private: |
| bool opened_ = false; |
| }; |
| |
| class FactoryfsDevice : public MockBlockDevice { |
| public: |
| static Options FactoryfsOptions() { |
| return Options{ |
| .topological_path = BaseTopologicalPath() + "/factory-001/block/verity/verified/block", |
| }; |
| } |
| |
| FactoryfsDevice() : MockBlockDevice(FactoryfsOptions()) {} |
| |
| const fuchsia_hardware_block_partition_GUID& GetTypeGuid() const final { |
| static fuchsia_hardware_block_partition_GUID guid = GPT_FACTORY_TYPE_GUID; |
| return guid; |
| } |
| zx_status_t CheckFilesystem() override { |
| checked_ = true; |
| return ZX_OK; |
| } |
| zx_status_t FormatFilesystem() override { |
| formatted_ = true; |
| return ZX_OK; |
| } |
| zx_status_t MountFilesystem() override { |
| mounted_ = true; |
| return ZX_OK; |
| } |
| bool checked() const { return checked_; } |
| bool formatted() const { return formatted_; } |
| bool mounted() const { return mounted_; } |
| |
| private: |
| bool checked_ = false; |
| bool formatted_ = false; |
| bool mounted_ = false; |
| }; |
| |
| class BlobDevice : public MockBlockDevice { |
| public: |
| static Options BlobfsOptions() { |
| return { |
| .topological_path = MockBlockDevice::BaseTopologicalPath() + "/fvm/blobfs-p-1/block", |
| .partition_name = "blobfs", |
| }; |
| } |
| |
| BlobDevice() : MockBlockDevice(BlobfsOptions()) {} |
| |
| const fuchsia_hardware_block_partition_GUID& GetTypeGuid() const override { |
| static fuchsia_hardware_block_partition_GUID guid = GUID_BLOB_VALUE; |
| return guid; |
| } |
| zx_status_t CheckFilesystem() override { |
| checked_ = true; |
| return ZX_OK; |
| } |
| zx_status_t FormatFilesystem() override { |
| formatted_ = true; |
| return ZX_OK; |
| } |
| zx_status_t MountFilesystem() override { |
| mounted_ = true; |
| return ZX_OK; |
| } |
| bool checked() const { return checked_; } |
| bool formatted() const { return formatted_; } |
| bool mounted() const { return mounted_; } |
| |
| private: |
| bool checked_ = false; |
| bool formatted_ = false; |
| bool mounted_ = false; |
| }; |
| |
| class ZxcryptDevice : public MockBlockDevice { |
| public: |
| static Options ZxcryptOptions() { |
| return { |
| .content_format = DISK_FORMAT_ZXCRYPT, |
| .driver_path = kZxcryptDriverPath, |
| .topological_path = MockBlockDevice::BaseTopologicalPath() + "/fvm/minfs-p-2/block", |
| .partition_name = "minfs", |
| }; |
| } |
| |
| ZxcryptDevice(const Options& options = ZxcryptOptions()) : MockBlockDevice(options) {} |
| |
| const fuchsia_hardware_block_partition_GUID& GetTypeGuid() const override { |
| static fuchsia_hardware_block_partition_GUID guid = GUID_DATA_VALUE; |
| return guid; |
| } |
| zx_status_t FormatZxcrypt() final { |
| formatted_zxcrypt_ = true; |
| return ZX_OK; |
| } |
| zx_status_t UnsealZxcrypt() final { return ZX_OK; } |
| bool formatted_zxcrypt() const { return formatted_zxcrypt_; } |
| |
| private: |
| bool formatted_zxcrypt_ = false; |
| }; |
| |
| class MinfsDevice : public MockBlockDevice { |
| public: |
| static Options MinfsOptions() { |
| return { |
| .topological_path = |
| MockBlockDevice::BaseTopologicalPath() + "/fvm/minfs-p-2/block/zxcrypt/unsealed/block", |
| }; |
| } |
| |
| MinfsDevice(Options options = MinfsOptions()) : MockBlockDevice(options) {} |
| |
| const fuchsia_hardware_block_partition_GUID& GetTypeGuid() const final { |
| static fuchsia_hardware_block_partition_GUID guid = GUID_DATA_VALUE; |
| return guid; |
| } |
| zx_status_t CheckFilesystem() override { |
| checked_ = true; |
| return ZX_OK; |
| } |
| zx_status_t FormatFilesystem() override { |
| formatted_ = true; |
| return ZX_OK; |
| } |
| zx_status_t MountFilesystem() override { |
| mounted_ = true; |
| return ZX_OK; |
| } |
| bool checked() const { return checked_; } |
| bool formatted() const { return formatted_; } |
| bool mounted() const { return mounted_; } |
| |
| private: |
| bool checked_ = false; |
| bool formatted_ = false; |
| bool mounted_ = false; |
| }; |
| |
| BlockDeviceManager::Options TestOptions() { return BlockDeviceManager::DefaultOptions(); } |
| |
| // Tests adding a device which has an unknown format. |
| TEST(AddDeviceTestCase, AddUnknownDevice) { |
| MockBlockDevice device; |
| BlockDeviceManager manager(TestOptions()); |
| EXPECT_EQ(ZX_ERR_NOT_SUPPORTED, manager.AddDevice(device)); |
| } |
| |
| // Tests adding a device which is smaller than the expected header size |
| TEST(AddDeviceTestCase, AddSmallDevice) { |
| class SmallDevice : public MockBlockDevice { |
| public: |
| zx_status_t GetInfo(fuchsia_hardware_block_BlockInfo* out_info) const override { |
| fuchsia_hardware_block_BlockInfo info = {}; |
| info.flags = 0; |
| info.block_size = 512; |
| info.block_count = 1; |
| *out_info = info; |
| return ZX_OK; |
| } |
| }; |
| SmallDevice device; |
| BlockDeviceManager manager(TestOptions()); |
| EXPECT_EQ(ZX_ERR_NOT_SUPPORTED, manager.AddDevice(device)); |
| } |
| |
| // Tests adding a device with a GPT format. |
| TEST(AddDeviceTestCase, AddGPTDevice) { |
| MockBlockDevice device(MockBlockDevice::GptOptions()); |
| BlockDeviceManager manager(TestOptions()); |
| EXPECT_OK(manager.AddDevice(device)); |
| EXPECT_TRUE(device.attached()); |
| } |
| |
| // Tests adding a device with an FVM format. |
| TEST(AddDeviceTestCase, AddFVMDevice) { |
| BlockDeviceManager manager(TestOptions()); |
| MockBlockDevice device(MockBlockDevice::FvmOptions()); |
| EXPECT_OK(manager.AddDevice(device)); |
| EXPECT_TRUE(device.attached()); |
| } |
| |
| // Tests adding a device with an MBR format. |
| TEST(AddDeviceTestCase, AddMBRDevice) { |
| auto options = TestOptions(); |
| options.options.emplace(BlockDeviceManager::Options::kMbr); |
| BlockDeviceManager manager(options); |
| MockBlockDevice device(MockBlockDevice::Options{ |
| .content_format = DISK_FORMAT_MBR, |
| .driver_path = kMBRDriverPath, |
| }); |
| EXPECT_OK(manager.AddDevice(device)); |
| EXPECT_TRUE(device.attached()); |
| } |
| |
| TEST(AddDeviceTestCase, AddBlockVerityDevice) { |
| BlockDeviceManager manager(TestOptions()); |
| MockBlockDevice gpt_device(MockBlockDevice::GptOptions()); |
| EXPECT_OK(manager.AddDevice(gpt_device)); |
| BlockVerityDevice device(/*allow_authoring=*/true); |
| EXPECT_OK(manager.AddDevice(device)); |
| EXPECT_TRUE(device.attached()); |
| } |
| |
| TEST(AddDeviceTestCase, NonFactoryBlockVerityDeviceNotAttached) { |
| BlockDeviceManager manager(TestOptions()); |
| MockBlockDevice gpt_device(MockBlockDevice::GptOptions()); |
| EXPECT_OK(manager.AddDevice(gpt_device)); |
| MockBlockDevice::Options options = BlockVerityDevice::VerityOptions(); |
| options.partition_name = "not-factory"; |
| BlockVerityDevice device(/*allow_authoring=*/true, options); |
| EXPECT_EQ(manager.AddDevice(device), ZX_ERR_NOT_SUPPORTED); |
| EXPECT_FALSE(device.attached()); |
| } |
| |
| // Tests adding a device with the block-verity disk format |
| TEST(AddDeviceTestCase, AddFormattedBlockVerityDevice) { |
| BlockDeviceManager manager(TestOptions()); |
| MockBlockDevice gpt_device(MockBlockDevice::GptOptions()); |
| EXPECT_OK(manager.AddDevice(gpt_device)); |
| SealedBlockVerityDevice device; |
| EXPECT_OK(manager.AddDevice(device)); |
| EXPECT_TRUE(device.attached()); |
| EXPECT_TRUE(device.opened()); |
| } |
| |
| // Tests adding a device with block-verity format but no seal provided by |
| // bootloader |
| TEST(AddDeviceTestCase, AddFormattedBlockVerityDeviceWithoutSeal) { |
| class BlockVerityDeviceWithNoSeal : public BlockVerityDevice { |
| public: |
| BlockVerityDeviceWithNoSeal() : BlockVerityDevice(/*allow_authoring=*/false) {} |
| |
| zx::status<std::string> VeritySeal() final { |
| seal_read_ = true; |
| return zx::error_status(ZX_ERR_NOT_FOUND); |
| } |
| zx_status_t OpenBlockVerityForVerifiedRead(std::string seal_hex) final { |
| ADD_FATAL_FAILURE("Should not call OpenBlockVerityForVerifiedRead"); |
| return ZX_OK; |
| } |
| bool seal_read() const { return seal_read_; } |
| |
| private: |
| bool seal_read_ = false; |
| }; |
| BlockDeviceManager manager(TestOptions()); |
| MockBlockDevice gpt_device(MockBlockDevice::GptOptions()); |
| EXPECT_OK(manager.AddDevice(gpt_device)); |
| BlockVerityDeviceWithNoSeal device; |
| EXPECT_EQ(ZX_ERR_NOT_FOUND, manager.AddDevice(device)); |
| EXPECT_TRUE(device.attached()); |
| EXPECT_TRUE(device.seal_read()); |
| } |
| |
| // Tests adding a device with block-verity format while in factory authoring mode |
| TEST(AddDeviceTestCase, AddFormattedBlockVerityDeviceInAuthoringMode) { |
| class BlockVerityDeviceInAuthoringMode : public BlockVerityDevice { |
| public: |
| BlockVerityDeviceInAuthoringMode() : BlockVerityDevice(/*allow_authoring=*/true) {} |
| |
| zx::status<std::string> VeritySeal() final { |
| ADD_FATAL_FAILURE("Should not call VeritySeal"); |
| return zx::error_status(ZX_ERR_NOT_FOUND); |
| } |
| zx_status_t OpenBlockVerityForVerifiedRead(std::string seal_hex) final { |
| ADD_FATAL_FAILURE("Should not call OpenBlockVerityForVerifiedRead"); |
| return ZX_OK; |
| } |
| }; |
| BlockDeviceManager manager(TestOptions()); |
| MockBlockDevice gpt_device(MockBlockDevice::GptOptions()); |
| EXPECT_OK(manager.AddDevice(gpt_device)); |
| BlockVerityDeviceInAuthoringMode device; |
| EXPECT_OK(manager.AddDevice(device)); |
| EXPECT_TRUE(device.attached()); |
| } |
| |
| // Tests adding blobfs which does not not have a valid type GUID. |
| TEST(AddDeviceTestCase, AddNoGUIDBlobDevice) { |
| class BlobDeviceWithInvalidTypeGuid : public BlobDevice { |
| public: |
| const fuchsia_hardware_block_partition_GUID& GetTypeGuid() const final { |
| static fuchsia_hardware_block_partition_GUID guid = GUID_TEST_VALUE; |
| return guid; |
| } |
| }; |
| |
| BlockDeviceManager manager(TestOptions()); |
| MockBlockDevice fvm_device(MockBlockDevice::FvmOptions()); |
| EXPECT_OK(manager.AddDevice(fvm_device)); |
| BlobDeviceWithInvalidTypeGuid device; |
| EXPECT_EQ(ZX_ERR_NOT_SUPPORTED, manager.AddDevice(device)); |
| EXPECT_FALSE(device.mounted()); |
| } |
| |
| // Tests adding blobfs with a valid type GUID, but invalid metadata. |
| TEST(AddDeviceTestCase, AddInvalidBlobDevice) { |
| class BlobDeviceWithInvalidMetadata : public BlobDevice { |
| public: |
| zx_status_t CheckFilesystem() final { |
| BlobDevice::CheckFilesystem(); |
| return ZX_ERR_BAD_STATE; |
| } |
| }; |
| BlockDeviceManager manager(TestOptions()); |
| MockBlockDevice fvm_device(MockBlockDevice::FvmOptions()); |
| EXPECT_OK(manager.AddDevice(fvm_device)); |
| BlobDeviceWithInvalidMetadata device; |
| EXPECT_EQ(ZX_ERR_BAD_STATE, manager.AddDevice(device)); |
| EXPECT_TRUE(device.checked()); |
| EXPECT_FALSE(device.formatted()); |
| EXPECT_FALSE(device.mounted()); |
| } |
| |
| // Tests adding blobfs with a valid type GUID and valid metadata. |
| TEST(AddDeviceTestCase, AddValidBlobDevice) { |
| BlockDeviceManager manager(TestOptions()); |
| MockBlockDevice fvm_device(MockBlockDevice::FvmOptions()); |
| EXPECT_OK(manager.AddDevice(fvm_device)); |
| BlobDevice device; |
| EXPECT_OK(manager.AddDevice(device)); |
| EXPECT_TRUE(device.checked()); |
| EXPECT_FALSE(device.formatted()); |
| EXPECT_TRUE(device.mounted()); |
| } |
| |
| TEST(AddDeviceTestCase, NetbootingDoesNotMountBlobfs) { |
| auto options = TestOptions(); |
| options.options.emplace(BlockDeviceManager::Options::kNetboot); |
| BlockDeviceManager manager(options); |
| MockBlockDevice fvm_device(MockBlockDevice::FvmOptions()); |
| EXPECT_OK(manager.AddDevice(fvm_device)); |
| BlobDevice device; |
| EXPECT_EQ(ZX_ERR_NOT_SUPPORTED, manager.AddDevice(device)); |
| EXPECT_FALSE(device.mounted()); |
| } |
| |
| // Tests adding minfs which does not not have a valid type GUID. |
| TEST(AddDeviceTestCase, AddNoGUIDMinfsDevice) { |
| class MinfsDeviceWithInvalidGuid : public MockBlockDevice { |
| public: |
| using MockBlockDevice::MockBlockDevice; |
| |
| const fuchsia_hardware_block_partition_GUID& GetTypeGuid() const final { |
| static fuchsia_hardware_block_partition_GUID guid = GUID_TEST_VALUE; |
| return guid; |
| } |
| }; |
| BlockDeviceManager manager(TestOptions()); |
| MockBlockDevice fvm_device(MockBlockDevice::FvmOptions()); |
| EXPECT_OK(manager.AddDevice(fvm_device)); |
| MinfsDeviceWithInvalidGuid device(ZxcryptDevice::ZxcryptOptions()); |
| EXPECT_EQ(ZX_ERR_NOT_SUPPORTED, manager.AddDevice(device)); |
| EXPECT_FALSE(device.attached()); |
| } |
| |
| // Tests adding minfs with a valid type GUID and invalid metadata. Observe that |
| // the filesystem reformats itself. |
| TEST(AddDeviceTestCase, AddInvalidMinfsDevice) { |
| class MinfsDeviceWithInvalidMetadata : public MinfsDevice { |
| public: |
| zx_status_t CheckFilesystem() final { |
| MinfsDevice::CheckFilesystem(); |
| return ZX_ERR_BAD_STATE; |
| } |
| }; |
| BlockDeviceManager manager(TestOptions()); |
| MockBlockDevice fvm_device(MockBlockDevice::FvmOptions()); |
| EXPECT_OK(manager.AddDevice(fvm_device)); |
| ZxcryptDevice zxcrypt_device; |
| EXPECT_OK(manager.AddDevice(zxcrypt_device)); |
| MinfsDeviceWithInvalidMetadata device; |
| EXPECT_OK(manager.AddDevice(device)); |
| EXPECT_TRUE(device.checked()); |
| EXPECT_TRUE(device.formatted()); |
| EXPECT_TRUE(device.mounted()); |
| } |
| |
| // Tests adding zxcrypt with a valid type GUID and invalid format. Observe that |
| // the partition reformats itself. |
| TEST(AddDeviceTestCase, FormatZxcryptDevice) { |
| BlockDeviceManager manager(TestOptions()); |
| MockBlockDevice fvm_device(MockBlockDevice::FvmOptions()); |
| EXPECT_OK(manager.AddDevice(fvm_device)); |
| MockBlockDevice::Options options = ZxcryptDevice::ZxcryptOptions(); |
| options.content_format = DISK_FORMAT_UNKNOWN; |
| ZxcryptDevice zxcrypt_device(options); |
| EXPECT_OK(manager.AddDevice(zxcrypt_device)); |
| MinfsDevice device; |
| EXPECT_OK(manager.AddDevice(device)); |
| EXPECT_TRUE(zxcrypt_device.formatted_zxcrypt()); |
| EXPECT_TRUE(device.formatted()); |
| EXPECT_TRUE(device.mounted()); |
| } |
| |
| // Tests adding zxcrypt with a valid type GUID and minfs format i.e. it's a minfs partition without |
| // zxcrypt. Observe that the partition reformats itself. |
| TEST(AddDeviceTestCase, FormatMinfsDeviceWithZxcrypt) { |
| BlockDeviceManager manager(TestOptions()); |
| MockBlockDevice fvm_device(MockBlockDevice::FvmOptions()); |
| EXPECT_OK(manager.AddDevice(fvm_device)); |
| MockBlockDevice::Options options = ZxcryptDevice::ZxcryptOptions(); |
| options.content_format = DISK_FORMAT_MINFS; |
| ZxcryptDevice zxcrypt_device(options); |
| EXPECT_OK(manager.AddDevice(zxcrypt_device)); |
| MinfsDevice device; |
| EXPECT_OK(manager.AddDevice(device)); |
| EXPECT_TRUE(zxcrypt_device.formatted_zxcrypt()); |
| EXPECT_TRUE(device.formatted()); |
| EXPECT_TRUE(device.mounted()); |
| } |
| |
| TEST(AddDeviceTestCase, MinfsWithNoZxcryptOptionMountsWithoutZxcrypt) { |
| auto options = TestOptions(); |
| options.options.emplace("no-zxcrypt"); |
| BlockDeviceManager manager(options); |
| MockBlockDevice fvm_device(MockBlockDevice::FvmOptions()); |
| EXPECT_OK(manager.AddDevice(fvm_device)); |
| auto minfs_options = MinfsDevice::MinfsOptions(); |
| minfs_options.topological_path = MockBlockDevice::BaseTopologicalPath() + "/fvm/minfs-p-2/block"; |
| minfs_options.partition_name = "minfs"; |
| MinfsDevice device(minfs_options); |
| EXPECT_OK(manager.AddDevice(device)); |
| EXPECT_TRUE(device.mounted()); |
| } |
| |
| TEST(AddDeviceTestCase, MinfsRamdiskMounts) { |
| // The fvm-ramdisk option will check that the topological path actually has an expected ramdisk |
| // prefix. |
| auto manager_options = TestOptions(); |
| manager_options.options.emplace(BlockDeviceManager::Options::kFvmRamdisk); |
| BlockDeviceManager manager(manager_options); |
| auto options = MockBlockDevice::FvmOptions(); |
| constexpr std::string_view kBasePath = "/dev/misc/ramctl/mock_device/block"; |
| options.topological_path = kBasePath; |
| MockBlockDevice fvm_device(options); |
| EXPECT_OK(manager.AddDevice(fvm_device)); |
| options = MinfsDevice::MinfsOptions(); |
| options.topological_path = std::string(kBasePath) + "/fvm/minfs-p-2/block"; |
| options.partition_name = "minfs"; |
| MinfsDevice device(options); |
| EXPECT_OK(manager.AddDevice(device)); |
| EXPECT_TRUE(device.mounted()); |
| } |
| |
| TEST(AddDeviceTestCase, MinfsRamdiskDeviceNotRamdiskDoesNotMount) { |
| auto options = TestOptions(); |
| options.options.emplace(BlockDeviceManager::Options::kFvmRamdisk); |
| options.options.emplace(BlockDeviceManager::Options::kAttachZxcryptToNonRamdisk); |
| BlockDeviceManager manager(options); |
| auto fvm_options = MockBlockDevice::FvmOptions(); |
| fvm_options.topological_path = "/dev/misc/ramctl/mock_device/block"; |
| MockBlockDevice ramdisk_fvm_device(fvm_options); |
| EXPECT_OK(manager.AddDevice(ramdisk_fvm_device)); |
| MockBlockDevice fvm_device(MockBlockDevice::FvmOptions()); |
| EXPECT_OK(manager.AddDevice(fvm_device)); |
| ZxcryptDevice zxcrypt_device; |
| EXPECT_OK(manager.AddDevice(zxcrypt_device)); |
| MinfsDevice device; |
| EXPECT_EQ(manager.AddDevice(device), ZX_ERR_NOT_SUPPORTED); |
| EXPECT_FALSE(device.mounted()); |
| } |
| |
| TEST(AddDeviceTestCase, MinfsRamdiskWithoutZxcryptAttachOption) { |
| auto options = TestOptions(); |
| options.options.emplace(BlockDeviceManager::Options::kFvmRamdisk); |
| BlockDeviceManager manager(options); |
| MockBlockDevice fvm_device(MockBlockDevice::FvmOptions()); |
| EXPECT_OK(manager.AddDevice(fvm_device)); |
| ZxcryptDevice zxcrypt_device; |
| EXPECT_EQ(manager.AddDevice(zxcrypt_device), ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| TEST(AddDeviceTestCase, MinfsWithAlternateNameMounts) { |
| for (std::string name : {GUID_DATA_NAME, "data"}) { |
| BlockDeviceManager manager(TestOptions()); |
| MockBlockDevice fvm_device(MockBlockDevice::FvmOptions()); |
| EXPECT_OK(manager.AddDevice(fvm_device)); |
| ZxcryptDevice zxcrypt_device; |
| EXPECT_OK(manager.AddDevice(zxcrypt_device)); |
| auto minfs_options = MinfsDevice::MinfsOptions(); |
| minfs_options.partition_name = name; |
| MinfsDevice device(minfs_options); |
| EXPECT_EQ(manager.AddDevice(device), ZX_OK); |
| EXPECT_TRUE(device.mounted()); |
| } |
| } |
| |
| // Durable partition tests |
| // Tests adding minfs on durable partition with a valid type GUID and valid metadata. |
| TEST(AddDeviceTestCase, AddValidDurableDevice) { |
| class DurableZxcryptDevice : public ZxcryptDevice { |
| public: |
| DurableZxcryptDevice() |
| : ZxcryptDevice(Options{ |
| .content_format = DISK_FORMAT_ZXCRYPT, |
| .driver_path = kZxcryptDriverPath, |
| .topological_path = |
| MockBlockDevice::BaseTopologicalPath() + "/" GPT_DURABLE_NAME "-004/block", |
| .partition_name = GPT_DURABLE_NAME, |
| }) {} |
| |
| const fuchsia_hardware_block_partition_GUID& GetTypeGuid() const final { |
| static fuchsia_hardware_block_partition_GUID guid = GPT_DURABLE_TYPE_GUID; |
| return guid; |
| } |
| }; |
| class DurableDevice : public MockBlockDevice { |
| public: |
| using MockBlockDevice::MockBlockDevice; |
| |
| const fuchsia_hardware_block_partition_GUID& GetTypeGuid() const final { |
| static fuchsia_hardware_block_partition_GUID guid = GPT_DURABLE_TYPE_GUID; |
| return guid; |
| } |
| zx_status_t CheckFilesystem() final { |
| checked_ = true; |
| return ZX_OK; |
| } |
| zx_status_t FormatFilesystem() final { |
| formatted_ = true; |
| return ZX_OK; |
| } |
| zx_status_t MountFilesystem() final { |
| mounted_ = true; |
| return ZX_OK; |
| } |
| |
| bool checked() const { return checked_; } |
| bool formatted() const { return formatted_; } |
| bool mounted() const { return mounted_; } |
| |
| private: |
| bool checked_ = false; |
| bool formatted_ = false; |
| bool mounted_ = false; |
| }; |
| BlockDeviceManager manager(TestOptions()); |
| MockBlockDevice gpt_device(MockBlockDevice::GptOptions()); |
| EXPECT_OK(manager.AddDevice(gpt_device)); |
| DurableZxcryptDevice zxcrypt_device; |
| EXPECT_OK(manager.AddDevice(zxcrypt_device)); |
| DurableDevice device(MockBlockDevice::DurableOptions()); |
| EXPECT_OK(manager.AddDevice(device)); |
| EXPECT_TRUE(device.checked()); |
| EXPECT_FALSE(device.formatted()); |
| EXPECT_TRUE(device.mounted()); |
| } |
| |
| // Tests adding a boot partition device with unknown format can be added with |
| // the correct driver. |
| TEST(AddDeviceTestCase, AddUnknownFormatBootPartitionDevice) { |
| class BootPartDevice : public MockBlockDevice { |
| public: |
| BootPartDevice() |
| : MockBlockDevice(Options{ |
| .driver_path = kBootpartDriverPath, |
| }) {} |
| zx_status_t GetInfo(fuchsia_hardware_block_BlockInfo* out_info) const override { |
| fuchsia_hardware_block_BlockInfo info = {}; |
| info.flags = BLOCK_FLAG_BOOTPART; |
| info.block_size = 512; |
| info.block_count = 1024; |
| *out_info = info; |
| return ZX_OK; |
| } |
| }; |
| BootPartDevice device; |
| BlockDeviceManager manager(TestOptions()); |
| EXPECT_OK(manager.AddDevice(device)); |
| EXPECT_TRUE(device.attached()); |
| } |
| |
| TEST(AddDeviceTestCase, AddPermanentlyMiskeyedZxcryptVolume) { |
| class ZxcryptVolume : public devmgr::EncryptedVolumeInterface { |
| public: |
| zx_status_t Unseal() final { |
| // Simulate a device where we've lost the key -- can't unlock until we |
| // format the device with a new key, but can afterwards. |
| if (formatted) { |
| postformat_unseal_attempt_count++; |
| return ZX_OK; |
| } else { |
| preformat_unseal_attempt_count++; |
| return ZX_ERR_ACCESS_DENIED; |
| } |
| } |
| zx_status_t Format() final { |
| formatted = true; |
| return ZX_OK; |
| } |
| |
| int preformat_unseal_attempt_count = 0; |
| int postformat_unseal_attempt_count = 0; |
| bool formatted = false; |
| }; |
| ZxcryptVolume volume; |
| EXPECT_OK(volume.EnsureUnsealedAndFormatIfNeeded()); |
| EXPECT_TRUE(volume.preformat_unseal_attempt_count > 1); |
| EXPECT_TRUE(volume.formatted); |
| EXPECT_EQ(volume.postformat_unseal_attempt_count, 1); |
| } |
| |
| TEST(AddDeviceTestCase, AddTransientlyMiskeyedZxcryptVolume) { |
| class ZxcryptVolume : public devmgr::EncryptedVolumeInterface { |
| public: |
| zx_status_t Unseal() final { |
| // Simulate a transient error -- fail the first time we try to unseal the |
| // volume, but succeed on a retry or any subsequent attempt. |
| unseal_attempt_count++; |
| if (unseal_attempt_count > 1) { |
| return ZX_OK; |
| } else { |
| return ZX_ERR_ACCESS_DENIED; |
| } |
| } |
| |
| zx_status_t Format() final { |
| // We expect this to never be called. |
| formatted = true; |
| return ZX_OK; |
| } |
| |
| int unseal_attempt_count = 0; |
| bool formatted = false; |
| }; |
| ZxcryptVolume volume; |
| EXPECT_OK(volume.EnsureUnsealedAndFormatIfNeeded()); |
| EXPECT_FALSE(volume.formatted); |
| EXPECT_EQ(volume.unseal_attempt_count, 2); |
| } |
| |
| TEST(AddDeviceTestCase, AddFailingZxcryptVolumeShouldNotFormat) { |
| class ZxcryptVolume : public devmgr::EncryptedVolumeInterface { |
| public: |
| zx_status_t Unseal() final { |
| // Errors that are not ZX_ERR_ACCESS_DENIED should not trigger |
| // formatting. |
| return ZX_ERR_INTERNAL; |
| } |
| zx_status_t Format() final { |
| // Expect this to not be called. |
| formatted = true; |
| return ZX_OK; |
| } |
| |
| bool formatted = false; |
| }; |
| ZxcryptVolume volume; |
| EXPECT_EQ(ZX_ERR_INTERNAL, volume.EnsureUnsealedAndFormatIfNeeded()); |
| EXPECT_FALSE(volume.formatted); |
| } |
| |
| // Tests adding factoryfs with valid factoryfs magic, as a verified child of a |
| // block-verity device, but with invalid metadata. |
| TEST(AddDeviceTestCase, AddInvalidFactoryfsDevice) { |
| class FactoryfsWithInvalidMetadata : public FactoryfsDevice { |
| zx_status_t CheckFilesystem() override { |
| FactoryfsDevice::CheckFilesystem(); |
| return ZX_ERR_BAD_STATE; |
| } |
| }; |
| BlockDeviceManager manager(TestOptions()); |
| MockBlockDevice gpt_device(MockBlockDevice::GptOptions()); |
| EXPECT_OK(manager.AddDevice(gpt_device)); |
| SealedBlockVerityDevice verity_device; |
| EXPECT_OK(manager.AddDevice(verity_device)); |
| FactoryfsWithInvalidMetadata device; |
| EXPECT_EQ(ZX_ERR_BAD_STATE, manager.AddDevice(device)); |
| EXPECT_TRUE(device.checked()); |
| EXPECT_FALSE(device.formatted()); |
| EXPECT_FALSE(device.mounted()); |
| } |
| |
| // Tests adding factoryfs with valid fasctoryfs magic, as a verified child of a |
| // block-verity device, and valid metadata. |
| TEST(AddDeviceTestCase, AddValidFactoryfsDevice) { |
| BlockDeviceManager manager(TestOptions()); |
| MockBlockDevice gpt_device(MockBlockDevice::GptOptions()); |
| EXPECT_OK(manager.AddDevice(gpt_device)); |
| SealedBlockVerityDevice verity_device; |
| EXPECT_OK(manager.AddDevice(verity_device)); |
| FactoryfsDevice device; |
| EXPECT_OK(manager.AddDevice(device)); |
| EXPECT_TRUE(device.checked()); |
| EXPECT_FALSE(device.formatted()); |
| EXPECT_TRUE(device.mounted()); |
| } |
| |
| // Tests adding factoryfs with a valid superblock, as a device which is not a |
| // verified child of a block-verity device. |
| TEST(AddDeviceTestCase, AddUnverifiedFactoryFsDevice) { |
| BlockDeviceManager manager(TestOptions()); |
| MockBlockDevice gpt_device(MockBlockDevice::GptOptions()); |
| EXPECT_OK(manager.AddDevice(gpt_device)); |
| FactoryfsDevice device; |
| EXPECT_EQ(manager.AddDevice(device), ZX_ERR_NOT_SUPPORTED); |
| EXPECT_FALSE(device.checked()); |
| EXPECT_FALSE(device.formatted()); |
| EXPECT_FALSE(device.mounted()); |
| } |
| |
| class BlockWatcherTest : public zxtest::Test { |
| protected: |
| BlockWatcherTest() { |
| // Launch the isolated devmgr. |
| IsolatedDevmgr::Args args; |
| args.driver_search_paths.push_back("/boot/driver"); |
| |
| args.disable_block_watcher = false; |
| ASSERT_OK(IsolatedDevmgr::Create(&args, &devmgr_)); |
| |
| fbl::unique_fd fd; |
| ASSERT_OK(RecursiveWaitForFile(devmgr_.devfs_root(), "misc/ramctl", &fd)); |
| |
| zx::channel remote; |
| ASSERT_OK(zx::channel::create(0, &watcher_chan_, &remote)); |
| ASSERT_OK(fdio_service_connect_at(devmgr_.fshost_outgoing_dir().get(), |
| "/svc/fuchsia.fshost.BlockWatcher", remote.release())); |
| } |
| |
| void CreateGptRamdisk(ramdisk_client** client) { |
| zx::vmo ramdisk_vmo; |
| ASSERT_OK(zx::vmo::create(kTestDiskSectors * kBlockSize, 0, &ramdisk_vmo)); |
| // Write the GPT into the VMO. |
| ASSERT_OK(ramdisk_vmo.write(kTestGptProtectiveMbr, 0, sizeof(kTestGptProtectiveMbr))); |
| ASSERT_OK(ramdisk_vmo.write(kTestGptBlock1, kBlockSize, sizeof(kTestGptBlock1))); |
| ASSERT_OK(ramdisk_vmo.write(kTestGptBlock2, 2 * kBlockSize, sizeof(kTestGptBlock2))); |
| |
| ASSERT_OK( |
| ramdisk_create_at_from_vmo(devmgr_.devfs_root().get(), ramdisk_vmo.release(), client)); |
| } |
| |
| void PauseWatcher() { |
| auto result = llcpp::fuchsia::fshost::BlockWatcher::Call::Pause(watcher_chan_.borrow()); |
| ASSERT_OK(result.status()); |
| ASSERT_OK(result->status); |
| } |
| |
| void ResumeWatcher() { |
| auto result = llcpp::fuchsia::fshost::BlockWatcher::Call::Resume(watcher_chan_.borrow()); |
| ASSERT_OK(result.status()); |
| ASSERT_OK(result->status); |
| } |
| |
| void WaitForBlockDevice(int number) { |
| auto path = fbl::StringPrintf("class/block/%03d", number); |
| fbl::unique_fd fd; |
| ASSERT_NO_FATAL_FAILURES(RecursiveWaitForFile(devmgr_.devfs_root(), path.data(), &fd)); |
| } |
| |
| // Check that the number of block devices bound by the block watcher |
| // matches what we expect. Can only be called while the block watcher is running. |
| // |
| // This works by adding a new block device with a valid GPT. |
| // We then wait for that block device to appear at class/block/|next_device_number|. |
| // The block watcher should then bind the GPT driver to that block device, causing |
| // another entry in class/block to appear representing the only partition on the GPT. |
| // |
| // We make sure that this entry's toplogical path corresponds to it being the first partition |
| // of the block device we added. |
| void CheckEventsDropped(int* next_device_number, ramdisk_client** client) { |
| ASSERT_NO_FATAL_FAILURES(CreateGptRamdisk(client)); |
| |
| // Wait for the basic block driver to be bound |
| auto path = fbl::StringPrintf("class/block/%03d", *next_device_number); |
| *next_device_number += 1; |
| fbl::unique_fd fd; |
| ASSERT_OK(RecursiveWaitForFile(devmgr_.devfs_root(), path.data(), &fd)); |
| |
| // And now, wait for the GPT driver to be bound, and the first |
| // partition to appear. |
| path = fbl::StringPrintf("class/block/%03d", *next_device_number); |
| *next_device_number += 1; |
| ASSERT_OK(RecursiveWaitForFile(devmgr_.devfs_root(), path.data(), &fd)); |
| |
| // Figure out the expected topological path of the last block device. |
| std::string expected_path(ramdisk_get_path(*client)); |
| expected_path = "/dev/" + expected_path + "/part-000/block"; |
| |
| zx_handle_t handle; |
| ASSERT_OK(fdio_get_service_handle(fd.release(), &handle)); |
| zx::channel channel(handle); |
| // Get the actual topological path of the block device. |
| auto result = llcpp::fuchsia::device::Controller::Call::GetTopologicalPath(channel.borrow()); |
| ASSERT_OK(result.status()); |
| ASSERT_FALSE(result->result.is_err()); |
| |
| auto actual_path = |
| std::string(result->result.response().path.begin(), result->result.response().path.size()); |
| // Make sure expected path matches the actual path. |
| ASSERT_EQ(expected_path, actual_path); |
| } |
| |
| IsolatedDevmgr devmgr_; |
| zx::channel watcher_chan_; |
| }; |
| |
| TEST_F(BlockWatcherTest, TestBlockWatcherDisable) { |
| ASSERT_NO_FATAL_FAILURES(PauseWatcher()); |
| int next_device_number = 0; |
| // Add a block device. |
| ramdisk_client* client; |
| ASSERT_NO_FATAL_FAILURES(CreateGptRamdisk(&client)); |
| ASSERT_NO_FATAL_FAILURES(WaitForBlockDevice(next_device_number)); |
| next_device_number++; |
| |
| ASSERT_NO_FATAL_FAILURES(ResumeWatcher()); |
| |
| ramdisk_client* client2; |
| ASSERT_NO_FATAL_FAILURES(CheckEventsDropped(&next_device_number, &client2)); |
| |
| ASSERT_OK(ramdisk_destroy(client)); |
| ASSERT_OK(ramdisk_destroy(client2)); |
| } |
| |
| TEST_F(BlockWatcherTest, TestBlockWatcherAdd) { |
| int next_device_number = 0; |
| // Add a block device. |
| ramdisk_client* client; |
| ASSERT_NO_FATAL_FAILURES(CreateGptRamdisk(&client)); |
| ASSERT_NO_FATAL_FAILURES(WaitForBlockDevice(next_device_number)); |
| next_device_number++; |
| |
| fbl::unique_fd fd; |
| // Look for the first partition of the device we just added. |
| ASSERT_OK(RecursiveWaitForFile(devmgr_.devfs_root(), "class/block/001", &fd)); |
| |
| ASSERT_OK(ramdisk_destroy(client)); |
| } |
| |
| TEST_F(BlockWatcherTest, TestBlockWatcherUnmatchedResume) { |
| auto result = llcpp::fuchsia::fshost::BlockWatcher::Call::Resume(watcher_chan_.borrow()); |
| ASSERT_OK(result.status()); |
| ASSERT_STATUS(result->status, ZX_ERR_BAD_STATE); |
| } |
| |
| TEST_F(BlockWatcherTest, TestMultiplePause) { |
| ASSERT_NO_FATAL_FAILURES(PauseWatcher()); |
| ASSERT_NO_FATAL_FAILURES(PauseWatcher()); |
| int next_device_number = 0; |
| |
| // Add a block device. |
| ramdisk_client* client; |
| ASSERT_NO_FATAL_FAILURES(CreateGptRamdisk(&client)); |
| ASSERT_NO_FATAL_FAILURES(WaitForBlockDevice(next_device_number)); |
| next_device_number++; |
| |
| // Resume once. |
| ASSERT_NO_FATAL_FAILURES(ResumeWatcher()); |
| |
| ramdisk_client* client2; |
| ASSERT_NO_FATAL_FAILURES(CreateGptRamdisk(&client2)); |
| ASSERT_NO_FATAL_FAILURES(WaitForBlockDevice(next_device_number)); |
| next_device_number++; |
| |
| fbl::unique_fd fd; |
| RecursiveWaitForFile(devmgr_.devfs_root(), ramdisk_get_path(client2), &fd); |
| // Resume again. The block watcher should be running again. |
| ASSERT_NO_FATAL_FAILURES(ResumeWatcher()); |
| |
| // Make sure neither device was seen by the watcher. |
| ramdisk_client* client3; |
| ASSERT_NO_FATAL_FAILURES(CheckEventsDropped(&next_device_number, &client3)); |
| |
| // Pause again. |
| ASSERT_NO_FATAL_FAILURES(PauseWatcher()); |
| ramdisk_client* client4; |
| ASSERT_NO_FATAL_FAILURES(CreateGptRamdisk(&client4)); |
| ASSERT_NO_FATAL_FAILURES(WaitForBlockDevice(next_device_number)); |
| next_device_number++; |
| // Resume again. |
| ASSERT_NO_FATAL_FAILURES(ResumeWatcher()); |
| |
| // Make sure the last device wasn't added. |
| ramdisk_client* client5; |
| ASSERT_NO_FATAL_FAILURES(CheckEventsDropped(&next_device_number, &client5)); |
| |
| ASSERT_OK(ramdisk_destroy(client)); |
| ASSERT_OK(ramdisk_destroy(client2)); |
| ASSERT_OK(ramdisk_destroy(client3)); |
| ASSERT_OK(ramdisk_destroy(client4)); |
| ASSERT_OK(ramdisk_destroy(client5)); |
| } |
| |
| TEST_F(BlockWatcherTest, TestResumeThenImmediatelyPause) { |
| ASSERT_NO_FATAL_FAILURES(PauseWatcher()); |
| int next_device_number = 0; |
| |
| // Add a block device, which should be ignored. |
| ramdisk_client* client; |
| ASSERT_NO_FATAL_FAILURES(CreateGptRamdisk(&client)); |
| ASSERT_NO_FATAL_FAILURES(WaitForBlockDevice(next_device_number)); |
| next_device_number++; |
| |
| // Resume. |
| ASSERT_NO_FATAL_FAILURES(ResumeWatcher()); |
| // Pause immediately. |
| ASSERT_NO_FATAL_FAILURES(PauseWatcher()); |
| |
| // Add another block device, which should also be ignored. |
| ramdisk_client* client2; |
| ASSERT_NO_FATAL_FAILURES(CreateGptRamdisk(&client2)); |
| ASSERT_NO_FATAL_FAILURES(WaitForBlockDevice(next_device_number)); |
| next_device_number++; |
| |
| // Resume again. |
| ASSERT_NO_FATAL_FAILURES(ResumeWatcher()); |
| |
| // Make sure the block watcher correctly resumed. |
| ramdisk_client* client3; |
| ASSERT_NO_FATAL_FAILURES(CheckEventsDropped(&next_device_number, &client3)); |
| |
| ASSERT_OK(ramdisk_destroy(client)); |
| ASSERT_OK(ramdisk_destroy(client2)); |
| ASSERT_OK(ramdisk_destroy(client3)); |
| } |
| |
| } // namespace |
| } // namespace devmgr |