| // 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 <fuchsia/hardware/block/c/banjo.h> |
| #include <fuchsia/hardware/block/volume/c/banjo.h> |
| #include <inttypes.h> |
| #include <lib/sync/completion.h> |
| #include <lib/zircon-internal/debug.h> |
| #include <lib/zx/vmo.h> |
| #include <zircon/assert.h> |
| #include <zircon/status.h> |
| |
| #include <memory> |
| #include <utility> |
| |
| #include <fbl/alloc_checker.h> |
| #include "src/security/zxcrypt/ddk-volume.h" |
| #include "src/security/zxcrypt/volume.h" |
| |
| #define ZXDEBUG 0 |
| |
| namespace zxcrypt { |
| |
| void SyncComplete(void* cookie, zx_status_t status, block_op_t* block) { |
| // Use the 32bit command field to shuttle the response back to the callsite that's waiting on |
| // the completion |
| block->command = status; |
| sync_completion_signal(static_cast<sync_completion_t*>(cookie)); |
| } |
| |
| // Performs synchronous I/O |
| zx_status_t SyncIO(zx_device_t* dev, uint32_t cmd, void* buf, size_t off, size_t len) { |
| zx_status_t rc; |
| |
| if (!dev || !buf || len == 0) { |
| xprintf("bad parameter(s): dev=%p, buf=%p, len=%zu\n", dev, buf, len); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| block_impl_protocol_t proto; |
| if ((rc = device_get_protocol(dev, ZX_PROTOCOL_BLOCK, &proto)) != ZX_OK) { |
| xprintf("block protocol not support\n"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx::vmo vmo; |
| if ((rc = zx::vmo::create(len, 0, &vmo)) != ZX_OK) { |
| xprintf("zx::vmo::create failed: %s\n", zx_status_get_string(rc)); |
| return rc; |
| } |
| |
| block_info_t info; |
| size_t op_size; |
| block_impl_query(&proto, &info, &op_size); |
| |
| size_t bsz = info.block_size; |
| ZX_DEBUG_ASSERT(off / bsz <= UINT32_MAX); |
| ZX_DEBUG_ASSERT(len / bsz <= UINT32_MAX); |
| fbl::AllocChecker ac; |
| std::unique_ptr<char[]> raw; |
| if constexpr (alignof(block_op_t) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) { |
| raw = std::unique_ptr<char[]>( |
| new (static_cast<std::align_val_t>(alignof(block_op_t)), &ac) char[op_size]); |
| } else { |
| raw = std::unique_ptr<char[]>(new (&ac) char[op_size]); |
| } |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| block_op_t* block = reinterpret_cast<block_op_t*>(raw.get()); |
| |
| sync_completion_t completion; |
| sync_completion_reset(&completion); |
| |
| block->command = cmd; |
| block->rw.vmo = vmo.get(); |
| block->rw.length = static_cast<uint32_t>(len / bsz); |
| block->rw.offset_dev = static_cast<uint32_t>(off / bsz); |
| block->rw.offset_vmo = 0; |
| |
| if (cmd == BLOCK_OP_WRITE && (rc = vmo.write(buf, 0, len)) != ZX_OK) { |
| xprintf("zx::vmo::write failed: %s\n", zx_status_get_string(rc)); |
| return rc; |
| } |
| |
| block_impl_queue(&proto, block, SyncComplete, &completion); |
| sync_completion_wait(&completion, ZX_TIME_INFINITE); |
| |
| rc = block->command; |
| if (rc != ZX_OK) { |
| xprintf("Block I/O failed: %s\n", zx_status_get_string(rc)); |
| return rc; |
| } |
| |
| if (cmd == BLOCK_OP_READ && (rc = vmo.read(buf, 0, len)) != ZX_OK) { |
| xprintf("zx::vmo::read failed: %s\n", zx_status_get_string(rc)); |
| return rc; |
| } |
| |
| return ZX_OK; |
| } |
| |
| DdkVolume::DdkVolume(zx_device_t* dev) : Volume(), dev_(dev) {} |
| |
| zx_status_t DdkVolume::Bind(crypto::Cipher::Direction direction, crypto::Cipher* cipher) const { |
| zx_status_t rc; |
| ZX_DEBUG_ASSERT(dev_); // Cannot bind from library |
| |
| if (!cipher) { |
| xprintf("bad parameter(s): cipher=%p\n", cipher); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (!block_.get()) { |
| xprintf("not initialized\n"); |
| return ZX_ERR_BAD_STATE; |
| } |
| if ((rc = cipher->Init(cipher_, direction, data_key_, data_iv_, block_.len())) != ZX_OK) { |
| return rc; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t DdkVolume::Unlock(zx_device_t* dev, const crypto::Secret& key, key_slot_t slot, |
| std::unique_ptr<DdkVolume>* out) { |
| zx_status_t rc; |
| std::unique_ptr<DdkVolume> volume; |
| if ((rc = DdkVolume::OpenOpaque(dev, &volume)) != ZX_OK) { |
| return rc; |
| } |
| |
| if ((rc = volume->Unlock(key, slot)) != ZX_OK) { |
| xprintf("volume->Unlock() failed: %s\n", zx_status_get_string(rc)); |
| return rc; |
| } |
| |
| *out = std::move(volume); |
| return ZX_OK; |
| } |
| |
| zx_status_t DdkVolume::OpenOpaque(zx_device_t* dev, std::unique_ptr<DdkVolume>* out) { |
| zx_status_t rc; |
| |
| if (!dev || !out) { |
| xprintf("bad parameter(s): dev=%p, out=%p\n", dev, out); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| fbl::AllocChecker ac; |
| std::unique_ptr<DdkVolume> volume(new (&ac) DdkVolume(dev)); |
| if (!ac.check()) { |
| xprintf("allocation failed: %zu bytes\n", sizeof(DdkVolume)); |
| return ZX_ERR_NO_MEMORY; |
| } |
| if ((rc = volume->Init()) != ZX_OK) { |
| xprintf("volume->Init() failed: %s\n", zx_status_get_string(rc)); |
| return rc; |
| } |
| |
| *out = std::move(volume); |
| return ZX_OK; |
| } |
| |
| zx_status_t DdkVolume::Init() { return Volume::Init(); } |
| |
| zx_status_t DdkVolume::Unlock(const crypto::Secret& key, key_slot_t slot) { |
| return Volume::Unlock(key, slot); |
| } |
| |
| zx_status_t DdkVolume::GetBlockInfo(BlockInfo* out) { |
| zx_status_t rc; |
| block_impl_protocol_t proto; |
| if ((rc = device_get_protocol(dev_, ZX_PROTOCOL_BLOCK, &proto)) != ZX_OK) { |
| xprintf("block protocol not supported\n"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| block_info_t info; |
| size_t block_op_size; |
| block_impl_query(&proto, &info, &block_op_size); |
| |
| out->block_count = info.block_count; |
| out->block_size = info.block_size; |
| return ZX_OK; |
| } |
| |
| zx_status_t DdkVolume::GetFvmSliceSize(uint64_t* out) { |
| zx_status_t rc; |
| block_volume_protocol_t proto; |
| if ((rc = device_get_protocol(dev_, ZX_PROTOCOL_BLOCK_VOLUME, &proto)) != ZX_OK) { |
| xprintf("block volume protocol not supported\n"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| parent_volume_info_t info; |
| if ((rc = block_volume_query(&proto, &info)) != ZX_OK) { |
| return rc; |
| } |
| |
| *out = info.slice_size; |
| return ZX_OK; |
| } |
| |
| zx_status_t DdkVolume::DoBlockFvmVsliceQuery(uint64_t vslice_start, |
| SliceRegion ranges[MAX_SLICE_REGIONS], |
| uint64_t* slice_count) { |
| static_assert(MAX_SLICE_QUERY_REQUESTS == Volume::MAX_SLICE_REGIONS, |
| "block volume slice response count must match"); |
| zx_status_t rc; |
| block_volume_protocol_t proto; |
| if ((rc = device_get_protocol(dev_, ZX_PROTOCOL_BLOCK_VOLUME, &proto)) != ZX_OK) { |
| xprintf("block volume protocol not supported\n"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| slice_region_t tmp_ranges[MAX_SLICE_QUERY_REQUESTS]; |
| size_t range_count; |
| if ((rc = block_volume_query_slices(&proto, &vslice_start, 1, tmp_ranges, |
| MAX_SLICE_QUERY_REQUESTS, &range_count)) != ZX_OK) { |
| return rc; |
| } |
| 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 DdkVolume::DoBlockFvmExtend(uint64_t start_slice, uint64_t slice_count) { |
| zx_status_t rc; |
| block_volume_protocol_t proto; |
| if ((rc = device_get_protocol(dev_, ZX_PROTOCOL_BLOCK_VOLUME, &proto)) != ZX_OK) { |
| xprintf("block volume protocol not supported\n"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| slice_extent_t extent; |
| extent.offset = start_slice; |
| extent.length = slice_count; |
| return block_volume_extend(&proto, &extent); |
| } |
| |
| zx_status_t DdkVolume::Read() { |
| return SyncIO(dev_, BLOCK_OP_READ, block_.get(), offset_, block_.len()); |
| } |
| |
| zx_status_t DdkVolume::Write() { |
| return SyncIO(dev_, BLOCK_OP_WRITE, block_.get(), offset_, block_.len()); |
| } |
| |
| } // namespace zxcrypt |