blob: 328fe3783de98bd54636543a53c80b03a5f70f99 [file] [log] [blame]
/*
* 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