| // Copyright 2020 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 "src/devices/block/drivers/block-verity/verified-volume-client.h" |
| |
| #include <fidl/fuchsia.device/cpp/wire.h> |
| #include <fidl/fuchsia.hardware.block.verified/cpp/wire.h> |
| #include <lib/device-watcher/cpp/device-watcher.h> |
| #include <lib/fdio/cpp/caller.h> |
| #include <lib/fdio/fdio.h> |
| #include <zircon/status.h> |
| |
| #include <cstring> |
| |
| #include <fbl/string.h> |
| #include <fbl/string_buffer.h> |
| |
| #include "lib/stdcompat/string_view.h" |
| |
| namespace block_verity { |
| namespace { |
| |
| const char* kDriverLib = "block-verity.cm"; |
| |
| zx_status_t BindVerityDriver(fidl::UnownedClientEnd<fuchsia_device::Controller> channel) { |
| const fidl::WireResult result = |
| fidl::WireCall(channel)->Bind(fidl::StringView::FromExternal(kDriverLib)); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| const fit::result response = result.value(); |
| if (response.is_error()) { |
| return response.error_value(); |
| } |
| return ZX_OK; |
| } |
| |
| zx::result<fbl::String> RelativeTopologicalPath( |
| fidl::UnownedClientEnd<fuchsia_device::Controller> channel) { |
| const fidl::WireResult result = fidl::WireCall(channel)->GetTopologicalPath(); |
| if (!result.ok()) { |
| return zx::error(result.status()); |
| } |
| const fit::result response = result.value(); |
| if (response.is_error()) { |
| return zx::error(response.error_value()); |
| } |
| std::string_view path = response.value()->path.get(); |
| |
| constexpr std::string_view kSlashDevSlash = "/dev/"; |
| if (!cpp20::starts_with(path, kSlashDevSlash)) { |
| return zx::error(ZX_ERR_INTERNAL); |
| } |
| return zx::ok(path.substr(kSlashDevSlash.size())); |
| } |
| |
| } // namespace |
| |
| VerifiedVolumeClient::VerifiedVolumeClient( |
| fidl::ClientEnd<fuchsia_hardware_block_verified::DeviceManager> verity_chan, |
| fidl::ClientEnd<fuchsia_device::Controller> verity_controller, fbl::unique_fd devfs_root_fd) |
| : verity_chan_(std::move(verity_chan)), |
| verity_controller_(std::move(verity_controller)), |
| devfs_root_fd_(std::move(devfs_root_fd)) {} |
| |
| zx::result<std::unique_ptr<VerifiedVolumeClient>> VerifiedVolumeClient::CreateFromBlockDevice( |
| fidl::UnownedClientEnd<fuchsia_device::Controller> device, fbl::unique_fd devfs_root_fd, |
| Disposition disposition, const zx::duration& timeout) { |
| // Bind the driver if called for by `disposition`. |
| if (disposition == kDriverNeedsBinding) { |
| if (zx_status_t status = BindVerityDriver(device); status != ZX_OK) { |
| printf("VerifiedVolumeClient: couldn't bind driver: %s", zx_status_get_string(status)); |
| return zx::error(status); |
| } |
| } |
| |
| // Compute the path at which we expect to see the verity child device appear. |
| zx::result block_dev_path = RelativeTopologicalPath(device); |
| if (block_dev_path.is_error()) { |
| printf("VerifiedVolumeClient: could not compute relative path: %s\n", |
| block_dev_path.status_string()); |
| return block_dev_path.take_error(); |
| } |
| fbl::String verity_path = fbl::String::Concat({block_dev_path.value(), "/verity"}); |
| |
| // Wait for the device to appear. |
| zx::result channel = |
| device_watcher::RecursiveWaitForFile(devfs_root_fd.get(), verity_path.c_str(), timeout); |
| if (channel.is_error()) { |
| printf("VerifiedVolumeClient: verity device failed to appear: %s\n", channel.status_string()); |
| return channel.take_error(); |
| } |
| |
| fbl::String controller_path = fbl::String::Concat({verity_path, "/device_controller"}); |
| zx::result controller_channel = |
| device_watcher::RecursiveWaitForFile(devfs_root_fd.get(), controller_path.c_str(), timeout); |
| if (controller_channel.is_error()) { |
| printf("VerifiedVolumeClient: verity controller failed to appear: %s\n", |
| controller_channel.status_string()); |
| return controller_channel.take_error(); |
| } |
| |
| return zx::ok(std::make_unique<VerifiedVolumeClient>( |
| fidl::ClientEnd<fuchsia_hardware_block_verified::DeviceManager>{std::move(channel.value())}, |
| fidl::ClientEnd<fuchsia_device::Controller>{std::move(controller_channel.value())}, |
| std::move(devfs_root_fd))); |
| } |
| |
| zx::result<fidl::ClientEnd<fuchsia_hardware_block::Block>> VerifiedVolumeClient::OpenForAuthoring( |
| const zx::duration& timeout) { |
| // make FIDL call to open in authoring mode |
| fidl::Arena allocator; |
| |
| // Request the device be opened for writes |
| auto open_resp = |
| fidl::WireCall(verity_chan_) |
| ->OpenForWrite( |
| fuchsia_hardware_block_verified::wire::Config::Builder(allocator) |
| .hash_function(fuchsia_hardware_block_verified::wire::HashFunction::kSha256) |
| .block_size(fuchsia_hardware_block_verified::wire::BlockSize::kSize4096) |
| .Build()); |
| if (open_resp.status() != ZX_OK) { |
| return zx::error(open_resp.status()); |
| } |
| if (open_resp->is_error()) { |
| return open_resp->take_error(); |
| } |
| |
| // Compute path of expected `mutable` child device via relative topological path |
| zx::result verity_path = RelativeTopologicalPath(verity_controller_); |
| if (verity_path.is_error()) { |
| printf("VerifiedVolumeClient: could not compute relative path: %s\n", |
| verity_path.status_string()); |
| return verity_path.take_error(); |
| } |
| fbl::String mutable_path = fbl::String::Concat({verity_path.value(), "/mutable"}); |
| |
| // Wait for the `mutable` child device to appear |
| if (zx::result channel = |
| device_watcher::RecursiveWaitForFile(devfs_root_fd_.get(), mutable_path.c_str(), timeout); |
| channel.is_error()) { |
| printf("VerifiedVolumeClient: mutable device failed to appear: %s\n", channel.status_string()); |
| return channel.take_error(); |
| } |
| |
| // Then wait for the `block` child of that mutable device |
| fbl::String mutable_block_path = fbl::String::Concat({mutable_path, "/block"}); |
| zx::result channel = device_watcher::RecursiveWaitForFile(devfs_root_fd_.get(), |
| mutable_block_path.c_str(), timeout); |
| if (channel.is_error()) { |
| printf("VerifiedVolumeClient: mutable block device failed to appear: %s\n", |
| channel.status_string()); |
| return channel.take_error(); |
| } |
| |
| // Open child device and return |
| fdio_cpp::UnownedFdioCaller caller(devfs_root_fd_.get()); |
| return component::ConnectAt<fuchsia_hardware_block::Block>(caller.directory(), |
| mutable_block_path); |
| } |
| |
| zx_status_t VerifiedVolumeClient::Close() { |
| // Close the device cleanly |
| auto close_resp = fidl::WireCall(verity_chan_)->Close(); |
| if (close_resp.status() != ZX_OK) { |
| return close_resp.status(); |
| } |
| if (close_resp->is_error()) { |
| return close_resp->error_value(); |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t VerifiedVolumeClient::CloseAndGenerateSeal( |
| fidl::AnyArena& arena, |
| fuchsia_hardware_block_verified::wire::DeviceManagerCloseAndGenerateSealResponse* out) { |
| // We use the caller-provided buffer FIDL call style because the caller |
| // needs to do something with the seal returned, so we need to keep the |
| // response object alive so that the caller can interact with it after this |
| // function returns. |
| auto seal_resp = fidl::WireCall(verity_chan_).buffer(arena)->CloseAndGenerateSeal(); |
| if (seal_resp.status() != ZX_OK) { |
| return seal_resp.status(); |
| } |
| if (seal_resp->is_error()) { |
| return seal_resp->error_value(); |
| } |
| |
| *out = *seal_resp.value().value(); |
| return ZX_OK; |
| } |
| |
| zx::result<fidl::ClientEnd<fuchsia_hardware_block::Block>> |
| VerifiedVolumeClient::OpenForVerifiedRead(const digest::Digest& expected_seal, |
| const zx::duration& timeout) { |
| // make FIDL call to open in authoring mode |
| fidl::Arena allocator; |
| |
| // Make a copy of the seal to send. |
| fuchsia_hardware_block_verified::wire::Sha256Seal sha256_seal; |
| expected_seal.CopyTo(sha256_seal.superblock_hash.begin(), sha256_seal.superblock_hash.size()); |
| |
| // Request the device be opened for verified read |
| auto open_resp = |
| fidl::WireCall(verity_chan_) |
| ->OpenForVerifiedRead( |
| fuchsia_hardware_block_verified::wire::Config::Builder(allocator) |
| .hash_function(fuchsia_hardware_block_verified::wire::HashFunction::kSha256) |
| .block_size(fuchsia_hardware_block_verified::wire::BlockSize::kSize4096) |
| .Build(), |
| fuchsia_hardware_block_verified::wire::Seal::WithSha256( |
| fidl::ObjectView<fuchsia_hardware_block_verified::wire::Sha256Seal>::FromExternal( |
| &sha256_seal))); |
| if (open_resp.status() != ZX_OK) { |
| return zx::error(open_resp.status()); |
| } |
| if (open_resp->is_error()) { |
| return open_resp->take_error(); |
| } |
| |
| // Compute path of expected `verified` child device via relative topological path |
| zx::result verity_path = RelativeTopologicalPath(verity_controller_); |
| if (verity_path.is_error()) { |
| printf("VerifiedVolumeClient: could not compute relative path: %s\n", |
| verity_path.status_string()); |
| return verity_path.take_error(); |
| } |
| fbl::String verified_path = fbl::String::Concat({verity_path.value(), "/verified"}); |
| |
| // Wait for the `verified` child device to appear |
| if (zx::result channel = device_watcher::RecursiveWaitForFile(devfs_root_fd_.get(), |
| verified_path.c_str(), timeout); |
| channel.is_error()) { |
| printf("VerifiedVolumeClient: verified device failed to appear: %s\n", channel.status_string()); |
| return channel.take_error(); |
| } |
| |
| // Then wait for the `block` child of that verified device |
| fbl::String verified_block_path = fbl::String::Concat({verified_path, "/block"}); |
| zx::result channel = device_watcher::RecursiveWaitForFile(devfs_root_fd_.get(), |
| verified_block_path.c_str(), timeout); |
| if (channel.is_error()) { |
| printf("VerifiedVolumeClient: verified block device failed to appear: %s\n", |
| channel.status_string()); |
| return channel.take_error(); |
| } |
| |
| fdio_cpp::UnownedFdioCaller caller(devfs_root_fd_.get()); |
| return component::ConnectAt<fuchsia_hardware_block::Block>(caller.directory(), |
| verified_block_path); |
| } |
| |
| } // namespace block_verity |