blob: 6e788c223625376e95183d38db6064b439440922 [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 <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