| // 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 <fuchsia/blobfs/c/fidl.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <zircon/status.h> |
| |
| #include <digest/digest.h> |
| #include <digest/merkle-tree.h> |
| #include <fs/trace.h> |
| #include <safemath/checked_math.h> |
| |
| #include "src/storage/blobfs/blob-layout.h" |
| |
| namespace blobfs { |
| |
| BlobVerifier::BlobVerifier(BlobfsMetrics* metrics) : metrics_(metrics) {} |
| |
| zx_status_t BlobVerifier::Create(digest::Digest digest, BlobfsMetrics* metrics, const void* merkle, |
| size_t merkle_size, BlobLayoutFormat blob_layout_format, |
| size_t data_size, const BlobCorruptionNotifier* notifier, |
| std::unique_ptr<BlobVerifier>* out) { |
| std::unique_ptr<BlobVerifier> verifier(new BlobVerifier(metrics)); |
| verifier->digest_ = std::move(digest); |
| verifier->corruption_notifier_ = notifier; |
| verifier->tree_verifier_.SetUseCompactFormat( |
| ShouldUseCompactMerkleTreeFormat(blob_layout_format)); |
| zx_status_t status = verifier->tree_verifier_.SetDataLength(data_size); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to set merkle data length: " << zx_status_get_string(status); |
| return status; |
| } |
| size_t actual_merkle_length = verifier->tree_verifier_.GetTreeLength(); |
| if (actual_merkle_length > merkle_size) { |
| FX_LOGS(ERROR) << "merkle too small for data"; |
| return ZX_ERR_BUFFER_TOO_SMALL; |
| } |
| if ((status = verifier->tree_verifier_.SetTree( |
| merkle, actual_merkle_length, verifier->digest_.get(), verifier->digest_.len())) != |
| ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to create merkle verifier: " << zx_status_get_string(status); |
| return status; |
| } |
| *out = std::move(verifier); |
| return ZX_OK; |
| } |
| |
| zx_status_t BlobVerifier::CreateWithoutTree(digest::Digest digest, BlobfsMetrics* metrics, |
| size_t data_size, |
| const BlobCorruptionNotifier* notifier, |
| std::unique_ptr<BlobVerifier>* out) { |
| std::unique_ptr<BlobVerifier> verifier(new BlobVerifier(metrics)); |
| verifier->digest_ = std::move(digest); |
| verifier->corruption_notifier_ = notifier; |
| zx_status_t status = verifier->tree_verifier_.SetDataLength(data_size); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to set merkle data length: " << zx_status_get_string(status); |
| return status; |
| } else if (verifier->tree_verifier_.GetTreeLength() > 0) { |
| FX_LOGS(ERROR) << "Failed to create merkle verifier -- data too big for empty tree"; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if ((status = verifier->tree_verifier_.SetTree(nullptr, 0, verifier->digest_.get(), |
| verifier->digest_.len())) != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to create merkle verifier: " << zx_status_get_string(status); |
| return status; |
| } |
| *out = std::move(verifier); |
| return ZX_OK; |
| } |
| |
| zx_status_t BlobVerifier::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(metrics_->Collecting()); |
| |
| zx_status_t 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()); |
| if (status == ZX_ERR_IO_DATA_INTEGRITY && corruption_notifier_) { |
| // Notify the corruption handler server about the corrupted blob. |
| // If there is any error to do this, we should not fail the verify. |
| zx_status_t notify_status = |
| corruption_notifier_->NotifyCorruptBlob(digest_.get(), digest_.len()); |
| if (notify_status != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to notify corruptionHandler for blob: " << digest_.ToString() |
| << " error: " << zx_status_get_string(notify_status); |
| } |
| } |
| 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(metrics_->Collecting()); |
| |
| zx_status_t 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()); |
| |
| if (status == ZX_ERR_IO_DATA_INTEGRITY && corruption_notifier_) { |
| // Notify the corruption handler server about the corrupted blob. |
| // If there is any error to do this, we should not fail the verify. |
| zx_status_t notify_status = |
| corruption_notifier_->NotifyCorruptBlob(digest_.get(), digest_.len()); |
| if (notify_status != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to notify corruptionHandler for blob: " << digest_.ToString() |
| << " error: " << zx_status_get_string(notify_status); |
| } |
| } |
| return status; |
| } |
| |
| } // namespace blobfs |