blob: 0934db72fcfe81d053e752953b9e419e82d509d5 [file] [log] [blame]
// Copyright 2020 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 "corrupt_blob.h"
#include <iterator>
#include <block-client/cpp/fake-device.h>
#include <storage/buffer/owned_vmoid.h>
#include <zxtest/zxtest.h>
#include "src/storage/blobfs/format.h"
#include "src/storage/blobfs/mkfs.h"
namespace {
using block_client::BlockDevice;
using block_client::FakeBlockDevice;
constexpr uint32_t kBlockSize = 512;
constexpr uint32_t kBlocksPerBlobfsBlock = blobfs::kBlobfsBlockSize / kBlockSize;
constexpr uint32_t kNumBlocks = 400 * kBlocksPerBlobfsBlock;
// Re-implement BlockDevice around a reference to one so CorruptBlob can continue to take ownership
// of a block device and the tests can check the block device after corrupting a blob.
class ProxyBlockDevice : public BlockDevice {
public:
ProxyBlockDevice(BlockDevice* inner) : inner_(inner) {}
zx_status_t ReadBlock(uint64_t block_num, uint64_t block_size, void* block) const {
return inner_->ReadBlock(block_num, block_size, block);
}
zx_status_t FifoTransaction(block_fifo_request_t* requests, size_t count) {
return inner_->FifoTransaction(requests, count);
}
zx_status_t GetDevicePath(size_t buffer_len, char* out_name, size_t* out_len) const {
return inner_->GetDevicePath(buffer_len, out_name, out_len);
}
zx_status_t BlockGetInfo(fuchsia_hardware_block_BlockInfo* out_info) const {
return inner_->BlockGetInfo(out_info);
}
zx_status_t BlockAttachVmo(const zx::vmo& vmo, storage::Vmoid* out_vmoid) {
return inner_->BlockAttachVmo(vmo, out_vmoid);
}
zx_status_t VolumeQuery(fuchsia_hardware_block_volume_VolumeInfo* out_info) const {
return inner_->VolumeQuery(out_info);
}
zx_status_t VolumeQuerySlices(const uint64_t* slices, size_t slices_count,
fuchsia_hardware_block_volume_VsliceRange* out_ranges,
size_t* out_ranges_count) const {
return inner_->VolumeQuerySlices(slices, slices_count, out_ranges, out_ranges_count);
}
zx_status_t VolumeExtend(uint64_t offset, uint64_t length) {
return inner_->VolumeExtend(offset, length);
}
zx_status_t VolumeShrink(uint64_t offset, uint64_t length) {
return inner_->VolumeShrink(offset, length);
}
private:
BlockDevice* inner_;
};
class MockBlockDevice : public FakeBlockDevice {
public:
MockBlockDevice(uint64_t block_count, uint32_t block_size)
: FakeBlockDevice(block_count, block_size), block_size_(block_size) {}
void WriteBlock(uint64_t block, uint64_t fs_block_size, const void* data);
private:
uint32_t block_size_;
};
void MockBlockDevice::WriteBlock(uint64_t block_num, uint64_t fs_block_size, const void* data) {
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(fs_block_size, 0, &vmo));
ASSERT_OK(vmo.write(data, 0, fs_block_size));
storage::Vmoid vmoid;
ASSERT_OK(BlockAttachVmo(vmo, &vmoid));
block_fifo_request_t requests[2] = {};
ASSERT_TRUE(fs_block_size % block_size_ == 0);
requests[0].opcode = BLOCKIO_WRITE;
requests[0].vmoid = vmoid.get();
requests[0].length = static_cast<uint32_t>(fs_block_size / block_size_);
requests[0].vmo_offset = 0;
requests[0].dev_offset = block_num * fs_block_size / block_size_;
requests[1].opcode = BLOCKIO_CLOSE_VMO;
requests[1].vmoid = vmoid.TakeId();
ASSERT_OK(FifoTransaction(requests, std::size(requests)));
}
std::unique_ptr<MockBlockDevice> CreateAndFormatDevice() {
auto device = std::make_unique<MockBlockDevice>(kNumBlocks, kBlockSize);
EXPECT_OK(blobfs::FormatFilesystem(device.get(), blobfs::FilesystemOptions{}));
if (CURRENT_TEST_HAS_FAILURES()) {
return nullptr;
}
return device;
}
class ZeroDiskTest : public zxtest::Test {
public:
void SetUp() { device_ = std::make_unique<MockBlockDevice>(kNumBlocks, kBlockSize); }
protected:
std::unique_ptr<MockBlockDevice> device_;
};
class BlobfsDiskTest : public ZeroDiskTest {
public:
void SetUp() {
device_ = CreateAndFormatDevice();
ASSERT_TRUE(device_);
uint8_t block[blobfs::kBlobfsBlockSize] = {};
ASSERT_OK(device_->ReadBlock(0, sizeof(block), &block));
superblock_ = *reinterpret_cast<blobfs::Superblock*>(block);
}
void WriteSuperblock() { device_->WriteBlock(0, sizeof(superblock_), &superblock_); }
protected:
std::unique_ptr<MockBlockDevice> device_;
blobfs::Superblock superblock_ = {};
};
TEST_F(ZeroDiskTest, StartStop) {}
TEST_F(ZeroDiskTest, FailsOnEmptyDisk) {
BlobCorruptOptions options;
ASSERT_EQ(ZX_ERR_INVALID_ARGS, CorruptBlob(std::move(device_), &options));
}
TEST_F(BlobfsDiskTest, StartStop) {}
TEST_F(BlobfsDiskTest, FailsOnNotFound) {
BlobCorruptOptions options;
ASSERT_EQ(ZX_ERR_NOT_FOUND, CorruptBlob(std::move(device_), &options));
}
TEST_F(BlobfsDiskTest, FailsOnUncleanDismount) {
superblock_.flags &= ~blobfs::kBlobFlagClean;
ASSERT_NO_FAILURES(WriteSuperblock());
BlobCorruptOptions options;
ASSERT_EQ(ZX_ERR_BAD_STATE, CorruptBlob(std::move(device_), &options));
}
TEST_F(BlobfsDiskTest, SucceedsIfFirstNodeMatches) {
superblock_.inode_count = blobfs::kBlobfsInodesPerBlock;
ASSERT_NO_FAILURES(WriteSuperblock());
BlobCorruptOptions options;
ASSERT_OK(
options.merkle.Parse("00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"));
blobfs::Inode node = {};
node.header.flags = blobfs::kBlobFlagAllocated;
options.merkle.CopyTo(node.merkle_root_hash);
node.blob_size = 20;
node.extent_count = 1;
node.extents[0] = blobfs::Extent(0, 1);
uint8_t block[blobfs::kBlobfsBlockSize] = {};
auto node_block_num = blobfs::NodeMapStartBlock(superblock_);
ASSERT_OK(device_->ReadBlock(node_block_num, sizeof(block), block));
reinterpret_cast<blobfs::Inode*>(block)[0] = node;
ASSERT_NO_FAILURES(device_->WriteBlock(node_block_num, sizeof(block), block));
// Corrupt the blob, and ensure the data block for the blob is different after.
auto data_block_num = blobfs::DataStartBlock(superblock_);
ASSERT_OK(device_->ReadBlock(data_block_num, sizeof(block), block));
ASSERT_OK(CorruptBlob(std::make_unique<ProxyBlockDevice>(device_.get()), &options));
uint8_t block_after[blobfs::kBlobfsBlockSize] = {};
ASSERT_OK(device_->ReadBlock(data_block_num, sizeof(block_after), block_after));
ASSERT_BYTES_NE(block, block_after, 20);
}
TEST_F(BlobfsDiskTest, SucceedsIfLastNodeMatches) {
superblock_.inode_count = blobfs::kBlobfsInodesPerBlock;
ASSERT_NO_FAILURES(WriteSuperblock());
BlobCorruptOptions options;
ASSERT_OK(
options.merkle.Parse("00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"));
blobfs::Inode node = {};
node.header.flags = blobfs::kBlobFlagAllocated;
options.merkle.CopyTo(node.merkle_root_hash);
node.blob_size = 20;
node.extent_count = 1;
node.extents[0] = blobfs::Extent(2, 1);
uint8_t block[blobfs::kBlobfsBlockSize] = {};
auto node_block_num = blobfs::NodeMapStartBlock(superblock_);
ASSERT_OK(device_->ReadBlock(node_block_num, sizeof(block), block));
reinterpret_cast<blobfs::Inode*>(block)[blobfs::kBlobfsInodesPerBlock - 1] = node;
ASSERT_NO_FAILURES(device_->WriteBlock(node_block_num, sizeof(block), block));
// Corrupt the blob, and ensure the data block for the blob is different after.
auto data_block_num = blobfs::DataStartBlock(superblock_) + 2;
ASSERT_OK(device_->ReadBlock(data_block_num, sizeof(block), block));
ASSERT_OK(CorruptBlob(std::make_unique<ProxyBlockDevice>(device_.get()), &options));
uint8_t block_after[blobfs::kBlobfsBlockSize] = {};
ASSERT_OK(device_->ReadBlock(data_block_num, sizeof(block_after), block_after));
ASSERT_BYTES_NE(block, block_after, 20);
}
} // namespace