| // 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 "src/security/zxcrypt/fdio-volume.h" |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <fuchsia/device/llcpp/fidl.h> |
| #include <fuchsia/hardware/block/c/fidl.h> |
| #include <fuchsia/hardware/block/encrypted/c/fidl.h> |
| #include <fuchsia/hardware/block/volume/c/fidl.h> |
| #include <inttypes.h> |
| #include <lib/fdio/cpp/caller.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fdio/fdio.h> |
| #include <lib/zircon-internal/debug.h> |
| #include <lib/zx/channel.h> |
| #include <unistd.h> |
| #include <zircon/status.h> |
| |
| #include <memory> |
| #include <utility> |
| |
| #include <fbl/string_buffer.h> |
| #include <fbl/vector.h> |
| #include <ramdevice-client/ramdisk.h> // Why does wait_for_device_at() come from here? |
| |
| #include "src/security/kms-stateless/kms-stateless.h" |
| #include "src/security/zxcrypt/volume.h" |
| |
| #define ZXDEBUG 0 |
| |
| namespace zxcrypt { |
| |
| // The zxcrypt driver |
| const char* kDriverLib = "zxcrypt.so"; |
| |
| namespace { |
| |
| // Null key should be 32 bytes. |
| const size_t kKeyLength = 32; |
| const char kHardwareKeyInfo[] = "zxcrypt"; |
| |
| // How many bytes to read from /boot/config/zxcrypt? |
| const size_t kMaxKeySourcePolicyLength = 32; |
| const char kZxcryptConfigFile[] = "/pkg/config/zxcrypt"; |
| |
| // Reads /boot/config/zxcrypt to determine what key source policy was selected for this product at |
| // build time. |
| // |
| // Returns ZX_OK and sets |out| to the appropriate KeySourcePolicy value if the file contents |
| // exactly match a known configuration value. |
| // Returns ZX_ERR_NOT_FOUND if the config file was not present |
| // Returns ZX_ERR_IO if the config file could not be read |
| // Returns ZX_ERR_BAD_STATE if the config value was not recognized. |
| zx_status_t SelectKeySourcePolicy(KeySourcePolicy* out) { |
| fbl::unique_fd fd(open(kZxcryptConfigFile, O_RDONLY)); |
| if (!fd) { |
| xprintf("zxcrypt: couldn't open %s\n", kZxcryptConfigFile); |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| char key_source_buf[kMaxKeySourcePolicyLength + 1]; |
| ssize_t len = read(fd.get(), key_source_buf, sizeof(key_source_buf) - 1); |
| if (len < 0) { |
| xprintf("zxcrypt: couldn't read %s\n", kZxcryptConfigFile); |
| return ZX_ERR_IO; |
| } else { |
| // add null terminator |
| key_source_buf[len] = '\0'; |
| // Dispatch if recognized |
| if (strcmp(key_source_buf, "null") == 0) { |
| *out = NullSource; |
| return ZX_OK; |
| } |
| if (strcmp(key_source_buf, "tee") == 0) { |
| *out = TeeRequiredSource; |
| return ZX_OK; |
| } |
| if (strcmp(key_source_buf, "tee-transitional") == 0) { |
| *out = TeeTransitionalSource; |
| return ZX_OK; |
| } |
| if (strcmp(key_source_buf, "tee-opportunistic") == 0) { |
| *out = TeeOpportunisticSource; |
| return ZX_OK; |
| } |
| return ZX_ERR_BAD_STATE; |
| } |
| } |
| |
| } // namespace |
| |
| // Returns a ordered vector of |KeySource|s, representing all key sources, |
| // ordered from most-preferred to least-preferred, that we should try for the |
| // purposes of creating a new volume |
| __EXPORT |
| fbl::Vector<KeySource> ComputeEffectiveCreatePolicy(KeySourcePolicy ksp) { |
| fbl::Vector<KeySource> r; |
| switch (ksp) { |
| case NullSource: |
| r = {kNullSource}; |
| break; |
| case TeeRequiredSource: |
| case TeeTransitionalSource: |
| r = {kTeeSource}; |
| break; |
| case TeeOpportunisticSource: |
| r = {kTeeSource, kNullSource}; |
| break; |
| } |
| return r; |
| } |
| |
| // Returns a ordered vector of |KeySource|s, representing all key sources, |
| // ordered from most-preferred to least-preferred, that we should try for the |
| // purposes of unsealing an existing volume |
| __EXPORT |
| fbl::Vector<KeySource> ComputeEffectiveUnsealPolicy(KeySourcePolicy ksp) { |
| fbl::Vector<KeySource> r; |
| switch (ksp) { |
| case NullSource: |
| r = {kNullSource}; |
| break; |
| case TeeRequiredSource: |
| r = {kTeeSource}; |
| break; |
| case TeeTransitionalSource: |
| case TeeOpportunisticSource: |
| r = {kTeeSource, kNullSource}; |
| break; |
| } |
| return r; |
| } |
| |
| __EXPORT |
| zx_status_t TryWithKeysFrom( |
| const fbl::Vector<KeySource>& ordered_key_sources, Activity activity, |
| fbl::Function<zx_status_t(std::unique_ptr<uint8_t[]>, size_t)> callback) { |
| zx_status_t rc = ZX_ERR_INTERNAL; |
| for (auto& key_source : ordered_key_sources) { |
| switch (key_source) { |
| case kNullSource: { |
| auto key_buf = std::unique_ptr<uint8_t[]>(new uint8_t[kKeyLength]); |
| memset(key_buf.get(), 0, kKeyLength); |
| rc = callback(std::move(key_buf), kKeyLength); |
| } break; |
| case kTeeSource: { |
| // key info is |kHardwareKeyInfo| padded with 0. |
| uint8_t key_info[kms_stateless::kExpectedKeyInfoSize] = {0}; |
| memcpy(key_info, kHardwareKeyInfo, sizeof(kHardwareKeyInfo)); |
| // make names for these so the callback to kms_stateless can |
| // copy them out later |
| std::unique_ptr<uint8_t[]> key_buf; |
| size_t key_size; |
| zx_status_t kms_rc = kms_stateless::GetHardwareDerivedKey( |
| [&](std::unique_ptr<uint8_t[]> cb_key_buffer, size_t cb_key_size) { |
| key_size = cb_key_size; |
| key_buf = std::unique_ptr<uint8_t[]>(new uint8_t[cb_key_size]); |
| memcpy(key_buf.get(), cb_key_buffer.get(), cb_key_size); |
| return ZX_OK; |
| }, |
| key_info); |
| |
| if (kms_rc != ZX_OK) { |
| rc = kms_rc; |
| break; |
| } |
| |
| rc = callback(std::move(key_buf), key_size); |
| } break; |
| } |
| if (rc == ZX_OK) { |
| return rc; |
| } |
| } |
| |
| xprintf("TryWithKeysFrom (%s): none of the %lu key sources succeeded\n", |
| activity == Activity::Create ? "create" : "unseal", ordered_key_sources.size()); |
| return rc; |
| } |
| |
| FdioVolumeManager::FdioVolumeManager(zx::channel&& chan) : chan_(std::move(chan)) {} |
| |
| zx_status_t FdioVolumeManager::Unseal(const uint8_t* key, size_t key_len, uint8_t slot) { |
| zx_status_t rc; |
| zx_status_t call_status; |
| if ((rc = fuchsia_hardware_block_encrypted_DeviceManagerUnseal(chan_.get(), key, key_len, slot, |
| &call_status)) != ZX_OK) { |
| xprintf("failed to call Unseal: %s\n", zx_status_get_string(rc)); |
| return rc; |
| } |
| |
| if (call_status != ZX_OK) { |
| xprintf("failed to Unseal: %s\n", zx_status_get_string(call_status)); |
| } |
| return call_status; |
| } |
| |
| zx_status_t FdioVolumeManager::UnsealWithDeviceKey(uint8_t slot) { |
| KeySourcePolicy source; |
| zx_status_t rc; |
| rc = SelectKeySourcePolicy(&source); |
| if (rc != ZX_OK) { |
| return rc; |
| } |
| |
| auto ordered_key_sources = ComputeEffectiveUnsealPolicy(source); |
| |
| return TryWithKeysFrom(ordered_key_sources, Activity::Unseal, |
| [&](std::unique_ptr<uint8_t[]> key_buffer, size_t key_size) { |
| return Unseal(key_buffer.get(), key_size, slot); |
| }); |
| } |
| |
| zx_status_t FdioVolumeManager::Seal() { |
| zx_status_t rc; |
| zx_status_t call_status; |
| if ((rc = fuchsia_hardware_block_encrypted_DeviceManagerSeal(chan_.get(), &call_status)) != |
| ZX_OK) { |
| xprintf("failed to call Seal: %s\n", zx_status_get_string(rc)); |
| return rc; |
| } |
| if (call_status != ZX_OK) { |
| xprintf("failed to Seal: %s\n", zx_status_get_string(call_status)); |
| } |
| return call_status; |
| } |
| |
| zx_status_t FdioVolumeManager::Shred() { |
| zx_status_t rc; |
| zx_status_t call_status; |
| if ((rc = fuchsia_hardware_block_encrypted_DeviceManagerShred(chan_.get(), &call_status)) != |
| ZX_OK) { |
| xprintf("failed to call Shred: %s\n", zx_status_get_string(rc)); |
| return rc; |
| } |
| if (call_status != ZX_OK) { |
| xprintf("failed to Shred: %s\n", zx_status_get_string(call_status)); |
| } |
| return call_status; |
| } |
| |
| FdioVolume::FdioVolume(fbl::unique_fd&& block_dev_fd, fbl::unique_fd&& devfs_root_fd) |
| : Volume(), block_dev_fd_(std::move(block_dev_fd)), devfs_root_fd_(std::move(devfs_root_fd)) {} |
| |
| zx_status_t FdioVolume::Init(fbl::unique_fd block_dev_fd, fbl::unique_fd devfs_root_fd, |
| std::unique_ptr<FdioVolume>* out) { |
| zx_status_t rc; |
| |
| if (!block_dev_fd || !devfs_root_fd || !out) { |
| xprintf("bad parameter(s): block_dev_fd=%d, devfs_root_fd=%d out=%p\n", block_dev_fd.get(), |
| devfs_root_fd.get(), out); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| fbl::AllocChecker ac; |
| std::unique_ptr<FdioVolume> volume( |
| new (&ac) FdioVolume(std::move(block_dev_fd), std::move(devfs_root_fd))); |
| if (!ac.check()) { |
| xprintf("allocation failed: %zu bytes\n", sizeof(FdioVolume)); |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| if ((rc = volume->Init()) != ZX_OK) { |
| return rc; |
| } |
| |
| *out = std::move(volume); |
| return ZX_OK; |
| } |
| |
| zx_status_t FdioVolume::Create(fbl::unique_fd block_dev_fd, fbl::unique_fd devfs_root_fd, |
| const crypto::Secret& key, std::unique_ptr<FdioVolume>* out) { |
| zx_status_t rc; |
| |
| std::unique_ptr<FdioVolume> volume; |
| |
| if ((rc = FdioVolume::Init(std::move(block_dev_fd), std::move(devfs_root_fd), &volume)) != |
| ZX_OK) { |
| xprintf("Init failed: %s\n", zx_status_get_string(rc)); |
| return rc; |
| } |
| if ((rc = volume->CreateBlock()) != ZX_OK) { |
| xprintf("CreateBlock failed: %s\n", zx_status_get_string(rc)); |
| return rc; |
| } |
| if ((rc = volume->SealBlock(key, 0)) != ZX_OK) { |
| xprintf("SealBlock failed: %s\n", zx_status_get_string(rc)); |
| return rc; |
| } |
| if ((rc = volume->CommitBlock()) != ZX_OK) { |
| xprintf("CommitBlock failed: %s\n", zx_status_get_string(rc)); |
| return rc; |
| } |
| |
| if (out) { |
| *out = std::move(volume); |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t FdioVolume::CreateWithDeviceKey(fbl::unique_fd&& block_dev_fd, |
| fbl::unique_fd&& devfs_root_fd, |
| std::unique_ptr<FdioVolume>* out) { |
| KeySourcePolicy source; |
| zx_status_t rc; |
| rc = SelectKeySourcePolicy(&source); |
| if (rc != ZX_OK) { |
| return rc; |
| } |
| |
| // Figure out which keying approaches we'll try, based on the key source |
| // policy and context we're using this key in |
| auto ordered_key_sources = ComputeEffectiveCreatePolicy(source); |
| return TryWithKeysFrom(ordered_key_sources, Activity::Create, |
| [&](std::unique_ptr<uint8_t[]> key_buffer, size_t key_size) { |
| crypto::Secret secret; |
| zx_status_t rc; |
| uint8_t* inner; |
| rc = secret.Allocate(key_size, &inner); |
| if (rc != ZX_OK) { |
| xprintf("zxcrypt: couldn't allocate secret\n"); |
| return rc; |
| } |
| memcpy(inner, key_buffer.get(), key_size); |
| rc = FdioVolume::Create(std::move(block_dev_fd), |
| std::move(devfs_root_fd), secret, out); |
| return rc; |
| }); |
| } |
| |
| zx_status_t FdioVolume::Unlock(fbl::unique_fd block_dev_fd, fbl::unique_fd devfs_root_fd, |
| const crypto::Secret& key, key_slot_t slot, |
| std::unique_ptr<FdioVolume>* out) { |
| zx_status_t rc; |
| |
| std::unique_ptr<FdioVolume> volume; |
| if ((rc = FdioVolume::Init(std::move(block_dev_fd), std::move(devfs_root_fd), &volume)) != |
| ZX_OK) { |
| xprintf("Init failed: %s\n", zx_status_get_string(rc)); |
| return rc; |
| } |
| if ((rc = volume->Unlock(key, slot)) != ZX_OK) { |
| xprintf("Unlock failed: %s\n", zx_status_get_string(rc)); |
| return rc; |
| } |
| |
| *out = std::move(volume); |
| return ZX_OK; |
| } |
| |
| zx_status_t FdioVolume::UnlockWithDeviceKey(fbl::unique_fd block_dev_fd, |
| fbl::unique_fd devfs_root_fd, key_slot_t slot, |
| std::unique_ptr<FdioVolume>* out) { |
| KeySourcePolicy source; |
| zx_status_t rc; |
| rc = SelectKeySourcePolicy(&source); |
| if (rc != ZX_OK) { |
| return rc; |
| } |
| |
| auto ordered_key_sources = ComputeEffectiveUnsealPolicy(source); |
| return TryWithKeysFrom(ordered_key_sources, Activity::Unseal, |
| [&](std::unique_ptr<uint8_t[]> key_buffer, size_t key_size) { |
| crypto::Secret secret; |
| zx_status_t rc; |
| uint8_t* inner; |
| rc = secret.Allocate(key_size, &inner); |
| if (rc != ZX_OK) { |
| xprintf("FdioVolume::UnlockWithDeviceKey: couldn't allocate secret\n"); |
| return rc; |
| } |
| memcpy(inner, key_buffer.get(), key_size); |
| rc = FdioVolume::Unlock(std::move(block_dev_fd), |
| std::move(devfs_root_fd), secret, slot, out); |
| return rc; |
| }); |
| } |
| |
| zx_status_t FdioVolume::Unlock(const crypto::Secret& key, key_slot_t slot) { |
| return Volume::Unlock(key, slot); |
| } |
| |
| // Configuration methods |
| zx_status_t FdioVolume::Enroll(const crypto::Secret& key, key_slot_t slot) { |
| zx_status_t rc; |
| |
| if ((rc = SealBlock(key, slot)) != ZX_OK) { |
| xprintf("SealBlock failed: %s\n", zx_status_get_string(rc)); |
| return rc; |
| } |
| if ((rc = CommitBlock()) != ZX_OK) { |
| xprintf("CommitBlock failed: %s\n", zx_status_get_string(rc)); |
| return rc; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t FdioVolume::Revoke(key_slot_t slot) { |
| zx_status_t rc; |
| |
| zx_off_t off; |
| crypto::Bytes invalid; |
| if ((rc = GetSlotOffset(slot, &off)) != ZX_OK) { |
| xprintf("GetSlotOffset failed: %s\n", zx_status_get_string(rc)); |
| return rc; |
| } |
| if ((rc = invalid.Randomize(slot_len_)) != ZX_OK) { |
| xprintf("Randomize failed: %s\n", zx_status_get_string(rc)); |
| return rc; |
| } |
| if ((rc = block_.Copy(invalid, off)) != ZX_OK) { |
| xprintf("Copy failed: %s\n", zx_status_get_string(rc)); |
| return rc; |
| } |
| if ((rc = CommitBlock()) != ZX_OK) { |
| xprintf("CommitBlock failed: %s\n", zx_status_get_string(rc)); |
| return rc; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t FdioVolume::Init() { return Volume::Init(); } |
| |
| zx_status_t FdioVolume::OpenManager(const zx::duration& timeout, zx_handle_t* out) { |
| fdio_cpp::UnownedFdioCaller caller(block_dev_fd_.get()); |
| if (!caller) { |
| xprintf("could not convert fd to io\n"); |
| return ZX_ERR_BAD_STATE; |
| } |
| return OpenManagerWithCaller(caller, timeout, out); |
| } |
| |
| zx_status_t FdioVolume::Open(const zx::duration& timeout, fbl::unique_fd* out) { |
| zx_status_t rc; |
| fbl::String path_base; |
| |
| fdio_cpp::UnownedFdioCaller caller(block_dev_fd_.get()); |
| if (!caller) { |
| xprintf("could not convert fd to io\n"); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| if ((rc = RelativeTopologicalPath(caller, &path_base)) != ZX_OK) { |
| xprintf("could not get topological path: %s\n", zx_status_get_string(rc)); |
| return rc; |
| } |
| fbl::String path_block_exposed = fbl::String::Concat({path_base, "/zxcrypt/unsealed/block"}); |
| |
| // Early return if path_block_exposed is already present in the device tree |
| fbl::unique_fd fd(openat(devfs_root_fd_.get(), path_block_exposed.c_str(), O_RDWR)); |
| if (fd) { |
| out->reset(fd.release()); |
| return ZX_OK; |
| } |
| |
| // Wait for the unsealed and block devices to bind |
| if ((rc = wait_for_device_at(devfs_root_fd_.get(), path_block_exposed.c_str(), timeout.get())) != |
| ZX_OK) { |
| xprintf("timed out waiting for %s to exist: %s\n", path_block_exposed.c_str(), |
| zx_status_get_string(rc)); |
| return rc; |
| } |
| fd.reset(openat(devfs_root_fd_.get(), path_block_exposed.c_str(), O_RDWR)); |
| if (!fd) { |
| xprintf("failed to open zxcrypt volume\n"); |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| out->reset(fd.release()); |
| return ZX_OK; |
| } |
| |
| zx_status_t FdioVolume::GetBlockInfo(BlockInfo* out) { |
| zx_status_t rc; |
| zx_status_t call_status; |
| fdio_cpp::UnownedFdioCaller caller(block_dev_fd_.get()); |
| if (!caller) { |
| return ZX_ERR_BAD_STATE; |
| } |
| fuchsia_hardware_block_BlockInfo block_info; |
| if ((rc = fuchsia_hardware_block_BlockGetInfo(caller.borrow_channel(), &call_status, |
| &block_info)) != ZX_OK) { |
| return rc; |
| } |
| if (call_status != ZX_OK) { |
| return call_status; |
| } |
| |
| out->block_count = block_info.block_count; |
| out->block_size = block_info.block_size; |
| return ZX_OK; |
| } |
| |
| zx_status_t FdioVolume::GetFvmSliceSize(uint64_t* out) { |
| zx_status_t rc; |
| zx_status_t call_status; |
| fdio_cpp::UnownedFdioCaller caller(block_dev_fd_.get()); |
| if (!caller) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| // When this function is called, we're not yet sure if the underlying device |
| // actually implements the block protocol, and we use the return value here |
| // to tell us if we should utilize FVM-specific codepaths or not. |
| // If the underlying channel doesn't respond to volume methods, when we call |
| // a method from fuchsia.hardware.block.volume the FIDL channel will be |
| // closed and we'll be unable to do other calls to it. So before making |
| // this call, we clone the channel. |
| zx::channel channel(fdio_service_clone(caller.borrow_channel())); |
| |
| fuchsia_hardware_block_volume_VolumeInfo volume_info; |
| if ((rc = fuchsia_hardware_block_volume_VolumeQuery(channel.get(), &call_status, &volume_info)) != |
| ZX_OK) { |
| if (rc == ZX_ERR_PEER_CLOSED) { |
| // The channel being closed here means that the thing at the other |
| // end of this channel does not speak the FVM protocol, and has |
| // closed the channel on us. Return the appropriate error to signal |
| // that we shouldn't bother with any of the FVM codepaths. |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| return rc; |
| } |
| if (call_status != ZX_OK) { |
| return call_status; |
| } |
| |
| *out = volume_info.slice_size; |
| return ZX_OK; |
| } |
| |
| zx_status_t FdioVolume::DoBlockFvmVsliceQuery(uint64_t vslice_start, |
| SliceRegion ranges[Volume::MAX_SLICE_REGIONS], |
| uint64_t* slice_count) { |
| static_assert(fuchsia_hardware_block_volume_MAX_SLICE_REQUESTS == Volume::MAX_SLICE_REGIONS, |
| "block volume slice response count must match"); |
| zx_status_t rc; |
| zx_status_t call_status; |
| fdio_cpp::UnownedFdioCaller caller(block_dev_fd_.get()); |
| if (!caller) { |
| return ZX_ERR_BAD_STATE; |
| } |
| fuchsia_hardware_block_volume_VsliceRange tmp_ranges[Volume::MAX_SLICE_REGIONS]; |
| uint64_t range_count; |
| |
| if ((rc = fuchsia_hardware_block_volume_VolumeQuerySlices( |
| caller.borrow_channel(), &vslice_start, 1, &call_status, tmp_ranges, &range_count)) != |
| ZX_OK) { |
| return rc; |
| } |
| if (call_status != ZX_OK) { |
| return call_status; |
| } |
| |
| if (range_count > Volume::MAX_SLICE_REGIONS) { |
| // Should be impossible. Trust nothing. |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| *slice_count = range_count; |
| for (size_t i = 0; i < range_count; i++) { |
| ranges[i].allocated = tmp_ranges[i].allocated; |
| ranges[i].count = tmp_ranges[i].count; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t FdioVolume::DoBlockFvmExtend(uint64_t start_slice, uint64_t slice_count) { |
| zx_status_t rc; |
| zx_status_t call_status; |
| fdio_cpp::UnownedFdioCaller caller(block_dev_fd_.get()); |
| if (!caller) { |
| return ZX_ERR_BAD_STATE; |
| } |
| if ((rc = fuchsia_hardware_block_volume_VolumeExtend(caller.borrow_channel(), start_slice, |
| slice_count, &call_status)) != ZX_OK) { |
| return rc; |
| } |
| if (call_status != ZX_OK) { |
| return call_status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t FdioVolume::Read() { |
| if (lseek(block_dev_fd_.get(), offset_, SEEK_SET) < 0) { |
| xprintf("lseek(%d, %" PRIu64 ", SEEK_SET) failed: %s\n", block_dev_fd_.get(), offset_, |
| strerror(errno)); |
| return ZX_ERR_IO; |
| } |
| ssize_t res; |
| if ((res = read(block_dev_fd_.get(), block_.get(), block_.len())) < 0) { |
| xprintf("read(%d, %p, %zu) failed: %s\n", block_dev_fd_.get(), block_.get(), block_.len(), |
| strerror(errno)); |
| return ZX_ERR_IO; |
| } |
| if (static_cast<size_t>(res) != block_.len()) { |
| xprintf("short read: have %zd, need %zu\n", res, block_.len()); |
| return ZX_ERR_IO; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t FdioVolume::Write() { |
| if (lseek(block_dev_fd_.get(), offset_, SEEK_SET) < 0) { |
| xprintf("lseek(%d, %" PRIu64 ", SEEK_SET) failed: %s\n", block_dev_fd_.get(), offset_, |
| strerror(errno)); |
| return ZX_ERR_IO; |
| } |
| ssize_t res; |
| if ((res = write(block_dev_fd_.get(), block_.get(), block_.len())) < 0) { |
| xprintf("write(%d, %p, %zu) failed: %s\n", block_dev_fd_.get(), block_.get(), block_.len(), |
| strerror(errno)); |
| return ZX_ERR_IO; |
| } |
| if (static_cast<size_t>(res) != block_.len()) { |
| xprintf("short write: have %zd, need %zu\n", res, block_.len()); |
| return ZX_ERR_IO; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t FdioVolume::OpenManagerWithCaller(fdio_cpp::UnownedFdioCaller& caller, |
| const zx::duration& timeout, zx_handle_t* out) { |
| zx_status_t rc; |
| fbl::String path_base; |
| |
| if ((rc = RelativeTopologicalPath(caller, &path_base)) != ZX_OK) { |
| xprintf("could not get topological path: %s\n", zx_status_get_string(rc)); |
| return rc; |
| } |
| fbl::String path_manager = fbl::String::Concat({path_base, "/zxcrypt"}); |
| |
| fbl::unique_fd fd(openat(devfs_root_fd_.get(), path_manager.c_str(), O_RDWR)); |
| if (!fd) { |
| // No manager device in the /dev tree yet. Try binding the zxcrypt |
| // driver and waiting for it to appear. |
| auto resp = |
| fidl::WireCall<fuchsia_device::Controller>(zx::unowned_channel(caller.borrow_channel())) |
| .Bind(::fidl::StringView::FromExternal(kDriverLib)); |
| rc = resp.status(); |
| if (rc == ZX_OK) { |
| if (resp->result.is_err()) { |
| rc = resp->result.err(); |
| } |
| } |
| if (rc != ZX_OK) { |
| xprintf("could not bind zxcrypt driver: %s\n", zx_status_get_string(rc)); |
| return rc; |
| } |
| |
| // Await the appearance of the zxcrypt device. |
| if ((rc = wait_for_device_at(devfs_root_fd_.get(), path_manager.c_str(), timeout.get())) != |
| ZX_OK) { |
| xprintf("zxcrypt driver failed to bind: %s\n", zx_status_get_string(rc)); |
| return rc; |
| } |
| |
| fd.reset(openat(devfs_root_fd_.get(), path_manager.c_str(), O_RDWR)); |
| if (!fd) { |
| xprintf("failed to open zxcrypt manager\n"); |
| return ZX_ERR_NOT_FOUND; |
| } |
| } |
| |
| if ((rc = fdio_get_service_handle(fd.release(), out)) != ZX_OK) { |
| xprintf("failed to get service handle for zxcrypt manager: %s\n", zx_status_get_string(rc)); |
| return rc; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t FdioVolume::RelativeTopologicalPath(fdio_cpp::UnownedFdioCaller& caller, |
| 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 = |
| fidl::WireCall<fuchsia_device::Controller>(zx::unowned_channel(caller.borrow_channel())) |
| .GetTopologicalPath(); |
| 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) { |
| xprintf("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)) { |
| xprintf("path_len way too short: %lu\n", path_len); |
| return ZX_ERR_INTERNAL; |
| } |
| if (strncmp(path.c_str(), kSlashDevSlash, strlen(kSlashDevSlash)) != 0) { |
| xprintf("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 zxcrypt |