From 9f75098c60ca2ee593b7f0b630d6e31750456b8a Mon Sep 17 00:00:00 2001 From: David Anderson Date: Thu, 12 Dec 2019 15:59:04 -0800 Subject: [PATCH] liblp: Expand the metadata header for future use. A few times we have wanted to stash small bits of information in the super header, but we haven't had any bits to do so. This patch addresses future needs in two ways: 1. A "flags" field has been added for miscellanious bits that do not need a version bump. 2. The header struct has been padded to 256 bytes to allow for future expansion without complicating the struct-parsing code. This is the first time we've materially changed the format, so this patch needs some extra explanation. In all the places we rely on sizeof(LpMetadataHeader), we now need to use the |header_size| field instead. To make newer versions of liblp compatible with older headers, we read the minimum required header size and fill in the extra bytes as needed. To make the validation and reading logic more clear, it is now combined into a single function, ReadMetdataHeader. MetadataBuilder will still emit 1.0-compatible headers, to avoid changing the on-disk format of existing devices. The new header will only be emitted as-needed. Bug: 134949511 Test: liblp_test gtest retrofit DAP device boots launch DAP device boots Change-Id: I6221123768ff0057a73967ecb2ff9b006c17af88 --- fs_mgr/liblp/builder.cpp | 16 +++- fs_mgr/liblp/builder_test.cpp | 20 +++++ fs_mgr/liblp/include/liblp/builder.h | 4 + fs_mgr/liblp/include/liblp/metadata_format.h | 40 +++++++++- fs_mgr/liblp/io_test.cpp | 27 ++++++- fs_mgr/liblp/reader.cpp | 79 ++++++++++++++------ fs_mgr/liblp/writer.cpp | 4 +- 7 files changed, 163 insertions(+), 27 deletions(-) diff --git a/fs_mgr/liblp/builder.cpp b/fs_mgr/liblp/builder.cpp index 54350a52d..4406696ae 100644 --- a/fs_mgr/liblp/builder.cpp +++ b/fs_mgr/liblp/builder.cpp @@ -253,7 +253,7 @@ MetadataBuilder::MetadataBuilder() : auto_slot_suffixing_(false) { header_.magic = LP_METADATA_HEADER_MAGIC; header_.major_version = LP_METADATA_MAJOR_VERSION; header_.minor_version = LP_METADATA_MINOR_VERSION_MIN; - header_.header_size = sizeof(header_); + header_.header_size = sizeof(LpMetadataHeaderV1_0); header_.partitions.entry_size = sizeof(LpMetadataPartition); header_.extents.entry_size = sizeof(LpMetadataExtent); header_.groups.entry_size = sizeof(LpMetadataPartitionGroup); @@ -264,6 +264,12 @@ bool MetadataBuilder::Init(const LpMetadata& metadata) { geometry_ = metadata.geometry; block_devices_ = metadata.block_devices; + // Bump the version as necessary to copy any newer fields. + if (metadata.header.minor_version >= LP_METADATA_VERSION_FOR_EXPANDED_HEADER) { + RequireExpandedMetadataHeader(); + header_.flags = metadata.header.flags; + } + for (const auto& group : metadata.groups) { std::string group_name = GetPartitionGroupName(group); if (!AddGroup(group_name, group.maximum_size)) { @@ -883,6 +889,14 @@ std::unique_ptr MetadataBuilder::Export() { return metadata; } +void MetadataBuilder::RequireExpandedMetadataHeader() { + if (header_.minor_version >= LP_METADATA_VERSION_FOR_EXPANDED_HEADER) { + return; + } + header_.minor_version = LP_METADATA_VERSION_FOR_EXPANDED_HEADER; + header_.header_size = sizeof(LpMetadataHeaderV1_2); +} + uint64_t MetadataBuilder::AllocatableSpace() const { uint64_t total_size = 0; for (const auto& block_device : block_devices_) { diff --git a/fs_mgr/liblp/builder_test.cpp b/fs_mgr/liblp/builder_test.cpp index a67ffa782..ca8df61fd 100644 --- a/fs_mgr/liblp/builder_test.cpp +++ b/fs_mgr/liblp/builder_test.cpp @@ -352,6 +352,7 @@ TEST_F(BuilderTest, BuilderExport) { EXPECT_EQ(header.magic, LP_METADATA_HEADER_MAGIC); EXPECT_EQ(header.major_version, LP_METADATA_MAJOR_VERSION); EXPECT_EQ(header.minor_version, LP_METADATA_MINOR_VERSION_MIN); + EXPECT_EQ(header.header_size, sizeof(LpMetadataHeaderV1_0)); ASSERT_EQ(exported->partitions.size(), 2); ASSERT_EQ(exported->extents.size(), 3); @@ -917,3 +918,22 @@ TEST_F(BuilderTest, Interval) { std::vector{Interval(0, 100, 150)}) .size()); } + +TEST_F(BuilderTest, ExpandedHeader) { + unique_ptr builder = MetadataBuilder::New(1024 * 1024, 1024, 2); + ASSERT_NE(builder, nullptr); + + builder->RequireExpandedMetadataHeader(); + + unique_ptr exported = builder->Export(); + ASSERT_NE(exported, nullptr); + EXPECT_EQ(exported->header.header_size, sizeof(LpMetadataHeaderV1_2)); + + exported->header.flags = 0x5e5e5e5e; + + builder = MetadataBuilder::New(*exported.get()); + exported = builder->Export(); + ASSERT_NE(exported, nullptr); + EXPECT_EQ(exported->header.header_size, sizeof(LpMetadataHeaderV1_2)); + EXPECT_EQ(exported->header.flags, 0x5e5e5e5e); +} diff --git a/fs_mgr/liblp/include/liblp/builder.h b/fs_mgr/liblp/include/liblp/builder.h index 1e9d636ff..7a334fbd9 100644 --- a/fs_mgr/liblp/include/liblp/builder.h +++ b/fs_mgr/liblp/include/liblp/builder.h @@ -325,6 +325,10 @@ class MetadataBuilder { bool GetBlockDeviceInfo(const std::string& partition_name, BlockDeviceInfo* info) const; bool UpdateBlockDeviceInfo(const std::string& partition_name, const BlockDeviceInfo& info); + // Require the expanded metadata header. This is exposed for testing, and + // is normally only called as needed by other methods. + void RequireExpandedMetadataHeader(); + // Attempt to preserve the named partitions from an older metadata. If this // is not possible (for example, the block device list has changed) then // false is returned. diff --git a/fs_mgr/liblp/include/liblp/metadata_format.h b/fs_mgr/liblp/include/liblp/metadata_format.h index 6e928b486..26cbf07ba 100644 --- a/fs_mgr/liblp/include/liblp/metadata_format.h +++ b/fs_mgr/liblp/include/liblp/metadata_format.h @@ -40,11 +40,14 @@ extern "C" { /* Current metadata version. */ #define LP_METADATA_MAJOR_VERSION 10 #define LP_METADATA_MINOR_VERSION_MIN 0 -#define LP_METADATA_MINOR_VERSION_MAX 1 +#define LP_METADATA_MINOR_VERSION_MAX 2 /* Metadata version needed to use the UPDATED partition attribute. */ #define LP_METADATA_VERSION_FOR_UPDATED_ATTR 1 +/* Metadata version needed for the new expanded header struct. */ +#define LP_METADATA_VERSION_FOR_EXPANDED_HEADER 2 + /* Attributes for the LpMetadataPartition::attributes field. * * READONLY - The partition should not be considered writable. When used with @@ -212,6 +215,22 @@ typedef struct LpMetadataHeader { LpMetadataTableDescriptor groups; /* 116: Block device table. */ LpMetadataTableDescriptor block_devices; + + /* Everything past here is header version 1.2+, and is only included if + * needed. When liblp supporting >= 1.2 reads a < 1.2 header, it must + * zero these additional fields. + */ + + /* 128: See LP_HEADER_FLAG_ constants for possible values. Header flags are + * independent of the version number and intended to be informational only. + * New flags can be added without bumping the version. + * + * (Note there are no flags currently defined.) + */ + uint32_t flags; + + /* 132: Reserved (zero), pad to 256 bytes. */ + uint8_t reserved[124]; } __attribute__((packed)) LpMetadataHeader; /* This struct defines a logical partition entry, similar to what would be @@ -351,6 +370,25 @@ typedef struct LpMetadataBlockDevice { */ #define LP_BLOCK_DEVICE_SLOT_SUFFIXED (1 << 0) +/* For ease of writing compatibility checks, the original metadata header is + * preserved below, and typedefs are provided for the current version. + */ +typedef struct LpMetadataHeaderV1_0 { + uint32_t magic; + uint16_t major_version; + uint16_t minor_version; + uint32_t header_size; + uint8_t header_checksum[32]; + uint32_t tables_size; + uint8_t tables_checksum[32]; + LpMetadataTableDescriptor partitions; + LpMetadataTableDescriptor extents; + LpMetadataTableDescriptor groups; + LpMetadataTableDescriptor block_devices; +} __attribute__((packed)) LpMetadataHeaderV1_0; + +typedef LpMetadataHeader LpMetadataHeaderV1_2; + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/fs_mgr/liblp/io_test.cpp b/fs_mgr/liblp/io_test.cpp index 22f674678..e67fb33b6 100644 --- a/fs_mgr/liblp/io_test.cpp +++ b/fs_mgr/liblp/io_test.cpp @@ -372,7 +372,7 @@ TEST_F(LiblpTest, TooManyPartitions) { // Compute the maximum number of partitions we can fit in 512 bytes of // metadata. By default there is the header, one partition group, and a // block device entry. - static const size_t kMaxPartitionTableSize = kMetadataSize - sizeof(LpMetadataHeader) - + static const size_t kMaxPartitionTableSize = kMetadataSize - sizeof(LpMetadataHeaderV1_0) - sizeof(LpMetadataPartitionGroup) - sizeof(LpMetadataBlockDevice); size_t max_partitions = kMaxPartitionTableSize / sizeof(LpMetadataPartition); @@ -742,3 +742,28 @@ TEST_F(LiblpTest, UpdateVirtualAB) { ASSERT_GE(metadata->partitions.size(), 1); ASSERT_NE(metadata->partitions[0].attributes & LP_PARTITION_ATTR_UPDATED, 0); } + +TEST_F(LiblpTest, ReadExpandedHeader) { + unique_ptr builder = CreateDefaultBuilder(); + ASSERT_NE(builder, nullptr); + ASSERT_TRUE(AddDefaultPartitions(builder.get())); + + builder->RequireExpandedMetadataHeader(); + + unique_fd fd = CreateFakeDisk(); + ASSERT_GE(fd, 0); + + DefaultPartitionOpener opener(fd); + + // Export and flash. + unique_ptr exported = builder->Export(); + ASSERT_NE(exported, nullptr); + exported->header.flags = 0x5e5e5e5e; + ASSERT_TRUE(FlashPartitionTable(opener, "super", *exported.get())); + + unique_ptr imported = ReadMetadata(opener, "super", 0); + ASSERT_NE(imported, nullptr); + EXPECT_EQ(imported->header.header_size, sizeof(LpMetadataHeaderV1_2)); + EXPECT_EQ(imported->header.header_size, exported->header.header_size); + EXPECT_EQ(imported->header.flags, exported->header.flags); +} diff --git a/fs_mgr/liblp/reader.cpp b/fs_mgr/liblp/reader.cpp index aecf6852e..30c17e42a 100644 --- a/fs_mgr/liblp/reader.cpp +++ b/fs_mgr/liblp/reader.cpp @@ -31,6 +31,9 @@ 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: @@ -161,30 +164,59 @@ static bool ValidateTableBounds(const LpMetadataHeader& header, 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; - } +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 validation of key metadata bits. + // 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; } - // Check that the version is compatible. 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) || @@ -215,19 +247,22 @@ static std::unique_ptr ParseMetadata(const LpMetadataGeometry& geome Reader* reader) { // First read and validate the header. std::unique_ptr metadata = std::make_unique(); - 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; + if (!ReadMetadataHeader(reader, metadata.get())) { + return nullptr; + } LpMetadataHeader& header = metadata->header; - // Read the metadata payload. Allocation is fallible in case the metadata is - // corrupt and has some huge value. + // 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 buffer(new (std::nothrow) uint8_t[header.tables_size]); if (!buffer) { LERROR << "Out of memory reading logical partition tables."; diff --git a/fs_mgr/liblp/writer.cpp b/fs_mgr/liblp/writer.cpp index bb24069aa..8bf1ee923 100644 --- a/fs_mgr/liblp/writer.cpp +++ b/fs_mgr/liblp/writer.cpp @@ -74,10 +74,10 @@ std::string SerializeMetadata(const LpMetadata& input) { // Compute header checksum. memset(header.header_checksum, 0, sizeof(header.header_checksum)); - SHA256(&header, sizeof(header), header.header_checksum); + SHA256(&header, header.header_size, header.header_checksum); std::string header_blob = - std::string(reinterpret_cast(&metadata.header), sizeof(metadata.header)); + std::string(reinterpret_cast(&header), header.header_size); return header_blob + tables; }