/* * 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 #include #include #include #include #include #include #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(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 buffer = std::make_unique(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 buffer = std::make_unique(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 validity 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(&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 ParseMetadata(const LpMetadataGeometry& geometry, Reader* reader) { // First read and validate the header. std::unique_ptr metadata = std::make_unique(); metadata->geometry = geometry; if (!ReadMetadataHeader(reader, metadata.get())) { return nullptr; } LpMetadataHeader& header = metadata->header; // 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 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 ParseMetadata(const LpMetadataGeometry& geometry, const void* buffer, size_t size) { MemoryReader reader(buffer, size); return ParseMetadata(geometry, &reader); } std::unique_ptr ParseMetadata(const LpMetadataGeometry& geometry, int fd) { FileReader reader(fd); return ParseMetadata(geometry, &reader); } std::unique_ptr 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 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 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 offsets = { GetPrimaryMetadataOffset(geometry, slot_number), GetBackupMetadataOffset(geometry, slot_number), }; std::unique_ptr 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 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