blob: 1c11afefad6c291e6c912183004a83eb347576a2 [file] [log] [blame]
// 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 <fuchsia/device/llcpp/fidl.h>
#include <fuchsia/hardware/block/verified/llcpp/fidl.h>
#include <lib/fdio/cpp/caller.h>
#include <lib/fdio/fdio.h>
#include <cstring>
#include <fbl/string.h>
#include <fbl/string_buffer.h>
#include <ramdevice-client/ramdisk.h> // for wait_for_device_at
namespace block_verity {
namespace {
const char* kDriverLib = "/boot/driver/block-verity.so";
zx_status_t BindVerityDriver(zx::unowned_channel block_dev_chan) {
zx_status_t rc;
auto resp = llcpp::fuchsia::device::Controller::Call::Bind(
std::move(block_dev_chan), ::fidl::unowned_str(kDriverLib, strlen(kDriverLib)));
rc = resp.status();
if (rc == ZX_OK) {
if (resp->result.is_err()) {
rc = resp->result.err();
}
}
return rc;
}
zx_status_t RelativeTopologicalPath(zx::unowned_channel channel, fbl::String* out) {
zx_status_t rc;
// Get the full device path
fbl::StringBuffer<PATH_MAX> path;
path.Resize(path.capacity());
size_t path_len;
auto resp =
llcpp::fuchsia::device::Controller::Call::GetTopologicalPath(zx::unowned_channel(channel));
rc = resp.status();
if (rc == ZX_OK) {
if (resp->result.is_err()) {
rc = resp->result.err();
} else {
auto& r = resp->result.response();
path_len = r.path.size();
memcpy(path.data(), r.path.data(), r.path.size());
}
}
if (rc != ZX_OK) {
printf("VerifiedVolumeClient: could not find parent device: %s\n", zx_status_get_string(rc));
return rc;
}
// Verify that the path returned starts with "/dev/"
const char* kSlashDevSlash = "/dev/";
if (path_len < strlen(kSlashDevSlash)) {
printf("VerifiedVolumeClient: path_len way too short: %lu\n", path_len);
return ZX_ERR_INTERNAL;
}
if (strncmp(path.c_str(), kSlashDevSlash, strlen(kSlashDevSlash)) != 0) {
printf("VerifiedVolumeClient: Expected device path to start with '/dev/' but got %s\n",
path.c_str());
return ZX_ERR_INTERNAL;
}
// Strip the leading "/dev/" and return the rest
size_t path_len_sans_dev = path_len - strlen(kSlashDevSlash);
memmove(path.begin(), path.begin() + strlen(kSlashDevSlash), path_len_sans_dev);
path.Resize(path_len_sans_dev);
*out = path.ToString();
return ZX_OK;
}
} // namespace
VerifiedVolumeClient::VerifiedVolumeClient(zx::channel verity_chan, fbl::unique_fd devfs_root_fd)
: verity_chan_(std::move(verity_chan)), devfs_root_fd_(std::move(devfs_root_fd)) {}
zx_status_t VerifiedVolumeClient::CreateFromBlockDevice(
int block_dev_fd, fbl::unique_fd devfs_root_fd, Disposition disposition,
const zx::duration& timeout, std::unique_ptr<VerifiedVolumeClient>* out) {
zx_status_t rc;
// Bind the driver if called for by `disposition`.
fdio_cpp::UnownedFdioCaller caller(block_dev_fd);
if (disposition == kDriverNeedsBinding) {
rc = BindVerityDriver(zx::unowned_channel(caller.borrow_channel()));
if (rc != ZX_OK) {
printf("VerifiedVolumeClient: couldn't bind driver: %s", zx_status_get_string(rc));
return rc;
}
}
// Compute the path at which we expect to see the verity child device appear.
fbl::String block_dev_path;
rc = RelativeTopologicalPath(zx::unowned_channel(caller.borrow_channel()), &block_dev_path);
if (rc != ZX_OK) {
printf("VerifiedVolumeClient: could not compute relative path: %s\n", zx_status_get_string(rc));
return rc;
}
fbl::String verity_path = fbl::String::Concat({block_dev_path, "/verity"});
// Wait for the device to appear.
rc = wait_for_device_at(devfs_root_fd.get(), verity_path.c_str(), timeout.get());
if (rc != ZX_OK) {
printf("VerifiedVolumeClient: verity device failed to appear: %s\n", zx_status_get_string(rc));
return rc;
}
// Open the device.
fbl::unique_fd verity_fd(openat(devfs_root_fd.get(), verity_path.c_str(), O_RDWR));
if (!verity_fd) {
printf("VerifiedVolumeClient: couldn't open verity device at %s\n", verity_path.c_str());
return ZX_ERR_NOT_FOUND;
}
// Take the channel from the FD.
zx::channel verity_chan;
rc = fdio_get_service_handle(verity_fd.release(), verity_chan.reset_and_get_address());
if (rc != ZX_OK) {
printf("VerifiedVolumeClient: couldn't get verity channel from fd: %s\n",
zx_status_get_string(rc));
return rc;
}
// Create client.
std::unique_ptr<VerifiedVolumeClient> vvc =
std::make_unique<VerifiedVolumeClient>(std::move(verity_chan), std::move(devfs_root_fd));
// Move to caller's buffer.
*out = std::move(vvc);
return ZX_OK;
}
zx_status_t VerifiedVolumeClient::OpenForAuthoring(const zx::duration& timeout,
fbl::unique_fd& mutable_block_fd_out) {
// make FIDL call to open in authoring mode
fidl::aligned<llcpp::fuchsia::hardware::block::verified::HashFunction> hash_function =
llcpp::fuchsia::hardware::block::verified::HashFunction::SHA256;
fidl::aligned<llcpp::fuchsia::hardware::block::verified::BlockSize> block_size =
llcpp::fuchsia::hardware::block::verified::BlockSize::SIZE_4096;
auto config = llcpp::fuchsia::hardware::block::verified::Config::Builder(
std::make_unique<llcpp::fuchsia::hardware::block::verified::Config::Frame>())
.set_hash_function(fidl::unowned_ptr(&hash_function))
.set_block_size(fidl::unowned_ptr(&block_size))
.build();
// Request the device be opened for writes
auto open_resp = llcpp::fuchsia::hardware::block::verified::DeviceManager::Call::OpenForWrite(
zx::unowned_channel(verity_chan_), std::move(config));
if (open_resp.status() != ZX_OK) {
return open_resp.status();
}
if (open_resp->result.is_err()) {
return open_resp->result.err();
}
// Compute path of expected `mutable` child device via relative topological path
fbl::String verity_path;
zx_status_t rc;
rc = RelativeTopologicalPath(zx::unowned_channel(verity_chan_), &verity_path);
if (rc != ZX_OK) {
printf("VerifiedVolumeClient: could not compute relative path: %s\n", zx_status_get_string(rc));
return rc;
}
fbl::String mutable_path = fbl::String::Concat({verity_path, "/mutable"});
// Wait for the `mutable` child device to appear
rc = wait_for_device_at(devfs_root_fd_.get(), mutable_path.c_str(), timeout.get());
if (rc != ZX_OK) {
printf("VerifiedVolumeClient: mutable device failed to appear: %s\n", zx_status_get_string(rc));
return rc;
}
// Then wait for the `block` child of that mutable device
fbl::String mutable_block_path = fbl::String::Concat({mutable_path, "/block"});
rc = wait_for_device_at(devfs_root_fd_.get(), mutable_block_path.c_str(), timeout.get());
if (rc != ZX_OK) {
printf("VerifiedVolumeClient: mutable block device failed to appear: %s\n",
zx_status_get_string(rc));
return rc;
}
// Open child device and return
mutable_block_fd_out.reset(openat(devfs_root_fd_.get(), mutable_block_path.c_str(), O_RDWR));
if (!mutable_block_fd_out) {
printf("VerifiedVolumeClient: failed to open %s\n", mutable_block_path.c_str());
return ZX_ERR_NOT_FOUND;
}
return ZX_OK;
}
zx_status_t VerifiedVolumeClient::Close() {
// Close the device cleanly
auto close_resp = llcpp::fuchsia::hardware::block::verified::DeviceManager::Call::Close(
zx::unowned_channel(verity_chan_));
if (close_resp.status() != ZX_OK) {
return close_resp.status();
}
if (close_resp->result.is_err()) {
return close_resp->result.err();
}
return ZX_OK;
}
zx_status_t VerifiedVolumeClient::CloseAndGenerateSeal(
fidl::Buffer<
llcpp::fuchsia::hardware::block::verified::DeviceManager::CloseAndGenerateSealResponse>*
seal_response_buffer,
llcpp::fuchsia::hardware::block::verified::DeviceManager_CloseAndGenerateSeal_Result* 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 =
llcpp::fuchsia::hardware::block::verified::DeviceManager::Call::CloseAndGenerateSeal(
zx::unowned_channel(verity_chan_), seal_response_buffer->view());
if (seal_resp.status() != ZX_OK) {
return seal_resp.status();
}
if (seal_resp->result.is_err()) {
return seal_resp->result.err();
}
*out = std::move(seal_resp->result);
return ZX_OK;
}
zx_status_t VerifiedVolumeClient::OpenForVerifiedRead(const digest::Digest& expected_seal,
const zx::duration& timeout,
fbl::unique_fd& verified_block_fd_out) {
// make FIDL call to open in authoring mode
fidl::aligned<llcpp::fuchsia::hardware::block::verified::HashFunction> hash_function =
llcpp::fuchsia::hardware::block::verified::HashFunction::SHA256;
fidl::aligned<llcpp::fuchsia::hardware::block::verified::BlockSize> block_size =
llcpp::fuchsia::hardware::block::verified::BlockSize::SIZE_4096;
auto config = llcpp::fuchsia::hardware::block::verified::Config::Builder(
std::make_unique<llcpp::fuchsia::hardware::block::verified::Config::Frame>())
.set_hash_function(fidl::unowned_ptr(&hash_function))
.set_block_size(fidl::unowned_ptr(&block_size))
.build();
// Make a copy of the seal to send.
llcpp::fuchsia::hardware::block::verified::Sha256Seal sha256_seal;
expected_seal.CopyTo(sha256_seal.superblock_hash.begin(), sha256_seal.superblock_hash.size());
fidl::aligned<llcpp::fuchsia::hardware::block::verified::Sha256Seal> aligned =
std::move(sha256_seal);
auto seal_to_send =
llcpp::fuchsia::hardware::block::verified::Seal::WithSha256(fidl::unowned_ptr(&aligned));
// Request the device be opened for verified read
auto open_resp =
llcpp::fuchsia::hardware::block::verified::DeviceManager::Call::OpenForVerifiedRead(
zx::unowned_channel(verity_chan_), std::move(config), std::move(seal_to_send));
if (open_resp.status() != ZX_OK) {
return open_resp.status();
}
if (open_resp->result.is_err()) {
return open_resp->result.err();
}
// Compute path of expected `verified` child device via relative topological path
fbl::String verity_path;
zx_status_t rc;
rc = RelativeTopologicalPath(zx::unowned_channel(verity_chan_), &verity_path);
if (rc != ZX_OK) {
printf("VerifiedVolumeClient: could not compute relative path: %s\n", zx_status_get_string(rc));
return rc;
}
fbl::String verified_path = fbl::String::Concat({verity_path, "/verified"});
// Wait for the `verified` child device to appear
fbl::unique_fd verified_fd;
rc = wait_for_device_at(devfs_root_fd_.get(), verified_path.c_str(), timeout.get());
if (rc != ZX_OK) {
printf("VerifiedVolumeClient: verified device failed to appear: %s\n",
zx_status_get_string(rc));
return rc;
}
// Then wait for the `block` child of that verified device
fbl::String verified_block_path = fbl::String::Concat({verified_path, "/block"});
rc = wait_for_device_at(devfs_root_fd_.get(), verified_block_path.c_str(), timeout.get());
if (rc != ZX_OK) {
printf("VerifiedVolumeClient: verified block device failed to appear: %s\n",
zx_status_get_string(rc));
return rc;
}
verified_block_fd_out.reset(openat(devfs_root_fd_.get(), verified_block_path.c_str(), O_RDWR));
if (!verified_block_fd_out) {
printf("VerifiedVolumeClient: failed to open %s\n", verified_block_path.c_str());
return ZX_ERR_NOT_FOUND;
}
return ZX_OK;
}
} // namespace block_verity