From 77585e50049839856baed37d120d4f705a2cf1d0 Mon Sep 17 00:00:00 2001 From: Richard Lyu Date: Tue, 7 Jul 2026 20:42:40 +0800 Subject: [PATCH] MdeModulePkg/GptLib: Add host-based unit tests for valid GPT behavior Add the positive-path host-based tests for the shared GptLib parser (extracted as part of the parser security hardening), ensuring the tightened checks in PartitionValidGptTable(), PartitionCheckGptEntry() and PartitionRestoreGptTable() do not falsely reject well-formed GPTs. The tests run against an in-memory mock disk and cover accepted primary/backup headers, boundary but legal header/entry sizes, correct entry-status flagging on valid entries, and primary/backup restore round-trips. Signed-off-by: Richard Lyu --- .../Library/GptLib/UnitTest/GptLibUnitTest.c | 477 ++++++++++++++++++ .../GptLib/UnitTest/GptLibUnitTestCommon.c | 298 +++++++++++ .../GptLib/UnitTest/GptLibUnitTestCommon.h | 110 ++++ .../GptLib/UnitTest/GptLibUnitTestHost.inf | 40 ++ MdeModulePkg/MdeModulePkg.ci.yaml | 1 + MdeModulePkg/Test/MdeModulePkgHostTest.dsc | 5 + 6 files changed, 931 insertions(+) create mode 100644 MdeModulePkg/Library/GptLib/UnitTest/GptLibUnitTest.c create mode 100644 MdeModulePkg/Library/GptLib/UnitTest/GptLibUnitTestCommon.c create mode 100644 MdeModulePkg/Library/GptLib/UnitTest/GptLibUnitTestCommon.h create mode 100644 MdeModulePkg/Library/GptLib/UnitTest/GptLibUnitTestHost.inf diff --git a/MdeModulePkg/Library/GptLib/UnitTest/GptLibUnitTest.c b/MdeModulePkg/Library/GptLib/UnitTest/GptLibUnitTest.c new file mode 100644 index 0000000000..fbc6e3ba46 --- /dev/null +++ b/MdeModulePkg/Library/GptLib/UnitTest/GptLibUnitTest.c @@ -0,0 +1,477 @@ +/** @file + Host-based unit tests for GptLib. + + These tests exercise PartitionValidGptTable(), PartitionCheckGptEntry() + and PartitionRestoreGptTable() against an in-memory mock disk, covering + their behavior on well-formed GPT structures. + + Copyright (c) 2026, SUSE LLC. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include +#include +#include +#include +#include +#include +#include "GptLibUnitTestCommon.h" + +#define UNIT_TEST_NAME "GptLibUnitTest" +#define UNIT_TEST_VERSION "1.0" + +// --------------------------------------------------------------------------- +// PartitionValidGptTable() tests +// --------------------------------------------------------------------------- + +/** + Verify that a well-formed primary GPT header is accepted. + + @param[in] Context Unit test context. + + @retval UNIT_TEST_PASSED The test passed. +**/ +STATIC +UNIT_TEST_STATUS +EFIAPI +TestValidPrimaryHeaderIsAccepted ( + IN UNIT_TEST_CONTEXT Context + ) +{ + EFI_PARTITION_TABLE_HEADER Header; + + SetupDiskWithValidPrimary (); + + UT_ASSERT_TRUE (ValidateAt (PRIMARY_PART_HEADER_LBA, &Header)); + UT_ASSERT_EQUAL (Header.MyLBA, PRIMARY_PART_HEADER_LBA); + UT_ASSERT_EQUAL (Header.AlternateLBA, LAST_LBA); + UT_ASSERT_EQUAL (Header.NumberOfPartitionEntries, NUM_PARTITION_ENTRIES); + UT_ASSERT_EQUAL (Header.SizeOfPartitionEntry, PARTITION_ENTRY_SIZE); + + return UNIT_TEST_PASSED; +} + +/** + Verify that a well-formed backup GPT header is accepted. + + @param[in] Context Unit test context. + + @retval UNIT_TEST_PASSED The test passed. +**/ +STATIC +UNIT_TEST_STATUS +EFIAPI +TestValidBackupHeaderIsAccepted ( + IN UNIT_TEST_CONTEXT Context + ) +{ + EFI_PARTITION_TABLE_HEADER Header; + + SetupDiskWithValidPrimary (); + + // + // Backup header at the last LBA with its entry array after the usable + // region, as laid out by real partitioning tools. The validator must not + // reject an entry array located past FirstUsableLBA. + // + WriteGptTableAt ( + LAST_LBA, + PRIMARY_PART_HEADER_LBA, + LAST_USABLE_LBA + 1, + NUM_PARTITION_ENTRIES, + PARTITION_ENTRY_SIZE, + 2 + ); + + UT_ASSERT_TRUE (ValidateAt (LAST_LBA, &Header)); + UT_ASSERT_EQUAL (Header.MyLBA, LAST_LBA); + UT_ASSERT_EQUAL (Header.AlternateLBA, PRIMARY_PART_HEADER_LBA); + + return UNIT_TEST_PASSED; +} + +/** + Verify that the minimum (92-byte) header size is accepted. + + @param[in] Context Unit test context. + + @retval UNIT_TEST_PASSED The test passed. +**/ +STATIC +UNIT_TEST_STATUS +EFIAPI +TestMinimumHeaderSizeIsAccepted ( + IN UNIT_TEST_CONTEXT Context + ) +{ + EFI_PARTITION_TABLE_HEADER Header; + + SetupDiskWithValidPrimary (); + + GetPrimaryHeader ()->Header.HeaderSize = TEST_GPT_HEADER_MIN_SIZE; + UpdateGptHeaderCrc (GetPrimaryHeader ()); + + UT_ASSERT_TRUE (ValidateAt (PRIMARY_PART_HEADER_LBA, &Header)); + + return UNIT_TEST_PASSED; +} + +/** + Verify that a larger power-of-two (256-byte) partition entry size is accepted. + + @param[in] Context Unit test context. + + @retval UNIT_TEST_PASSED The test passed. +**/ +STATIC +UNIT_TEST_STATUS +EFIAPI +TestLargerPowerOfTwoEntrySizeIsAccepted ( + IN UNIT_TEST_CONTEXT Context + ) +{ + EFI_PARTITION_TABLE_HEADER Header; + + SetupDiskWithValidPrimary (); + + // + // SizeOfPartitionEntry of 256 (128 * 2^1) is legal per the UEFI spec. + // + WriteGptTableAt (PRIMARY_PART_HEADER_LBA, LAST_LBA, PRIMARY_PART_HEADER_LBA + 1, 8, 256, 2); + + UT_ASSERT_TRUE (ValidateAt (PRIMARY_PART_HEADER_LBA, &Header)); + UT_ASSERT_EQUAL (Header.SizeOfPartitionEntry, 256U); + + return UNIT_TEST_PASSED; +} + +// --------------------------------------------------------------------------- +// PartitionCheckGptEntry() tests +// --------------------------------------------------------------------------- + +/** + Verify that valid partition entries report no flags. + + @param[in] Context Unit test context. + + @retval UNIT_TEST_PASSED The test passed. +**/ +STATIC +UNIT_TEST_STATUS +EFIAPI +TestValidEntriesReportNoFlags ( + IN UNIT_TEST_CONTEXT Context + ) +{ + EFI_PARTITION_TABLE_HEADER Header; + EFI_PARTITION_ENTRY Entries[4]; + EFI_PARTITION_ENTRY_STATUS Status[4]; + UINTN Index; + + InitCheckEntryHeader (&Header, 4, sizeof (EFI_PARTITION_ENTRY)); + ZeroMem (Entries, sizeof (Entries)); + ZeroMem (Status, sizeof (Status)); + + FillPartitionEntry (&Entries[0], &mPartitionGuid1, 34, 100); + FillPartitionEntry (&Entries[1], &mPartitionGuid2, 101, 200); + + PartitionCheckGptEntry (&Header, Entries, Status); + + for (Index = 0; Index < 4; Index++) { + UT_ASSERT_FALSE (Status[Index].OutOfRange); + UT_ASSERT_FALSE (Status[Index].Overlap); + UT_ASSERT_FALSE (Status[Index].OsSpecific); + } + + return UNIT_TEST_PASSED; +} + +/** + Verify that an OS-specific partition attribute is reported. + + @param[in] Context Unit test context. + + @retval UNIT_TEST_PASSED The test passed. +**/ +STATIC +UNIT_TEST_STATUS +EFIAPI +TestOsSpecificAttributeIsReported ( + IN UNIT_TEST_CONTEXT Context + ) +{ + EFI_PARTITION_TABLE_HEADER Header; + EFI_PARTITION_ENTRY Entries[1]; + EFI_PARTITION_ENTRY_STATUS Status[1]; + + InitCheckEntryHeader (&Header, 1, sizeof (EFI_PARTITION_ENTRY)); + ZeroMem (Entries, sizeof (Entries)); + ZeroMem (Status, sizeof (Status)); + + FillPartitionEntry (&Entries[0], &mPartitionGuid1, 40, 100); + Entries[0].Attributes = BIT1; + + PartitionCheckGptEntry (&Header, Entries, Status); + + UT_ASSERT_TRUE (Status[0].OsSpecific); + UT_ASSERT_FALSE (Status[0].OutOfRange); + + return UNIT_TEST_PASSED; +} + +/** + Verify that unused partition entries are ignored. + + @param[in] Context Unit test context. + + @retval UNIT_TEST_PASSED The test passed. +**/ +STATIC +UNIT_TEST_STATUS +EFIAPI +TestUnusedEntriesAreIgnored ( + IN UNIT_TEST_CONTEXT Context + ) +{ + EFI_PARTITION_TABLE_HEADER Header; + EFI_PARTITION_ENTRY Entries[2]; + EFI_PARTITION_ENTRY_STATUS Status[2]; + + InitCheckEntryHeader (&Header, 2, sizeof (EFI_PARTITION_ENTRY)); + ZeroMem (Entries, sizeof (Entries)); + ZeroMem (Status, sizeof (Status)); + + // + // Unused (zero PartitionTypeGUID) entry with nonsense LBAs must be skipped + // and must not participate in the overlap scan. + // + Entries[0].StartingLBA = MAX_UINT64; + Entries[0].EndingLBA = 0; + FillPartitionEntry (&Entries[1], &mPartitionGuid1, 40, 100); + + PartitionCheckGptEntry (&Header, Entries, Status); + + UT_ASSERT_FALSE (Status[0].OutOfRange); + UT_ASSERT_FALSE (Status[0].Overlap); + UT_ASSERT_FALSE (Status[1].OutOfRange); + UT_ASSERT_FALSE (Status[1].Overlap); + + return UNIT_TEST_PASSED; +} + +/** + Verify that the entry stride follows SizeOfPartitionEntry. + + @param[in] Context Unit test context. + + @retval UNIT_TEST_PASSED The test passed. +**/ +STATIC +UNIT_TEST_STATUS +EFIAPI +TestEntryStrideFollowsSizeOfPartitionEntry ( + IN UNIT_TEST_CONTEXT Context + ) +{ + EFI_PARTITION_TABLE_HEADER Header; + UINT8 Buffer[2 * 256]; + EFI_PARTITION_ENTRY_STATUS Status[2]; + EFI_PARTITION_ENTRY *DecoyEntry; + + InitCheckEntryHeader (&Header, 2, 256); + ZeroMem (Buffer, sizeof (Buffer)); + ZeroMem (Status, sizeof (Status)); + + // + // Real entries at 256-byte stride: entry 0 at offset 0, entry 1 at offset + // 256, deliberately overlapping. A decoy non-overlapping but out-of-range + // entry is placed at offset 128: a checker that wrongly walked with a + // 128-byte stride would flag OutOfRange instead of Overlap on index 1. + // + FillPartitionEntry ((EFI_PARTITION_ENTRY *)(VOID *)&Buffer[0], &mPartitionGuid1, 40, 100); + FillPartitionEntry ((EFI_PARTITION_ENTRY *)(VOID *)&Buffer[256], &mPartitionGuid2, 90, 150); + DecoyEntry = (EFI_PARTITION_ENTRY *)(VOID *)&Buffer[128]; + FillPartitionEntry (DecoyEntry, &mPartitionGuid1, 1000, 2000); + + PartitionCheckGptEntry (&Header, (EFI_PARTITION_ENTRY *)(VOID *)Buffer, Status); + + UT_ASSERT_TRUE (Status[0].Overlap); + UT_ASSERT_TRUE (Status[1].Overlap); + UT_ASSERT_FALSE (Status[1].OutOfRange); + + return UNIT_TEST_PASSED; +} + +// --------------------------------------------------------------------------- +// PartitionRestoreGptTable() tests +// --------------------------------------------------------------------------- + +/** + Verify that a corrupted primary GPT is restored from the backup. + + @param[in] Context Unit test context. + + @retval UNIT_TEST_PASSED The test passed. +**/ +STATIC +UNIT_TEST_STATUS +EFIAPI +TestRestorePrimaryFromBackup ( + IN UNIT_TEST_CONTEXT Context + ) +{ + EFI_PARTITION_TABLE_HEADER BackupHeader; + EFI_PARTITION_TABLE_HEADER RestoredHeader; + + SetupDiskWithValidPrimary (); + + // + // Wipe the primary and keep only a valid backup whose entry array follows + // the usable region. + // + WriteGptTableAt ( + LAST_LBA, + PRIMARY_PART_HEADER_LBA, + LAST_USABLE_LBA + 1, + NUM_PARTITION_ENTRIES, + PARTITION_ENTRY_SIZE, + 2 + ); + ZeroMem (GetDiskLba (PRIMARY_PART_HEADER_LBA), SECTOR_SIZE); + ZeroMem (GetDiskLba (PRIMARY_PART_HEADER_LBA + 1), PART_ARRAY_SIZE); + + UT_ASSERT_FALSE (ValidateAt (PRIMARY_PART_HEADER_LBA, &RestoredHeader)); + UT_ASSERT_TRUE (ValidateAt (LAST_LBA, &BackupHeader)); + + UT_ASSERT_TRUE (PartitionRestoreGptTable (&mBlockIo, &mDiskIo, &BackupHeader)); + + UT_ASSERT_TRUE (ValidateAt (PRIMARY_PART_HEADER_LBA, &RestoredHeader)); + UT_ASSERT_EQUAL (RestoredHeader.MyLBA, PRIMARY_PART_HEADER_LBA); + UT_ASSERT_EQUAL (RestoredHeader.AlternateLBA, LAST_LBA); + UT_ASSERT_EQUAL (RestoredHeader.PartitionEntryLBA, PRIMARY_PART_HEADER_LBA + 1); + + return UNIT_TEST_PASSED; +} + +/** + Verify that a corrupted backup GPT is restored from the primary. + + @param[in] Context Unit test context. + + @retval UNIT_TEST_PASSED The test passed. +**/ +STATIC +UNIT_TEST_STATUS +EFIAPI +TestRestoreBackupFromPrimary ( + IN UNIT_TEST_CONTEXT Context + ) +{ + EFI_PARTITION_TABLE_HEADER PrimaryHeader; + EFI_PARTITION_TABLE_HEADER RestoredHeader; + + SetupDiskWithValidPrimary (); + + UT_ASSERT_TRUE (ValidateAt (PRIMARY_PART_HEADER_LBA, &PrimaryHeader)); + UT_ASSERT_FALSE (ValidateAt (LAST_LBA, &RestoredHeader)); + + UT_ASSERT_TRUE (PartitionRestoreGptTable (&mBlockIo, &mDiskIo, &PrimaryHeader)); + + UT_ASSERT_TRUE (ValidateAt (LAST_LBA, &RestoredHeader)); + UT_ASSERT_EQUAL (RestoredHeader.MyLBA, LAST_LBA); + UT_ASSERT_EQUAL (RestoredHeader.AlternateLBA, PRIMARY_PART_HEADER_LBA); + UT_ASSERT_EQUAL (RestoredHeader.PartitionEntryLBA, LAST_USABLE_LBA + 1); + + return UNIT_TEST_PASSED; +} + +// --------------------------------------------------------------------------- +// Test runner +// --------------------------------------------------------------------------- + +/** + Configure and run the GptLib unit test suites. + + @retval EFI_SUCCESS The unit tests ran to completion. + @retval other An error occurred setting up or running the tests. +**/ +EFI_STATUS +EFIAPI +UefiTestMain ( + VOID + ) +{ + EFI_STATUS Status; + UNIT_TEST_FRAMEWORK_HANDLE Framework; + UNIT_TEST_SUITE_HANDLE ValidSuite; + UNIT_TEST_SUITE_HANDLE EntrySuite; + UNIT_TEST_SUITE_HANDLE RestoreSuite; + + Framework = NULL; + + Status = InitUnitTestFramework (&Framework, UNIT_TEST_NAME, gEfiCallerBaseName, UNIT_TEST_VERSION); + if (EFI_ERROR (Status)) { + return Status; + } + + Status = CreateUnitTestSuite (&ValidSuite, Framework, "PartitionValidGptTable tests", "GptLib.ValidGptTable", NULL, NULL); + if (EFI_ERROR (Status)) { + goto Exit; + } + + Status = CreateUnitTestSuite (&EntrySuite, Framework, "PartitionCheckGptEntry tests", "GptLib.CheckGptEntry", NULL, NULL); + if (EFI_ERROR (Status)) { + goto Exit; + } + + Status = CreateUnitTestSuite (&RestoreSuite, Framework, "PartitionRestoreGptTable tests", "GptLib.RestoreGptTable", NULL, NULL); + if (EFI_ERROR (Status)) { + goto Exit; + } + + AddTestCase (ValidSuite, "Valid primary header is accepted", "ValidPrimary", TestValidPrimaryHeaderIsAccepted, NULL, NULL, NULL); + AddTestCase (ValidSuite, "Valid backup header is accepted", "ValidBackup", TestValidBackupHeaderIsAccepted, NULL, NULL, NULL); + AddTestCase (ValidSuite, "Minimum (92-byte) header size is accepted", "MinHeaderSize", TestMinimumHeaderSizeIsAccepted, NULL, NULL, NULL); + AddTestCase (ValidSuite, "256-byte partition entry size is accepted", "EntrySize256", TestLargerPowerOfTwoEntrySizeIsAccepted, NULL, NULL, NULL); + + AddTestCase (EntrySuite, "Valid entries report no flags", "ValidEntries", TestValidEntriesReportNoFlags, NULL, NULL, NULL); + AddTestCase (EntrySuite, "OS-specific attribute is reported", "OsSpecificAttribute", TestOsSpecificAttributeIsReported, NULL, NULL, NULL); + AddTestCase (EntrySuite, "Unused entries are ignored", "UnusedEntriesIgnored", TestUnusedEntriesAreIgnored, NULL, NULL, NULL); + AddTestCase (EntrySuite, "Entry stride follows SizeOfPartitionEntry", "EntryStride", TestEntryStrideFollowsSizeOfPartitionEntry, NULL, NULL, NULL); + + AddTestCase (RestoreSuite, "Restore primary from backup", "RestorePrimary", TestRestorePrimaryFromBackup, NULL, NULL, NULL); + AddTestCase (RestoreSuite, "Restore backup from primary", "RestoreBackup", TestRestoreBackupFromPrimary, NULL, NULL, NULL); + + Status = RunAllTestSuites (Framework); + +Exit: + if (Framework != NULL) { + FreeUnitTestFramework (Framework); + } + + return Status; +} + +/// +/// Avoid ECC error for function name that starts with lower case letter +/// +#define GptLibUnitTestMain main + +/** + Standard POSIX C entry point for host based unit test execution. + + @param[in] Argc Number of arguments + @param[in] Argv Array of pointers to arguments + + @retval 0 Success + @retval other Error +**/ +INT32 +GptLibUnitTestMain ( + IN INT32 Argc, + IN CHAR8 *Argv[] + ) +{ + UefiTestMain (); + return 0; +} diff --git a/MdeModulePkg/Library/GptLib/UnitTest/GptLibUnitTestCommon.c b/MdeModulePkg/Library/GptLib/UnitTest/GptLibUnitTestCommon.c new file mode 100644 index 0000000000..e7061a7e8f --- /dev/null +++ b/MdeModulePkg/Library/GptLib/UnitTest/GptLibUnitTestCommon.c @@ -0,0 +1,298 @@ +/** @file + Shared mock disk and GPT construction helpers used by the GptLib + host-based unit tests. + + Copyright (c) 2026, SUSE LLC. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include +#include +#include "GptLibUnitTestCommon.h" + +UINT8 mDiskImage[DISK_IMAGE_SIZE]; + +EFI_BLOCK_IO_MEDIA mBlockIoMedia; +EFI_BLOCK_IO_PROTOCOL mBlockIo; +EFI_DISK_IO_PROTOCOL mDiskIo; +BOOLEAN mWriteProtected; + +CONST EFI_GUID mEspTypeGuid = { + 0xC12A7328, 0xF81F, 0x11D2, { 0xBA, 0x4B, 0x00, 0xA0, 0xC9, 0x3E, 0xC9, 0x3B } +}; + +CONST EFI_GUID mPartitionGuid1 = { + 0x11111111, 0x2222, 0x3333, { 0x44, 0x44, 0x55, 0x55, 0x66, 0x66, 0x77, 0x77 } +}; + +CONST EFI_GUID mPartitionGuid2 = { + 0x88888888, 0x9999, 0xAAAA, { 0xBB, 0xBB, 0xCC, 0xCC, 0xDD, 0xDD, 0xEE, 0xEE } +}; + +/** + Mock implementation of EFI_DISK_IO_PROTOCOL.ReadDisk backed by the in-memory + disk image. + + @param[in] This Pointer to the EFI_DISK_IO_PROTOCOL instance. + @param[in] MediaId ID of the medium to read from. + @param[in] Offset Starting byte offset on the logical disk. + @param[in] BufferSize Number of bytes to read. + @param[out] Buffer Buffer into which the data is read. + + @retval EFI_SUCCESS The data was read successfully. + @retval EFI_DEVICE_ERROR The request was invalid or out of range. +**/ +STATIC +EFI_STATUS +EFIAPI +MockReadDisk ( + IN EFI_DISK_IO_PROTOCOL *This, + IN UINT32 MediaId, + IN UINT64 Offset, + IN UINTN BufferSize, + OUT VOID *Buffer + ) +{ + if ((Buffer == NULL) || + (MediaId != mBlockIoMedia.MediaId) || + (Offset > DISK_IMAGE_SIZE) || + (BufferSize > (DISK_IMAGE_SIZE - Offset))) + { + return EFI_DEVICE_ERROR; + } + + CopyMem (Buffer, mDiskImage + Offset, BufferSize); + return EFI_SUCCESS; +} + +/** + Mock implementation of EFI_DISK_IO_PROTOCOL.WriteDisk backed by the in-memory + disk image, honoring the write-protected flag. + + @param[in] This Pointer to the EFI_DISK_IO_PROTOCOL instance. + @param[in] MediaId ID of the medium to write to. + @param[in] Offset Starting byte offset on the logical disk. + @param[in] BufferSize Number of bytes to write. + @param[in] Buffer Buffer holding the data to write. + + @retval EFI_SUCCESS The data was written successfully. + @retval EFI_WRITE_PROTECTED The mock medium is write-protected. + @retval EFI_DEVICE_ERROR The request was invalid or out of range. +**/ +STATIC +EFI_STATUS +EFIAPI +MockWriteDisk ( + IN EFI_DISK_IO_PROTOCOL *This, + IN UINT32 MediaId, + IN UINT64 Offset, + IN UINTN BufferSize, + IN VOID *Buffer + ) +{ + if (mWriteProtected) { + return EFI_WRITE_PROTECTED; + } + + if ((Buffer == NULL) || + (MediaId != mBlockIoMedia.MediaId) || + (Offset > DISK_IMAGE_SIZE) || + (BufferSize > (DISK_IMAGE_SIZE - Offset))) + { + return EFI_DEVICE_ERROR; + } + + CopyMem (mDiskImage + Offset, Buffer, BufferSize); + return EFI_SUCCESS; +} + +/** + Return a pointer into the in-memory disk image at the given LBA. + + @param[in] Lba Logical block address to locate. + + @return Pointer to the start of the requested block within the disk image. +**/ +UINT8 * +GetDiskLba ( + IN EFI_LBA Lba + ) +{ + return mDiskImage + (UINTN)(Lba * SECTOR_SIZE); +} + +/** + Return a pointer to the primary GPT header within the in-memory disk image. + + @return Pointer to the primary EFI_PARTITION_TABLE_HEADER. +**/ +EFI_PARTITION_TABLE_HEADER * +GetPrimaryHeader ( + VOID + ) +{ + return (EFI_PARTITION_TABLE_HEADER *)(VOID *)GetDiskLba (PRIMARY_PART_HEADER_LBA); +} + +/** + Recompute and store the header CRC32 for the given GPT header. + + @param[in,out] Header The GPT header whose CRC32 field is updated. +**/ +VOID +UpdateGptHeaderCrc ( + IN OUT EFI_PARTITION_TABLE_HEADER *Header + ) +{ + Header->Header.CRC32 = 0; + Header->Header.CRC32 = CalculateCrc32 (Header, Header->Header.HeaderSize); +} + +/** + Populate a partition entry with the ESP type GUID and the given attributes. + + @param[in,out] Entry The partition entry to fill. + @param[in] UniquePartitionGuid Unique partition GUID to assign. + @param[in] StartingLba Starting LBA of the partition. + @param[in] EndingLba Ending LBA of the partition. +**/ +VOID +FillPartitionEntry ( + IN OUT EFI_PARTITION_ENTRY *Entry, + IN CONST EFI_GUID *UniquePartitionGuid, + IN EFI_LBA StartingLba, + IN EFI_LBA EndingLba + ) +{ + CopyGuid (&Entry->PartitionTypeGUID, &mEspTypeGuid); + CopyGuid (&Entry->UniquePartitionGUID, UniquePartitionGuid); + Entry->StartingLBA = StartingLba; + Entry->EndingLBA = EndingLba; +} + +/** + Construct a complete GPT header and partition entry array at the given LBA in + the in-memory disk image, recalculating all CRCs. + + @param[in] HeaderLba LBA at which to write the GPT header. + @param[in] AlternateLba Value stored in the header AlternateLBA field. + @param[in] EntryArrayLba LBA at which to write the partition entry array. + @param[in] NumEntries Number of partition entries in the array. + @param[in] EntrySize Size in bytes of each partition entry. + @param[in] NumPartitions Number of populated partition entries. +**/ +VOID +WriteGptTableAt ( + IN EFI_LBA HeaderLba, + IN EFI_LBA AlternateLba, + IN EFI_LBA EntryArrayLba, + IN UINT32 NumEntries, + IN UINT32 EntrySize, + IN UINT32 NumPartitions + ) +{ + EFI_PARTITION_TABLE_HEADER *Header; + UINT8 *Array; + UINT32 ArraySize; + + ArraySize = NumEntries * EntrySize; + Array = GetDiskLba (EntryArrayLba); + ZeroMem (Array, ArraySize); + if (NumPartitions >= 1) { + FillPartitionEntry ((EFI_PARTITION_ENTRY *)(VOID *)Array, &mPartitionGuid1, 34, 133); + } + + if (NumPartitions >= 2) { + FillPartitionEntry ((EFI_PARTITION_ENTRY *)(VOID *)(Array + EntrySize), &mPartitionGuid2, 134, 233); + } + + Header = (EFI_PARTITION_TABLE_HEADER *)(VOID *)GetDiskLba (HeaderLba); + ZeroMem (Header, SECTOR_SIZE); + Header->Header.Signature = EFI_PTAB_HEADER_ID; + Header->Header.Revision = TEST_GPT_REVISION_V1; + Header->Header.HeaderSize = sizeof (EFI_PARTITION_TABLE_HEADER); + Header->MyLBA = HeaderLba; + Header->AlternateLBA = AlternateLba; + Header->FirstUsableLBA = FIRST_USABLE_LBA; + Header->LastUsableLBA = LAST_USABLE_LBA; + Header->PartitionEntryLBA = EntryArrayLba; + Header->NumberOfPartitionEntries = NumEntries; + Header->SizeOfPartitionEntry = EntrySize; + Header->PartitionEntryArrayCRC32 = CalculateCrc32 (Array, ArraySize); + UpdateGptHeaderCrc (Header); +} + +/** + Reset the mock disk and protocol state and lay down a valid primary GPT. +**/ +VOID +SetupDiskWithValidPrimary ( + VOID + ) +{ + ZeroMem (mDiskImage, sizeof (mDiskImage)); + + ZeroMem (&mBlockIoMedia, sizeof (mBlockIoMedia)); + mBlockIoMedia.MediaId = 1; + mBlockIoMedia.BlockSize = SECTOR_SIZE; + mBlockIoMedia.LastBlock = LAST_LBA; + + ZeroMem (&mBlockIo, sizeof (mBlockIo)); + mBlockIo.Revision = EFI_BLOCK_IO_PROTOCOL_REVISION; + mBlockIo.Media = &mBlockIoMedia; + + ZeroMem (&mDiskIo, sizeof (mDiskIo)); + mDiskIo.Revision = EFI_DISK_IO_PROTOCOL_REVISION; + mDiskIo.ReadDisk = MockReadDisk; + mDiskIo.WriteDisk = MockWriteDisk; + + mWriteProtected = FALSE; + + WriteGptTableAt ( + PRIMARY_PART_HEADER_LBA, + LAST_LBA, + PRIMARY_PART_HEADER_LBA + 1, + NUM_PARTITION_ENTRIES, + PARTITION_ENTRY_SIZE, + 2 + ); +} + +/** + Invoke PartitionValidGptTable against the mock disk at the given LBA. + + @param[in] Lba LBA of the GPT header to validate. + @param[out] Header Buffer to receive the validated GPT header. + + @retval TRUE The GPT table at Lba is valid. + @retval FALSE The GPT table at Lba is invalid. +**/ +BOOLEAN +ValidateAt ( + IN EFI_LBA Lba, + OUT EFI_PARTITION_TABLE_HEADER *Header + ) +{ + return PartitionValidGptTable (&mBlockIo, &mDiskIo, Lba, Header); +} + +/** + Initialize a GPT header in memory for PartitionCheckGptEntry tests. + + @param[out] Header The GPT header to initialize. + @param[in] NumEntries Number of partition entries to record. + @param[in] EntrySize Size in bytes of each partition entry. +**/ +VOID +InitCheckEntryHeader ( + OUT EFI_PARTITION_TABLE_HEADER *Header, + IN UINT32 NumEntries, + IN UINT32 EntrySize + ) +{ + ZeroMem (Header, sizeof (*Header)); + Header->FirstUsableLBA = FIRST_USABLE_LBA; + Header->LastUsableLBA = 200; + Header->NumberOfPartitionEntries = NumEntries; + Header->SizeOfPartitionEntry = EntrySize; +} diff --git a/MdeModulePkg/Library/GptLib/UnitTest/GptLibUnitTestCommon.h b/MdeModulePkg/Library/GptLib/UnitTest/GptLibUnitTestCommon.h new file mode 100644 index 0000000000..677dbc4c4f --- /dev/null +++ b/MdeModulePkg/Library/GptLib/UnitTest/GptLibUnitTestCommon.h @@ -0,0 +1,110 @@ +/** @file + Shared mock disk and GPT construction helpers used by the GptLib + host-based unit tests. + + Copyright (c) 2026, SUSE LLC. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#pragma once + +#include +#include +#include +#include + +#define SECTOR_SIZE 512U +#define DISK_SECTORS 4096U +#define DISK_IMAGE_SIZE (DISK_SECTORS * SECTOR_SIZE) +#define LAST_LBA (DISK_SECTORS - 1U) +#define NUM_PARTITION_ENTRIES 128U +#define PARTITION_ENTRY_SIZE 128U +#define PART_ARRAY_SIZE (NUM_PARTITION_ENTRIES * PARTITION_ENTRY_SIZE) +#define FIRST_USABLE_LBA 34U +#define LAST_USABLE_LBA (DISK_SECTORS - 34U) + +// +// The only GPT header revision defined by the UEFI specification, and the +// minimum on-disk header size (through PartitionEntryArrayCRC32). +// +#define TEST_GPT_REVISION_V1 0x00010000U +#define TEST_GPT_HEADER_MIN_SIZE 92U + +extern UINT8 mDiskImage[DISK_IMAGE_SIZE]; + +extern EFI_BLOCK_IO_MEDIA mBlockIoMedia; +extern EFI_BLOCK_IO_PROTOCOL mBlockIo; +extern EFI_DISK_IO_PROTOCOL mDiskIo; +extern BOOLEAN mWriteProtected; + +extern CONST EFI_GUID mEspTypeGuid; +extern CONST EFI_GUID mPartitionGuid1; +extern CONST EFI_GUID mPartitionGuid2; + +UINT8 * +GetDiskLba ( + IN EFI_LBA Lba + ); + +EFI_PARTITION_TABLE_HEADER * +GetPrimaryHeader ( + VOID + ); + +VOID +UpdateGptHeaderCrc ( + IN OUT EFI_PARTITION_TABLE_HEADER *Header + ); + +VOID +FillPartitionEntry ( + IN OUT EFI_PARTITION_ENTRY *Entry, + IN CONST EFI_GUID *UniquePartitionGuid, + IN EFI_LBA StartingLba, + IN EFI_LBA EndingLba + ); + +/** + Write a self-consistent GPT table (header plus partition entry array) to the + mock disk at the requested locations, computing correct header and + entry-array CRC32 values. + + @param[in] HeaderLba LBA where the GPT header is written. Also stored + in the header's MyLBA field. + @param[in] AlternateLba Value stored in the header's AlternateLBA field. + @param[in] EntryArrayLba LBA where the partition entry array is written. + @param[in] NumEntries NumberOfPartitionEntries value. + @param[in] EntrySize SizeOfPartitionEntry value. + @param[in] NumPartitions Number of non-empty partition entries to populate. +**/ +VOID +WriteGptTableAt ( + IN EFI_LBA HeaderLba, + IN EFI_LBA AlternateLba, + IN EFI_LBA EntryArrayLba, + IN UINT32 NumEntries, + IN UINT32 EntrySize, + IN UINT32 NumPartitions + ); + +/** + Reset the mock disk to a single valid primary GPT: header at LBA 1, a + 128 x 128-byte entry array at LBA 2 holding two partitions. +**/ +VOID +SetupDiskWithValidPrimary ( + VOID + ); + +BOOLEAN +ValidateAt ( + IN EFI_LBA Lba, + OUT EFI_PARTITION_TABLE_HEADER *Header + ); + +VOID +InitCheckEntryHeader ( + OUT EFI_PARTITION_TABLE_HEADER *Header, + IN UINT32 NumEntries, + IN UINT32 EntrySize + ); diff --git a/MdeModulePkg/Library/GptLib/UnitTest/GptLibUnitTestHost.inf b/MdeModulePkg/Library/GptLib/UnitTest/GptLibUnitTestHost.inf new file mode 100644 index 0000000000..a387e6afda --- /dev/null +++ b/MdeModulePkg/Library/GptLib/UnitTest/GptLibUnitTestHost.inf @@ -0,0 +1,40 @@ +## @file +# Host-based unit tests for GptLib. +# +# Exercises PartitionValidGptTable(), PartitionCheckGptEntry() and +# PartitionRestoreGptTable() against an in-memory mock disk, covering their +# behavior on well-formed GPT structures. +# +# Copyright (c) 2026, SUSE LLC. All rights reserved.
+# SPDX-License-Identifier: BSD-2-Clause-Patent +## + +[Defines] + INF_VERSION = 0x00010006 + BASE_NAME = GptLibUnitTestHost + FILE_GUID = 2F5B4C8E-6D3A-4B1F-9E07-8A2C5D14E6B3 + MODULE_TYPE = HOST_APPLICATION + VERSION_STRING = 1.0 + +# +# The following information is for reference only and not required by the build tools. +# +# VALID_ARCHITECTURES = IA32 X64 +# + +[Sources] + GptLibUnitTestCommon.h + GptLibUnitTestCommon.c + GptLibUnitTest.c + +[Packages] + MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec + UnitTestFrameworkPkg/UnitTestFrameworkPkg.dec + +[LibraryClasses] + BaseLib + BaseMemoryLib + DebugLib + GptLib + UnitTestLib diff --git a/MdeModulePkg/MdeModulePkg.ci.yaml b/MdeModulePkg/MdeModulePkg.ci.yaml index 37dc5caaf6..6056e263a6 100644 --- a/MdeModulePkg/MdeModulePkg.ci.yaml +++ b/MdeModulePkg/MdeModulePkg.ci.yaml @@ -24,6 +24,7 @@ "8005", "UNIVERSAL_PAYLOAD_PCI_ROOT_BRIDGE.HID", "8001", "UefiSortLibUnitTestMain", "8001", "MediaSanitizeUnitTestMain", + "8001", "GptLibUnitTestMain", ], ## Both file path and directory path are accepted. "IgnoreFiles": [ diff --git a/MdeModulePkg/Test/MdeModulePkgHostTest.dsc b/MdeModulePkg/Test/MdeModulePkgHostTest.dsc index 4f04a6dd79..05fd4652ff 100644 --- a/MdeModulePkg/Test/MdeModulePkgHostTest.dsc +++ b/MdeModulePkg/Test/MdeModulePkgHostTest.dsc @@ -77,6 +77,11 @@ HobLib|MdePkg/Test/Mock/Library/GoogleTest/MockHobLib/MockHobLib.inf } + MdeModulePkg/Library/GptLib/UnitTest/GptLibUnitTestHost.inf { + + GptLib|MdeModulePkg/Library/GptLib/GptLib.inf + } + # # Build HOST_APPLICATION Libraries #