blob: b1caddb443c4292a7c81ff456856dc990d4d675a [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 <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <sys/stat.h>
#include <crypto/bytes.h>
#include <crypto/cipher.h>
#include <fbl/unique_fd.h>
#include <fvm/fvm.h>
#include <unittest/unittest.h>
#include <zircon/device/block.h>
#include <zircon/device/ramdisk.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <zxcrypt/volume.h>
#include "test-device.h"
namespace zxcrypt {
namespace testing {
namespace {
// See test-device.h; the following macros allow reusing tests for each of the supported versions.
#define EACH_PARAM(OP, Test) OP(Test, Volume, AES256_XTS_SHA256)
bool TestBind(Volume::Version version, bool fvm) {
BEGIN_TEST;
TestDevice device;
EXPECT_TRUE(device.Bind(version, fvm));
END_TEST;
}
DEFINE_EACH_DEVICE(TestBind);
// TODO(aarongreen): When ZX-1130 is resolved, add tests that check zxcrypt_rekey and zxcrypt_shred.
// Device::DdkGetSize tests
bool TestDdkGetSize(Volume::Version version, bool fvm) {
BEGIN_TEST;
TestDevice device;
ASSERT_TRUE(device.Bind(version, fvm));
fbl::unique_fd parent = device.parent();
fbl::unique_fd zxcrypt = device.zxcrypt();
struct stat parent_buf, zxcrypt_buf;
ASSERT_EQ(fstat(parent.get(), &parent_buf), 0, strerror(errno));
ASSERT_EQ(fstat(zxcrypt.get(), &zxcrypt_buf), 0, strerror(errno));
EXPECT_GT(zxcrypt_buf.st_size, 0);
END_TEST;
}
DEFINE_EACH_DEVICE(TestDdkGetSize);
// Device::DdkIoctl tests
bool TestBlockGetInfo(Volume::Version version, bool fvm) {
BEGIN_TEST;
TestDevice device;
ASSERT_TRUE(device.Bind(version, fvm));
fbl::unique_fd parent = device.parent();
fbl::unique_fd zxcrypt = device.zxcrypt();
block_info_t parent_blk, zxcrypt_blk;
EXPECT_EQ(ioctl_block_get_info(parent.get(), nullptr),
ioctl_block_get_info(zxcrypt.get(), nullptr));
EXPECT_GE(ioctl_block_get_info(parent.get(), &parent_blk), 0);
EXPECT_GE(ioctl_block_get_info(zxcrypt.get(), &zxcrypt_blk), 0);
EXPECT_EQ(parent_blk.block_size, zxcrypt_blk.block_size);
EXPECT_GE(parent_blk.block_count, zxcrypt_blk.block_count + device.reserved_blocks());
END_TEST;
}
DEFINE_EACH_DEVICE(TestBlockGetInfo);
bool TestBlockFvmQuery(Volume::Version version, bool fvm) {
BEGIN_TEST;
TestDevice device;
ASSERT_TRUE(device.Bind(version, fvm));
fbl::unique_fd parent = device.parent();
fbl::unique_fd zxcrypt = device.zxcrypt();
fvm_info_t parent_fvm, zxcrypt_fvm;
if (!fvm) {
// Send FVM query to non-FVM device
EXPECT_EQ(ioctl_block_fvm_query(zxcrypt.get(), &zxcrypt_fvm), ZX_ERR_NOT_SUPPORTED);
} else {
// Get the zxcrypt info
EXPECT_EQ(ioctl_block_fvm_query(parent.get(), nullptr),
ioctl_block_fvm_query(zxcrypt.get(), nullptr));
EXPECT_GE(ioctl_block_fvm_query(parent.get(), &parent_fvm), 0);
EXPECT_GE(ioctl_block_fvm_query(zxcrypt.get(), &zxcrypt_fvm), 0);
EXPECT_EQ(parent_fvm.slice_size, zxcrypt_fvm.slice_size);
EXPECT_EQ(parent_fvm.vslice_count, zxcrypt_fvm.vslice_count + device.reserved_slices());
}
END_TEST;
}
DEFINE_EACH_DEVICE(TestBlockFvmQuery);
bool QueryLeadingFvmSlice(const TestDevice& device) {
BEGIN_HELPER;
fbl::unique_fd parent = device.parent();
fbl::unique_fd zxcrypt = device.zxcrypt();
query_request_t req;
req.count = 1;
req.vslice_start[0] = 0;
query_response_t parent_resp, zxcrypt_resp;
ssize_t res = ioctl_block_fvm_vslice_query(parent.get(), &req, &parent_resp);
EXPECT_EQ(res, ioctl_block_fvm_vslice_query(zxcrypt.get(), &req, &zxcrypt_resp));
if (res >= 0) {
// Query zxcrypt about the slices, which should omit those reserved
ASSERT_EQ(parent_resp.count, 1U);
EXPECT_TRUE(parent_resp.vslice_range[0].allocated);
ASSERT_EQ(zxcrypt_resp.count, 1U);
EXPECT_TRUE(zxcrypt_resp.vslice_range[0].allocated);
EXPECT_EQ(parent_resp.vslice_range[0].count,
zxcrypt_resp.vslice_range[0].count + device.reserved_slices());
}
END_HELPER;
}
bool TestBlockFvmVSliceQuery(Volume::Version version, bool fvm) {
BEGIN_TEST;
TestDevice device;
ASSERT_TRUE(device.Bind(version, fvm));
EXPECT_TRUE(QueryLeadingFvmSlice(device));
END_TEST;
}
DEFINE_EACH_DEVICE(TestBlockFvmVSliceQuery);
bool TestBlockFvmShrinkAndExtend(Volume::Version version, bool fvm) {
BEGIN_TEST;
TestDevice device;
ASSERT_TRUE(device.Bind(version, fvm));
fbl::unique_fd zxcrypt = device.zxcrypt();
extend_request_t mod;
mod.offset = 1;
mod.length = 1;
if (!fvm) {
// Send FVM ioctl to non-FVM device
EXPECT_EQ(ioctl_block_fvm_shrink(zxcrypt.get(), &mod), ZX_ERR_NOT_SUPPORTED);
EXPECT_EQ(ioctl_block_fvm_extend(zxcrypt.get(), &mod), ZX_ERR_NOT_SUPPORTED);
} else {
// Shrink the FVM partition and make sure the change in size is reflected
EXPECT_GE(ioctl_block_fvm_shrink(zxcrypt.get(), &mod), 0);
EXPECT_TRUE(QueryLeadingFvmSlice(device));
// Extend the FVM partition and make sure the change in size is reflected
EXPECT_GE(ioctl_block_fvm_extend(zxcrypt.get(), &mod), 0);
EXPECT_TRUE(QueryLeadingFvmSlice(device));
}
END_TEST;
}
DEFINE_EACH_DEVICE(TestBlockFvmShrinkAndExtend);
// Device::DdkIotxnQueue tests
bool TestFdZeroLength(Volume::Version version, bool fvm) {
BEGIN_TEST;
TestDevice device;
ASSERT_TRUE(device.Bind(version, fvm));
EXPECT_TRUE(device.WriteFd(0, 0));
EXPECT_TRUE(device.ReadFd(0, 0));
END_TEST;
}
DEFINE_EACH_DEVICE(TestFdZeroLength);
bool TestFdFirstBlock(Volume::Version version, bool fvm) {
BEGIN_TEST;
TestDevice device;
ASSERT_TRUE(device.Bind(version, fvm));
size_t one = device.block_size();
EXPECT_TRUE(device.WriteFd(0, one));
EXPECT_TRUE(device.ReadFd(0, one));
END_TEST;
}
DEFINE_EACH_DEVICE(TestFdFirstBlock);
bool TestFdLastBlock(Volume::Version version, bool fvm) {
BEGIN_TEST;
TestDevice device;
ASSERT_TRUE(device.Bind(version, fvm));
size_t n = device.size();
size_t one = device.block_size();
EXPECT_TRUE(device.WriteFd(n - one, one));
EXPECT_TRUE(device.ReadFd(n - one, one));
END_TEST;
}
DEFINE_EACH_DEVICE(TestFdLastBlock);
bool TestFdAllBlocks(Volume::Version version, bool fvm) {
BEGIN_TEST;
TestDevice device;
ASSERT_TRUE(device.Bind(version, fvm));
size_t n = device.size();
EXPECT_TRUE(device.WriteFd(0, n));
EXPECT_TRUE(device.ReadFd(0, n));
END_TEST;
}
DEFINE_EACH_DEVICE(TestFdAllBlocks);
bool TestFdUnaligned(Volume::Version version, bool fvm) {
BEGIN_TEST;
TestDevice device;
ASSERT_TRUE(device.Bind(version, fvm));
size_t one = device.block_size();
ssize_t one_s = static_cast<ssize_t>(one);
ASSERT_TRUE(device.WriteFd(one, one));
ASSERT_TRUE(device.ReadFd(one, one));
EXPECT_EQ(device.lseek(one - 1), one_s - 1);
EXPECT_LT(device.write(one, one), 0);
EXPECT_LT(device.read(one, one), 0);
EXPECT_EQ(device.lseek(one + 1), one_s + 1);
EXPECT_LT(device.write(one, one), 0);
EXPECT_LT(device.read(one, one), 0);
EXPECT_EQ(device.lseek(one), one_s);
EXPECT_LT(device.write(one, one - 1), 0);
EXPECT_LT(device.read(one, one - 1), 0);
EXPECT_EQ(device.lseek(one), one_s);
EXPECT_LT(device.write(one, one + 1), 0);
EXPECT_LT(device.read(one, one + 1), 0);
END_TEST;
}
DEFINE_EACH_DEVICE(TestFdUnaligned);
bool TestFdOutOfBounds(Volume::Version version, bool fvm) {
BEGIN_TEST;
TestDevice device;
ASSERT_TRUE(device.Bind(version, fvm));
size_t n = device.size();
ssize_t n_s = static_cast<ssize_t>(n);
size_t one = device.block_size();
ssize_t one_s = static_cast<ssize_t>(one);
size_t two = one + one;
ssize_t two_s = static_cast<ssize_t>(two);
ASSERT_TRUE(device.WriteFd(0, one));
EXPECT_EQ(device.lseek(n), n_s);
EXPECT_NE(device.write(n, one), one_s);
EXPECT_EQ(device.lseek(n - one), n_s - one_s);
EXPECT_NE(device.write(n - one, two), two_s);
EXPECT_EQ(device.lseek(two), two_s);
EXPECT_NE(device.write(two, n - one), n_s - one_s);
EXPECT_EQ(device.lseek(one), one_s);
EXPECT_NE(device.write(one, n), n_s);
ASSERT_TRUE(device.ReadFd(0, one));
EXPECT_EQ(device.lseek(n), n_s);
EXPECT_NE(device.read(n, one), one_s);
EXPECT_EQ(device.lseek(n - one), n_s - one_s);
EXPECT_NE(device.read(n - one, two), two_s);
EXPECT_EQ(device.lseek(two), two_s);
EXPECT_NE(device.read(two, n - one), n_s - one_s);
EXPECT_EQ(device.lseek(one), one_s);
EXPECT_NE(device.read(one, n), n_s);
END_TEST;
}
DEFINE_EACH_DEVICE(TestFdOutOfBounds);
bool TestFdOneToMany(Volume::Version version, bool fvm) {
BEGIN_TEST;
TestDevice device;
ASSERT_TRUE(device.Bind(version, fvm));
size_t n = device.size();
size_t one = device.block_size();
ASSERT_TRUE(device.WriteFd(0, n));
ASSERT_TRUE(device.Rebind());
for (size_t off = 0; off < n; off += one) {
EXPECT_TRUE(device.ReadFd(off, one));
}
END_TEST;
}
DEFINE_EACH_DEVICE(TestFdOneToMany);
bool TestFdManyToOne(Volume::Version version, bool fvm) {
BEGIN_TEST;
TestDevice device;
ASSERT_TRUE(device.Bind(version, fvm));
size_t n = device.size();
size_t one = device.block_size();
for (size_t off = 0; off < n; off += one) {
EXPECT_TRUE(device.WriteFd(off, one));
}
ASSERT_TRUE(device.Rebind());
EXPECT_TRUE(device.ReadFd(0, n));
END_TEST;
}
DEFINE_EACH_DEVICE(TestFdManyToOne);
// Device::BlockWrite and Device::BlockRead tests
bool TestVmoZeroLength(Volume::Version version, bool fvm) {
BEGIN_TEST;
TestDevice device;
ASSERT_TRUE(device.Bind(version, fvm));
// Zero length is illegal for the block fifo
EXPECT_ZX(device.block_fifo_txn(BLOCKIO_WRITE, 0, 0), ZX_ERR_INVALID_ARGS);
EXPECT_ZX(device.block_fifo_txn(BLOCKIO_READ, 0, 0), ZX_ERR_INVALID_ARGS);
END_TEST;
}
DEFINE_EACH_DEVICE(TestVmoZeroLength);
bool TestVmoFirstBlock(Volume::Version version, bool fvm) {
BEGIN_TEST;
TestDevice device;
ASSERT_TRUE(device.Bind(version, fvm));
EXPECT_TRUE(device.WriteVmo(0, 1));
EXPECT_TRUE(device.ReadVmo(0, 1));
END_TEST;
}
DEFINE_EACH_DEVICE(TestVmoFirstBlock);
bool TestVmoLastBlock(Volume::Version version, bool fvm) {
BEGIN_TEST;
TestDevice device;
ASSERT_TRUE(device.Bind(version, fvm));
size_t n = device.block_count();
EXPECT_TRUE(device.WriteVmo(n - 1, 1));
EXPECT_TRUE(device.ReadVmo(n - 1, 1));
END_TEST;
}
DEFINE_EACH_DEVICE(TestVmoLastBlock);
bool TestVmoAllBlocks(Volume::Version version, bool fvm) {
BEGIN_TEST;
TestDevice device;
ASSERT_TRUE(device.Bind(version, fvm));
size_t n = device.block_count();
EXPECT_TRUE(device.WriteVmo(0, n));
EXPECT_TRUE(device.ReadVmo(0, n));
END_TEST;
}
DEFINE_EACH_DEVICE(TestVmoAllBlocks);
bool TestVmoOutOfBounds(Volume::Version version, bool fvm) {
BEGIN_TEST;
TestDevice device;
ASSERT_TRUE(device.Bind(version, fvm));
size_t n = device.block_count();
ASSERT_TRUE(device.WriteVmo(0, 1));
EXPECT_ZX(device.block_fifo_txn(BLOCKIO_WRITE, n, 1), ZX_ERR_OUT_OF_RANGE);
EXPECT_ZX(device.block_fifo_txn(BLOCKIO_WRITE, n - 1, 2), ZX_ERR_OUT_OF_RANGE);
EXPECT_ZX(device.block_fifo_txn(BLOCKIO_WRITE, 2, n - 1), ZX_ERR_OUT_OF_RANGE);
EXPECT_ZX(device.block_fifo_txn(BLOCKIO_WRITE, 1, n), ZX_ERR_OUT_OF_RANGE);
ASSERT_TRUE(device.ReadVmo(0, 1));
EXPECT_ZX(device.block_fifo_txn(BLOCKIO_READ, n, 1), ZX_ERR_OUT_OF_RANGE);
EXPECT_ZX(device.block_fifo_txn(BLOCKIO_READ, n - 1, 2), ZX_ERR_OUT_OF_RANGE);
EXPECT_ZX(device.block_fifo_txn(BLOCKIO_READ, 2, n - 1), ZX_ERR_OUT_OF_RANGE);
EXPECT_ZX(device.block_fifo_txn(BLOCKIO_READ, 1, n), ZX_ERR_OUT_OF_RANGE);
END_TEST;
}
DEFINE_EACH_DEVICE(TestVmoOutOfBounds);
bool TestVmoOneToMany(Volume::Version version, bool fvm) {
BEGIN_TEST;
TestDevice device;
ASSERT_TRUE(device.Bind(version, fvm));
size_t n = device.block_count();
EXPECT_TRUE(device.WriteVmo(0, n));
ASSERT_TRUE(device.Rebind());
for (size_t off = 0; off < n; ++off) {
EXPECT_TRUE(device.ReadVmo(off, 1));
}
END_TEST;
}
DEFINE_EACH_DEVICE(TestVmoOneToMany);
bool TestVmoManyToOne(Volume::Version version, bool fvm) {
BEGIN_TEST;
TestDevice device;
ASSERT_TRUE(device.Bind(version, fvm));
size_t n = device.block_count();
for (size_t off = 0; off < n; ++off) {
EXPECT_TRUE(device.WriteVmo(off, 1));
}
ASSERT_TRUE(device.Rebind());
EXPECT_TRUE(device.ReadVmo(0, n));
END_TEST;
}
DEFINE_EACH_DEVICE(TestVmoManyToOne);
bool TestVmoStall(Volume::Version version, bool fvm) {
BEGIN_TEST;
TestDevice device;
ASSERT_TRUE(device.Bind(version, fvm));
fbl::unique_fd zxcrypt = device.zxcrypt();
// The device can have up to 4 * max_transfer_size bytes in flight before it begins queuing them
// internally.
block_info_t zxcrypt_blk;
EXPECT_GE(ioctl_block_get_info(zxcrypt.get(), &zxcrypt_blk), 0);
size_t blks_per_req = 4;
size_t max = Volume::kBufferSize / (device.block_size() * blks_per_req);
size_t num = max + 1;
fbl::AllocChecker ac;
fbl::unique_ptr<block_fifo_request_t[]> requests(new (&ac) block_fifo_request_t[num]);
ASSERT_TRUE(ac.check());
for (size_t i = 0; i < num; ++i) {
requests[i].opcode = (i % 2 == 0 ? BLOCKIO_WRITE : BLOCKIO_READ);
requests[i].length = blks_per_req;
requests[i].dev_offset = 0;
requests[i].vmo_offset = 0;
}
EXPECT_TRUE(device.SleepUntil(max, true /* defer transactions */));
EXPECT_EQ(device.block_fifo_txn(requests.get(), num), ZX_OK);
EXPECT_TRUE(device.WakeUp());
END_TEST;
}
DEFINE_EACH_DEVICE(TestVmoStall);
bool TestWriteAfterFvmExtend(Volume::Version version) {
BEGIN_TEST;
TestDevice device;
ASSERT_TRUE(device.Bind(version, true));
fbl::unique_fd zxcrypt = device.zxcrypt();
size_t n = device.size();
ssize_t n_s = static_cast<ssize_t>(n);
size_t one = device.block_size();
ssize_t one_s = static_cast<ssize_t>(one);
EXPECT_EQ(device.lseek(n), n_s);
EXPECT_NE(device.write(n, one), one_s);
fvm_info_t info;
EXPECT_GE(ioctl_block_fvm_query(zxcrypt.get(), &info), 0);
extend_request_t mod;
mod.offset = device.size() / info.slice_size;
mod.length = 1;
EXPECT_GE(ioctl_block_fvm_extend(zxcrypt.get(), &mod), 0);
EXPECT_EQ(device.lseek(n), n_s);
EXPECT_EQ(device.write(n, one), one_s);
END_TEST;
}
DEFINE_EACH(TestWriteAfterFvmExtend);
// TODO(aarongreen): Currently, we're using XTS, which provides no data integrity. When possible,
// we should switch to an AEAD, which would allow us to detect data corruption when doing I/O.
// bool TestBadData(void) {
// BEGIN_TEST;
// END_TEST;
// }
BEGIN_TEST_CASE(ZxcryptTest)
RUN_EACH_DEVICE(TestBind)
RUN_EACH_DEVICE(TestDdkGetSize)
RUN_EACH_DEVICE(TestBlockGetInfo)
RUN_EACH_DEVICE(TestBlockFvmQuery)
RUN_EACH_DEVICE(TestBlockFvmVSliceQuery)
RUN_EACH_DEVICE(TestBlockFvmShrinkAndExtend)
RUN_EACH_DEVICE(TestFdZeroLength)
RUN_EACH_DEVICE(TestFdFirstBlock)
RUN_EACH_DEVICE(TestFdLastBlock)
RUN_EACH_DEVICE(TestFdAllBlocks)
RUN_EACH_DEVICE(TestFdUnaligned)
RUN_EACH_DEVICE(TestFdOutOfBounds)
RUN_EACH_DEVICE(TestFdOneToMany)
RUN_EACH_DEVICE(TestFdManyToOne)
RUN_EACH_DEVICE(TestVmoZeroLength)
RUN_EACH_DEVICE(TestVmoFirstBlock)
RUN_EACH_DEVICE(TestVmoLastBlock)
RUN_EACH_DEVICE(TestVmoAllBlocks)
RUN_EACH_DEVICE(TestVmoOutOfBounds)
RUN_EACH_DEVICE(TestVmoOneToMany)
RUN_EACH_DEVICE(TestVmoManyToOne)
// Disabled (See ZX-2112): RUN_EACH_DEVICE(TestVmoStall)
RUN_EACH(TestWriteAfterFvmExtend)
END_TEST_CASE(ZxcryptTest)
} // namespace
} // namespace testing
} // namespace zxcrypt