1// Copyright 2018 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <inttypes.h>
6#include <stddef.h>
7#include <stdint.h>
8#include <threads.h>
9
10#include <crypto/cipher.h>
11#include <ddk/debug.h>
12#include <fbl/auto_call.h>
13#include <lib/zx/port.h>
14#include <zircon/listnode.h>
15#include <zircon/status.h>
16#include <zircon/syscalls/port.h>
17#include <zircon/types.h>
18#include <zxcrypt/volume.h>
19
20#include "debug.h"
21#include "device.h"
22#include "extra.h"
23#include "worker.h"
24
25namespace zxcrypt {
26
27Worker::Worker() : device_(nullptr) {
28    LOG_ENTRY();
29}
30
31Worker::~Worker() {
32    LOG_ENTRY();
33}
34
35void Worker::MakeRequest(zx_port_packet_t* packet, uint64_t op, void* arg) {
36    static_assert(sizeof(uintptr_t) <= sizeof(uint64_t), "cannot store pointer as uint64_t");
37    ZX_DEBUG_ASSERT(packet);
38    packet->key = 0;
39    packet->type = ZX_PKT_TYPE_USER;
40    packet->status = ZX_OK;
41    packet->user.u64[0] = op;
42    packet->user.u64[1] = reinterpret_cast<uint64_t>(arg);
43}
44
45zx_status_t Worker::Start(Device* device, const Volume& volume, zx::port&& port) {
46    LOG_ENTRY_ARGS("device=%p, volume=%p, port=%p", device, &volume, &port);
47    zx_status_t rc;
48
49    if (!device) {
50        zxlogf(ERROR, "bad parameters: device=%p\n", device);
51        return ZX_ERR_INVALID_ARGS;
52    }
53    device_ = device;
54
55    if ((rc = volume.Bind(crypto::Cipher::kEncrypt, &encrypt_)) != ZX_OK ||
56        (rc = volume.Bind(crypto::Cipher::kDecrypt, &decrypt_)) != ZX_OK) {
57        zxlogf(ERROR, "failed to bind ciphers: %s\n", zx_status_get_string(rc));
58        return rc;
59    }
60
61    port_ = fbl::move(port);
62
63    if (thrd_create(&thrd_, WorkerRun, this) != thrd_success) {
64        zxlogf(ERROR, "failed to start thread\n");
65        return ZX_ERR_INTERNAL;
66    }
67
68    return ZX_OK;
69}
70
71zx_status_t Worker::Run() {
72    LOG_ENTRY();
73    ZX_DEBUG_ASSERT(device_);
74    zx_status_t rc;
75
76    zx_port_packet_t packet;
77    while (true) {
78        // Read request
79        if ((rc = port_.wait(zx::time::infinite(), &packet)) != ZX_OK) {
80            zxlogf(ERROR, "failed to read request: %s\n", zx_status_get_string(rc));
81            return rc;
82        }
83        ZX_DEBUG_ASSERT(packet.key == 0);
84        ZX_DEBUG_ASSERT(packet.type == ZX_PKT_TYPE_USER);
85        ZX_DEBUG_ASSERT(packet.status == ZX_OK);
86
87        // Handle control messages
88        switch (packet.user.u64[0]) {
89        case kBlockRequest:
90            break;
91        case kStopRequest:
92            zxlogf(TRACE, "worker %p stopping.\n", this);
93            return ZX_OK;
94        default:
95            zxlogf(ERROR, "unknown request: 0x%016" PRIx64 "\n", packet.user.u64[0]);
96            return ZX_ERR_NOT_SUPPORTED;
97        }
98
99        // Dispatch block request
100        block_op_t* block = reinterpret_cast<block_op_t*>(packet.user.u64[1]);
101        switch (block->command & BLOCK_OP_MASK) {
102        case BLOCK_OP_WRITE:
103            device_->BlockForward(block, EncryptWrite(block));
104            break;
105
106        case BLOCK_OP_READ:
107            device_->BlockComplete(block, DecryptRead(block));
108            break;
109
110        default:
111            device_->BlockComplete(block, ZX_ERR_NOT_SUPPORTED);
112        }
113    }
114}
115
116zx_status_t Worker::EncryptWrite(block_op_t* block) {
117    LOG_ENTRY_ARGS("block=%p", block);
118    zx_status_t rc;
119
120    // Convert blocks to bytes
121    extra_op_t* extra = BlockToExtra(block, device_->op_size());
122    uint32_t length;
123    uint64_t offset_dev, offset_vmo;
124    if (mul_overflow(block->rw.length, device_->block_size(), &length) ||
125        mul_overflow(block->rw.offset_dev, device_->block_size(), &offset_dev) ||
126        mul_overflow(extra->offset_vmo, device_->block_size(), &offset_vmo)) {
127        zxlogf(ERROR,
128               "overflow; length=%" PRIu32 "; offset_dev=%" PRIu64 "; offset_vmo=%" PRIu64 "\n",
129               block->rw.length, block->rw.offset_dev, extra->offset_vmo);
130        return ZX_ERR_OUT_OF_RANGE;
131    }
132
133    // Copy and encrypt the plaintext
134    if ((rc = zx_vmo_read(extra->vmo, extra->data, offset_vmo, length)) != ZX_OK) {
135        zxlogf(ERROR, "zx_vmo_read() failed: %s\n", zx_status_get_string(rc));
136        return rc;
137    }
138    if ((rc = encrypt_.Encrypt(extra->data, offset_dev, length, extra->data) != ZX_OK)) {
139        zxlogf(ERROR, "failed to encrypt: %s\n", zx_status_get_string(rc));
140        return rc;
141    }
142
143    return ZX_OK;
144}
145
146zx_status_t Worker::DecryptRead(block_op_t* block) {
147    LOG_ENTRY_ARGS("block=%p", block);
148    zx_status_t rc;
149
150    // Convert blocks to bytes
151    uint32_t length;
152    uint64_t offset_dev, offset_vmo;
153    if (mul_overflow(block->rw.length, device_->block_size(), &length) ||
154        mul_overflow(block->rw.offset_dev, device_->block_size(), &offset_dev) ||
155        mul_overflow(block->rw.offset_vmo, device_->block_size(), &offset_vmo)) {
156        zxlogf(ERROR,
157               "overflow; length=%" PRIu32 "; offset_dev=%" PRIu64 "; offset_vmo=%" PRIu64 "\n",
158               block->rw.length, block->rw.offset_dev, block->rw.offset_vmo);
159        return ZX_ERR_OUT_OF_RANGE;
160    }
161
162    // Map the ciphertext
163    zx_handle_t root = zx_vmar_root_self();
164    uintptr_t address;
165    constexpr uint32_t flags = ZX_VM_PERM_READ | ZX_VM_PERM_WRITE;
166    if ((rc = zx_vmar_map(root, flags, 0, block->rw.vmo, offset_vmo, length, &address)) != ZX_OK) {
167        zxlogf(ERROR, "zx::vmar::root_self()->map() failed: %s\n", zx_status_get_string(rc));
168        return rc;
169    }
170    auto cleanup =
171        fbl::MakeAutoCall([root, address, length]() { zx_vmar_unmap(root, address, length); });
172
173    // Decrypt in place
174    uint8_t* data = reinterpret_cast<uint8_t*>(address);
175    if ((rc = decrypt_.Decrypt(data, offset_dev, length, data)) != ZX_OK) {
176        zxlogf(ERROR, "failed to decrypt: %s\n", zx_status_get_string(rc));
177        return rc;
178    }
179
180    return ZX_OK;
181}
182
183zx_status_t Worker::Stop() {
184    LOG_ENTRY();
185    zx_status_t rc;
186
187    thrd_join(thrd_, &rc);
188
189    if (rc != ZX_OK) {
190        zxlogf(WARN, "worker exited with error: %s\n", zx_status_get_string(rc));
191        return rc;
192    }
193
194    return ZX_OK;
195}
196
197} // namespace zxcrypt
198