| // Copyright 2017 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 <fcntl.h> |
| #include <inttypes.h> |
| #include <stdbool.h> |
| #include <stddef.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| #include <zircon/assert.h> |
| |
| #include <fvm/fvm.h> |
| |
| #ifdef __Fuchsia__ |
| #include <lib/zx/vmo.h> |
| #include <zircon/syscalls.h> |
| #endif |
| |
| #include <zircon/compiler.h> |
| #include <zircon/types.h> |
| |
| #include <fbl/algorithm.h> |
| #include <fbl/unique_fd.h> |
| #include <fvm/format.h> |
| |
| namespace fvm { |
| |
| namespace { |
| |
| // Return true if g1 is greater than or equal to g2. |
| // Safe against integer overflow. |
| bool GenerationGE(uint64_t g1, uint64_t g2) { |
| if (g1 == UINT64_MAX && g2 == 0) { |
| return false; |
| } else if (g1 == 0 && g2 == UINT64_MAX) { |
| return true; |
| } |
| return g1 >= g2; |
| } |
| |
| // Validate the metadata's hash value. |
| // Returns 'true' if it matches, 'false' otherwise. |
| bool CheckHash(const void* metadata, size_t metadata_size) { |
| ZX_ASSERT(metadata_size >= sizeof(Header)); |
| const Header* header = static_cast<const Header*>(metadata); |
| const void* metadata_after_hash = |
| reinterpret_cast<const void*>(header->hash + sizeof(header->hash)); |
| uint8_t empty_hash[sizeof(header->hash)]; |
| memset(empty_hash, 0, sizeof(empty_hash)); |
| |
| digest::Digest digest; |
| digest.Init(); |
| digest.Update(metadata, offsetof(Header, hash)); |
| digest.Update(empty_hash, sizeof(empty_hash)); |
| digest.Update(metadata_after_hash, |
| metadata_size - (offsetof(Header, hash) + sizeof(header->hash))); |
| digest.Final(); |
| return digest == header->hash; |
| } |
| |
| } // namespace |
| |
| void UpdateHash(void* metadata, size_t metadata_size) { |
| Header* header = static_cast<Header*>(metadata); |
| memset(header->hash, 0, sizeof(header->hash)); |
| digest::Digest digest; |
| const uint8_t* hash = digest.Hash(metadata, metadata_size); |
| memcpy(header->hash, hash, sizeof(header->hash)); |
| } |
| |
| std::optional<SuperblockType> ValidateHeader(const void* primary_metadata, |
| const void* secondary_metadata, size_t metadata_size) { |
| const Header* primary_header = static_cast<const Header*>(primary_metadata); |
| size_t primary_metadata_size = primary_header->GetMetadataUsedBytes(); |
| |
| const Header* secondary_header = static_cast<const Header*>(secondary_metadata); |
| size_t secondary_metadata_size = secondary_header->GetMetadataUsedBytes(); |
| |
| auto check_value_consistency = [metadata_size](const Header& header) { |
| // Check header signature and version. |
| if (header.magic != kMagic) { |
| fprintf(stderr, "fvm: Bad magic\n"); |
| return false; |
| } |
| if (header.format_version > kCurrentFormatVersion) { |
| fprintf(stderr, |
| "fvm: Header format version (=%" PRIu64 ") does not match fvm driver (=%" PRIu64 |
| ")\n", |
| header.format_version, kCurrentFormatVersion); |
| return false; |
| } |
| |
| // Check no overflow for each region of metadata. |
| uint64_t calculated_metadata_size = 0; |
| if (add_overflow(header.allocation_table_size, header.GetAllocationTableOffset(), |
| &calculated_metadata_size)) { |
| fprintf(stderr, "fvm: Calculated metadata size produces overflow(%" PRIu64 ", %zu).\n", |
| header.allocation_table_size, header.GetAllocationTableOffset()); |
| return false; |
| } |
| |
| // Check that the reported metadata size matches the calculated metadata size by format info. |
| // This may be slightly redundant since presumably the caller who passed in the metadata_size |
| // computed it from the header values, but it's useful to check for the backup copy. |
| if (header.GetMetadataUsedBytes() > metadata_size) { |
| fprintf(stderr, |
| "fvm: Reported metadata size of %zu is smaller than header buffer size %zu.\n", |
| header.GetMetadataUsedBytes(), metadata_size); |
| return false; |
| } |
| |
| // Check metadata size is as least as big as the header. |
| if (header.GetMetadataUsedBytes() < sizeof(Header)) { |
| fprintf(stderr, |
| "fvm: Reported metadata size of %zu is smaller than header buffer size %lu.\n", |
| header.GetMetadataUsedBytes(), sizeof(Header)); |
| return false; |
| } |
| |
| // Check metadata allocated size is greater than used size. |
| if (header.GetMetadataUsedBytes() > header.GetMetadataAllocatedBytes()) { |
| fprintf(stderr, "fvm: Reported metadata size of %zu is greater than allocated size %lu.\n", |
| header.GetMetadataUsedBytes(), header.GetMetadataAllocatedBytes()); |
| return false; |
| } |
| |
| // Check bounds of slice size and partition. |
| if (header.fvm_partition_size < 2 * header.GetMetadataAllocatedBytes()) { |
| fprintf(stderr, |
| "fvm: Partition of size %" PRIu64 " can't fit both metadata copies of size %zu.\n", |
| header.fvm_partition_size, header.GetMetadataAllocatedBytes()); |
| return false; |
| } |
| |
| // Check that addressable slices fit in the partition. |
| if (header.GetDataStartOffset() + |
| (header.GetAllocationTableUsedEntryCount() * header.slice_size) > |
| header.fvm_partition_size) { |
| fprintf(stderr, |
| "fvm: Slice count %zu Slice Size %" PRIu64 " out of range for partition sz %" PRIu64 |
| ".\n", |
| header.GetAllocationTableUsedEntryCount(), header.slice_size, |
| header.fvm_partition_size); |
| return false; |
| } |
| |
| return true; |
| }; |
| |
| // Assume that the reported metadata size by each header is correct. This size must be smaller |
| // than metadata buffer size(|metadata_size|. If this is the case, then check that the contents |
| // from [start, reported_size] are valid. |
| // The metadata size should always be at least the size of the header. |
| bool primary_valid = false; |
| if (check_value_consistency(*primary_header)) { |
| if (CheckHash(primary_metadata, primary_metadata_size)) { |
| primary_valid = true; |
| } else { |
| fprintf(stderr, "fvm: Primary metadata has invalid content hash\n"); |
| } |
| } |
| if (!primary_valid) { |
| fprintf(stderr, "fvm: Primary metadata invalid\n"); |
| } |
| bool secondary_valid = false; |
| if (check_value_consistency(*secondary_header)) { |
| if (CheckHash(secondary_metadata, secondary_metadata_size)) { |
| secondary_valid = true; |
| } else { |
| fprintf(stderr, "fvm: Secondary metadata has invalid content hash\n"); |
| } |
| } |
| if (!secondary_valid) { |
| fprintf(stderr, "fvm: Secondary metadata invalid\n"); |
| } |
| |
| // Decide if we should use the primary or the secondary copy of metadata for reading. |
| if (!primary_valid && !secondary_valid) { |
| return std::nullopt; |
| } |
| |
| if (primary_valid && !secondary_valid) { |
| return SuperblockType::kPrimary; |
| } |
| if (!primary_valid && secondary_valid) { |
| return SuperblockType::kSecondary; |
| } |
| |
| // Both valid, pick the newest. |
| return GenerationGE(primary_header->generation, secondary_header->generation) |
| ? SuperblockType::kPrimary |
| : SuperblockType::kSecondary; |
| } |
| |
| } // namespace fvm |