edk2/MdeModulePkg/Library/GptLib/UnitTest/GptLibUnitTest.c
Richard Lyu b1029265b1 MdeModulePkg/GptLib: Add host-based unit tests for malformed GPT input
Extend the GptLib host-based tests with negative cases that guard the
security hardening in PartitionValidGptTable(), PartitionCheckGptEntry()
and PartitionRestoreGptTable() against future regressions. These tests
exercise the shared parser, not the specific fix itself.

The new cases drive the parser with malformed GPT structures that an
attacker may present: bad signature/revision, header-size boundaries,
CRC corruption, MyLBA replay, zero/non-power-of-two entry sizes, LBA
multiplication overflow, out-of-range and overlapping entries, and
restore failure on write-protected media. The INF file header is
updated to note the added malformed coverage.

Signed-off-by: Richard Lyu <richard.lyu@suse.com>
2026-07-20 21:50:57 +00:00

1045 lines
29 KiB
C

/** @file
Host-based unit tests for GptLib.
These tests exercise PartitionValidGptTable(), PartitionCheckGptEntry()
and PartitionRestoreGptTable() against an in-memory mock disk, covering
both well-formed GPT structures and malformed ones an attacker may
present: bad signature/revision, header size
boundaries, CRC corruption, MyLBA replay, zero/non-power-of-two entry
sizes, LBA multiplication overflow, out-of-range and overlapping
entries, and backup/primary restore behavior.
Copyright (c) 2026, SUSE LLC. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include <Uefi.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/UnitTestLib.h>
#include <Library/GptLib.h>
#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;
}
/**
Verify that a GPT header read failure is rejected.
@param[in] Context Unit test context.
@retval UNIT_TEST_PASSED The test passed.
**/
STATIC
UNIT_TEST_STATUS
EFIAPI
TestHeaderReadFailureIsRejected (
IN UNIT_TEST_CONTEXT Context
)
{
EFI_PARTITION_TABLE_HEADER Header;
SetupDiskWithValidPrimary ();
UT_ASSERT_FALSE (ValidateAt (DISK_SECTORS + 10, &Header));
return UNIT_TEST_PASSED;
}
/**
Verify that an invalid GPT signature is rejected.
@param[in] Context Unit test context.
@retval UNIT_TEST_PASSED The test passed.
**/
STATIC
UNIT_TEST_STATUS
EFIAPI
TestInvalidSignatureIsRejected (
IN UNIT_TEST_CONTEXT Context
)
{
EFI_PARTITION_TABLE_HEADER Header;
SetupDiskWithValidPrimary ();
GetPrimaryHeader ()->Header.Signature = SIGNATURE_64 ('X', 'X', 'X', 'X', 'X', 'X', 'X', 'X');
UpdateGptHeaderCrc (GetPrimaryHeader ());
UT_ASSERT_FALSE (ValidateAt (PRIMARY_PART_HEADER_LBA, &Header));
return UNIT_TEST_PASSED;
}
/**
Verify that an invalid GPT revision is rejected.
@param[in] Context Unit test context.
@retval UNIT_TEST_PASSED The test passed.
**/
STATIC
UNIT_TEST_STATUS
EFIAPI
TestInvalidRevisionIsRejected (
IN UNIT_TEST_CONTEXT Context
)
{
EFI_PARTITION_TABLE_HEADER Header;
STATIC CONST UINT32 BadRevisions[] = { 0x00000000, 0x00010001, 0x00020000, 0xFFFFFFFF };
UINTN Index;
for (Index = 0; Index < ARRAY_SIZE (BadRevisions); Index++) {
SetupDiskWithValidPrimary ();
GetPrimaryHeader ()->Header.Revision = BadRevisions[Index];
UpdateGptHeaderCrc (GetPrimaryHeader ());
UT_ASSERT_FALSE (ValidateAt (PRIMARY_PART_HEADER_LBA, &Header));
}
return UNIT_TEST_PASSED;
}
/**
Verify that a header size below the minimum is rejected.
@param[in] Context Unit test context.
@retval UNIT_TEST_PASSED The test passed.
**/
STATIC
UNIT_TEST_STATUS
EFIAPI
TestHeaderSizeBelowMinimumIsRejected (
IN UNIT_TEST_CONTEXT Context
)
{
EFI_PARTITION_TABLE_HEADER Header;
SetupDiskWithValidPrimary ();
GetPrimaryHeader ()->Header.HeaderSize = TEST_GPT_HEADER_MIN_SIZE - 1;
GetPrimaryHeader ()->Header.CRC32 = 0;
GetPrimaryHeader ()->Header.CRC32 = CalculateCrc32 (GetPrimaryHeader (), TEST_GPT_HEADER_MIN_SIZE - 1);
UT_ASSERT_FALSE (ValidateAt (PRIMARY_PART_HEADER_LBA, &Header));
return UNIT_TEST_PASSED;
}
/**
Verify that a zero header size is rejected.
@param[in] Context Unit test context.
@retval UNIT_TEST_PASSED The test passed.
**/
STATIC
UNIT_TEST_STATUS
EFIAPI
TestZeroHeaderSizeIsRejected (
IN UNIT_TEST_CONTEXT Context
)
{
EFI_PARTITION_TABLE_HEADER Header;
SetupDiskWithValidPrimary ();
GetPrimaryHeader ()->Header.HeaderSize = 0;
UT_ASSERT_FALSE (ValidateAt (PRIMARY_PART_HEADER_LBA, &Header));
return UNIT_TEST_PASSED;
}
/**
Verify that a header size beyond the block size is rejected.
@param[in] Context Unit test context.
@retval UNIT_TEST_PASSED The test passed.
**/
STATIC
UNIT_TEST_STATUS
EFIAPI
TestHeaderSizeBeyondBlockSizeIsRejected (
IN UNIT_TEST_CONTEXT Context
)
{
EFI_PARTITION_TABLE_HEADER Header;
SetupDiskWithValidPrimary ();
//
// HeaderSize larger than the block that was read: the CRC check must
// refuse to scan past the buffer instead of reading out of bounds.
//
GetPrimaryHeader ()->Header.HeaderSize = SECTOR_SIZE + 1;
UT_ASSERT_FALSE (ValidateAt (PRIMARY_PART_HEADER_LBA, &Header));
return UNIT_TEST_PASSED;
}
/**
Verify that a corrupt header CRC is rejected.
@param[in] Context Unit test context.
@retval UNIT_TEST_PASSED The test passed.
**/
STATIC
UNIT_TEST_STATUS
EFIAPI
TestCorruptHeaderCrcIsRejected (
IN UNIT_TEST_CONTEXT Context
)
{
EFI_PARTITION_TABLE_HEADER Header;
SetupDiskWithValidPrimary ();
GetPrimaryHeader ()->Header.CRC32 ^= 0xFFFFFFFF;
UT_ASSERT_FALSE (ValidateAt (PRIMARY_PART_HEADER_LBA, &Header));
return UNIT_TEST_PASSED;
}
/**
Verify that a MyLBA mismatch is rejected.
@param[in] Context Unit test context.
@retval UNIT_TEST_PASSED The test passed.
**/
STATIC
UNIT_TEST_STATUS
EFIAPI
TestMyLbaMismatchIsRejected (
IN UNIT_TEST_CONTEXT Context
)
{
EFI_PARTITION_TABLE_HEADER Header;
SetupDiskWithValidPrimary ();
//
// A header claiming to live at a different LBA than the one it was read
// from (e.g. a copied/replayed header) must be rejected.
//
GetPrimaryHeader ()->MyLBA = 5;
UpdateGptHeaderCrc (GetPrimaryHeader ());
UT_ASSERT_FALSE (ValidateAt (PRIMARY_PART_HEADER_LBA, &Header));
return UNIT_TEST_PASSED;
}
/**
Verify that a header reporting zero partition entries is rejected.
@param[in] Context Unit test context.
@retval UNIT_TEST_PASSED The test passed.
**/
STATIC
UNIT_TEST_STATUS
EFIAPI
TestZeroPartitionEntriesIsRejected (
IN UNIT_TEST_CONTEXT Context
)
{
EFI_PARTITION_TABLE_HEADER Header;
SetupDiskWithValidPrimary ();
GetPrimaryHeader ()->NumberOfPartitionEntries = 0;
UpdateGptHeaderCrc (GetPrimaryHeader ());
UT_ASSERT_FALSE (ValidateAt (PRIMARY_PART_HEADER_LBA, &Header));
return UNIT_TEST_PASSED;
}
/**
Verify that a partition entry size below 128 bytes is rejected.
@param[in] Context Unit test context.
@retval UNIT_TEST_PASSED The test passed.
**/
STATIC
UNIT_TEST_STATUS
EFIAPI
TestEntrySizeTooSmallIsRejected (
IN UNIT_TEST_CONTEXT Context
)
{
EFI_PARTITION_TABLE_HEADER Header;
SetupDiskWithValidPrimary ();
GetPrimaryHeader ()->SizeOfPartitionEntry = (UINT32)(sizeof (EFI_PARTITION_ENTRY) / 2);
UpdateGptHeaderCrc (GetPrimaryHeader ());
UT_ASSERT_FALSE (ValidateAt (PRIMARY_PART_HEADER_LBA, &Header));
return UNIT_TEST_PASSED;
}
/**
Verify that a zero partition entry size is rejected.
@param[in] Context Unit test context.
@retval UNIT_TEST_PASSED The test passed.
**/
STATIC
UNIT_TEST_STATUS
EFIAPI
TestZeroEntrySizeIsRejected (
IN UNIT_TEST_CONTEXT Context
)
{
EFI_PARTITION_TABLE_HEADER Header;
SetupDiskWithValidPrimary ();
//
// Must be rejected by the size check before the later division by
// SizeOfPartitionEntry (division by zero) can be reached.
//
GetPrimaryHeader ()->SizeOfPartitionEntry = 0;
UpdateGptHeaderCrc (GetPrimaryHeader ());
UT_ASSERT_FALSE (ValidateAt (PRIMARY_PART_HEADER_LBA, &Header));
return UNIT_TEST_PASSED;
}
/**
Verify that a non power-of-two partition entry size is rejected.
@param[in] Context Unit test context.
@retval UNIT_TEST_PASSED The test passed.
**/
STATIC
UNIT_TEST_STATUS
EFIAPI
TestNonPowerOfTwoEntrySizeIsRejected (
IN UNIT_TEST_CONTEXT Context
)
{
EFI_PARTITION_TABLE_HEADER Header;
SetupDiskWithValidPrimary ();
//
// 192 is >= 128 but not 128 * 2^n.
//
GetPrimaryHeader ()->SizeOfPartitionEntry = 192;
UpdateGptHeaderCrc (GetPrimaryHeader ());
UT_ASSERT_FALSE (ValidateAt (PRIMARY_PART_HEADER_LBA, &Header));
return UNIT_TEST_PASSED;
}
/**
Verify that a PartitionEntryLBA causing multiplication overflow is rejected.
@param[in] Context Unit test context.
@retval UNIT_TEST_PASSED The test passed.
**/
STATIC
UNIT_TEST_STATUS
EFIAPI
TestEntryLbaMultiplicationOverflowIsRejected (
IN UNIT_TEST_CONTEXT Context
)
{
EFI_PARTITION_TABLE_HEADER Header;
SetupDiskWithValidPrimary ();
//
// PartitionEntryLBA * BlockSize would wrap around UINT64. Must be rejected
// before any read is attempted with the truncated offset.
//
GetPrimaryHeader ()->PartitionEntryLBA = MAX_UINT64;
UpdateGptHeaderCrc (GetPrimaryHeader ());
UT_ASSERT_FALSE (ValidateAt (PRIMARY_PART_HEADER_LBA, &Header));
return UNIT_TEST_PASSED;
}
/**
Verify that an entry array CRC mismatch is rejected.
@param[in] Context Unit test context.
@retval UNIT_TEST_PASSED The test passed.
**/
STATIC
UNIT_TEST_STATUS
EFIAPI
TestEntryArrayCrcMismatchIsRejected (
IN UNIT_TEST_CONTEXT Context
)
{
EFI_PARTITION_TABLE_HEADER Header;
SetupDiskWithValidPrimary ();
//
// Flip one byte inside an unused entry slot without refreshing the
// entry-array CRC32 recorded in the header.
//
GetDiskLba (PRIMARY_PART_HEADER_LBA + 1)[(2U * PARTITION_ENTRY_SIZE) + 7] ^= 0xA5;
UT_ASSERT_FALSE (ValidateAt (PRIMARY_PART_HEADER_LBA, &Header));
return UNIT_TEST_PASSED;
}
/**
Verify that an entry array read failure is rejected.
@param[in] Context Unit test context.
@retval UNIT_TEST_PASSED The test passed.
**/
STATIC
UNIT_TEST_STATUS
EFIAPI
TestEntryArrayReadFailureIsRejected (
IN UNIT_TEST_CONTEXT Context
)
{
EFI_PARTITION_TABLE_HEADER Header;
SetupDiskWithValidPrimary ();
//
// Entry array placed so close to the end of the device that reading
// NumberOfPartitionEntries * SizeOfPartitionEntry bytes runs off the disk.
//
GetPrimaryHeader ()->PartitionEntryLBA = LAST_LBA;
UpdateGptHeaderCrc (GetPrimaryHeader ());
UT_ASSERT_FALSE (ValidateAt (PRIMARY_PART_HEADER_LBA, &Header));
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;
}
/**
Verify that an inverted LBA range reports OutOfRange.
@param[in] Context Unit test context.
@retval UNIT_TEST_PASSED The test passed.
**/
STATIC
UNIT_TEST_STATUS
EFIAPI
TestInvertedLbaRangeReportsOutOfRange (
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, 100, 50);
PartitionCheckGptEntry (&Header, Entries, Status);
UT_ASSERT_TRUE (Status[0].OutOfRange);
return UNIT_TEST_PASSED;
}
/**
Verify that an entry outside the usable region reports OutOfRange.
@param[in] Context Unit test context.
@retval UNIT_TEST_PASSED The test passed.
**/
STATIC
UNIT_TEST_STATUS
EFIAPI
TestEntryOutsideUsableRegionReportsOutOfRange (
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));
//
// Entry 0 starts before FirstUsableLBA; entry 1 ends after LastUsableLBA.
//
FillPartitionEntry (&Entries[0], &mPartitionGuid1, FIRST_USABLE_LBA - 1, 100);
FillPartitionEntry (&Entries[1], &mPartitionGuid2, 150, 201);
PartitionCheckGptEntry (&Header, Entries, Status);
UT_ASSERT_TRUE (Status[0].OutOfRange);
UT_ASSERT_TRUE (Status[1].OutOfRange);
return UNIT_TEST_PASSED;
}
/**
Verify that overlapping partition entries report Overlap.
@param[in] Context Unit test context.
@retval UNIT_TEST_PASSED The test passed.
**/
STATIC
UNIT_TEST_STATUS
EFIAPI
TestOverlappingEntriesReportOverlap (
IN UNIT_TEST_CONTEXT Context
)
{
EFI_PARTITION_TABLE_HEADER Header;
EFI_PARTITION_ENTRY Entries[3];
EFI_PARTITION_ENTRY_STATUS Status[3];
InitCheckEntryHeader (&Header, 3, sizeof (EFI_PARTITION_ENTRY));
ZeroMem (Entries, sizeof (Entries));
ZeroMem (Status, sizeof (Status));
FillPartitionEntry (&Entries[0], &mPartitionGuid1, 40, 100);
FillPartitionEntry (&Entries[1], &mPartitionGuid2, 90, 150);
FillPartitionEntry (&Entries[2], &mPartitionGuid1, 160, 200);
PartitionCheckGptEntry (&Header, Entries, Status);
UT_ASSERT_TRUE (Status[0].Overlap);
UT_ASSERT_TRUE (Status[1].Overlap);
UT_ASSERT_FALSE (Status[2].Overlap);
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;
}
/**
Verify that a restore attempt fails on write-protected media.
@param[in] Context Unit test context.
@retval UNIT_TEST_PASSED The test passed.
**/
STATIC
UNIT_TEST_STATUS
EFIAPI
TestRestoreFailsOnWriteProtectedMedia (
IN UNIT_TEST_CONTEXT Context
)
{
EFI_PARTITION_TABLE_HEADER PrimaryHeader;
SetupDiskWithValidPrimary ();
UT_ASSERT_TRUE (ValidateAt (PRIMARY_PART_HEADER_LBA, &PrimaryHeader));
mWriteProtected = TRUE;
UT_ASSERT_FALSE (PartitionRestoreGptTable (&mBlockIo, &mDiskIo, &PrimaryHeader));
UT_ASSERT_FALSE (ValidateAt (LAST_LBA, &PrimaryHeader));
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 (ValidSuite, "Header read failure is rejected", "HeaderReadFailure", TestHeaderReadFailureIsRejected, NULL, NULL, NULL);
AddTestCase (ValidSuite, "Invalid signature is rejected", "InvalidSignature", TestInvalidSignatureIsRejected, NULL, NULL, NULL);
AddTestCase (ValidSuite, "Invalid revision is rejected", "InvalidRevision", TestInvalidRevisionIsRejected, NULL, NULL, NULL);
AddTestCase (ValidSuite, "Header size below minimum is rejected", "HeaderSizeTooSmall", TestHeaderSizeBelowMinimumIsRejected, NULL, NULL, NULL);
AddTestCase (ValidSuite, "Zero header size is rejected", "ZeroHeaderSize", TestZeroHeaderSizeIsRejected, NULL, NULL, NULL);
AddTestCase (ValidSuite, "Header size beyond block size is rejected", "HeaderSizeBeyondBlock", TestHeaderSizeBeyondBlockSizeIsRejected, NULL, NULL, NULL);
AddTestCase (ValidSuite, "Corrupt header CRC is rejected", "CorruptHeaderCrc", TestCorruptHeaderCrcIsRejected, NULL, NULL, NULL);
AddTestCase (ValidSuite, "MyLBA mismatch is rejected", "MyLbaMismatch", TestMyLbaMismatchIsRejected, NULL, NULL, NULL);
AddTestCase (ValidSuite, "Zero partition entries is rejected", "ZeroEntries", TestZeroPartitionEntriesIsRejected, NULL, NULL, NULL);
AddTestCase (ValidSuite, "Entry size below 128 is rejected", "EntrySizeTooSmall", TestEntrySizeTooSmallIsRejected, NULL, NULL, NULL);
AddTestCase (ValidSuite, "Zero entry size is rejected", "ZeroEntrySize", TestZeroEntrySizeIsRejected, NULL, NULL, NULL);
AddTestCase (ValidSuite, "Non power-of-two entry size is rejected", "NonPowerOfTwoEntrySize", TestNonPowerOfTwoEntrySizeIsRejected, NULL, NULL, NULL);
AddTestCase (ValidSuite, "PartitionEntryLBA overflow is rejected", "EntryLbaOverflow", TestEntryLbaMultiplicationOverflowIsRejected, NULL, NULL, NULL);
AddTestCase (ValidSuite, "Entry array CRC mismatch is rejected", "EntryArrayCrcMismatch", TestEntryArrayCrcMismatchIsRejected, NULL, NULL, NULL);
AddTestCase (ValidSuite, "Entry array read failure is rejected", "EntryArrayReadFailure", TestEntryArrayReadFailureIsRejected, 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 (EntrySuite, "Inverted LBA range reports OutOfRange", "InvertedRange", TestInvertedLbaRangeReportsOutOfRange, NULL, NULL, NULL);
AddTestCase (EntrySuite, "Entry outside usable region reports OutOfRange", "OutsideUsableRegion", TestEntryOutsideUsableRegionReportsOutOfRange, NULL, NULL, NULL);
AddTestCase (EntrySuite, "Overlapping entries report Overlap", "OverlappingEntries", TestOverlappingEntriesReportOverlap, 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);
AddTestCase (RestoreSuite, "Restore fails on write-protected media", "RestoreWriteProtected", TestRestoreFailsOnWriteProtectedMedia, 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;
}