505 lines
19 KiB
C++
505 lines
19 KiB
C++
/*
|
|
* Copyright (C) 2018 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "reader.h"
|
|
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include <functional>
|
|
|
|
#include <android-base/file.h>
|
|
#include <android-base/unique_fd.h>
|
|
|
|
#include "utility.h"
|
|
|
|
namespace android {
|
|
namespace fs_mgr {
|
|
|
|
static_assert(sizeof(LpMetadataHeaderV1_0) == offsetof(LpMetadataHeader, flags),
|
|
"Incorrect LpMetadataHeader v0 size");
|
|
|
|
// Helper class for reading descriptors and memory buffers in the same manner.
|
|
class Reader {
|
|
public:
|
|
virtual ~Reader(){};
|
|
virtual bool ReadFully(void* buffer, size_t length) = 0;
|
|
};
|
|
|
|
class FileReader final : public Reader {
|
|
public:
|
|
explicit FileReader(int fd) : fd_(fd) {}
|
|
bool ReadFully(void* buffer, size_t length) override {
|
|
return android::base::ReadFully(fd_, buffer, length);
|
|
}
|
|
|
|
private:
|
|
int fd_;
|
|
};
|
|
|
|
class MemoryReader final : public Reader {
|
|
public:
|
|
MemoryReader(const void* buffer, size_t size)
|
|
: buffer_(reinterpret_cast<const uint8_t*>(buffer)), size_(size), pos_(0) {}
|
|
bool ReadFully(void* out, size_t length) override {
|
|
if (size_ - pos_ < length) {
|
|
errno = EINVAL;
|
|
return false;
|
|
}
|
|
memcpy(out, buffer_ + pos_, length);
|
|
pos_ += length;
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
const uint8_t* buffer_;
|
|
size_t size_;
|
|
size_t pos_;
|
|
};
|
|
|
|
bool ParseGeometry(const void* buffer, LpMetadataGeometry* geometry) {
|
|
static_assert(sizeof(*geometry) <= LP_METADATA_GEOMETRY_SIZE);
|
|
memcpy(geometry, buffer, sizeof(*geometry));
|
|
|
|
// Check the magic signature.
|
|
if (geometry->magic != LP_METADATA_GEOMETRY_MAGIC) {
|
|
LERROR << "Logical partition metadata has invalid geometry magic signature.";
|
|
return false;
|
|
}
|
|
// Reject if the struct size is larger than what we compiled. This is so we
|
|
// can compute a checksum with the |struct_size| field rather than using
|
|
// sizeof.
|
|
if (geometry->struct_size > sizeof(LpMetadataGeometry)) {
|
|
LERROR << "Logical partition metadata has unrecognized fields.";
|
|
return false;
|
|
}
|
|
// Recompute and check the CRC32.
|
|
{
|
|
LpMetadataGeometry temp = *geometry;
|
|
memset(&temp.checksum, 0, sizeof(temp.checksum));
|
|
SHA256(&temp, temp.struct_size, temp.checksum);
|
|
if (memcmp(temp.checksum, geometry->checksum, sizeof(temp.checksum)) != 0) {
|
|
LERROR << "Logical partition metadata has invalid geometry checksum.";
|
|
return false;
|
|
}
|
|
}
|
|
// Check that the struct size is equal (this will have to change if we ever
|
|
// change the struct size in a release).
|
|
if (geometry->struct_size != sizeof(LpMetadataGeometry)) {
|
|
LERROR << "Logical partition metadata has invalid struct size.";
|
|
return false;
|
|
}
|
|
if (geometry->metadata_slot_count == 0) {
|
|
LERROR << "Logical partition metadata has invalid slot count.";
|
|
return false;
|
|
}
|
|
if (geometry->metadata_max_size % LP_SECTOR_SIZE != 0) {
|
|
LERROR << "Metadata max size is not sector-aligned.";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ReadPrimaryGeometry(int fd, LpMetadataGeometry* geometry) {
|
|
std::unique_ptr<uint8_t[]> buffer = std::make_unique<uint8_t[]>(LP_METADATA_GEOMETRY_SIZE);
|
|
if (SeekFile64(fd, GetPrimaryGeometryOffset(), SEEK_SET) < 0) {
|
|
PERROR << __PRETTY_FUNCTION__ << " lseek failed";
|
|
return false;
|
|
}
|
|
if (!android::base::ReadFully(fd, buffer.get(), LP_METADATA_GEOMETRY_SIZE)) {
|
|
PERROR << __PRETTY_FUNCTION__ << " read " << LP_METADATA_GEOMETRY_SIZE << " bytes failed";
|
|
return false;
|
|
}
|
|
return ParseGeometry(buffer.get(), geometry);
|
|
}
|
|
|
|
bool ReadBackupGeometry(int fd, LpMetadataGeometry* geometry) {
|
|
std::unique_ptr<uint8_t[]> buffer = std::make_unique<uint8_t[]>(LP_METADATA_GEOMETRY_SIZE);
|
|
if (SeekFile64(fd, GetBackupGeometryOffset(), SEEK_SET) < 0) {
|
|
PERROR << __PRETTY_FUNCTION__ << " lseek failed";
|
|
return false;
|
|
}
|
|
if (!android::base::ReadFully(fd, buffer.get(), LP_METADATA_GEOMETRY_SIZE)) {
|
|
PERROR << __PRETTY_FUNCTION__ << " backup read " << LP_METADATA_GEOMETRY_SIZE
|
|
<< " bytes failed";
|
|
return false;
|
|
}
|
|
return ParseGeometry(buffer.get(), geometry);
|
|
}
|
|
|
|
// Read and validate geometry information from a block device that holds
|
|
// logical partitions. If the information is corrupted, this will attempt
|
|
// to read it from a secondary backup location.
|
|
bool ReadLogicalPartitionGeometry(int fd, LpMetadataGeometry* geometry) {
|
|
if (ReadPrimaryGeometry(fd, geometry)) {
|
|
return true;
|
|
}
|
|
return ReadBackupGeometry(fd, geometry);
|
|
}
|
|
|
|
static bool ValidateTableBounds(const LpMetadataHeader& header,
|
|
const LpMetadataTableDescriptor& table) {
|
|
if (table.offset > header.tables_size) {
|
|
return false;
|
|
}
|
|
uint64_t table_size = uint64_t(table.num_entries) * table.entry_size;
|
|
if (header.tables_size - table.offset < table_size) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool ReadMetadataHeader(Reader* reader, LpMetadata* metadata) {
|
|
// Note we zero the struct since older files will result in a partial read.
|
|
LpMetadataHeader& header = metadata->header;
|
|
memset(&header, 0, sizeof(header));
|
|
|
|
if (!reader->ReadFully(&header, sizeof(LpMetadataHeaderV1_0))) {
|
|
PERROR << __PRETTY_FUNCTION__ << " read failed";
|
|
return false;
|
|
}
|
|
|
|
// Do basic sanity checks before computing the checksum.
|
|
if (header.magic != LP_METADATA_HEADER_MAGIC) {
|
|
LERROR << "Logical partition metadata has invalid magic value.";
|
|
return false;
|
|
}
|
|
if (header.major_version != LP_METADATA_MAJOR_VERSION ||
|
|
header.minor_version > LP_METADATA_MINOR_VERSION_MAX) {
|
|
LERROR << "Logical partition metadata has incompatible version.";
|
|
return false;
|
|
}
|
|
|
|
// Validate the header struct size against the reported version.
|
|
uint32_t expected_struct_size = sizeof(header);
|
|
if (header.minor_version < LP_METADATA_VERSION_FOR_EXPANDED_HEADER) {
|
|
expected_struct_size = sizeof(LpMetadataHeaderV1_0);
|
|
}
|
|
if (header.header_size != expected_struct_size) {
|
|
LERROR << "Invalid partition metadata header struct size.";
|
|
return false;
|
|
}
|
|
|
|
// Read in any remaining fields, the last step needed before checksumming.
|
|
if (size_t remaining_bytes = header.header_size - sizeof(LpMetadataHeaderV1_0)) {
|
|
uint8_t* offset = reinterpret_cast<uint8_t*>(&header) + sizeof(LpMetadataHeaderV1_0);
|
|
if (!reader->ReadFully(offset, remaining_bytes)) {
|
|
PERROR << __PRETTY_FUNCTION__ << " read failed";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// To compute the header's checksum, we have to temporarily set its checksum
|
|
// field to 0. Note that we must only compute up to |header_size|.
|
|
{
|
|
LpMetadataHeader temp = header;
|
|
memset(&temp.header_checksum, 0, sizeof(temp.header_checksum));
|
|
SHA256(&temp, temp.header_size, temp.header_checksum);
|
|
if (memcmp(temp.header_checksum, header.header_checksum, sizeof(temp.header_checksum)) !=
|
|
0) {
|
|
LERROR << "Logical partition metadata has invalid checksum.";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!ValidateTableBounds(header, header.partitions) ||
|
|
!ValidateTableBounds(header, header.extents) ||
|
|
!ValidateTableBounds(header, header.groups) ||
|
|
!ValidateTableBounds(header, header.block_devices)) {
|
|
LERROR << "Logical partition metadata has invalid table bounds.";
|
|
return false;
|
|
}
|
|
// Check that table entry sizes can accomodate their respective structs. If
|
|
// table sizes change, these checks will have to be adjusted.
|
|
if (header.partitions.entry_size != sizeof(LpMetadataPartition)) {
|
|
LERROR << "Logical partition metadata has invalid partition table entry size.";
|
|
return false;
|
|
}
|
|
if (header.extents.entry_size != sizeof(LpMetadataExtent)) {
|
|
LERROR << "Logical partition metadata has invalid extent table entry size.";
|
|
return false;
|
|
}
|
|
if (header.groups.entry_size != sizeof(LpMetadataPartitionGroup)) {
|
|
LERROR << "Logical partition metadata has invalid group table entry size.";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Parse and validate all metadata at the current position in the given file
|
|
// descriptor.
|
|
static std::unique_ptr<LpMetadata> ParseMetadata(const LpMetadataGeometry& geometry,
|
|
Reader* reader) {
|
|
// First read and validate the header.
|
|
std::unique_ptr<LpMetadata> metadata = std::make_unique<LpMetadata>();
|
|
|
|
metadata->geometry = geometry;
|
|
if (!ReadMetadataHeader(reader, metadata.get())) {
|
|
return nullptr;
|
|
}
|
|
|
|
LpMetadataHeader& header = metadata->header;
|
|
|
|
// Sanity check the table size.
|
|
if (header.tables_size > geometry.metadata_max_size) {
|
|
LERROR << "Invalid partition metadata header table size.";
|
|
return nullptr;
|
|
}
|
|
|
|
// Read the metadata payload. Allocation is fallible since the table size
|
|
// could be large.
|
|
std::unique_ptr<uint8_t[]> buffer(new (std::nothrow) uint8_t[header.tables_size]);
|
|
if (!buffer) {
|
|
LERROR << "Out of memory reading logical partition tables.";
|
|
return nullptr;
|
|
}
|
|
if (!reader->ReadFully(buffer.get(), header.tables_size)) {
|
|
PERROR << __PRETTY_FUNCTION__ << " read " << header.tables_size << "bytes failed";
|
|
return nullptr;
|
|
}
|
|
|
|
uint8_t checksum[32];
|
|
SHA256(buffer.get(), header.tables_size, checksum);
|
|
if (memcmp(checksum, header.tables_checksum, sizeof(checksum)) != 0) {
|
|
LERROR << "Logical partition metadata has invalid table checksum.";
|
|
return nullptr;
|
|
}
|
|
|
|
uint32_t valid_attributes = LP_PARTITION_ATTRIBUTE_MASK_V0;
|
|
if (metadata->header.minor_version >= LP_METADATA_VERSION_FOR_UPDATED_ATTR) {
|
|
valid_attributes |= LP_PARTITION_ATTRIBUTE_MASK_V1;
|
|
}
|
|
|
|
// ValidateTableSize ensured that |cursor| is valid for the number of
|
|
// entries in the table.
|
|
uint8_t* cursor = buffer.get() + header.partitions.offset;
|
|
for (size_t i = 0; i < header.partitions.num_entries; i++) {
|
|
LpMetadataPartition partition;
|
|
memcpy(&partition, cursor, sizeof(partition));
|
|
cursor += header.partitions.entry_size;
|
|
|
|
if (partition.attributes & ~valid_attributes) {
|
|
LERROR << "Logical partition has invalid attribute set.";
|
|
return nullptr;
|
|
}
|
|
if (partition.first_extent_index + partition.num_extents < partition.first_extent_index) {
|
|
LERROR << "Logical partition first_extent_index + num_extents overflowed.";
|
|
return nullptr;
|
|
}
|
|
if (partition.first_extent_index + partition.num_extents > header.extents.num_entries) {
|
|
LERROR << "Logical partition has invalid extent list.";
|
|
return nullptr;
|
|
}
|
|
if (partition.group_index >= header.groups.num_entries) {
|
|
LERROR << "Logical partition has invalid group index.";
|
|
return nullptr;
|
|
}
|
|
|
|
metadata->partitions.push_back(partition);
|
|
}
|
|
|
|
cursor = buffer.get() + header.extents.offset;
|
|
for (size_t i = 0; i < header.extents.num_entries; i++) {
|
|
LpMetadataExtent extent;
|
|
memcpy(&extent, cursor, sizeof(extent));
|
|
cursor += header.extents.entry_size;
|
|
|
|
if (extent.target_type == LP_TARGET_TYPE_LINEAR &&
|
|
extent.target_source >= header.block_devices.num_entries) {
|
|
LERROR << "Logical partition extent has invalid block device.";
|
|
return nullptr;
|
|
}
|
|
|
|
metadata->extents.push_back(extent);
|
|
}
|
|
|
|
cursor = buffer.get() + header.groups.offset;
|
|
for (size_t i = 0; i < header.groups.num_entries; i++) {
|
|
LpMetadataPartitionGroup group = {};
|
|
memcpy(&group, cursor, sizeof(group));
|
|
cursor += header.groups.entry_size;
|
|
|
|
metadata->groups.push_back(group);
|
|
}
|
|
|
|
cursor = buffer.get() + header.block_devices.offset;
|
|
for (size_t i = 0; i < header.block_devices.num_entries; i++) {
|
|
LpMetadataBlockDevice device = {};
|
|
memcpy(&device, cursor, sizeof(device));
|
|
cursor += header.block_devices.entry_size;
|
|
|
|
metadata->block_devices.push_back(device);
|
|
}
|
|
|
|
const LpMetadataBlockDevice* super_device = GetMetadataSuperBlockDevice(*metadata.get());
|
|
if (!super_device) {
|
|
LERROR << "Metadata does not specify a super device.";
|
|
return nullptr;
|
|
}
|
|
|
|
// Check that the metadata area and logical partition areas don't overlap.
|
|
uint64_t metadata_region =
|
|
GetTotalMetadataSize(geometry.metadata_max_size, geometry.metadata_slot_count);
|
|
if (metadata_region > super_device->first_logical_sector * LP_SECTOR_SIZE) {
|
|
LERROR << "Logical partition metadata overlaps with logical partition contents.";
|
|
return nullptr;
|
|
}
|
|
return metadata;
|
|
}
|
|
|
|
std::unique_ptr<LpMetadata> ParseMetadata(const LpMetadataGeometry& geometry, const void* buffer,
|
|
size_t size) {
|
|
MemoryReader reader(buffer, size);
|
|
return ParseMetadata(geometry, &reader);
|
|
}
|
|
|
|
std::unique_ptr<LpMetadata> ParseMetadata(const LpMetadataGeometry& geometry, int fd) {
|
|
FileReader reader(fd);
|
|
return ParseMetadata(geometry, &reader);
|
|
}
|
|
|
|
std::unique_ptr<LpMetadata> ReadPrimaryMetadata(int fd, const LpMetadataGeometry& geometry,
|
|
uint32_t slot_number) {
|
|
int64_t offset = GetPrimaryMetadataOffset(geometry, slot_number);
|
|
if (SeekFile64(fd, offset, SEEK_SET) < 0) {
|
|
PERROR << __PRETTY_FUNCTION__ << " lseek failed: offset " << offset;
|
|
return nullptr;
|
|
}
|
|
return ParseMetadata(geometry, fd);
|
|
}
|
|
|
|
std::unique_ptr<LpMetadata> ReadBackupMetadata(int fd, const LpMetadataGeometry& geometry,
|
|
uint32_t slot_number) {
|
|
int64_t offset = GetBackupMetadataOffset(geometry, slot_number);
|
|
if (SeekFile64(fd, offset, SEEK_SET) < 0) {
|
|
PERROR << __PRETTY_FUNCTION__ << " lseek failed: offset " << offset;
|
|
return nullptr;
|
|
}
|
|
return ParseMetadata(geometry, fd);
|
|
}
|
|
|
|
namespace {
|
|
|
|
bool AdjustMetadataForSlot(LpMetadata* metadata, uint32_t slot_number) {
|
|
std::string slot_suffix = SlotSuffixForSlotNumber(slot_number);
|
|
for (auto& partition : metadata->partitions) {
|
|
if (!(partition.attributes & LP_PARTITION_ATTR_SLOT_SUFFIXED)) {
|
|
continue;
|
|
}
|
|
std::string partition_name = GetPartitionName(partition) + slot_suffix;
|
|
if (partition_name.size() > sizeof(partition.name)) {
|
|
LERROR << __PRETTY_FUNCTION__ << " partition name too long: " << partition_name;
|
|
return false;
|
|
}
|
|
strncpy(partition.name, partition_name.c_str(), sizeof(partition.name));
|
|
partition.attributes &= ~LP_PARTITION_ATTR_SLOT_SUFFIXED;
|
|
}
|
|
for (auto& block_device : metadata->block_devices) {
|
|
if (!(block_device.flags & LP_BLOCK_DEVICE_SLOT_SUFFIXED)) {
|
|
continue;
|
|
}
|
|
std::string partition_name = GetBlockDevicePartitionName(block_device) + slot_suffix;
|
|
if (!UpdateBlockDevicePartitionName(&block_device, partition_name)) {
|
|
LERROR << __PRETTY_FUNCTION__ << " partition name too long: " << partition_name;
|
|
return false;
|
|
}
|
|
block_device.flags &= ~LP_BLOCK_DEVICE_SLOT_SUFFIXED;
|
|
}
|
|
for (auto& group : metadata->groups) {
|
|
if (!(group.flags & LP_GROUP_SLOT_SUFFIXED)) {
|
|
continue;
|
|
}
|
|
std::string group_name = GetPartitionGroupName(group) + slot_suffix;
|
|
if (!UpdatePartitionGroupName(&group, group_name)) {
|
|
LERROR << __PRETTY_FUNCTION__ << " group name too long: " << group_name;
|
|
return false;
|
|
}
|
|
group.flags &= ~LP_GROUP_SLOT_SUFFIXED;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::unique_ptr<LpMetadata> ReadMetadata(const IPartitionOpener& opener,
|
|
const std::string& super_partition, uint32_t slot_number) {
|
|
android::base::unique_fd fd = opener.Open(super_partition, O_RDONLY);
|
|
if (fd < 0) {
|
|
PERROR << __PRETTY_FUNCTION__ << " open failed: " << super_partition;
|
|
return nullptr;
|
|
}
|
|
|
|
LpMetadataGeometry geometry;
|
|
if (!ReadLogicalPartitionGeometry(fd, &geometry)) {
|
|
return nullptr;
|
|
}
|
|
if (slot_number >= geometry.metadata_slot_count) {
|
|
LERROR << __PRETTY_FUNCTION__ << " invalid metadata slot number";
|
|
return nullptr;
|
|
}
|
|
|
|
std::vector<int64_t> offsets = {
|
|
GetPrimaryMetadataOffset(geometry, slot_number),
|
|
GetBackupMetadataOffset(geometry, slot_number),
|
|
};
|
|
std::unique_ptr<LpMetadata> metadata;
|
|
|
|
for (const auto& offset : offsets) {
|
|
if (SeekFile64(fd, offset, SEEK_SET) < 0) {
|
|
PERROR << __PRETTY_FUNCTION__ << " lseek failed, offset " << offset;
|
|
continue;
|
|
}
|
|
if ((metadata = ParseMetadata(geometry, fd)) != nullptr) {
|
|
break;
|
|
}
|
|
}
|
|
if (!metadata || !AdjustMetadataForSlot(metadata.get(), slot_number)) {
|
|
return nullptr;
|
|
}
|
|
return metadata;
|
|
}
|
|
|
|
std::unique_ptr<LpMetadata> ReadMetadata(const std::string& super_partition, uint32_t slot_number) {
|
|
return ReadMetadata(PartitionOpener(), super_partition, slot_number);
|
|
}
|
|
|
|
static std::string NameFromFixedArray(const char* name, size_t buffer_size) {
|
|
// If the end of the buffer has a null character, it's safe to assume the
|
|
// buffer is null terminated. Otherwise, we cap the string to the input
|
|
// buffer size.
|
|
if (name[buffer_size - 1] == '\0') {
|
|
return std::string(name);
|
|
}
|
|
return std::string(name, buffer_size);
|
|
}
|
|
|
|
std::string GetPartitionName(const LpMetadataPartition& partition) {
|
|
return NameFromFixedArray(partition.name, sizeof(partition.name));
|
|
}
|
|
|
|
std::string GetPartitionGroupName(const LpMetadataPartitionGroup& group) {
|
|
return NameFromFixedArray(group.name, sizeof(group.name));
|
|
}
|
|
|
|
std::string GetBlockDevicePartitionName(const LpMetadataBlockDevice& block_device) {
|
|
return NameFromFixedArray(block_device.partition_name, sizeof(block_device.partition_name));
|
|
}
|
|
|
|
} // namespace fs_mgr
|
|
} // namespace android
|