blob: eda68fd66239cbe06c240dd5d78f86327ce8b56d [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 <fcntl.h>
#include <linux/memfd.h>
#include <stdio.h>
#include <sys/syscall.h>
#include <android-base/file.h>
#include <android-base/unique_fd.h>
#include <gtest/gtest.h>
#include <liblp/builder.h>
#include "images.h"
#include "reader.h"
#include "utility.h"
#include "writer.h"
using namespace std;
using namespace android::fs_mgr;
using unique_fd = android::base::unique_fd;
// Our tests assume a 128KiB disk with two 512 byte metadata slots.
static const size_t kDiskSize = 131072;
static const size_t kMetadataSize = 512;
static const size_t kMetadataSlots = 2;
static const char* TEST_GUID_BASE = "A799D1D6-669F-41D8-A3F0-EBB7572D830";
static const char* TEST_GUID = "A799D1D6-669F-41D8-A3F0-EBB7572D8302";
// Helper function for creating an in-memory file descriptor. This lets us
// simulate read/writing logical partition metadata as if we had a block device
// for a physical partition.
static unique_fd CreateFakeDisk(off_t size) {
unique_fd fd(syscall(__NR_memfd_create, "fake_disk", MFD_ALLOW_SEALING));
if (fd < 0) {
perror("memfd_create");
return {};
}
if (ftruncate(fd, size) < 0) {
perror("ftruncate");
return {};
}
// Prevent anything from accidentally growing/shrinking the file, as it
// would not be allowed on an actual partition.
if (fcntl(fd, F_ADD_SEALS, F_SEAL_GROW | F_SEAL_SHRINK) < 0) {
perror("fcntl");
return {};
}
// Write garbage to the "disk" so we can tell what has been zeroed or not.
unique_ptr<uint8_t[]> buffer = make_unique<uint8_t[]>(size);
memset(buffer.get(), 0xcc, size);
if (!android::base::WriteFully(fd, buffer.get(), size)) {
return {};
}
return fd;
}
// Create a disk of the default size.
static unique_fd CreateFakeDisk() {
return CreateFakeDisk(kDiskSize);
}
// Create a MetadataBuilder around some default sizes.
static unique_ptr<MetadataBuilder> CreateDefaultBuilder() {
unique_ptr<MetadataBuilder> builder =
MetadataBuilder::New(kDiskSize, kMetadataSize, kMetadataSlots);
return builder;
}
static bool AddDefaultPartitions(MetadataBuilder* builder) {
Partition* system = builder->AddPartition("system", TEST_GUID, LP_PARTITION_ATTR_NONE);
if (!system) {
return false;
}
return builder->ResizePartition(system, 24 * 1024);
}
// Create a temporary disk and flash it with the default partition setup.
static unique_fd CreateFlashedDisk() {
unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder();
if (!builder || !AddDefaultPartitions(builder.get())) {
return {};
}
unique_fd fd = CreateFakeDisk();
if (fd < 0) {
return {};
}
// Export and flash.
unique_ptr<LpMetadata> exported = builder->Export();
if (!exported) {
return {};
}
if (!FlashPartitionTable(fd, *exported.get())) {
return {};
}
return fd;
}
// Test that our CreateFakeDisk() function works.
TEST(liblp, CreateFakeDisk) {
unique_fd fd = CreateFakeDisk();
ASSERT_GE(fd, 0);
uint64_t size;
ASSERT_TRUE(GetDescriptorSize(fd, &size));
ASSERT_EQ(size, kDiskSize);
// Verify that we can't read unwritten metadata.
ASSERT_EQ(ReadMetadata(fd, 1), nullptr);
}
// Flashing metadata should not work if the metadata was created for a larger
// disk than the destination disk.
TEST(liblp, ExportDiskTooSmall) {
unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(kDiskSize + 1024, 512, 2);
ASSERT_NE(builder, nullptr);
unique_ptr<LpMetadata> exported = builder->Export();
ASSERT_NE(exported, nullptr);
// A larger geometry should fail to flash, since there won't be enough
// space to store the logical partition range that was specified.
unique_fd fd = CreateFakeDisk();
ASSERT_GE(fd, 0);
EXPECT_FALSE(FlashPartitionTable(fd, *exported.get()));
}
// Test the basics of flashing a partition and reading it back.
TEST(liblp, FlashAndReadback) {
unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder();
ASSERT_NE(builder, nullptr);
ASSERT_TRUE(AddDefaultPartitions(builder.get()));
unique_fd fd = CreateFakeDisk();
ASSERT_GE(fd, 0);
// Export and flash.
unique_ptr<LpMetadata> exported = builder->Export();
ASSERT_NE(exported, nullptr);
ASSERT_TRUE(FlashPartitionTable(fd, *exported.get()));
// Read back. Note that some fields are only filled in during
// serialization, so exported and imported will not be identical. For
// example, table sizes and checksums are computed in WritePartitionTable.
// Therefore we check on a field-by-field basis.
unique_ptr<LpMetadata> imported = ReadMetadata(fd, 0);
ASSERT_NE(imported, nullptr);
// Check geometry and header.
EXPECT_EQ(exported->geometry.metadata_max_size, imported->geometry.metadata_max_size);
EXPECT_EQ(exported->geometry.metadata_slot_count, imported->geometry.metadata_slot_count);
EXPECT_EQ(exported->geometry.first_logical_sector, imported->geometry.first_logical_sector);
EXPECT_EQ(exported->geometry.last_logical_sector, imported->geometry.last_logical_sector);
EXPECT_EQ(exported->header.major_version, imported->header.major_version);
EXPECT_EQ(exported->header.minor_version, imported->header.minor_version);
EXPECT_EQ(exported->header.header_size, imported->header.header_size);
// Check partition tables.
ASSERT_EQ(exported->partitions.size(), imported->partitions.size());
EXPECT_EQ(GetPartitionName(exported->partitions[0]), GetPartitionName(imported->partitions[0]));
EXPECT_EQ(GetPartitionGuid(exported->partitions[0]), GetPartitionGuid(imported->partitions[0]));
EXPECT_EQ(exported->partitions[0].attributes, imported->partitions[0].attributes);
EXPECT_EQ(exported->partitions[0].first_extent_index,
imported->partitions[0].first_extent_index);
EXPECT_EQ(exported->partitions[0].num_extents, imported->partitions[0].num_extents);
// Check extent tables.
ASSERT_EQ(exported->extents.size(), imported->extents.size());
EXPECT_EQ(exported->extents[0].num_sectors, imported->extents[0].num_sectors);
EXPECT_EQ(exported->extents[0].target_type, imported->extents[0].target_type);
EXPECT_EQ(exported->extents[0].target_data, imported->extents[0].target_data);
}
// Test that we can update metadata slots without disturbing others.
TEST(liblp, UpdateAnyMetadataSlot) {
unique_fd fd = CreateFlashedDisk();
ASSERT_GE(fd, 0);
unique_ptr<LpMetadata> imported = ReadMetadata(fd, 0);
ASSERT_NE(imported, nullptr);
ASSERT_EQ(imported->partitions.size(), 1);
EXPECT_EQ(GetPartitionName(imported->partitions[0]), "system");
// Change the name before writing to the next slot.
strncpy(imported->partitions[0].name, "vendor", sizeof(imported->partitions[0].name));
ASSERT_TRUE(UpdatePartitionTable(fd, *imported.get(), 1));
// Read back the original slot, make sure it hasn't changed.
imported = ReadMetadata(fd, 0);
ASSERT_NE(imported, nullptr);
ASSERT_EQ(imported->partitions.size(), 1);
EXPECT_EQ(GetPartitionName(imported->partitions[0]), "system");
// Now read back the new slot, and verify that it has a different name.
imported = ReadMetadata(fd, 1);
ASSERT_NE(imported, nullptr);
ASSERT_EQ(imported->partitions.size(), 1);
EXPECT_EQ(GetPartitionName(imported->partitions[0]), "vendor");
// Verify that we didn't overwrite anything in the logical paritition area.
// We expect the disk to be filled with 0xcc on creation so we can read
// this back and compare it.
char expected[LP_SECTOR_SIZE];
memset(expected, 0xcc, sizeof(expected));
for (uint64_t i = imported->geometry.first_logical_sector;
i <= imported->geometry.last_logical_sector; i++) {
char buffer[LP_SECTOR_SIZE];
ASSERT_GE(lseek(fd, i * LP_SECTOR_SIZE, SEEK_SET), 0);
ASSERT_TRUE(android::base::ReadFully(fd, buffer, sizeof(buffer)));
ASSERT_EQ(memcmp(expected, buffer, LP_SECTOR_SIZE), 0);
}
}
TEST(liblp, InvalidMetadataSlot) {
unique_fd fd = CreateFlashedDisk();
ASSERT_GE(fd, 0);
// Make sure all slots are filled.
unique_ptr<LpMetadata> metadata = ReadMetadata(fd, 0);
ASSERT_NE(metadata, nullptr);
for (uint32_t i = 1; i < kMetadataSlots; i++) {
ASSERT_TRUE(UpdatePartitionTable(fd, *metadata.get(), i));
}
// Verify that we can't read unavailable slots.
EXPECT_EQ(ReadMetadata(fd, kMetadataSlots), nullptr);
}
// Test that updating a metadata slot does not allow it to be computed based
// on mismatching geometry.
TEST(liblp, NoChangingGeometry) {
unique_fd fd = CreateFlashedDisk();
ASSERT_GE(fd, 0);
unique_ptr<LpMetadata> imported = ReadMetadata(fd, 0);
ASSERT_NE(imported, nullptr);
ASSERT_TRUE(UpdatePartitionTable(fd, *imported.get(), 1));
imported->geometry.metadata_max_size += LP_SECTOR_SIZE;
ASSERT_FALSE(UpdatePartitionTable(fd, *imported.get(), 1));
imported = ReadMetadata(fd, 0);
ASSERT_NE(imported, nullptr);
imported->geometry.metadata_slot_count++;
ASSERT_FALSE(UpdatePartitionTable(fd, *imported.get(), 1));
imported = ReadMetadata(fd, 0);
ASSERT_NE(imported, nullptr);
imported->geometry.first_logical_sector++;
ASSERT_FALSE(UpdatePartitionTable(fd, *imported.get(), 1));
imported = ReadMetadata(fd, 0);
ASSERT_NE(imported, nullptr);
imported->geometry.last_logical_sector--;
ASSERT_FALSE(UpdatePartitionTable(fd, *imported.get(), 1));
}
// Test that changing one bit of metadata is enough to break the checksum.
TEST(liblp, BitFlipGeometry) {
unique_fd fd = CreateFlashedDisk();
ASSERT_GE(fd, 0);
LpMetadataGeometry geometry;
ASSERT_GE(lseek(fd, 0, SEEK_SET), 0);
ASSERT_TRUE(android::base::ReadFully(fd, &geometry, sizeof(geometry)));
LpMetadataGeometry bad_geometry = geometry;
bad_geometry.metadata_slot_count++;
ASSERT_TRUE(android::base::WriteFully(fd, &bad_geometry, sizeof(bad_geometry)));
unique_ptr<LpMetadata> metadata = ReadMetadata(fd, 0);
ASSERT_NE(metadata, nullptr);
EXPECT_EQ(metadata->geometry.metadata_slot_count, 2);
}
TEST(liblp, ReadBackupGeometry) {
unique_fd fd = CreateFlashedDisk();
ASSERT_GE(fd, 0);
char corruption[LP_METADATA_GEOMETRY_SIZE];
memset(corruption, 0xff, sizeof(corruption));
// Corrupt the first 4096 bytes of the disk.
ASSERT_GE(lseek(fd, 0, SEEK_SET), 0);
ASSERT_TRUE(android::base::WriteFully(fd, corruption, sizeof(corruption)));
EXPECT_NE(ReadMetadata(fd, 0), nullptr);
// Corrupt the last 4096 bytes too.
ASSERT_GE(lseek(fd, -LP_METADATA_GEOMETRY_SIZE, SEEK_END), 0);
ASSERT_TRUE(android::base::WriteFully(fd, corruption, sizeof(corruption)));
EXPECT_EQ(ReadMetadata(fd, 0), nullptr);
}
TEST(liblp, ReadBackupMetadata) {
unique_fd fd = CreateFlashedDisk();
ASSERT_GE(fd, 0);
unique_ptr<LpMetadata> metadata = ReadMetadata(fd, 0);
char corruption[kMetadataSize];
memset(corruption, 0xff, sizeof(corruption));
ASSERT_GE(lseek(fd, LP_METADATA_GEOMETRY_SIZE, SEEK_SET), 0);
ASSERT_TRUE(android::base::WriteFully(fd, corruption, sizeof(corruption)));
EXPECT_NE(ReadMetadata(fd, 0), nullptr);
off_t offset = LP_METADATA_GEOMETRY_SIZE + kMetadataSize * 2;
// Corrupt the backup metadata.
ASSERT_GE(lseek(fd, -offset, SEEK_END), 0);
ASSERT_TRUE(android::base::WriteFully(fd, corruption, sizeof(corruption)));
EXPECT_EQ(ReadMetadata(fd, 0), nullptr);
}
// Test that we don't attempt to write metadata if it would overflow its
// reserved space.
TEST(liblp, TooManyPartitions) {
unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder();
ASSERT_NE(builder, nullptr);
// Compute the maximum number of partitions we can fit in 1024 bytes of metadata.
size_t max_partitions = (kMetadataSize - sizeof(LpMetadataHeader)) / sizeof(LpMetadataPartition);
EXPECT_LT(max_partitions, 10);
// Add this number of partitions.
Partition* partition = nullptr;
for (size_t i = 0; i < max_partitions; i++) {
std::string guid = std::string(TEST_GUID) + to_string(i);
partition = builder->AddPartition(to_string(i), TEST_GUID, LP_PARTITION_ATTR_NONE);
ASSERT_NE(partition, nullptr);
}
ASSERT_NE(partition, nullptr);
// Add one extent to any partition to fill up more space - we're at 508
// bytes after this, out of 512.
ASSERT_TRUE(builder->ResizePartition(partition, 1024));
unique_ptr<LpMetadata> exported = builder->Export();
ASSERT_NE(exported, nullptr);
unique_fd fd = CreateFakeDisk();
ASSERT_GE(fd, 0);
// Check that we are able to write our table.
ASSERT_TRUE(FlashPartitionTable(fd, *exported.get()));
// Check that adding one more partition overflows the metadata allotment.
partition = builder->AddPartition("final", TEST_GUID, LP_PARTITION_ATTR_NONE);
EXPECT_NE(partition, nullptr);
exported = builder->Export();
ASSERT_NE(exported, nullptr);
// The new table should be too large to be written.
ASSERT_FALSE(UpdatePartitionTable(fd, *exported.get(), 1));
// Check that the first and last logical sectors weren't touched when we
// wrote this almost-full metadata.
char expected[LP_SECTOR_SIZE];
memset(expected, 0xcc, sizeof(expected));
char buffer[LP_SECTOR_SIZE];
ASSERT_GE(lseek(fd, exported->geometry.first_logical_sector * LP_SECTOR_SIZE, SEEK_SET), 0);
ASSERT_TRUE(android::base::ReadFully(fd, buffer, sizeof(buffer)));
EXPECT_EQ(memcmp(expected, buffer, LP_SECTOR_SIZE), 0);
ASSERT_GE(lseek(fd, exported->geometry.last_logical_sector * LP_SECTOR_SIZE, SEEK_SET), 0);
ASSERT_TRUE(android::base::ReadFully(fd, buffer, sizeof(buffer)));
EXPECT_EQ(memcmp(expected, buffer, LP_SECTOR_SIZE), 0);
}
// Test that we can read and write image files.
TEST(liblp, ImageFiles) {
unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder();
ASSERT_NE(builder, nullptr);
ASSERT_TRUE(AddDefaultPartitions(builder.get()));
unique_ptr<LpMetadata> exported = builder->Export();
unique_fd fd(syscall(__NR_memfd_create, "image_file", 0));
ASSERT_GE(fd, 0);
ASSERT_TRUE(WriteToImageFile(fd, *exported.get()));
unique_ptr<LpMetadata> imported = ReadFromImageFile(fd);
ASSERT_NE(imported, nullptr);
}
// Test that we can read images from buffers.
TEST(liblp, ImageFilesInMemory) {
unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder();
ASSERT_NE(builder, nullptr);
ASSERT_TRUE(AddDefaultPartitions(builder.get()));
unique_ptr<LpMetadata> exported = builder->Export();
unique_fd fd(syscall(__NR_memfd_create, "image_file", 0));
ASSERT_GE(fd, 0);
ASSERT_TRUE(WriteToImageFile(fd, *exported.get()));
int64_t offset = SeekFile64(fd, 0, SEEK_CUR);
ASSERT_GE(offset, 0);
ASSERT_EQ(SeekFile64(fd, 0, SEEK_SET), 0);
size_t bytes = static_cast<size_t>(offset);
std::unique_ptr<char[]> buffer = std::make_unique<char[]>(bytes);
ASSERT_TRUE(android::base::ReadFully(fd, buffer.get(), bytes));
ASSERT_NE(ReadFromImageBlob(buffer.get(), bytes), nullptr);
}
class BadWriter {
public:
// When requested, write garbage instead of the requested bytes, then
// return false.
bool operator()(int fd, const std::string& blob) {
write_count_++;
if (write_count_ == fail_on_write_) {
std::unique_ptr<char[]> new_data = std::make_unique<char[]>(blob.size());
memset(new_data.get(), 0xe5, blob.size());
EXPECT_TRUE(android::base::WriteFully(fd, new_data.get(), blob.size()));
return false;
} else {
if (!android::base::WriteFully(fd, blob.data(), blob.size())) {
return false;
}
return fail_after_write_ != write_count_;
}
}
void Reset() {
fail_on_write_ = 0;
fail_after_write_ = 0;
write_count_ = 0;
}
void FailOnWrite(int number) {
Reset();
fail_on_write_ = number;
}
void FailAfterWrite(int number) {
Reset();
fail_after_write_ = number;
}
private:
int fail_on_write_ = 0;
int fail_after_write_ = 0;
int write_count_ = 0;
};
// Test that an interrupted flash operation on the "primary" copy of metadata
// is not fatal.
TEST(liblp, UpdatePrimaryMetadataFailure) {
unique_fd fd = CreateFlashedDisk();
ASSERT_GE(fd, 0);
BadWriter writer;
// Read and write it back.
writer.FailOnWrite(1);
unique_ptr<LpMetadata> imported = ReadMetadata(fd, 0);
ASSERT_NE(imported, nullptr);
ASSERT_FALSE(UpdatePartitionTable(fd, *imported.get(), 0, writer));
// We should still be able to read the backup copy.
imported = ReadMetadata(fd, 0);
ASSERT_NE(imported, nullptr);
// Flash again, this time fail the backup copy. We should still be able
// to read the primary.
writer.FailOnWrite(3);
ASSERT_FALSE(UpdatePartitionTable(fd, *imported.get(), 0, writer));
imported = ReadMetadata(fd, 0);
ASSERT_NE(imported, nullptr);
}
// Test that an interrupted flash operation on the "backup" copy of metadata
// is not fatal.
TEST(liblp, UpdateBackupMetadataFailure) {
unique_fd fd = CreateFlashedDisk();
ASSERT_GE(fd, 0);
BadWriter writer;
// Read and write it back.
writer.FailOnWrite(2);
unique_ptr<LpMetadata> imported = ReadMetadata(fd, 0);
ASSERT_NE(imported, nullptr);
ASSERT_FALSE(UpdatePartitionTable(fd, *imported.get(), 0, writer));
// We should still be able to read the primary copy.
imported = ReadMetadata(fd, 0);
ASSERT_NE(imported, nullptr);
// Flash again, this time fail the primary copy. We should still be able
// to read the primary.
writer.FailOnWrite(2);
ASSERT_FALSE(UpdatePartitionTable(fd, *imported.get(), 0, writer));
imported = ReadMetadata(fd, 0);
ASSERT_NE(imported, nullptr);
}
// Test that an interrupted write *in between* writing metadata will read
// the correct metadata copy. The primary is always considered newer than
// the backup.
TEST(liblp, UpdateMetadataCleanFailure) {
unique_fd fd = CreateFlashedDisk();
ASSERT_GE(fd, 0);
BadWriter writer;
// Change the name of the existing partition.
unique_ptr<LpMetadata> new_table = ReadMetadata(fd, 0);
ASSERT_NE(new_table, nullptr);
ASSERT_GE(new_table->partitions.size(), 1);
new_table->partitions[0].name[0]++;
// Flash it, but fail to write the backup copy.
writer.FailAfterWrite(2);
ASSERT_FALSE(UpdatePartitionTable(fd, *new_table.get(), 0, writer));
// When we read back, we should get the updated primary copy.
unique_ptr<LpMetadata> imported = ReadMetadata(fd, 0);
ASSERT_NE(imported, nullptr);
ASSERT_GE(new_table->partitions.size(), 1);
ASSERT_EQ(GetPartitionName(new_table->partitions[0]), GetPartitionName(imported->partitions[0]));
// Flash again. After, the backup and primary copy should be coherent.
// Note that the sync step should have used the primary to sync, not
// the backup.
writer.Reset();
ASSERT_TRUE(UpdatePartitionTable(fd, *new_table.get(), 0, writer));
imported = ReadMetadata(fd, 0);
ASSERT_NE(imported, nullptr);
ASSERT_GE(new_table->partitions.size(), 1);
ASSERT_EQ(GetPartitionName(new_table->partitions[0]), GetPartitionName(imported->partitions[0]));
}