From 2dcfdba2e3fe666404a8b8fafc5574cfed66056c Mon Sep 17 00:00:00 2001 From: Jeff Seibert <jseibert@google.com> Date: Wed, 24 Apr 2019 23:12:01 +0000 Subject: [PATCH] [zircon] Reformat zxcrypt and minfs partitions when the partition is missing. Added a unit test that failed without the changes. Change-Id: Idaf9913bf9006384e021f39047df1e8e8a855369 --- .../core/devmgr/fshost/block-watcher.cpp | 51 +++++ .../include/kms-stateless/kms-stateless.h | 2 +- .../ulib/kms-stateless/kms-stateless.cpp | 2 +- zircon/system/ulib/zxcrypt/fdio-volume.cpp | 12 +- zircon/system/utest/BUILD.gn | 1 + zircon/system/utest/fs-recovery/BUILD.gn | 31 ++++ zircon/system/utest/fs-recovery/recovery.cpp | 175 ++++++++++++++++++ 7 files changed, 266 insertions(+), 8 deletions(-) create mode 100644 zircon/system/utest/fs-recovery/BUILD.gn create mode 100644 zircon/system/utest/fs-recovery/recovery.cpp diff --git a/zircon/system/core/devmgr/fshost/block-watcher.cpp b/zircon/system/core/devmgr/fshost/block-watcher.cpp index 7aa1582c830..77e518e9989 100644 --- a/zircon/system/core/devmgr/fshost/block-watcher.cpp +++ b/zircon/system/core/devmgr/fshost/block-watcher.cpp @@ -494,6 +494,53 @@ int UnsealZxcrypt(void* arg) { return 0; } +disk_format_t ReformatDataPartition(fbl::unique_fd fd, zx_handle_t disk_channel, + const char* device_path) { + zx_status_t call_status; + fbl::StringBuffer<PATH_MAX> path; + path.Resize(path.capacity()); + size_t path_len; + // Both the zxcrypt and minfs partitions have the same gpt guid, so here we + // determine which one we actually need to format. We do this by looking up + // the topological path, if it is the zxcrypt driver, then we format it as + // minfs, otherwise as zxcrypt. + if (fuchsia_device_ControllerGetTopologicalPath(disk_channel, &call_status, path.data(), + path.capacity(), &path_len) != ZX_OK) { + return DISK_FORMAT_UNKNOWN; + } + const fbl::StringPiece kZxcryptPath("/zxcrypt/unsealed/block"); + if (fbl::StringPiece(path.begin() + path_len - kZxcryptPath.length()).compare(kZxcryptPath) == 0) { + printf("fshost: Minfs data partition is corrupt. Will attempt to reformat %s\n", device_path); + if (mkfs(device_path, DISK_FORMAT_MINFS, launch_stdio_sync, &default_mkfs_options) == ZX_OK) { + return DISK_FORMAT_MINFS; + } + } else { + printf("fshost: zxcrypt volume is corrupt. Will attempt to reformat %s\n", device_path); + if (zxcrypt::FdioVolume::CreateWithDeviceKey(std::move(fd), nullptr) == ZX_OK) { + return DISK_FORMAT_ZXCRYPT; + } + } + return DISK_FORMAT_UNKNOWN; +} + +// Attempts to reformat the partition at the device path. Returns the specific +// disk format if successful and unknown otherwise. Currently only works for +// minfs and zxcrypt data partitions. +disk_format_t ReformatPartition(fbl::unique_fd fd, zx_handle_t disk_channel, + const char* device_path) { + zx_status_t call_status, io_status; + fuchsia_hardware_block_partition_GUID guid; + io_status = fuchsia_hardware_block_partition_PartitionGetTypeGuid(disk_channel, &call_status, + &guid); + if (io_status != ZX_OK || call_status != ZX_OK) { + return DISK_FORMAT_UNKNOWN; + } + if (gpt_is_data_guid(guid.value, GPT_GUID_LEN)) { + return ReformatDataPartition(std::move(fd), disk_channel, device_path); + } + return DISK_FORMAT_UNKNOWN; +} + zx_status_t FormatMinfs(const fbl::unique_fd& block_device, const fuchsia_hardware_block_BlockInfo& info) { @@ -543,6 +590,10 @@ zx_status_t BlockDeviceAdded(int dirfd, int event, const char* name, void* cooki return ZX_OK; } + if (df == DISK_FORMAT_UNKNOWN && !watcher->Netbooting()) { + df = ReformatPartition(fd.duplicate(), disk->get(), device_path); + } + if (info.flags & BLOCK_FLAG_BOOTPART) { fuchsia_device_ControllerBind(disk->get(), BOOTPART_DRIVER_LIB, STRLEN(BOOTPART_DRIVER_LIB), &call_status); diff --git a/zircon/system/ulib/kms-stateless/include/kms-stateless/kms-stateless.h b/zircon/system/ulib/kms-stateless/include/kms-stateless/kms-stateless.h index 7d93c82f880..f0960fb1598 100644 --- a/zircon/system/ulib/kms-stateless/include/kms-stateless/kms-stateless.h +++ b/zircon/system/ulib/kms-stateless/include/kms-stateless/kms-stateless.h @@ -11,7 +11,7 @@ namespace kms_stateless { // The callback called when a hardware key is successfully derived. Arguments to the callback // is a unique_ptr of the key buffer and the key size. - using GetHardwareDerivedKeyCallback = fbl::Function<zx_status_t(fbl::unique_ptr<uint8_t>, + using GetHardwareDerivedKeyCallback = fbl::Function<zx_status_t(fbl::unique_ptr<uint8_t[]>, size_t)>; // Get a hardware derived key using diff --git a/zircon/system/ulib/kms-stateless/kms-stateless.cpp b/zircon/system/ulib/kms-stateless/kms-stateless.cpp index fe7d295f874..f5d68ec0333 100644 --- a/zircon/system/ulib/kms-stateless/kms-stateless.cpp +++ b/zircon/system/ulib/kms-stateless/kms-stateless.cpp @@ -148,7 +148,7 @@ zx_status_t WatchTee(int dirfd, int event, const char* filename, void* cookie) { fbl::StringBuffer<kMaxPathLen> device_path; device_path.Append(kDeviceClass).Append("/").Append(filename); // Hardware derived key is expected to be 128-bit AES key. - fbl::unique_ptr<uint8_t> key_buffer(new uint8_t[kDerivedKeySize]); + fbl::unique_ptr<uint8_t[]> key_buffer(new uint8_t[kDerivedKeySize]); size_t key_size = 0; WatchTeeArgs* args = reinterpret_cast<WatchTeeArgs*>(cookie); if (!GetKeyFromTeeDevice(device_path.c_str(), std::move(args->key_info), args->key_info_size, diff --git a/zircon/system/ulib/zxcrypt/fdio-volume.cpp b/zircon/system/ulib/zxcrypt/fdio-volume.cpp index b97dd2fbf99..291f222467d 100644 --- a/zircon/system/ulib/zxcrypt/fdio-volume.cpp +++ b/zircon/system/ulib/zxcrypt/fdio-volume.cpp @@ -77,7 +77,7 @@ zx_status_t SelectKeySource(KeySource* out) { } } -zx_status_t DoWithHardwareKey(fbl::Function<zx_status_t(fbl::unique_ptr<uint8_t>, size_t)> +zx_status_t DoWithHardwareKey(fbl::Function<zx_status_t(fbl::unique_ptr<uint8_t[]>, size_t)> callback) { KeySource source; zx_status_t rc; @@ -91,13 +91,13 @@ zx_status_t DoWithHardwareKey(fbl::Function<zx_status_t(fbl::unique_ptr<uint8_t> uint8_t key_info[kms_stateless::kExpectedKeyInfoSize] = {0}; memcpy(key_info, kHardwareKeyInfo, sizeof(kHardwareKeyInfo)); return kms_stateless::GetHardwareDerivedKey([&]( - fbl::unique_ptr<uint8_t> key_buffer, size_t key_size) { + fbl::unique_ptr<uint8_t[]> key_buffer, size_t key_size) { return callback(std::move(key_buffer), key_size); }, key_info); } case NullSource: { // If we don't have secure storage for the root key, just use the null key - fbl::unique_ptr<uint8_t> key_buffer(new uint8_t[kKeyLength]); + fbl::unique_ptr<uint8_t[]> key_buffer(new uint8_t[kKeyLength]); memset(key_buffer.get(), 0, kKeyLength); return callback(std::move(key_buffer), kKeyLength); } @@ -125,7 +125,7 @@ zx_status_t FdioVolumeManager::Unseal(const uint8_t* key, size_t key_len, uint8_ } zx_status_t FdioVolumeManager::UnsealWithDeviceKey(uint8_t slot) { - return DoWithHardwareKey([&](fbl::unique_ptr<uint8_t> key_buffer, size_t key_size) { + return DoWithHardwareKey([&](fbl::unique_ptr<uint8_t[]> key_buffer, size_t key_size) { return Unseal(key_buffer.release(), key_size, slot); }); } @@ -199,7 +199,7 @@ zx_status_t FdioVolume::Create(fbl::unique_fd fd, const crypto::Secret& key, zx_status_t FdioVolume::CreateWithDeviceKey(fbl::unique_fd&& fd, fbl::unique_ptr<FdioVolume>* out) { - return DoWithHardwareKey([&](fbl::unique_ptr<uint8_t> key_buffer, size_t key_size) { + return DoWithHardwareKey([&](fbl::unique_ptr<uint8_t[]> key_buffer, size_t key_size) { crypto::Secret secret; zx_status_t rc; uint8_t* inner; @@ -235,7 +235,7 @@ zx_status_t FdioVolume::Unlock(fbl::unique_fd fd, const crypto::Secret& key, key zx_status_t FdioVolume::UnlockWithDeviceKey(fbl::unique_fd fd, key_slot_t slot, fbl::unique_ptr<FdioVolume>* out) { - return DoWithHardwareKey([&](fbl::unique_ptr<uint8_t> key_buffer, size_t key_size) { + return DoWithHardwareKey([&](fbl::unique_ptr<uint8_t[]> key_buffer, size_t key_size) { crypto::Secret secret; zx_status_t rc; uint8_t* inner; diff --git a/zircon/system/utest/BUILD.gn b/zircon/system/utest/BUILD.gn index d3f4e862ca3..ff0d2aec981 100644 --- a/zircon/system/utest/BUILD.gn +++ b/zircon/system/utest/BUILD.gn @@ -107,6 +107,7 @@ if (current_cpu != "") { "fs", "fs-bench", "fs-management", + "fs-recovery", "fs-test-utils", "fs-vnode", "futex-ownership", diff --git a/zircon/system/utest/fs-recovery/BUILD.gn b/zircon/system/utest/fs-recovery/BUILD.gn new file mode 100644 index 00000000000..4911a286230 --- /dev/null +++ b/zircon/system/utest/fs-recovery/BUILD.gn @@ -0,0 +1,31 @@ +# 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. + +import("$zx/public/gn/resource.gni") + +generated_resource("zxcrypt_config.txt") { + testonly = true + contents = "null" + outputs = [ + "config/zxcrypt", + ] +} + + +test("fs-recovery") { + sources = [ + "recovery.cpp", + ] + deps = [ + "$zx/system/ulib/devmgr-integration-test", + "$zx/system/ulib/fbl", + "$zx/system/ulib/fdio", + "$zx/system/ulib/fs-management", + "$zx/system/ulib/fvm", + "$zx/system/fidl/fuchsia-hardware-ramdisk:c", + "$zx/system/ulib/unittest", + "$zx/system/ulib/zx", + ":zxcrypt_config.txt", + ] +} diff --git a/zircon/system/utest/fs-recovery/recovery.cpp b/zircon/system/utest/fs-recovery/recovery.cpp new file mode 100644 index 00000000000..bb69561a1ca --- /dev/null +++ b/zircon/system/utest/fs-recovery/recovery.cpp @@ -0,0 +1,175 @@ +// 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 <fcntl.h> + +#include <fs-management/fvm.h> +#include <fs-management/mount.h> +#include <fuchsia/hardware/ramdisk/c/fidl.h> +#include <fvm/format.h> +#include <lib/devmgr-integration-test/fixture.h> +#include <lib/fdio/fd.h> +#include <lib/fdio/fdio.h> +#include <lib/zx/vmo.h> +#include <unittest/unittest.h> + +namespace { + +using devmgr_integration_test::IsolatedDevmgr; + +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 = "fs-recovery-data"; +const char* kRamdiskPath = "misc/ramctl"; + +// Test fixture that builds a ramdisk and destroys it when destructed. +class FsRecoveryTest { +public: + // Create an IsolatedDevmgr that can load device drivers such as fvm, + // zxcrypt, etc. + bool Initialize() { + BEGIN_HELPER; + 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_), ZX_OK); + END_HELPER; + } + + // Create a ram disk that is back by a VMO, which is formatted to look like + // an FVM volume. + bool CreateFvmRamdisk(size_t device_size, size_t block_size) { + BEGIN_HELPER; + + // Calculate total size of data + metadata. + device_size = fbl::round_up(device_size, fvm::kBlockSize); + size_t old_meta = fvm::MetadataSize(device_size, fvm::kBlockSize); + size_t new_meta = fvm::MetadataSize(old_meta + device_size, fvm::kBlockSize); + while (old_meta != new_meta) { + old_meta = new_meta; + new_meta = fvm::MetadataSize(old_meta + device_size, fvm::kBlockSize); + } + device_size = device_size + (new_meta * 2); + + zx::vmo disk; + ASSERT_EQ(zx::vmo::create(device_size, 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, device_size, kSliceSize), ZX_OK); + + fbl::unique_fd ramdisk; + ASSERT_TRUE(WaitForDevice(kRamdiskPath, &ramdisk)); + + zx_handle_t ramctl; + ASSERT_EQ(fdio_get_service_handle(ramdisk.get(), &ramctl), ZX_OK); + + size_t name_len = 0; + zx_status_t status; + ASSERT_EQ(fuchsia_hardware_ramdisk_RamdiskControllerCreateFromVmo( + ramctl, disk.release(), &status, ramdisk_name_, + sizeof(ramdisk_name_) - 1, &name_len), + ZX_OK); + ASSERT_EQ(status, ZX_OK); + END_HELPER; + } + + // Create a partition in the FVM volume that has the data guid. + bool CreateFvmPartition(char* fvm_path) { + BEGIN_HELPER; + + snprintf(fvm_path, PATH_MAX, "%s/%s/block/fvm", kRamdiskPath, ramdisk_name_); + fbl::unique_fd fvm_fd; + ASSERT_TRUE(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); + // This attempts to return an fd to a partition under /dev/class/block + // which is not running under the IsolatedDevmgr, thus we do not check + // its return value. + fvm_allocate_partition(fvm_fd.get(), &req); + + END_HELPER; + } + + // Wait for the device to be available and then check to make sure it is + // formatted of the passed in type. Since formatting can take some time + // after the device becomes available, we must recheck. + bool WaitForDiskFormat(const char* path, disk_format_t format, zx::duration deadline) { + fbl::unique_fd fd; + if (!WaitForDevice(path, &fd)) { + return false; + } + while (deadline.get() > 0) { + fd.reset(openat(devmgr_.devfs_root().get(), path, O_RDONLY)); + if (detect_disk_format(fd.get()) == format) + return true; + sleep(1); + deadline -= zx::duration(ZX_SEC(1)); + } + return false; + } + +private: + bool WaitForDevice(const char* path, fbl::unique_fd* fd) { + BEGIN_HELPER; + printf("Wait for device %s\n", path); + ASSERT_EQ(devmgr_integration_test::RecursiveWaitForFile(devmgr_.devfs_root(), path, + zx::deadline_after(zx::sec(5)), fd), + ZX_OK); + + ASSERT_TRUE(*fd); + END_HELPER; + } + + char ramdisk_name_[fuchsia_hardware_ramdisk_MAX_NAME_LENGTH + 1]; + devmgr_integration_test::IsolatedDevmgr devmgr_; +}; + +bool EmptyPartitionRecoveryTest() { + BEGIN_TEST; + + char fvm_path[PATH_MAX]; + fbl::unique_ptr<FsRecoveryTest> recovery(new FsRecoveryTest()); + ASSERT_TRUE(recovery->Initialize()); + // Creates an FVM partition under an isolated devmgr, but does not properly + // create the data partitions. + ASSERT_TRUE(recovery->CreateFvmRamdisk(kDeviceSize, kBlockSize)); + ASSERT_TRUE(recovery->CreateFvmPartition(fvm_path)); + + // We then expect the devmgr to self-recover, i.e., format the zxcrypt/data + // partitions as expected from the FVM partition. + + // First, wait for the zxcrypt partition to be formatted. + char fvm_block_path[PATH_MAX]; + snprintf(fvm_block_path, sizeof(fvm_block_path), + "%s/%s-p-1/block", fvm_path, kDataName); + EXPECT_TRUE(recovery->WaitForDiskFormat(fvm_block_path, DISK_FORMAT_ZXCRYPT, + zx::duration(ZX_SEC(3)))); + + // Second, wait for the data partition to be formatted. + char data_path[PATH_MAX]; + snprintf(data_path, sizeof(data_path), + "%s/zxcrypt/unsealed/block", fvm_block_path); + EXPECT_TRUE(recovery->WaitForDiskFormat(data_path, DISK_FORMAT_MINFS, + zx::duration(ZX_SEC(3)))); + + END_TEST; +} + +BEGIN_TEST_CASE(FsRecoveryTest) +RUN_TEST(EmptyPartitionRecoveryTest) +END_TEST_CASE(FsRecoveryTest) + +} // namespace -- GitLab