blob: d4192f80aa09b81bcd75ef1680406de97b2bb889 [file] [log] [blame]
// Copyright 2018 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/zxcrypt/worker.h"
#include <inttypes.h>
#include <lib/ddk/debug.h>
#include <lib/fit/defer.h>
#include <lib/trace/event.h>
#include <lib/zircon-internal/align.h>
#include <lib/zx/port.h>
#include <stddef.h>
#include <stdint.h>
#include <threads.h>
#include <zircon/listnode.h>
#include <zircon/status.h>
#include <zircon/syscalls/port.h>
#include <zircon/types.h>
#include <utility>
#include "src/devices/block/drivers/zxcrypt/debug.h"
#include "src/devices/block/drivers/zxcrypt/device.h"
#include "src/devices/block/drivers/zxcrypt/extra.h"
#include "src/devices/block/drivers/zxcrypt/queue.h"
#include "src/security/lib/fcrypto/cipher.h"
#include "src/security/lib/zxcrypt/ddk-volume.h"
#include "src/security/lib/zxcrypt/volume.h"
namespace zxcrypt {
Worker::Worker() : device_(nullptr), started_(false) { LOG_ENTRY(); }
Worker::~Worker() {
LOG_ENTRY();
ZX_DEBUG_ASSERT(!started_.load());
}
zx_status_t Worker::Start(Device* device, const DdkVolume& volume, Queue<block_op_t*>& queue) {
LOG_ENTRY_ARGS("device=%p, volume=%p, queue=%p", device, &volume, &queue);
zx_status_t rc;
if (!device) {
zxlogf(ERROR, "bad parameters: device=%p", device);
return ZX_ERR_INVALID_ARGS;
}
device_ = device;
if ((rc = volume.Bind(crypto::Cipher::kEncrypt, &encrypt_)) != ZX_OK ||
(rc = volume.Bind(crypto::Cipher::kDecrypt, &decrypt_)) != ZX_OK) {
zxlogf(ERROR, "failed to bind ciphers: %s", zx_status_get_string(rc));
return rc;
}
queue_ = &queue;
if (thrd_create_with_name(&thrd_, WorkerRun, this, "zxcrypt_worker") != thrd_success) {
zxlogf(ERROR, "failed to start thread");
return ZX_ERR_INTERNAL;
}
started_.store(true);
return ZX_OK;
}
zx_status_t Worker::Run() {
LOG_ENTRY();
ZX_DEBUG_ASSERT(device_);
for (;;) {
block_op_t* block;
if (auto block_or = queue_->Pop()) {
block = *block_or;
} else {
zxlogf(DEBUG, "worker %p stopping.", this);
return ZX_OK;
}
TRACE_DURATION("zxcrypt", "zxcrypt::Worker::Dispatch");
// Dispatch block request
switch (block->command.opcode) {
case BLOCK_OPCODE_WRITE:
device_->BlockForward(block, EncryptWrite(block));
break;
case BLOCK_OPCODE_READ:
device_->BlockComplete(block, DecryptRead(block));
break;
default:
device_->BlockComplete(block, ZX_ERR_NOT_SUPPORTED);
}
}
}
zx_status_t Worker::EncryptWrite(block_op_t* block) {
LOG_ENTRY_ARGS("block=%p", block);
zx_status_t rc;
// Convert blocks to bytes
extra_op_t* extra = BlockToExtra(block, device_->op_size());
uint32_t length;
uint64_t offset_dev, offset_vmo;
if (mul_overflow(block->rw.length, device_->block_size(), &length) ||
mul_overflow(block->rw.offset_dev, device_->block_size(), &offset_dev) ||
mul_overflow(extra->offset_vmo, device_->block_size(), &offset_vmo)) {
zxlogf(ERROR, "overflow; length=%" PRIu32 "; offset_dev=%" PRIu64 "; offset_vmo=%" PRIu64 "",
block->rw.length, block->rw.offset_dev, extra->offset_vmo);
return ZX_ERR_OUT_OF_RANGE;
}
TRACE_DURATION("zxcrypt", "zxcrypt::Worker::EncryptWrite", "len", length);
// Copy and encrypt the plaintext
if ((rc = zx_vmo_read(extra->vmo, extra->data, offset_vmo, length)) != ZX_OK) {
zxlogf(ERROR, "zx_vmo_read() failed: %s", zx_status_get_string(rc));
return rc;
}
if ((rc = encrypt_.Encrypt(extra->data, offset_dev, length, extra->data) != ZX_OK)) {
zxlogf(ERROR, "failed to encrypt: %s", zx_status_get_string(rc));
return rc;
}
return ZX_OK;
}
zx_status_t Worker::DecryptRead(block_op_t* block) {
LOG_ENTRY_ARGS("block=%p", block);
zx_status_t rc;
// Convert blocks to bytes
uint32_t length;
uint64_t offset_dev, offset_vmo;
uint64_t mapping_offset = 0;
if (mul_overflow(block->rw.length, device_->block_size(), &length) ||
mul_overflow(block->rw.offset_dev, device_->block_size(), &offset_dev) ||
mul_overflow(block->rw.offset_vmo, device_->block_size(), &offset_vmo)) {
zxlogf(ERROR, "overflow; length=%" PRIu32 "; offset_dev=%" PRIu64 "; offset_vmo=%" PRIu64 "",
block->rw.length, block->rw.offset_dev, block->rw.offset_vmo);
return ZX_ERR_OUT_OF_RANGE;
}
uint32_t aligned_length = length;
TRACE_DURATION("zxcrypt", "zxcrypt::Worker::DecryptRead", "len", length);
const size_t kPageSize = zx_system_get_page_size();
if (ZX_ROUNDDOWN(offset_vmo, kPageSize) != offset_vmo) {
// Ensure the range inside the VMO we map is page aligned so that requests smaller than a page
// still work.
mapping_offset = offset_vmo - ZX_ROUNDDOWN(offset_vmo, kPageSize);
offset_vmo = ZX_ROUNDDOWN(offset_vmo, kPageSize);
aligned_length += mapping_offset;
}
// Map the ciphertext
zx_handle_t root = zx_vmar_root_self();
uintptr_t address;
constexpr uint32_t flags = ZX_VM_PERM_READ | ZX_VM_PERM_WRITE;
if ((rc = zx_vmar_map(root, flags, 0, block->rw.vmo, offset_vmo, aligned_length, &address)) !=
ZX_OK) {
zxlogf(ERROR, "zx::vmar::root_self()->map() failed: %s", zx_status_get_string(rc));
return rc;
}
auto cleanup = fit::defer(
[root, address, aligned_length]() { zx_vmar_unmap(root, address, aligned_length); });
// Decrypt in place
uint8_t* data = reinterpret_cast<uint8_t*>(address + mapping_offset);
if ((rc = decrypt_.Decrypt(data, offset_dev, length, data)) != ZX_OK) {
zxlogf(ERROR, "failed to decrypt: %s", zx_status_get_string(rc));
return rc;
}
return ZX_OK;
}
zx_status_t Worker::Stop() {
LOG_ENTRY();
zx_status_t rc;
// Only join once per call to |Start|.
if (!started_.exchange(false)) {
return ZX_OK;
}
thrd_join(thrd_, &rc);
if (rc != ZX_OK) {
zxlogf(WARNING, "worker exited with error: %s", zx_status_get_string(rc));
return rc;
}
return ZX_OK;
}
} // namespace zxcrypt