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