blob: 72f4606d79d736b4825b39a032341db050f4db9a [file] [log] [blame]
// Copyright 2018 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/fvm/fvm_check.h"
#include <fidl/fuchsia.hardware.block/cpp/wire.h>
#include <fidl/fuchsia.io/cpp/wire.h>
#include <inttypes.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <zircon/errors.h>
#include <zircon/status.h>
#include <memory>
#include <utility>
#include <fbl/array.h>
#include <fbl/unique_fd.h>
#include <fbl/vector.h>
#include <gpt/guid.h>
#include "src/storage/fvm/format.h"
#include "src/storage/fvm/fvm.h"
#include "src/storage/lib/block_client/cpp/remote_block_device.h"
namespace fvm {
Checker::Block::Block(fidl::UnownedClientEnd<fuchsia_hardware_block::Block> block)
: block_(block) {}
Checker::Block::~Block() = default;
zx::result<size_t> Checker::Block::Size() const {
const fidl::WireResult result = fidl::WireCall(block_)->GetInfo();
if (!result.ok()) {
return zx::error(result.status());
}
fit::result response = result.value();
if (response.is_error()) {
return response.take_error();
}
const fuchsia_hardware_block::wire::BlockInfo& info = response.value()->info;
return zx::ok(info.block_count * info.block_size);
}
zx::result<size_t> Checker::Block::Read(void* buf, size_t count) const {
return zx::make_result(block_client::SingleReadBytes(block_, buf, count, 0), count);
}
Checker::File::File(fidl::UnownedClientEnd<fuchsia_io::File> file) : file_(file) {}
Checker::File::~File() = default;
zx::result<size_t> Checker::File::Size() const {
const fidl::WireResult result = fidl::WireCall(file_)->GetAttr();
if (!result.ok()) {
return zx::error(result.status());
}
const fidl::WireResponse response = result.value();
if (zx_status_t status = response.s; status != ZX_OK) {
return zx::error(status);
}
return zx::ok(response.attributes.content_size);
}
zx::result<size_t> Checker::File::Read(void* buf, size_t count) const {
uint8_t* dst = static_cast<uint8_t*>(buf);
for (size_t offset = 0; offset != count;) {
size_t len = std::min(count - offset, fuchsia_io::wire::kMaxTransferSize);
const fidl::WireResult result = fidl::WireCall(file_)->ReadAt(len, offset);
if (!result.ok()) {
return zx::error(result.status());
}
const fit::result response = result.value();
if (response.is_error()) {
return zx::error(response.error_value());
}
fidl::VectorView<uint8_t> data = response.value()->data;
memcpy(dst + offset, data.data(), data.count());
offset += data.count();
}
return zx::ok(count);
}
Checker::Checker(fidl::UnownedClientEnd<fuchsia_hardware_block::Block> block, uint32_t block_size,
bool silent)
: Checker(std::make_unique<Block>(block), block_size, silent) {}
Checker::Checker(fidl::UnownedClientEnd<fuchsia_io::File> file, uint32_t block_size, bool silent)
: Checker(std::make_unique<File>(file), block_size, silent) {}
Checker::Checker(std::unique_ptr<Interface> interface, uint32_t block_size, bool silent)
: interface_(std::move(interface)), block_size_(block_size), logger_(silent) {}
bool Checker::Validate() const {
FvmInfo info;
if (!LoadFVM(&info)) {
return false;
}
return CheckFVM(info);
}
bool Checker::LoadFVM(FvmInfo* out) const {
zx::result device_size = interface_->Size();
if (device_size.is_error()) {
logger_.Error("Could not get device size: %s\n", device_size.status_string());
return false;
}
if (device_size.value() % block_size_ != 0) {
logger_.Error("device size (%d) is not divisible by block size %d\n", device_size.value(),
block_size_);
return false;
}
const size_t block_count = device_size.value() / block_size_;
uint8_t header[fvm::kBlockSize];
{
zx::result result = interface_->Read(header, sizeof(header));
if (result.is_error()) {
logger_.Error("Could not read header: %s\n", result.status_string());
return false;
}
if (result.value() != sizeof(header)) {
logger_.Error("Could not read header: %d/%d bytes read\n", result.value(), sizeof(header));
return false;
}
}
fvm::Header superblock;
memcpy(&superblock, header, sizeof(superblock));
if (superblock.slice_size % block_size_ != 0) {
logger_.Error("Slice size not divisible by block size\n");
return false;
}
if (superblock.slice_size == 0) {
logger_.Error("Slice size cannot be zero\n");
return false;
}
// Validate sizes to prevent allocating overlarge buffers for the metadata. Check the table
// sizes separately to prevent numeric overflow when combining them.
if (superblock.GetAllocationTableAllocatedByteSize() > fvm::kMaxAllocationTableByteSize) {
logger_.Error("Slice allocation table is too large.");
return false;
}
if (superblock.GetPartitionTableByteSize() > fvm::kMaxPartitionTableByteSize) {
logger_.Error("FVM header partition table is too large.");
return false;
}
size_t metadata_allocated_bytes = superblock.GetMetadataAllocatedBytes();
if (metadata_allocated_bytes > fvm::kMaxMetadataByteSize) {
logger_.Error("FVM metadata size exceeds maximum limit.");
return false;
}
// The metadata buffer holds both primary and secondary copies of the metadata.
size_t metadata_buffer_size = metadata_allocated_bytes * 2;
std::unique_ptr<uint8_t[]> metadata(new uint8_t[metadata_buffer_size]);
if (zx::result result = interface_->Read(metadata.get(), metadata_buffer_size);
result.is_error()) {
logger_.Error("Could not read metadata %s\n", result.status_string());
return false;
}
std::optional<fvm::SuperblockType> use_superblock = fvm::PickValidHeader(
metadata.get(), metadata.get() + metadata_allocated_bytes, metadata_allocated_bytes);
if (!use_superblock) {
logger_.Error("Invalid FVM metadata\n");
return false;
}
fvm::SuperblockType invalid_superblock = *use_superblock == fvm::SuperblockType::kPrimary
? fvm::SuperblockType::kSecondary
: fvm::SuperblockType::kPrimary;
const uint8_t* valid_metadata = metadata.get() + superblock.GetSuperblockOffset(*use_superblock);
const uint8_t* invalid_metadata =
metadata.get() + superblock.GetSuperblockOffset(invalid_superblock);
FvmInfo info = {
fbl::Array<uint8_t>(metadata.release(), superblock.GetMetadataAllocatedBytes() * 2),
superblock.GetSuperblockOffset(*use_superblock),
valid_metadata,
invalid_metadata,
block_size_,
block_count,
device_size.value(),
superblock.slice_size,
};
*out = std::move(info);
return true;
}
bool Checker::LoadPartitions(const size_t slice_count, const fvm::SliceEntry* slice_table,
const fvm::VPartitionEntry* vpart_table,
fbl::Vector<Slice>* out_slices,
fbl::Array<Partition>* out_partitions) const {
fbl::Vector<Slice> slices;
fbl::Array<Partition> partitions(new Partition[fvm::kMaxVPartitions], fvm::kMaxVPartitions);
bool valid = true;
// Initialize all allocated partitions.
for (size_t i = 1; i < fvm::kMaxVPartitions; i++) {
const uint32_t slices = vpart_table[i].slices;
if (slices != 0) {
partitions[i].entry = &vpart_table[i];
}
}
// Initialize all slices, ensure they are used for allocated partitions.
for (size_t i = 1; i <= slice_count; i++) {
if (slice_table[i].IsAllocated()) {
const uint64_t vpart = slice_table[i].VPartition();
if (vpart >= kMaxVPartitions) {
logger_.Error("Invalid vslice entry; claims vpart which is out of range.\n");
valid = false;
} else if (!partitions[vpart].entry || partitions[vpart].entry->IsFree()) {
logger_.Error("Invalid slice entry; claims that it is allocated to unallocated ");
logger_.Error("partition %zu\n", vpart);
valid = false;
}
Slice slice = {vpart, slice_table[i].VSlice(), i};
slices.push_back(slice);
partitions[vpart].slices.push_back(slice);
}
}
// Validate that all allocated partitions are correct about the number of slices used.
for (size_t i = 1; i < fvm::kMaxVPartitions; i++) {
if (partitions[i].Allocated()) {
const size_t claimed = partitions[i].entry->slices;
const size_t actual = partitions[i].slices.size();
if (claimed != actual) {
logger_.Error("Disagreement about allocated slice count: ");
logger_.Error("Partition %zu claims %zu slices, has %zu\n", i, claimed, actual);
valid = false;
}
}
}
*out_slices = std::move(slices);
*out_partitions = std::move(partitions);
return valid;
}
void Checker::DumpSlices(const fbl::Vector<Slice>& slices) const {
logger_.Log("[ Slice Info ]\n");
const Slice* run_start = nullptr;
size_t run_length = 0;
// Prints whatever information we can from the current contiguous range of
// virtual / physical slices, then reset the "run" information.
//
// A run is a contiguous set of virtual / physical slices, all allocated to the same
// virtual partition. Noncontiguity in either the virtual or physical range
// "breaks" the run, since these cases provide new information.
auto start_run = [&run_start, &run_length](const Slice* slice) {
run_start = slice;
run_length = 1;
};
auto end_run = [this, &run_start, &run_length]() {
if (run_length == 1) {
logger_.Log("Physical Slice %zu allocated\n", run_start->physical_slice);
logger_.Log(" Allocated as virtual slice %zu\n", run_start->virtual_slice);
logger_.Log(" Allocated to partition %zu\n", run_start->virtual_partition);
} else if (run_length > 1) {
logger_.Log("%zu Physical Slices [%" PRIu64 ", %" PRIu64 "] allocated\n", run_length,
run_start->physical_slice, run_start->physical_slice + run_length - 1);
logger_.Log(" Allocated as virtual slices [%zu, %zu]\n", run_start->virtual_slice,
run_start->virtual_slice + run_length - 1);
logger_.Log(" Allocated to partition %zu\n", run_start->virtual_partition);
}
run_start = nullptr;
run_length = 0;
};
if (!slices.is_empty()) {
start_run(slices.data());
}
for (size_t i = 1; i < slices.size(); i++) {
const auto& slice = slices[i];
const size_t expected_pslice = run_start->physical_slice + run_length;
const size_t expected_vslice = run_start->virtual_slice + run_length;
if (slice.physical_slice == expected_pslice && slice.virtual_slice == expected_vslice &&
slice.virtual_partition == run_start->virtual_partition) {
run_length++;
} else {
end_run();
start_run(&slices[i]);
}
}
end_run();
}
bool Checker::CheckFVM(const FvmInfo& info) const {
const auto& superblock = *reinterpret_cast<const fvm::Header*>(info.valid_metadata);
const auto& invalid_superblock = *reinterpret_cast<const fvm::Header*>(info.invalid_metadata);
logger_.Log("[ FVM Info ]\n");
logger_.Log("Major version: %" PRIu64 "\n", superblock.major_version);
logger_.Log("Oldest minor version: %" PRIu64 "\n", superblock.oldest_minor_version);
logger_.Log("Generation number: %" PRIu64 "\n", superblock.generation);
logger_.Log("Generation number: %" PRIu64 " (invalid copy)\n", invalid_superblock.generation);
logger_.Log("\n");
const size_t slice_count = superblock.GetAllocationTableUsedEntryCount();
logger_.Log("[ Size Info ]\n");
logger_.Log("%-15s %10zu\n", "Device Length:", info.device_size);
logger_.Log("%-15s %10zu\n", "Block size:", info.block_size);
logger_.Log("%-15s %10zu\n", "Slice size:", info.slice_size);
logger_.Log("%-15s %10zu\n", "Slice count:", slice_count);
logger_.Log("\n");
const size_t metadata_size = superblock.GetMetadataAllocatedBytes();
const size_t metadata_count = 2;
const size_t metadata_end = metadata_size * metadata_count;
logger_.Log("[ Metadata ]\n");
logger_.Log("%-25s 0x%016zx\n", "Valid metadata start:", info.valid_metadata_offset);
logger_.Log("%-25s 0x%016x\n", "Metadata start:", 0);
logger_.Log("%-25s %16zu (for each copy)\n", "Metadata size:", metadata_size);
logger_.Log("%-25s %16zu\n", "Metadata count:", metadata_count);
logger_.Log("%-25s 0x%016zx\n", "Metadata end:", metadata_end);
logger_.Log("\n");
logger_.Log("[ All Subsequent Offsets Relative to Valid Metadata Start ]\n");
logger_.Log("\n");
const size_t vpart_table_start = superblock.GetPartitionTableOffset();
const size_t vpart_entry_size = sizeof(fvm::VPartitionEntry);
const size_t vpart_table_size = superblock.GetPartitionTableByteSize();
const size_t vpart_table_end = vpart_table_start + vpart_table_size;
logger_.Log("[ Virtual Partition Table ]\n");
logger_.Log("%-25s 0x%016zx\n", "VPartition Entry Start:", vpart_table_start);
logger_.Log("%-25s %16zu\n", "VPartition entry size:", vpart_entry_size);
logger_.Log("%-25s %16zu\n", "VPartition table size:", vpart_table_size);
logger_.Log("%-25s 0x%016zx\n", "VPartition table end:", vpart_table_end);
logger_.Log("\n");
const size_t slice_table_start = superblock.GetAllocationTableOffset();
const size_t slice_entry_size = sizeof(fvm::SliceEntry);
const size_t slice_table_size = superblock.GetAllocationTableUsedByteSize();
const size_t slice_table_end = slice_table_start + slice_table_size;
logger_.Log("[ Slice Allocation Table ]\n");
logger_.Log("%-25s 0x%016zx\n", "Slice table start:", slice_table_start);
logger_.Log("%-25s %16zu\n", "Slice entry size:", slice_entry_size);
logger_.Log("%-25s %16zu\n", "Slice table size:", slice_table_size);
logger_.Log("%-25s 0x%016zx\n", "Slice table end:", slice_table_end);
logger_.Log("\n");
const fvm::SliceEntry* slice_table =
reinterpret_cast<const fvm::SliceEntry*>(info.valid_metadata + slice_table_start);
const fvm::VPartitionEntry* vpart_table =
reinterpret_cast<const fvm::VPartitionEntry*>(info.valid_metadata + vpart_table_start);
fbl::Vector<Slice> slices;
fbl::Array<Partition> partitions;
bool valid = true;
if (!LoadPartitions(slice_count, slice_table, vpart_table, &slices, &partitions)) {
valid = false;
logger_.Log("Partitions invalid; displaying info anyway...\n");
}
logger_.Log("[ Partition Info ]\n");
for (size_t i = 1; i < fvm::kMaxVPartitions; i++) {
const uint32_t slices = vpart_table[i].slices;
if (slices != 0) {
logger_.Log("Partition %zu allocated\n", i);
logger_.Log(" Has %u slices allocated\n", slices);
#ifdef __Fuchsia__
logger_.Log(" Type: %s\n", gpt::KnownGuid::TypeDescription(vpart_table[i].type).c_str());
#endif
logger_.Log(" Name: %s\n", vpart_table[i].name().c_str());
}
}
logger_.Log("\n");
DumpSlices(slices);
return valid;
}
} // namespace fvm