// 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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)); ASSERT_GT(parent_buf.st_size, zxcrypt_buf.st_size); EXPECT_EQ((parent_buf.st_size - zxcrypt_buf.st_size) / device.block_size(), device.reserved_blocks()); 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(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(n); size_t one = device.block_size(); ssize_t one_s = static_cast(one); size_t two = one + one; ssize_t two_s = static_cast(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 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 = static_cast(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(n); size_t one = device.block_size(); ssize_t one_s = static_cast(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