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
diff --git a/fs_mgr/liblp/builder.cpp b/fs_mgr/liblp/builder.cpp
index 54350a5..4406696 100644
--- a/fs_mgr/liblp/builder.cpp
+++ b/fs_mgr/liblp/builder.cpp
@@ -253,7 +253,7 @@
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 @@
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 @@
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 a67ffa7..ca8df61 100644
--- a/fs_mgr/liblp/builder_test.cpp
+++ b/fs_mgr/liblp/builder_test.cpp
@@ -352,6 +352,7 @@
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 @@
std::vector<Interval>{Interval(0, 100, 150)})
.size());
}
+
+TEST_F(BuilderTest, ExpandedHeader) {
+ unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(1024 * 1024, 1024, 2);
+ ASSERT_NE(builder, nullptr);
+
+ builder->RequireExpandedMetadataHeader();
+
+ unique_ptr<LpMetadata> 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 1e9d636..7a334fb 100644
--- a/fs_mgr/liblp/include/liblp/builder.h
+++ b/fs_mgr/liblp/include/liblp/builder.h
@@ -325,6 +325,10 @@
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 6e928b4..26cbf07 100644
--- a/fs_mgr/liblp/include/liblp/metadata_format.h
+++ b/fs_mgr/liblp/include/liblp/metadata_format.h
@@ -40,11 +40,14 @@
/* 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 @@
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 @@
*/
#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 22f6746..e67fb33 100644
--- a/fs_mgr/liblp/io_test.cpp
+++ b/fs_mgr/liblp/io_test.cpp
@@ -372,7 +372,7 @@
// 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 @@
ASSERT_GE(metadata->partitions.size(), 1);
ASSERT_NE(metadata->partitions[0].attributes & LP_PARTITION_ATTR_UPDATED, 0);
}
+
+TEST_F(LiblpTest, ReadExpandedHeader) {
+ unique_ptr<MetadataBuilder> 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<LpMetadata> exported = builder->Export();
+ ASSERT_NE(exported, nullptr);
+ exported->header.flags = 0x5e5e5e5e;
+ ASSERT_TRUE(FlashPartitionTable(opener, "super", *exported.get()));
+
+ unique_ptr<LpMetadata> 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 aecf685..30c17e4 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 @@
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<uint8_t*>(&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 @@
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;
+ 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<uint8_t[]> 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 bb24069..8bf1ee9 100644
--- a/fs_mgr/liblp/writer.cpp
+++ b/fs_mgr/liblp/writer.cpp
@@ -74,10 +74,10 @@
// 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<const char*>(&metadata.header), sizeof(metadata.header));
+ std::string(reinterpret_cast<const char*>(&header), header.header_size);
return header_blob + tables;
}