| // 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/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 "src/security/zxcrypt/volume.h" |
| |
| #define ZXDEBUG 0 |
| |
| namespace zxcrypt { |
| |
| FdioVolume::FdioVolume(fbl::unique_fd&& block_dev_fd) |
| : Volume(), block_dev_fd_(std::move(block_dev_fd)) {} |
| |
| zx_status_t FdioVolume::Init(fbl::unique_fd block_dev_fd, std::unique_ptr<FdioVolume>* out) { |
| zx_status_t rc; |
| |
| if (!block_dev_fd || !out) { |
| xprintf("bad parameter(s): block_dev_fd=%d, out=%p\n", block_dev_fd.get(), out); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| fbl::AllocChecker ac; |
| std::unique_ptr<FdioVolume> volume(new (&ac) FdioVolume(std::move(block_dev_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, 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), &volume)) != ZX_OK) { |
| xprintf("Init failed: %s\n", zx_status_get_string(rc)); |
| return rc; |
| } |
| |
| uint8_t slot = 0; |
| if ((rc = volume->Format(key, slot)) != ZX_OK) { |
| xprintf("Format failed: %s\n", zx_status_get_string(rc)); |
| return rc; |
| } |
| |
| if (out) { |
| *out = std::move(volume); |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t FdioVolume::Unlock(fbl::unique_fd block_dev_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), &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::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::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_VolumeManagerInfo manager_info; |
| fuchsia_hardware_block_volume_VolumeInfo volume_info; |
| if ((rc = fuchsia_hardware_block_volume_VolumeGetVolumeInfo( |
| channel.get(), &call_status, &manager_info, &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 = manager_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::Flush() { |
| // On Fuchsia, an FD produced by opening a block device out of the device tree doesn't implement |
| // fsync(), so we stub this out. FdioVolume is only used for tests anyway, which don't need to |
| // worry too much about durability. |
| return ZX_OK; |
| } |
| |
| } // namespace zxcrypt |