| /* | |
| * 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 <unistd.h> | |
| #include <functional> | |
| #include <android-base/file.h> | |
| #include <android-base/unique_fd.h> | |
| #include "utility.h" | |
| namespace android { | |
| namespace fs_mgr { | |
| // 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 ValidateMetadataHeader(const LpMetadataHeader& header) { | |
| // To compute the header's checksum, we have to temporarily set its checksum | |
| // field to 0. | |
| { | |
| LpMetadataHeader temp = header; | |
| memset(&temp.header_checksum, 0, sizeof(temp.header_checksum)); | |
| SHA256(&temp, sizeof(temp), 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; | |
| } | |
| } | |
| // Do basic validation of key metadata bits. | |
| if (header.magic != LP_METADATA_HEADER_MAGIC) { | |
| LERROR << "Logical partition metadata has invalid magic value."; | |
| return false; | |
| } | |
| // Check that the version is compatible. | |
| if (header.major_version != LP_METADATA_MAJOR_VERSION || | |
| header.minor_version > LP_METADATA_MINOR_VERSION) { | |
| LERROR << "Logical partition metadata has incompatible version."; | |
| 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>(); | |
| if (!reader->ReadFully(&metadata->header, sizeof(metadata->header))) { | |
| PERROR << __PRETTY_FUNCTION__ << " read " << sizeof(metadata->header) << "bytes failed"; | |
| return nullptr; | |
| } | |
| if (!ValidateMetadataHeader(metadata->header)) { | |
| return nullptr; | |
| } | |
| metadata->geometry = geometry; | |
| LpMetadataHeader& header = metadata->header; | |
| // Read the metadata payload. Allocation is fallible in case the metadata is | |
| // corrupt and has some huge value. | |
| 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; | |
| } | |
| // 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 & ~LP_PARTITION_ATTRIBUTE_MASK) { | |
| 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 |