| // 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 "factory_reset.h" |
| |
| #include <fbl/algorithm.h> |
| #include <fcntl.h> |
| #include <fs-management/fvm.h> |
| #include <fs-management/mount.h> |
| #include <fuchsia/device/manager/cpp/fidl.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/devmgr-integration-test/fixture.h> |
| #include <lib/fdio/fd.h> |
| #include <lib/fdio/fdio.h> |
| #include <lib/fidl/cpp/binding_set.h> |
| #include <lib/fzl/fdio.h> |
| #include <lib/zx/vmo.h> |
| #include <ramdevice-client/ramdisk.h> |
| #include <zircon/hw/gpt.h> |
| |
| #include "gtest/gtest.h" |
| |
| namespace { |
| |
| using devmgr_integration_test::IsolatedDevmgr; |
| using ::testing::Test; |
| |
| const uint32_t kBlockCount = 1024 * 256; |
| const uint32_t kBlockSize = 512; |
| const uint32_t kSliceSize = (1 << 20); |
| const size_t kDeviceSize = kBlockCount * kBlockSize; |
| const char* kDataName = "fdr-data"; |
| const char* kRamdiskPath = "misc/ramctl"; |
| |
| class MockAdmin : public fuchsia::device::manager::Administrator { |
| public: |
| bool suspend_called() { |
| return suspend_called_; |
| } |
| |
| private: |
| void Suspend(uint32_t flags, SuspendCallback callback) override { |
| ASSERT_FALSE(suspend_called_); |
| suspend_called_ = true; |
| ASSERT_EQ(fuchsia::device::manager::SUSPEND_FLAG_REBOOT, flags); |
| callback(ZX_OK); |
| } |
| |
| bool suspend_called_ = false; |
| }; |
| |
| class FactoryResetTest : public Test { |
| public: |
| // Create an IsolatedDevmgr that can load device drivers such as fvm, |
| // zxcrypt, etc. |
| void SetUp() { |
| devmgr_.reset(new IsolatedDevmgr()); |
| auto args = IsolatedDevmgr::DefaultArgs(); |
| args.disable_block_watcher = false; |
| args.sys_device_driver = |
| devmgr_integration_test::IsolatedDevmgr::kSysdevDriver; |
| args.load_drivers.push_back( |
| devmgr_integration_test::IsolatedDevmgr::kSysdevDriver); |
| args.driver_search_paths.push_back("/boot/driver"); |
| ASSERT_EQ(IsolatedDevmgr::Create(std::move(args), devmgr_.get()), ZX_OK); |
| |
| CreateRamdisk(); |
| CreateFvmPartition(); |
| WaitForZxcrypt(); |
| } |
| |
| bool PartitionHasFormat(disk_format_t format) { |
| fbl::unique_fd fd(openat(devmgr_->devfs_root().get(), fvm_block_path_, O_RDONLY)); |
| return detect_disk_format(fd.get()) == format; |
| } |
| |
| fbl::unique_fd devfs_root() { return devmgr_->devfs_root().duplicate(); } |
| |
| private: |
| void WaitForZxcrypt() { |
| char data_block_path[PATH_MAX]; |
| // Second, wait for the data partition to be formatted. |
| snprintf(data_block_path, sizeof(data_block_path), |
| "%s/zxcrypt/unsealed/block", fvm_block_path_); |
| fbl::unique_fd fd; |
| WaitForDevice(data_block_path, &fd); |
| } |
| |
| void CreateRamdisk() { |
| zx::vmo disk; |
| ASSERT_EQ(zx::vmo::create(kDeviceSize, 0, &disk), ZX_OK); |
| int fd = -1; |
| ASSERT_EQ(fdio_fd_create(disk.get(), &fd), ZX_OK); |
| ASSERT_GE(fd, 0); |
| ASSERT_EQ(fvm_init_with_size(fd, kDeviceSize, kSliceSize), ZX_OK); |
| |
| fbl::unique_fd ramdisk; |
| WaitForDevice(kRamdiskPath, &ramdisk); |
| ASSERT_EQ(ramdisk_create_at_from_vmo(devfs_root().get(), disk.release(), |
| &ramdisk_client_), |
| ZX_OK); |
| } |
| |
| void CreateFvmPartition() { |
| fbl::unique_fd fvm_fd; |
| char fvm_path[PATH_MAX]; |
| snprintf(fvm_path, PATH_MAX, "%s/fvm", ramdisk_get_path(ramdisk_client_)); |
| WaitForDevice(fvm_path, &fvm_fd); |
| |
| // Allocate a FVM partition with the data guid but don't actually format the |
| // partition. |
| alloc_req_t req; |
| memset(&req, 0, sizeof(alloc_req_t)); |
| req.slice_count = 1; |
| static const uint8_t data_guid[GPT_GUID_LEN] = GUID_DATA_VALUE; |
| memcpy(req.type, data_guid, GUID_LEN); |
| snprintf(req.name, NAME_LEN, "%s", kDataName); |
| |
| fuchsia_hardware_block_partition_GUID type_guid; |
| memcpy(type_guid.value, req.type, GUID_LEN); |
| fuchsia_hardware_block_partition_GUID instance_guid; |
| memcpy(instance_guid.value, req.guid, GUID_LEN); |
| |
| fzl::UnownedFdioCaller caller(fvm_fd.get()); |
| zx_status_t status; |
| ASSERT_EQ(fuchsia_hardware_block_volume_VolumeManagerAllocatePartition( |
| caller.borrow_channel(), req.slice_count, &type_guid, |
| &instance_guid, req.name, NAME_LEN, req.flags, &status), |
| ZX_OK); |
| ASSERT_EQ(status, ZX_OK); |
| |
| snprintf(fvm_block_path_, sizeof(fvm_block_path_), "%s/%s-p-1/block", |
| fvm_path, kDataName); |
| fbl::unique_fd fd; |
| WaitForDevice(fvm_block_path_, &fd); |
| } |
| |
| void WaitForDevice(const char* path, fbl::unique_fd* fd) { |
| printf("wait for device %s\n", path); |
| ASSERT_EQ(devmgr_integration_test::RecursiveWaitForFile( |
| devfs_root(), path, zx::deadline_after(zx::sec(5)), fd), |
| ZX_OK); |
| |
| ASSERT_TRUE(*fd); |
| } |
| |
| ramdisk_client_t* ramdisk_client_; |
| char fvm_block_path_[PATH_MAX]; |
| std::unique_ptr<IsolatedDevmgr> devmgr_; |
| }; |
| |
| // Tests that FactoryReset can find the correct block device and overwrite its |
| // superblocks, causing it to look like an unknown partition (which upon reboot |
| // will cause recovery to happen). |
| TEST_F(FactoryResetTest, CanShredVolume) { |
| async::Loop loop(&kAsyncLoopConfigAttachToThread); |
| |
| MockAdmin mock_admin; |
| fidl::BindingSet<fuchsia::device::manager::Administrator> binding; |
| fidl::InterfacePtr<fuchsia::device::manager::Administrator> admin = |
| binding.AddBinding(&mock_admin).Bind(); |
| |
| factory_reset::FactoryReset reset((fbl::unique_fd(devfs_root())), std::move(admin)); |
| EXPECT_TRUE(PartitionHasFormat(DISK_FORMAT_ZXCRYPT)); |
| zx_status_t status = ZX_ERR_BAD_STATE; |
| reset.Reset([&status](zx_status_t s) { status = s; }); |
| loop.RunUntilIdle(); |
| ASSERT_TRUE(mock_admin.suspend_called()); |
| EXPECT_EQ(status, ZX_OK); |
| EXPECT_TRUE(PartitionHasFormat(DISK_FORMAT_UNKNOWN)); |
| } |
| |
| } // namespace |