blob: 7b5784e191ba04d5dcb0bb6df562d275106aea8d [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 <dirent.h>
#include <fcntl.h>
#include <fidl/fuchsia.hardware.block/cpp/wire.h>
#include <fuchsia/fs/cpp/fidl.h>
#include <fuchsia/hardware/block/c/fidl.h>
#include <lib/fdio/fdio.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/channel.h>
#include <zircon/status.h>
#include "lib/fdio/cpp/caller.h"
#include "src/lib/storage/fs_management/cpp/format.h"
#include "src/lib/storage/fs_management/cpp/mount.h"
#include "src/security/kms-stateless/kms-stateless.h"
#include "src/security/zxcrypt/client.h"
namespace factory_reset {
const char* kBlockPath = "class/block";
zx_status_t ShredZxcryptDevice(fbl::unique_fd fd, fbl::unique_fd devfs_root_fd) {
zx_status_t status;
zxcrypt::VolumeManager volume(std::move(fd), 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;
status = volume.OpenClient(zx::sec(5), driver_chan);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Couldn't open channel to zxcrypt volume manager: " << status << " ("
<< zx_status_get_string(status) << ")";
return status;
}
zxcrypt::EncryptedVolumeClient zxc_manager(std::move(driver_chan));
status = zxc_manager.Shred();
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Couldn't shred volume: " << status << " (" << zx_status_get_string(status)
<< ")";
return status;
}
return ZX_OK;
}
zx_status_t ShredFxfsDevice(fbl::unique_fd fd) {
// Overwrite the magic bytes of both superblocks.
//
// Note: This may occasionally be racy. Superblocks may be writen after
// the overwrite below but before reboot. When we move this to fshost, we
// will have access to the running filesystem and can wait for shutdown with
// something like:
// fdio_cpp::FdioCaller caller(std::move(fd));
// if (zx::status<> status = fs_management::Shutdown(caller.directory()); !status.is_ok()) {
// return status.error_value();
// }
// TODO(https://fxbug.dev/98889): Perform secure erase once we have keybag support.
zx_status_t call_status;
fdio_cpp::UnownedFdioCaller caller(fd.get());
fuchsia_hardware_block_BlockInfo block_info;
if (auto status =
fuchsia_hardware_block_BlockGetInfo(caller.borrow_channel(), &call_status, &block_info);
status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to fetch block size.";
return status;
}
ssize_t block_size = block_info.block_size;
std::unique_ptr<uint8_t[]> block = std::make_unique<uint8_t[]>(block_size);
memset(block.get(), 0, block_size);
for (off_t offset : {0L, 512L << 10}) {
if (auto status = ::lseek(fd.get(), offset, SEEK_SET); status < 0) {
FX_LOGS(ERROR) << "Seek on fxfs device shred failed.";
return ZX_ERR_IO;
}
if (auto status = ::write(fd.get(), block.get(), block_size); status < 0) {
FX_LOGS(ERROR) << "Write to fxfs device at offset " << offset << " failed:" << status;
return ZX_ERR_IO;
}
}
return ZX_OK;
}
FactoryReset::FactoryReset(fbl::unique_fd dev_fd,
fuchsia::hardware::power::statecontrol::AdminPtr admin) {
dev_fd_ = std::move(dev_fd);
admin_ = std::move(admin);
}
zx_status_t FactoryReset::Shred() const {
fbl::unique_fd block_dir(openat(dev_fd_.get(), kBlockPath, O_RDONLY | O_DIRECTORY));
if (!block_dir) {
FX_LOGS(ERROR) << "Error opening " << kBlockPath;
return ZX_ERR_NOT_FOUND;
}
struct dirent* de;
DIR* dir = fdopendir(block_dir.get());
// Attempts to shred every zxcrypt volume found.
while ((de = readdir(dir)) != nullptr) {
fbl::unique_fd block_fd(openat(dirfd(dir), de->d_name, O_RDWR));
if (!block_fd) {
continue;
}
zx_status_t status = ZX_OK;
switch (fs_management::DetectDiskFormat(block_fd.get())) {
case fs_management::kDiskFormatZxcrypt: {
status = ShredZxcryptDevice(std::move(block_fd), dev_fd_.duplicate());
break;
}
case fs_management::kDiskFormatFxfs: {
status = ShredFxfsDevice(std::move(block_fd));
break;
}
default:
continue;
}
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Error shredding " << de->d_name;
closedir(dir);
return status;
} else {
FX_LOGS(INFO) << "Successfully shredded " << de->d_name;
}
}
closedir(dir);
return ZX_OK;
}
void FactoryReset::Reset(ResetCallback callback) {
FX_LOGS(ERROR) << "Reset called. Starting shred.";
zx_status_t status = Shred();
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Shred failed: " << status << " (" << zx_status_get_string(status) << ")";
callback(std::move(status));
return;
}
FX_LOGS(ERROR) << "Finished shred.";
uint8_t key_info[kms_stateless::kExpectedKeyInfoSize] = "zxcrypt";
status = kms_stateless::RotateHardwareDerivedKeyFromService(key_info);
if (status == ZX_ERR_NOT_SUPPORTED) {
FX_LOGS(ERROR)
<< "FactoryReset: The device does not support rotatable hardware keys. Ignoring.";
status = ZX_OK;
} else if (status != ZX_OK) {
FX_LOGS(ERROR) << "FactoryReset: RotateHardwareDerivedKey() failed: " << status << " ("
<< zx_status_get_string(status) << ")";
callback(std::move(status));
return;
}
// Reboot to initiate the recovery.
FX_LOGS(ERROR) << "Requesting reboot...";
admin_->Reboot(fuchsia::hardware::power::statecontrol::RebootReason::FACTORY_DATA_RESET,
[callback{std::move(callback)}](
fuchsia::hardware::power::statecontrol::Admin_Reboot_Result status) {
if (status.is_err()) {
FX_LOGS(ERROR) << "Reboot call failed: " << status.err();
callback(status.err());
} else {
callback(ZX_OK);
}
});
}
} // namespace factory_reset