blob: 38d35dd8abb2541915c0a4e918e3b747d23f71a2 [file] [log] [blame]
// 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/async-loop/default.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, 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(&kAsyncLoopConfigAttachToCurrentThread);
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