blob: 048c536857d389f0e9b1a37bb165a27631572359 [file] [log] [blame]
// Copyright 2017 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 <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <fbl/algorithm.h>
#include <fbl/auto_call.h>
#include <fbl/unique_fd.h>
#include <fdio/debug.h>
#include <fdio/watcher.h>
#include <fs-management/ramdisk.h>
#include <fvm/fvm.h>
#include <unittest/unittest.h>
#include <zircon/types.h>
#include <zx/time.h>
#include <zxcrypt/volume.h>
#include "test-device.h"
#define ZXDEBUG 0
namespace zxcrypt {
namespace testing {
namespace {
// Request to block watcher
struct BlockDeviceRequest {
explicit BlockDeviceRequest(char* path) : driver(path), out_fd(-1) {}
~BlockDeviceRequest() {}
const char* driver;
int out_fd;
};
// Path to zxcrypt driver
const char* kZxcryptLib = "/boot/driver/zxcrypt.so";
// Translates a topological device tree path for a block device to its driver.
char* GetBlockDriver(char* path) {
char* block = strrchr(path, '/');
if (!block || strcmp(block, "/block") != 0 || block == path) {
xprintf("unable to find block driver in '%s'\n", path);
return nullptr;
}
*block = 0;
char* driver = strrchr(path, '/');
*block = '/';
if (!driver) {
xprintf("unable to find block driver in '%s'\n", path);
return nullptr;
}
return driver + 1;
}
// Routine to watch directory in the device tree for a new block device.
zx_status_t BlockWatcher(int dirfd, int event, const char* filename, void* cookie) {
BlockDeviceRequest* req = static_cast<BlockDeviceRequest*>(cookie);
if (event != WATCH_EVENT_ADD_FILE) {
return ZX_OK;
}
fbl::unique_fd devfd(openat(dirfd, filename, O_RDWR));
if (!devfd) {
xprintf("openat(%d, '%s', O_RDWR) failed: %s\n", dirfd, filename, strerror(errno));
return ZX_OK;
}
char path[PATH_MAX] = {0};
ssize_t res;
if ((res = ioctl_device_get_topo_path(devfd.get(), path, PATH_MAX - 1)) < 0) {
xprintf("ioctl_device_get_topo_path(%d, %p, %zu) failed: %s\n", devfd.get(), path,
sizeof(path), zx_status_get_string(static_cast<zx_status_t>(res)));
return ZX_OK;
}
xprintf("%s added\n", path);
// Trim trailing /block
const char* driver = GetBlockDriver(path);
if (!driver) {
return ZX_ERR_INTERNAL;
}
if (strcmp(driver, req->driver) != 0) {
xprintf("found '%s', want '.../%s'\n", path, req->driver);
return ZX_OK;
}
req->out_fd = devfd.release();
return ZX_ERR_STOP;
}
zx_status_t WaitForBlockDevice(char* driver, fbl::unique_fd* out_fd) {
zx_status_t rc;
static const char* kBlockDevPath = "/dev/class/block";
DIR* dir = opendir(kBlockDevPath);
if (!dir) {
xprintf("opendir('%s') failed: %s\n", kBlockDevPath, strerror(errno));
return ZX_ERR_IO;
}
auto cleanup = fbl::MakeAutoCall([&] { closedir(dir); });
zx_time_t deadline = zx_deadline_after(ZX_SEC(3));
BlockDeviceRequest req(driver);
if ((rc = fdio_watch_directory(dirfd(dir), BlockWatcher, deadline, &req)) != ZX_ERR_STOP) {
xprintf("fdio_watch_directory(%d, %p, %" PRIu64 ", %p) failed: %s\n", dirfd(dir),
BlockWatcher, deadline, &req, zx_status_get_string(rc));
return rc;
}
out_fd->reset(req.out_fd);
return ZX_OK;
}
} // namespace
TestDevice::TestDevice() : has_ramdisk_(false), block_size_(0) {
Reset();
}
TestDevice::~TestDevice() {
Reset();
}
bool TestDevice::GenerateKey(Volume::Version version) {
BEGIN_HELPER;
// TODO(aarongreen): See ZX-1130. The code below should be enabled when that bug is fixed.
#if 0
crypto::digest::Algorithm digest;
switch (version) {
case Volume::kAES256_XTS_SHA256:
digest = crypto::digest::kSHA256;
break;
default:
digest = crypto::digest::kUninitialized;
break;
}
size_t digest_len;
key_.Reset();
if ((rc = crypto::digest::GetDigestLen(digest, &digest_len)) != ZX_OK ||
(rc = key_.Randomize(digest_len)) != ZX_OK) {
return rc;
}
#else
key_.Reset();
ASSERT_OK(key_.InitZero(kZx1130KeyLen));
#endif
END_HELPER;
}
bool TestDevice::Create(size_t device_size, size_t block_size, bool fvm) {
BEGIN_HELPER;
ASSERT_LT(device_size, SSIZE_MAX);
if (fvm) {
ASSERT_TRUE(CreateFvmPart(device_size, block_size));
} else {
ASSERT_TRUE(CreateRamdisk(device_size, block_size));
}
END_HELPER;
}
bool TestDevice::BindZxcrypt() {
BEGIN_HELPER;
if (zxcrypt_) {
fvm_part_.reset();
ASSERT_GE(ioctl_block_rr_part(ramdisk_.get()), 0);
ramdisk_.reset();
ASSERT_OK(WaitForBlockDevice(GetBlockDriver(ramdisk_path_), &ramdisk_));
if (strlen(fvm_part_path_) != 0) {
ASSERT_OK(WaitForBlockDevice(GetBlockDriver(fvm_part_path_), &fvm_part_));
}
}
fbl::unique_fd parent = this->parent();
ASSERT_GE(ioctl_device_bind(parent.get(), kZxcryptLib, strlen(kZxcryptLib)), 0);
char zxcrypt_relpath[PATH_MAX];
snprintf(zxcrypt_relpath, PATH_MAX, "zxcrypt/block");
ASSERT_OK(WaitForBlockDevice(zxcrypt_relpath, &zxcrypt_));
block_info_t blk;
ASSERT_GE(ioctl_block_get_info(zxcrypt_.get(), &blk), 0);
block_size_ = blk.block_size;
block_count_ = blk.block_count;
zx_handle_t fifo;
ASSERT_GE(ioctl_block_get_fifos(zxcrypt_.get(), &fifo), 0);
ASSERT_GE(ioctl_block_alloc_txn(zxcrypt_.get(), &req_.txnid), 0);
ASSERT_OK(block_fifo_create_client(fifo, &client_));
// Create the vmo and get a transferable handle to give to the block server
ASSERT_OK(zx::vmo::create(size(), 0, &vmo_));
zx_handle_t xfer;
ASSERT_OK(zx_handle_duplicate(vmo_.get(), ZX_RIGHT_SAME_RIGHTS, &xfer));
ASSERT_GE(ioctl_block_attach_vmo(zxcrypt_.get(), &xfer, &req_.vmoid), 0);
END_HELPER;
}
bool TestDevice::DefaultInit(Volume::Version version, bool fvm) {
BEGIN_HELPER;
ASSERT_TRUE(GenerateKey(version));
ASSERT_TRUE(Create(kDeviceSize, kBlockSize, fvm));
ASSERT_OK(Volume::Create(parent(), key_));
ASSERT_TRUE(BindZxcrypt());
END_HELPER;
}
bool TestDevice::ReadFd(zx_off_t off, size_t len) {
BEGIN_HELPER;
EXPECT_GE(lseek(off), 0);
EXPECT_GE(read(off, len), 0);
EXPECT_EQ(memcmp(as_read_.get() + off, to_write_.get() + off, len), 0);
END_HELPER;
}
bool TestDevice::WriteFd(zx_off_t off, size_t len) {
BEGIN_HELPER;
EXPECT_GE(lseek(off), 0);
EXPECT_GE(write(off, len), 0);
END_HELPER;
}
bool TestDevice::ReadVmo(zx_off_t off, size_t len) {
BEGIN_HELPER;
EXPECT_OK(block_fifo_txn(BLOCKIO_READ, off, len));
off *= block_size_;
len *= block_size_;
EXPECT_OK(vmo_read(off, len));
EXPECT_EQ(memcmp(as_read_.get() + off, to_write_.get() + off, len), 0);
END_HELPER;
}
bool TestDevice::WriteVmo(zx_off_t off, size_t len) {
BEGIN_HELPER;
EXPECT_OK(vmo_write(off * block_size_, len * block_size_));
EXPECT_OK(block_fifo_txn(BLOCKIO_WRITE, off, len));
END_HELPER;
}
bool TestDevice::Corrupt(zx_off_t offset) {
BEGIN_HELPER;
uint8_t block[block_size_];
zx_off_t block_off = offset % block_size_;
offset -= block_off;
ASSERT_GE(::lseek(ramdisk_.get(), offset, SEEK_SET), 0);
ASSERT_GE(::read(ramdisk_.get(), block, block_size_), 0);
int bit = rand() % 8;
uint8_t flip = static_cast<uint8_t>(1U << bit);
block[block_off] ^= flip;
ASSERT_GE(::lseek(ramdisk_.get(), offset, SEEK_SET), 0);
ASSERT_GE(::write(ramdisk_.get(), block, block_size_), 0);
END_HELPER;
}
// Private methods
bool TestDevice::CreateRamdisk(size_t device_size, size_t block_size) {
BEGIN_HELPER;
Reset();
auto cleanup = fbl::MakeAutoCall([&] { Reset(); });
fbl::AllocChecker ac;
size_t count = fbl::round_up(device_size, block_size) / block_size;
to_write_.reset(new (&ac) uint8_t[device_size]);
ASSERT_TRUE(ac.check());
for (size_t i = 0; i < device_size; ++i) {
to_write_[i] = static_cast<uint8_t>(rand());
}
as_read_.reset(new (&ac) uint8_t[device_size]);
ASSERT_TRUE(ac.check());
memset(as_read_.get(), 0, block_size);
ASSERT_GE(create_ramdisk(block_size, count, ramdisk_path_), 0);
has_ramdisk_ = true;
ASSERT_OK(WaitForBlockDevice(GetBlockDriver(ramdisk_path_), &ramdisk_));
block_size_ = block_size;
block_count_ = count;
cleanup.cancel();
END_HELPER;
}
// Creates a ramdisk, formats it, and binds to it.
bool TestDevice::CreateFvmPart(size_t device_size, size_t block_size) {
BEGIN_HELPER;
// Calculate total size of data + metadata.
device_size = fbl::round_up(device_size, FVM_BLOCK_SIZE);
size_t old_meta = fvm::MetadataSize(device_size, FVM_BLOCK_SIZE);
size_t new_meta = fvm::MetadataSize(old_meta + device_size, FVM_BLOCK_SIZE);
while (old_meta != new_meta) {
old_meta = new_meta;
new_meta = fvm::MetadataSize(old_meta + device_size, FVM_BLOCK_SIZE);
}
ASSERT_TRUE(CreateRamdisk(device_size + (new_meta * 2), block_size));
// Format the ramdisk as FVM and bind to it
static const char* kFvmLib = "/boot/driver/fvm.so";
ASSERT_OK(fvm_init(ramdisk_.get(), FVM_BLOCK_SIZE));
ASSERT_GE(ioctl_device_bind(ramdisk_.get(), kFvmLib, strlen(kFvmLib)), 0);
// Wait and open the target device
ASSERT_GE(wait_for_driver_bind(ramdisk_path_, "fvm"), 0);
char fvm_path[PATH_MAX];
snprintf(fvm_path, PATH_MAX, "%s/fvm", ramdisk_path_);
fbl::unique_fd fvm_fd(open(fvm_path, O_RDWR));
ASSERT_TRUE(fvm_fd);
// Allocate a FVM partition with the last slice unallocated.
alloc_req_t req;
memset(&req, 0, sizeof(alloc_req_t));
req.slice_count = (kDeviceSize / FVM_BLOCK_SIZE) - 1;
memcpy(req.type, kTypeGuid, GUID_LEN);
for (uint8_t i = 0; i < GUID_LEN; ++i) {
req.guid[i] = i;
}
snprintf(req.name, NAME_LEN, "data");
fvm_part_.reset(fvm_allocate_partition(fvm_fd.get(), &req));
ASSERT_TRUE(fvm_part_);
// Save the topological path for rebinding
ASSERT_GE(ioctl_device_get_topo_path(fvm_part_.get(), fvm_part_path_, sizeof(fvm_part_path_)),
0);
END_HELPER;
}
void TestDevice::Reset() {
zxcrypt_.reset();
fvm_part_.reset();
ramdisk_.reset();
if (has_ramdisk_) {
destroy_ramdisk(ramdisk_path_);
}
memset(ramdisk_path_, 0, sizeof(ramdisk_path_));
memset(fvm_part_path_, 0, sizeof(fvm_part_path_));
has_ramdisk_ = false;
}
} // namespace testing
} // namespace zxcrypt