blob: 0d7c20fb941f322cd0553f784a5c241c5b75c9d7 [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 "src/storage/blobfs/blob_verifier.h"
#include <memory>
#include <random>
#include <gtest/gtest.h>
#include "src/lib/digest/merkle-tree.h"
#include "src/storage/blobfs/blob_layout.h"
#include "src/storage/blobfs/test/blob_utils.h"
#include "src/storage/blobfs/test/unit/utils.h"
namespace blobfs {
namespace {
struct BlockMerkleTreeInfo {
fzl::OwnedVmoMapper blocks;
// Points into |blocks|, will be offset according to blob format.
uint8_t* merkle_data = nullptr;
Digest root;
cpp20::span<const uint8_t> GetMerkleDataBlocks() const {
return cpp20::span(static_cast<const uint8_t*>(blocks.start()), blocks.size());
}
};
class BlobVerifierTest : public testing::TestWithParam<BlobLayoutFormat> {
public:
std::shared_ptr<BlobfsMetrics> GetMetrics() { return metrics_; }
void SetUp() override { srand(testing::UnitTest::GetInstance()->random_seed()); }
// Creates a default blob layout for an uncompressed file of the given size.
static std::unique_ptr<BlobLayout> GetBlobLayout(size_t size) {
auto layout_or = BlobLayout::CreateFromSizes(GetParam(), size, size, kBlobfsBlockSize);
EXPECT_TRUE(layout_or.is_ok()); // Should always succeed in this use.
return std::move(*layout_or);
}
static std::unique_ptr<MerkleTreeInfo> GenerateTree(const uint8_t* data, size_t len) {
return CreateMerkleTree(data, len, ShouldUseCompactMerkleTreeFormat(GetParam()));
}
// Like GenerateTree but puts the merkle data into a VMO that will have the merkle data aligned
// inside of it as it would on disk according to the given layout.
static BlockMerkleTreeInfo GenerateMerkleTreeBlocks(const BlobLayout& layout, const uint8_t* data,
size_t len) {
std::unique_ptr<MerkleTreeInfo> normal_info = GenerateTree(data, len);
BlockMerkleTreeInfo block_info;
EXPECT_EQ(ZX_OK,
block_info.blocks.CreateAndMap(layout.MerkleTreeBlockAlignedSize(), "Merkle blocks"));
auto start_offset = layout.MerkleTreeOffsetWithinBlockOffset();
EXPECT_LE(normal_info->merkle_tree_size + start_offset, block_info.blocks.size());
block_info.merkle_data = &static_cast<uint8_t*>(block_info.blocks.start())[start_offset];
memcpy(block_info.merkle_data, normal_info->merkle_tree.get(), normal_info->merkle_tree_size);
block_info.root = normal_info->root;
return block_info;
}
private:
std::shared_ptr<BlobfsMetrics> metrics_ = std::make_shared<BlobfsMetrics>(false);
};
void FillWithRandom(uint8_t* buf, size_t len) {
for (unsigned i = 0; i < len; ++i) {
buf[i] = static_cast<uint8_t>(rand());
}
}
TEST_P(BlobVerifierTest, CreateAndVerify_NullBlob) {
auto merkle_tree = GenerateTree(nullptr, 0);
auto verifier_or = BlobVerifier::CreateWithoutTree(merkle_tree->root, GetMetrics(), 0ul);
ASSERT_TRUE(verifier_or.is_ok());
BlobVerifier* verifier = verifier_or.value().get();
EXPECT_EQ(verifier->Verify(nullptr, 0ul, 0ul), ZX_OK);
EXPECT_EQ(verifier->VerifyPartial(nullptr, 0ul, 0ul, 0ul), ZX_OK);
}
TEST_P(BlobVerifierTest, CreateAndVerify_SmallBlob) {
uint8_t buf[8192];
FillWithRandom(buf, sizeof(buf));
auto merkle_tree = GenerateTree(buf, sizeof(buf));
auto verifier_or = BlobVerifier::CreateWithoutTree(merkle_tree->root, GetMetrics(), sizeof(buf));
ASSERT_TRUE(verifier_or.is_ok());
BlobVerifier* verifier = verifier_or.value().get();
EXPECT_EQ(verifier->Verify(buf, sizeof(buf), sizeof(buf)), ZX_OK);
EXPECT_EQ(verifier->VerifyPartial(buf, 8192, 0, 8192), ZX_OK);
// Partial ranges
EXPECT_EQ(verifier->VerifyPartial(buf, 8191, 0, 8191), ZX_ERR_INVALID_ARGS);
// Verify past the end
EXPECT_EQ(
verifier->VerifyPartial(buf, static_cast<size_t>(2) * 8192, 0, static_cast<size_t>(2) * 8192),
ZX_ERR_INVALID_ARGS);
}
TEST_P(BlobVerifierTest, CreateAndVerify_SmallBlob_DataCorrupted) {
uint8_t buf[8192];
FillWithRandom(buf, sizeof(buf));
auto merkle_tree = GenerateTree(buf, sizeof(buf));
// Invert one character
buf[42] = ~(buf[42]);
auto verifier_or = BlobVerifier::CreateWithoutTree(merkle_tree->root, GetMetrics(), sizeof(buf));
ASSERT_TRUE(verifier_or.is_ok());
BlobVerifier* verifier = verifier_or.value().get();
EXPECT_EQ(verifier->Verify(buf, sizeof(buf), sizeof(buf)), ZX_ERR_IO_DATA_INTEGRITY);
EXPECT_EQ(verifier->VerifyPartial(buf, 8192, 0, 8192), ZX_ERR_IO_DATA_INTEGRITY);
}
TEST_P(BlobVerifierTest, CreateAndVerify_BigBlob) {
size_t sz = 1 << 16;
fbl::Array<uint8_t> buf(new uint8_t[sz], sz);
FillWithRandom(buf.get(), sz);
auto layout = GetBlobLayout(sz);
BlockMerkleTreeInfo info = GenerateMerkleTreeBlocks(*layout, buf.get(), sz);
auto verifier_or =
BlobVerifier::Create(info.root, GetMetrics(), info.GetMerkleDataBlocks(), *layout);
ASSERT_TRUE(verifier_or.is_ok());
BlobVerifier* verifier = verifier_or.value().get();
EXPECT_EQ(verifier->Verify(buf.get(), sz, sz), ZX_OK);
EXPECT_EQ(verifier->VerifyPartial(buf.get(), sz, 0, sz), ZX_OK);
// Block-by-block
for (size_t i = 0; i < sz; i += 8192) {
EXPECT_EQ(verifier->VerifyPartial(buf.get() + i, 8192, i, 8192), ZX_OK);
}
// Partial ranges
EXPECT_EQ(verifier->VerifyPartial(buf.data(), 8191, 0, 8191), ZX_ERR_INVALID_ARGS);
// Verify past the end
EXPECT_EQ(verifier->VerifyPartial(buf.data() + (sz - 8192), static_cast<size_t>(2) * 8192,
sz - 8192, static_cast<size_t>(2) * 8192),
ZX_ERR_INVALID_ARGS);
}
TEST_P(BlobVerifierTest, CreateAndVerify_BigBlob_DataCorrupted) {
size_t sz = 1 << 16;
fbl::Array<uint8_t> buf(new uint8_t[sz], sz);
FillWithRandom(buf.get(), sz);
auto layout = GetBlobLayout(sz);
BlockMerkleTreeInfo info = GenerateMerkleTreeBlocks(*layout, buf.get(), sz);
// Invert a char in the first block. All other blocks are still valid.
buf.get()[42] = ~(buf.get()[42]);
auto verifier_or =
BlobVerifier::Create(info.root, GetMetrics(), info.GetMerkleDataBlocks(), *layout);
ASSERT_TRUE(verifier_or.is_ok());
BlobVerifier* verifier = verifier_or.value().get();
EXPECT_EQ(verifier->Verify(buf.get(), sz, sz), ZX_ERR_IO_DATA_INTEGRITY);
EXPECT_EQ(verifier->VerifyPartial(buf.get(), sz, 0, sz), ZX_ERR_IO_DATA_INTEGRITY);
// Block-by-block -- first block fails, rest succeed
for (size_t i = 0; i < sz; i += 8192) {
zx_status_t status = verifier->VerifyPartial(buf.get() + i, 8192, i, 8192);
if (i == 0) {
EXPECT_EQ(status, ZX_ERR_IO_DATA_INTEGRITY);
} else {
EXPECT_EQ(status, ZX_OK);
}
}
}
TEST_P(BlobVerifierTest, CreateAndVerify_BigBlob_MerkleCorrupted) {
size_t sz = 1 << 16;
fbl::Array<uint8_t> buf(new uint8_t[sz], sz);
FillWithRandom(buf.get(), sz);
auto layout = GetBlobLayout(sz);
BlockMerkleTreeInfo info = GenerateMerkleTreeBlocks(*layout, buf.get(), sz);
// Invert a char in the tree.
info.merkle_data[0] ^= 0xff;
auto verifier_or =
BlobVerifier::Create(info.root, GetMetrics(), info.GetMerkleDataBlocks(), *layout);
ASSERT_TRUE(verifier_or.is_ok());
BlobVerifier* verifier = verifier_or.value().get();
EXPECT_EQ(verifier->Verify(buf.get(), sz, sz), ZX_ERR_IO_DATA_INTEGRITY);
EXPECT_EQ(verifier->VerifyPartial(buf.get(), sz, 0, sz), ZX_ERR_IO_DATA_INTEGRITY);
// Block-by-block -- everything fails
for (size_t i = 0; i < sz; i += 8192) {
EXPECT_EQ(verifier->VerifyPartial(buf.get() + i, 8192, i, 8192), ZX_ERR_IO_DATA_INTEGRITY);
}
}
TEST_P(BlobVerifierTest, NonZeroTailCausesVerifyToFail) {
constexpr int kBlobSize = 8000;
uint8_t buf[kBlobfsBlockSize];
FillWithRandom(buf, kBlobSize);
// Zero the tail.
memset(&buf[kBlobSize], 0, kBlobfsBlockSize - kBlobSize);
auto merkle_tree = GenerateTree(buf, kBlobSize);
auto verifier_or = BlobVerifier::CreateWithoutTree(merkle_tree->root, GetMetrics(), kBlobSize);
ASSERT_TRUE(verifier_or.is_ok());
BlobVerifier* verifier = verifier_or.value().get();
EXPECT_EQ(verifier->Verify(buf, kBlobSize, sizeof(buf)), ZX_OK);
buf[kBlobSize] = 1;
EXPECT_EQ(verifier->Verify(buf, kBlobSize, sizeof(buf)), ZX_ERR_IO_DATA_INTEGRITY);
}
TEST_P(BlobVerifierTest, NonZeroTailCausesVerifyPartialToFail) {
constexpr unsigned kBlobSize = (1 << 16) - 100;
std::vector<uint8_t> buf(fbl::round_up(kBlobSize, kBlobfsBlockSize));
FillWithRandom(buf.data(), kBlobSize);
auto layout = GetBlobLayout(kBlobSize);
BlockMerkleTreeInfo info = GenerateMerkleTreeBlocks(*layout, buf.data(), kBlobSize);
auto verifier_or =
BlobVerifier::Create(info.root, GetMetrics(), info.GetMerkleDataBlocks(), *layout);
ASSERT_TRUE(verifier_or.is_ok());
BlobVerifier* verifier = verifier_or.value().get();
constexpr int kVerifyOffset = kBlobSize - kBlobSize % kBlobfsBlockSize;
EXPECT_EQ(verifier->VerifyPartial(&buf[kVerifyOffset], kBlobSize - kVerifyOffset, kVerifyOffset,
buf.size() - kVerifyOffset),
ZX_OK);
buf[kBlobSize] = 1;
EXPECT_EQ(verifier->VerifyPartial(&buf[kVerifyOffset], kBlobSize - kVerifyOffset, kVerifyOffset,
buf.size() - kVerifyOffset),
ZX_ERR_IO_DATA_INTEGRITY);
}
std::string GetTestName(const testing::TestParamInfo<BlobLayoutFormat>& param) {
return GetBlobLayoutFormatNameForTests(param.param);
}
INSTANTIATE_TEST_SUITE_P(/*no prefix*/, BlobVerifierTest,
::testing::Values(BlobLayoutFormat::kDeprecatedPaddedMerkleTreeAtStart,
BlobLayoutFormat::kCompactMerkleTreeAtEnd),
GetTestName);
} // namespace
} // namespace blobfs