| /* |
| * 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 "liblp/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 { |
| |
| // Parse an LpMetadataGeometry from a buffer. The buffer must be at least |
| // LP_METADATA_GEOMETRY_SIZE bytes in size. |
| static 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; |
| } |
| // Recompute and check the CRC32. |
| { |
| LpMetadataGeometry temp = *geometry; |
| memset(&temp.checksum, 0, sizeof(temp.checksum)); |
| SHA256(&temp, sizeof(temp), 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; |
| } |
| |
| // Check that the metadata area and logical partition areas don't overlap. |
| int64_t end_of_metadata = |
| GetPrimaryMetadataOffset(*geometry, geometry->metadata_slot_count - 1) + |
| geometry->metadata_max_size; |
| if (uint64_t(end_of_metadata) > geometry->first_logical_sector * LP_SECTOR_SIZE) { |
| LERROR << "Logical partition metadata overlaps with logical partition contents."; |
| return false; |
| } |
| return true; |
| } |
| |
| // 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. |
| static bool ReadLogicalPartitionGeometry(int fd, LpMetadataGeometry* geometry) { |
| // Read the first 4096 bytes. |
| std::unique_ptr<uint8_t[]> buffer = std::make_unique<uint8_t[]>(LP_METADATA_GEOMETRY_SIZE); |
| if (SeekFile64(fd, 0, 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; |
| } |
| if (ParseGeometry(buffer.get(), geometry)) { |
| return true; |
| } |
| |
| // Try the backup copy in the last 4096 bytes. |
| if (SeekFile64(fd, -LP_METADATA_GEOMETRY_SIZE, SEEK_END) < 0) { |
| PERROR << __PRETTY_FUNCTION__ << "lseek failed, offset " << -LP_METADATA_GEOMETRY_SIZE; |
| 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); |
| } |
| |
| // Helper function to read geometry from a device without an open descriptor. |
| bool ReadLogicalPartitionGeometry(const char* block_device, LpMetadataGeometry* geometry) { |
| android::base::unique_fd fd(open(block_device, O_RDONLY)); |
| if (fd < 0) { |
| PERROR << __PRETTY_FUNCTION__ << "open failed: " << block_device; |
| return false; |
| } |
| return ReadLogicalPartitionGeometry(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)) { |
| 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; |
| } |
| return true; |
| } |
| |
| using ReadMetadataFn = std::function<bool(void* buffer, size_t num_bytes)>; |
| |
| // Parse and validate all metadata at the current position in the given file |
| // descriptor. |
| static std::unique_ptr<LpMetadata> ParseMetadata(int fd) { |
| // First read and validate the header. |
| std::unique_ptr<LpMetadata> metadata = std::make_unique<LpMetadata>(); |
| if (!android::base::ReadFully(fd, &metadata->header, sizeof(metadata->header))) { |
| PERROR << __PRETTY_FUNCTION__ << "read " << sizeof(metadata->header) << "bytes failed"; |
| return nullptr; |
| } |
| if (!ValidateMetadataHeader(metadata->header)) { |
| return nullptr; |
| } |
| |
| 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 (!android::base::ReadFully(fd, 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 > header.extents.num_entries) { |
| LERROR << "Logical partition has invalid extent list."; |
| 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; |
| |
| metadata->extents.push_back(extent); |
| } |
| |
| return metadata; |
| } |
| |
| std::unique_ptr<LpMetadata> ReadMetadata(const char* block_device, uint32_t slot_number) { |
| android::base::unique_fd fd(open(block_device, O_RDONLY)); |
| if (fd < 0) { |
| PERROR << __PRETTY_FUNCTION__ << "open failed: " << block_device; |
| return nullptr; |
| } |
| LpMetadataGeometry geometry; |
| if (!ReadLogicalPartitionGeometry(fd, &geometry)) { |
| return nullptr; |
| } |
| |
| // First try the primary copy. |
| int64_t offset = GetPrimaryMetadataOffset(geometry, slot_number); |
| if (SeekFile64(fd, offset, SEEK_SET) < 0) { |
| PERROR << __PRETTY_FUNCTION__ << "lseek failed: offset " << offset; |
| return nullptr; |
| } |
| if (std::unique_ptr<LpMetadata> metadata = ParseMetadata(fd)) { |
| return metadata; |
| } |
| |
| // Next try the backup copy. |
| offset = GetBackupMetadataOffset(geometry, slot_number); |
| if (SeekFile64(fd, offset, SEEK_END) < 0) { |
| PERROR << __PRETTY_FUNCTION__ << "lseek failed: offset " << offset; |
| return nullptr; |
| } |
| return ParseMetadata(fd); |
| } |
| |
| std::unique_ptr<LpMetadata> ReadFromImageFile(const char* file) { |
| android::base::unique_fd fd(open(file, O_RDONLY)); |
| if (fd < 0) { |
| PERROR << __PRETTY_FUNCTION__ << "open failed: " << file; |
| return nullptr; |
| } |
| |
| LpMetadataGeometry geometry; |
| if (!ReadLogicalPartitionGeometry(fd, &geometry)) { |
| return nullptr; |
| } |
| if (SeekFile64(fd, LP_METADATA_GEOMETRY_SIZE, SEEK_SET) < 0) { |
| PERROR << __PRETTY_FUNCTION__ << "lseek failed: offset " << LP_METADATA_GEOMETRY_SIZE; |
| return nullptr; |
| } |
| std::unique_ptr<LpMetadata> metadata = ParseMetadata(fd); |
| if (!metadata) { |
| return nullptr; |
| } |
| metadata->geometry = geometry; |
| return metadata; |
| } |
| |
| 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)); |
| } |
| |
| } // namespace fs_mgr |
| } // namespace android |