| // 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 <dirent.h> |
| #include <fcntl.h> |
| #include <fidl/fuchsia.fshost/cpp/wire.h> |
| #include <fidl/fuchsia.hardware.block/cpp/wire.h> |
| #include <lib/component/incoming/cpp/clone.h> |
| #include <lib/component/incoming/cpp/protocol.h> |
| #include <lib/fdio/cpp/caller.h> |
| #include <lib/fdio/fdio.h> |
| #include <lib/fit/defer.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/zx/channel.h> |
| #include <zircon/errors.h> |
| #include <zircon/status.h> |
| |
| #include "src/recovery/factory_reset/factory_reset_config.h" |
| #include "src/security/lib/kms-stateless/kms-stateless.h" |
| #include "src/security/lib/zxcrypt/client.h" |
| #include "src/storage/lib/block_client/cpp/remote_block_device.h" |
| #include "src/storage/lib/fs_management/cpp/format.h" |
| namespace factory_reset { |
| |
| const char* kBlockPath = "class/block"; |
| |
| zx_status_t ShredZxcryptDevice(fidl::ClientEnd<fuchsia_device::Controller> device, |
| fbl::unique_fd devfs_root_fd) { |
| zxcrypt::VolumeManager volume(std::move(device), std::move(devfs_root_fd)); |
| |
| // Note: the access to /dev/sys/platform from the manifest is load-bearing |
| // here, because we can only find the related zxcrypt device for a particular |
| // block device via appending "/zxcrypt" to its topological path, and the |
| // canonical topological path sits under sys/platform. |
| zx::channel driver_chan; |
| if (zx_status_t status = volume.OpenClient(zx::sec(5), driver_chan); status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Couldn't open channel to zxcrypt volume manager"; |
| return status; |
| } |
| |
| zxcrypt::EncryptedVolumeClient zxc_manager(std::move(driver_chan)); |
| if (zx_status_t status = zxc_manager.Shred(); status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Couldn't shred volume"; |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| FactoryReset::FactoryReset(async_dispatcher_t* dispatcher, |
| fidl::ClientEnd<fuchsia_io::Directory> dev, |
| fidl::ClientEnd<fuchsia_hardware_power_statecontrol::Admin> admin, |
| fidl::ClientEnd<fuchsia_fshost::Admin> fshost_admin, |
| factory_reset_config::Config config) |
| : dev_(std::move(dev)), |
| admin_(std::move(admin), dispatcher), |
| fshost_admin_(std::move(fshost_admin), dispatcher), |
| config_(config) {} |
| |
| void FactoryReset::Shred(fit::callback<void(zx_status_t)> callback) const { |
| // First try and shred the data volume using fshost. |
| auto cb = [this, callback = std::move(callback)](const auto& result) mutable { |
| callback([this, &result]() { |
| zx_status_t status; |
| if (result.ok()) { |
| const fit::result response = result.value(); |
| if (response.is_ok()) { |
| FX_LOGS(INFO) << "fshost ShredDataVolume succeeded"; |
| return ZX_OK; |
| } |
| if (response.is_error()) { |
| if (response.error_value() != ZX_ERR_NOT_SUPPORTED) { |
| FX_PLOGS(ERROR, response.error_value()) << "fshost ShredDataVolume failed"; |
| } |
| } |
| status = response.error_value(); |
| } else { |
| FX_LOGS(ERROR) << "Failed to call ShredDataVolume: " << result.FormatDescription(); |
| status = result.status(); |
| } |
| if (config_.use_fxblob()) { |
| // We can't fall back to shredding manually for Fxblob, so fail now. |
| return status; |
| } |
| // Fall back to shredding all zxcrypt devices... |
| FX_LOGS(INFO) << "Falling back to manually shredding zxcrypt..."; |
| zx::result block_dir = component::ConnectAt<fuchsia_io::Directory>(dev_, kBlockPath); |
| if (block_dir.is_error()) { |
| FX_PLOGS(ERROR, block_dir.error_value()) << "Failed to open '" << kBlockPath << "'"; |
| return block_dir.error_value(); |
| } |
| int fd; |
| if (zx_status_t status = fdio_fd_create(block_dir.value().TakeChannel().release(), &fd); |
| status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to create fd from '" << kBlockPath << "'"; |
| return status; |
| } |
| DIR* const dir = fdopendir(fd); |
| auto cleanup = fit::defer([dir]() { closedir(dir); }); |
| fdio_cpp::UnownedFdioCaller caller(dirfd(dir)); |
| // Attempts to shred every zxcrypt volume found. |
| while (true) { |
| dirent* de = readdir(dir); |
| if (de == nullptr) { |
| return ZX_OK; |
| } |
| if (std::string_view(de->d_name) == ".") { |
| continue; |
| } |
| zx::result block = |
| component::ConnectAt<fuchsia_hardware_block::Block>(caller.directory(), de->d_name); |
| if (block.is_error()) { |
| FX_PLOGS(ERROR, block.status_value()) << "Error opening " << de->d_name; |
| continue; |
| } |
| |
| std::string controller_path = std::string(de->d_name) + "/device_controller"; |
| zx::result block_controller = |
| component::ConnectAt<fuchsia_device::Controller>(caller.directory(), controller_path); |
| if (block_controller.is_error()) { |
| FX_PLOGS(ERROR, block_controller.status_value()) << "Error opening " << controller_path; |
| continue; |
| } |
| if (fs_management::DetectDiskFormat(block.value()) == fs_management::kDiskFormatZxcrypt) { |
| fbl::unique_fd dev_fd; |
| { |
| zx::result dev = component::Clone(dev_); |
| if (dev.is_error()) { |
| FX_PLOGS(ERROR, dev.error_value()) << "Error cloning connection to /dev"; |
| continue; |
| } |
| if (zx_status_t status = fdio_fd_create(dev.value().TakeChannel().release(), |
| dev_fd.reset_and_get_address()); |
| status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Error creating file descriptor from /dev"; |
| continue; |
| } |
| } |
| |
| zx_status_t status = |
| ShredZxcryptDevice(std::move(block_controller.value()), std::move(dev_fd)); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Error shredding " << de->d_name; |
| return status; |
| } |
| FX_LOGS(INFO) << "Successfully shredded " << de->d_name; |
| } |
| } |
| }()); |
| }; |
| fshost_admin_->ShredDataVolume().ThenExactlyOnce(std::move(cb)); |
| } |
| |
| void FactoryReset::Reset(fit::callback<void(zx_status_t)> callback) { |
| FX_LOGS(INFO) << "Reset called. Starting shred"; |
| Shred([this, callback = std::move(callback)](zx_status_t status) mutable { |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Shred failed"; |
| callback(status); |
| return; |
| } |
| FX_LOGS(INFO) << "Finished shred"; |
| |
| uint8_t key_info[kms_stateless::kExpectedKeyInfoSize] = "zxcrypt"; |
| switch (zx_status_t status = kms_stateless::RotateHardwareDerivedKeyFromService(key_info); |
| status) { |
| case ZX_OK: |
| break; |
| case ZX_ERR_NOT_SUPPORTED: |
| FX_LOGS(WARNING) |
| << "FactoryReset: The device does not support rotatable hardware keys. Ignoring"; |
| break; |
| default: |
| FX_PLOGS(ERROR, status) << "FactoryReset: RotateHardwareDerivedKey() failed"; |
| callback(status); |
| return; |
| } |
| // Reboot to initiate the recovery. |
| FX_LOGS(INFO) << "Requesting reboot..."; |
| auto cb = [callback = std::move(callback)](const auto& result) mutable { |
| if (!result.ok()) { |
| FX_PLOGS(ERROR, result.status()) << "Reboot call failed"; |
| callback(result.status()); |
| return; |
| } |
| const auto& response = result.value(); |
| if (response.is_error()) { |
| FX_PLOGS(ERROR, response.error_value()) << "Reboot returned error"; |
| callback(response.error_value()); |
| return; |
| } |
| callback(ZX_OK); |
| }; |
| admin_->Reboot(fuchsia_hardware_power_statecontrol::wire::RebootReason::kFactoryDataReset) |
| .ThenExactlyOnce(std::move(cb)); |
| }); |
| } |
| |
| void FactoryReset::Reset(ResetCompleter::Sync& completer) { |
| Reset([completer = completer.ToAsync()](zx_status_t status) mutable { completer.Reply(status); }); |
| } |
| |
| } // namespace factory_reset |