| // 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 <lib/syslog/cpp/macros.h> |
| #include <lib/trace/event.h> |
| #include <zircon/status.h> |
| |
| #include <safemath/checked_math.h> |
| |
| #include "src/lib/digest/digest.h" |
| #include "src/lib/digest/merkle-tree.h" |
| #include "src/storage/blobfs/blob_layout.h" |
| |
| namespace blobfs { |
| |
| BlobVerifier::BlobVerifier(digest::Digest digest, std::shared_ptr<BlobfsMetrics> metrics) |
| : digest_(std::move(digest)), metrics_(std::move(metrics)) {} |
| |
| zx::result<std::unique_ptr<BlobVerifier>> BlobVerifier::Create( |
| digest::Digest digest, std::shared_ptr<BlobfsMetrics> metrics, |
| cpp20::span<const uint8_t> merkle_data_blocks, const BlobLayout& layout) { |
| std::unique_ptr<BlobVerifier> verifier(new BlobVerifier(std::move(digest), std::move(metrics))); |
| verifier->tree_verifier_.SetUseCompactFormat(ShouldUseCompactMerkleTreeFormat(layout.Format())); |
| |
| if (zx_status_t status = verifier->tree_verifier_.SetDataLength(layout.FileSize()); |
| status != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to set merkle data length: " << zx_status_get_string(status); |
| return zx::error(status); |
| } |
| |
| size_t actual_merkle_length = verifier->tree_verifier_.GetTreeLength(); |
| if (actual_merkle_length > layout.MerkleTreeSize() || |
| layout.MerkleTreeOffsetWithinBlockOffset() + actual_merkle_length > |
| merkle_data_blocks.size()) { |
| FX_LOGS(ERROR) << "merkle too small for data"; |
| return zx::error(ZX_ERR_BUFFER_TOO_SMALL); |
| } |
| |
| const uint8_t* merkle_tree_data = |
| merkle_data_blocks.data() + layout.MerkleTreeOffsetWithinBlockOffset(); |
| verifier->merkle_data_ = std::make_unique<uint8_t[]>(actual_merkle_length); |
| memcpy(verifier->merkle_data_.get(), merkle_tree_data, actual_merkle_length); |
| |
| if (zx_status_t status = |
| verifier->tree_verifier_.SetTree(verifier->merkle_data_.get(), actual_merkle_length, |
| verifier->digest_.get(), verifier->digest_.len()); |
| status != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to create merkle verifier: " << zx_status_get_string(status); |
| return zx::error(status); |
| } |
| |
| return zx::ok(std::move(verifier)); |
| } |
| |
| zx::result<std::unique_ptr<BlobVerifier>> BlobVerifier::CreateWithoutTree( |
| digest::Digest digest, std::shared_ptr<BlobfsMetrics> metrics, size_t data_size) { |
| std::unique_ptr<BlobVerifier> verifier(new BlobVerifier(std::move(digest), std::move(metrics))); |
| |
| if (zx_status_t status = verifier->tree_verifier_.SetDataLength(data_size); status != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to set merkle data length: " << zx_status_get_string(status); |
| return zx::error(status); |
| } else if (verifier->tree_verifier_.GetTreeLength() > 0) { |
| FX_LOGS(ERROR) << "Failed to create merkle verifier -- data too big for empty tree"; |
| return zx::error(ZX_ERR_INVALID_ARGS); |
| } |
| |
| if (zx_status_t status = verifier->tree_verifier_.SetTree(nullptr, 0, verifier->digest_.get(), |
| verifier->digest_.len()); |
| status != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to create merkle verifier: " << zx_status_get_string(status); |
| return zx::error(status); |
| } |
| |
| return zx::ok(std::move(verifier)); |
| } |
| |
| zx_status_t VerifyTailZeroed(const void* data, size_t data_size, size_t buffer_size) { |
| size_t tail; |
| if (!safemath::CheckSub(buffer_size, data_size).AssignIfValid(&tail)) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (tail == 0) { |
| return ZX_OK; |
| } |
| // Check unaligned part first. |
| const uint8_t* u8_ptr = static_cast<const uint8_t*>(data) + data_size; |
| while (tail & 7) { |
| if (*u8_ptr) { |
| return ZX_ERR_IO_DATA_INTEGRITY; |
| } |
| ++u8_ptr; |
| --tail; |
| } |
| // Check remaining aligned part. |
| const uint64_t* u64_ptr = reinterpret_cast<const uint64_t*>(u8_ptr); |
| while (tail > 0) { |
| if (*u64_ptr) { |
| return ZX_ERR_IO_DATA_INTEGRITY; |
| } |
| ++u64_ptr; |
| tail -= 8; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t BlobVerifier::Verify(const void* data, size_t data_size, size_t buffer_size) { |
| TRACE_DURATION("blobfs", "BlobVerifier::Verify", "data_size", data_size); |
| fs::Ticker ticker; |
| |
| zx_status_t status; |
| { |
| std::lock_guard l(verification_lock_); |
| status = tree_verifier_.Verify(data, data_size, 0); |
| } |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Verify(" << digest_.ToString() << ", " << data_size << ", " << buffer_size |
| << ") failed: " << zx_status_get_string(status); |
| } else { |
| status = VerifyTailZeroed(data, data_size, buffer_size); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "VerifyTailZeroed(" << digest_.ToString() << ", " << data_size << ", " |
| << buffer_size << ") failed: " << zx_status_get_string(status); |
| } |
| } |
| metrics_->verification_metrics().Increment(data_size, tree_verifier_.GetTreeLength(), |
| ticker.End()); |
| return status; |
| } |
| |
| zx_status_t BlobVerifier::VerifyPartial(const void* data, size_t length, size_t data_offset, |
| size_t buffer_size) { |
| TRACE_DURATION("blobfs", "BlobVerifier::VerifyPartial", "length", length, "offset", data_offset); |
| fs::Ticker ticker; |
| |
| zx_status_t status; |
| { |
| std::lock_guard l(verification_lock_); |
| status = tree_verifier_.Verify(data, length, data_offset); |
| } |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "VerifyPartial(" << digest_.ToString() << ", " << data_offset << ", " |
| << length << ", " << buffer_size << ") failed: " << zx_status_get_string(status); |
| } else { |
| status = VerifyTailZeroed(data, length, buffer_size); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "VerifyTailZeroed(" << digest_.ToString() << ", " << length << ", " |
| << buffer_size << ") failed: " << zx_status_get_string(status); |
| } |
| } |
| metrics_->verification_metrics().Increment(length, tree_verifier_.GetTreeLength(), ticker.End()); |
| |
| return status; |
| } |
| |
| } // namespace blobfs |