| // 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 "blob-verifier.h" |
| |
| #include <random> |
| |
| #include <gtest/gtest.h> |
| |
| #include "utils.h" |
| |
| namespace blobfs { |
| namespace { |
| |
| class BlobVerifierTest : public testing::Test { |
| public: |
| BlobfsMetrics* Metrics() { return &metrics_; } |
| |
| void SetUp() override { srand(testing::UnitTest::GetInstance()->random_seed()); } |
| |
| private: |
| BlobfsMetrics metrics_{false}; |
| }; |
| |
| void GenerateTree(const uint8_t* data, size_t len, Digest* out_digest, |
| fbl::Array<uint8_t>* out_tree) { |
| digest::MerkleTreeCreator mtc; |
| ASSERT_EQ(mtc.SetDataLength(len), ZX_OK); |
| size_t merkle_size = mtc.GetTreeLength(); |
| fbl::Array<uint8_t> merkle_buf(new uint8_t[merkle_size], merkle_size); |
| uint8_t root[digest::kSha256Length]; |
| ASSERT_EQ(mtc.SetTree(merkle_buf.get(), merkle_size, root, sizeof(root)), ZX_OK); |
| ASSERT_EQ(mtc.Append(data, len), ZX_OK); |
| *out_digest = Digest(root); |
| *out_tree = std::move(merkle_buf); |
| } |
| |
| void FillWithRandom(uint8_t* buf, size_t len) { |
| for (unsigned i = 0; i < len; ++i) { |
| buf[i] = (uint8_t)rand(); |
| } |
| } |
| |
| TEST_F(BlobVerifierTest, CreateAndVerify_NullBlob) { |
| fbl::Array<uint8_t> unused_merkle_buf; |
| Digest digest; |
| GenerateTree(nullptr, 0, &digest, &unused_merkle_buf); |
| |
| std::unique_ptr<BlobVerifier> verifier; |
| ASSERT_EQ(BlobVerifier::CreateWithoutTree(std::move(digest), Metrics(), 0ul, nullptr, &verifier), |
| ZX_OK); |
| EXPECT_EQ(verifier->Verify(nullptr, 0ul, 0ul), ZX_OK); |
| EXPECT_EQ(verifier->VerifyPartial(nullptr, 0ul, 0ul, 0ul), ZX_OK); |
| } |
| |
| TEST_F(BlobVerifierTest, CreateAndVerify_SmallBlob) { |
| uint8_t buf[8192]; |
| FillWithRandom(buf, sizeof(buf)); |
| |
| fbl::Array<uint8_t> unused_merkle_buf; |
| Digest digest; |
| GenerateTree(buf, sizeof(buf), &digest, &unused_merkle_buf); |
| |
| std::unique_ptr<BlobVerifier> verifier; |
| ASSERT_EQ(BlobVerifier::CreateWithoutTree(std::move(digest), Metrics(), sizeof(buf), nullptr, |
| &verifier), |
| ZX_OK); |
| |
| 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, 2 * 8192, 0, 2 * 8192), ZX_ERR_INVALID_ARGS); |
| } |
| |
| TEST_F(BlobVerifierTest, CreateAndVerify_SmallBlob_DataCorrupted) { |
| uint8_t buf[8192]; |
| FillWithRandom(buf, sizeof(buf)); |
| |
| fbl::Array<uint8_t> unused_merkle_buf; |
| Digest digest; |
| GenerateTree(buf, sizeof(buf), &digest, &unused_merkle_buf); |
| |
| // Invert one character |
| buf[42] = ~(buf[42]); |
| |
| std::unique_ptr<BlobVerifier> verifier; |
| ASSERT_EQ(BlobVerifier::CreateWithoutTree(std::move(digest), Metrics(), sizeof(buf), nullptr, |
| &verifier), |
| ZX_OK); |
| |
| 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_F(BlobVerifierTest, CreateAndVerify_BigBlob) { |
| size_t sz = 1 << 16; |
| fbl::Array<uint8_t> buf(new uint8_t[sz], sz); |
| FillWithRandom(buf.get(), sz); |
| |
| fbl::Array<uint8_t> merkle_buf; |
| Digest digest; |
| GenerateTree(buf.get(), sz, &digest, &merkle_buf); |
| |
| std::unique_ptr<BlobVerifier> verifier; |
| ASSERT_EQ(BlobVerifier::Create(std::move(digest), Metrics(), merkle_buf.get(), merkle_buf.size(), |
| sz, nullptr, &verifier), |
| ZX_OK); |
| |
| 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), 2 * 8192, sz - 8192, 2 * 8192), |
| ZX_ERR_INVALID_ARGS); |
| } |
| |
| TEST_F(BlobVerifierTest, CreateAndVerify_BigBlob_DataCorrupted) { |
| size_t sz = 1 << 16; |
| fbl::Array<uint8_t> buf(new uint8_t[sz], sz); |
| FillWithRandom(buf.get(), sz); |
| |
| fbl::Array<uint8_t> merkle_buf; |
| Digest digest; |
| GenerateTree(buf.get(), sz, &digest, &merkle_buf); |
| |
| // Invert a char in the first block. All other blocks are still valid. |
| buf.get()[42] = ~(buf.get()[42]); |
| |
| std::unique_ptr<BlobVerifier> verifier; |
| ASSERT_EQ(BlobVerifier::Create(std::move(digest), Metrics(), merkle_buf.get(), merkle_buf.size(), |
| sz, nullptr, &verifier), |
| ZX_OK); |
| |
| 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 expected_status = i == 0 ? ZX_ERR_IO_DATA_INTEGRITY : ZX_OK; |
| EXPECT_EQ(verifier->VerifyPartial(buf.get() + i, 8192, i, 8192), expected_status); |
| } |
| } |
| |
| TEST_F(BlobVerifierTest, CreateAndVerify_BigBlob_MerkleCorrupted) { |
| size_t sz = 1 << 16; |
| fbl::Array<uint8_t> buf(new uint8_t[sz], sz); |
| FillWithRandom(buf.get(), sz); |
| |
| fbl::Array<uint8_t> merkle_buf; |
| Digest digest; |
| GenerateTree(buf.get(), sz, &digest, &merkle_buf); |
| |
| // Invert a char in the tree. |
| merkle_buf.get()[0] = ~(merkle_buf.get()[0]); |
| |
| std::unique_ptr<BlobVerifier> verifier; |
| ASSERT_EQ(BlobVerifier::Create(std::move(digest), Metrics(), merkle_buf.get(), merkle_buf.size(), |
| sz, nullptr, &verifier), |
| ZX_OK); |
| |
| 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_F(BlobVerifierTest, NonZeroTailCausesVerifyToFail) { |
| constexpr int kBlobSize = 8000; |
| uint8_t buf[kBlobfsBlockSize]; |
| FillWithRandom(buf, kBlobSize); |
| // Zero the tail. |
| memset(&buf[kBlobSize], 0, kBlobfsBlockSize - kBlobSize); |
| |
| fbl::Array<uint8_t> unused_merkle_buf; |
| Digest digest; |
| GenerateTree(buf, kBlobSize, &digest, &unused_merkle_buf); |
| |
| std::unique_ptr<BlobVerifier> verifier; |
| EXPECT_EQ( |
| BlobVerifier::CreateWithoutTree(std::move(digest), Metrics(), kBlobSize, nullptr, &verifier), |
| ZX_OK); |
| |
| 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_F(BlobVerifierTest, NonZeroTailCausesVerifyPartialToFail) { |
| constexpr unsigned kBlobSize = (1 << 16) - 100; |
| std::vector<uint8_t> buf(fbl::round_up(kBlobSize, kBlobfsBlockSize)); |
| FillWithRandom(buf.data(), kBlobSize); |
| |
| fbl::Array<uint8_t> merkle_buf; |
| Digest digest; |
| GenerateTree(buf.data(), kBlobSize, &digest, &merkle_buf); |
| |
| std::unique_ptr<BlobVerifier> verifier; |
| ASSERT_EQ(BlobVerifier::Create(std::move(digest), Metrics(), merkle_buf.get(), merkle_buf.size(), |
| kBlobSize, nullptr, &verifier), |
| ZX_OK); |
| |
| 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); |
| } |
| |
| } // namespace |
| } // namespace blobfs |