diff --git a/MdePkg/Include/IndustryStandard/Tpm2Acpi.h b/MdePkg/Include/IndustryStandard/Tpm2Acpi.h index 879a6058d73..90573e7f88f 100644 --- a/MdePkg/Include/IndustryStandard/Tpm2Acpi.h +++ b/MdePkg/Include/IndustryStandard/Tpm2Acpi.h @@ -80,6 +80,36 @@ typedef struct { UINT8 Reserved[8]; } EFI_TPM2_ACPI_START_METHOD_SPECIFIC_PARAMETERS_ARM_FFA; +// MU_CHANGE - [BEGIN] + +typedef struct { + EFI_ACPI_DESCRIPTION_HEADER Header; + // Flags field is replaced in version 4 and above + // BIT0~15: PlatformClass This field is only valid for version 4 and above + // BIT16~31: Reserved + UINT32 Flags; + UINT64 AddressOfControlArea; + UINT32 StartMethod; + UINT8 PlatformSpecificParameters[12]; // size up to 12 + UINT32 Laml; // Optional + UINT64 Lasa; // Optional +} EFI_TPM2_ACPI_TABLE_V4; + +typedef struct { + EFI_ACPI_DESCRIPTION_HEADER Header; + // Flags field is replaced in version 4 and above + // BIT0~15: PlatformClass This field is only valid for version 4 and above + // BIT16~31: Reserved + UINT32 Flags; + UINT64 AddressOfControlArea; + UINT32 StartMethod; + EFI_TPM2_ACPI_START_METHOD_SPECIFIC_PARAMETERS_ARM_FFA FfaParameters; + UINT32 Laml; // Optional + UINT64 Lasa; // Optional +} EFI_TPM2_ACPI_TABLE_V5; + +// MU_CHANGE - [END] + #define EFI_TPM2_ACPI_TABLE_ARM_FFA_PARAMETER_FLAG_NOTIFICATION_SUPPORT BIT0 #define EFI_TPM2_ACPI_TABLE_ARM_FFA_PARAMETER_ATTR_MEM_TYPE_MASK 0x3 diff --git a/MdePkg/Include/IndustryStandard/UefiTcgPlatform.h b/MdePkg/Include/IndustryStandard/UefiTcgPlatform.h index 1b7b2406e9d..b7abedd82c5 100644 --- a/MdePkg/Include/IndustryStandard/UefiTcgPlatform.h +++ b/MdePkg/Include/IndustryStandard/UefiTcgPlatform.h @@ -101,6 +101,11 @@ #define FIRMWARE_DEBUGGER_EVENT_STRING "UEFI Debug Mode" #define FIRMWARE_DEBUGGER_EVENT_STRING_LEN (sizeof(FIRMWARE_DEBUGGER_EVENT_STRING) - 1) +// MU_CHANGE +// String logged as a NO_ACTION event to mark the ACPI-visible TCG +// log as truncated when dynamic scaling occurs post ReadyToBoot. +#define TCG_LOG_TRUNCATION_EVENT_STRING "TCG Event Log Truncated" + // // Set structure alignment to 1-byte // diff --git a/SecurityPkg/SecurityPkg.dec b/SecurityPkg/SecurityPkg.dec index 70d70da0e7e..eb5d260218b 100644 --- a/SecurityPkg/SecurityPkg.dec +++ b/SecurityPkg/SecurityPkg.dec @@ -301,6 +301,13 @@ # Include/Protocol/MuTcg2Protocol.h gMuTcg2ProtocolExGuid = {0x227e7984, 0x1a77, 0x4762, { 0x96, 0x69, 0x57, 0x4c, 0xda, 0xd1, 0xa0, 0x1e }} ## MU_CHANGE - END - Add a new protocol to support Log-only events. + + ## MU_CHANGE - [BEGIN] + ## Protocol used to test dynamic TCG log scaling functionality. + # Tcg/TcgLogTest/TcgLogTest.h + gTcgLogTestProtocolGuid = {0xa3c12f80, 0x7d9e, 0x4b5a, { 0x91, 0xe4, 0x6c, 0xf8, 0x2d, 0xa1, 0xb7, 0x03 }} + ## MU_CHANGE - [END] + [Ppis] ## The PPI GUID for that TPM physical presence should be locked. # Include/Ppi/LockPhysicalPresence.h diff --git a/SecurityPkg/SecurityPkg.dsc b/SecurityPkg/SecurityPkg.dsc index 82dd0be714c..247a1000254 100644 --- a/SecurityPkg/SecurityPkg.dsc +++ b/SecurityPkg/SecurityPkg.dsc @@ -249,6 +249,16 @@ SecurityPkg/Applications/TpmShellApp/TpmShellApp.inf ## MU_CHANGE + ## MU_CHANGE - [BEGIN] + SecurityPkg/Tcg/TcgLogTest/TcgLogTestDxe.inf + SecurityPkg/Tcg/TcgLogTest/TcgLogTestApp.inf { + + UnitTestLib|UnitTestFrameworkPkg/Library/UnitTestLib/UnitTestLib.inf + UnitTestPersistenceLib|UnitTestFrameworkPkg/Library/UnitTestPersistenceLibNull/UnitTestPersistenceLibNull.inf + UnitTestResultReportLib|UnitTestFrameworkPkg/Library/UnitTestResultReportLib/UnitTestResultReportLibDebugLib.inf + } + ## MU_CHANGE - [END] + # # TCG Storage. # diff --git a/SecurityPkg/Tcg/Tcg2Acpi/Tcg2Acpi.c b/SecurityPkg/Tcg/Tcg2Acpi/Tcg2Acpi.c index 87bd22cc93a..f523728442d 100644 --- a/SecurityPkg/Tcg/Tcg2Acpi/Tcg2Acpi.c +++ b/SecurityPkg/Tcg/Tcg2Acpi/Tcg2Acpi.c @@ -74,7 +74,11 @@ SPDX-License-Identifier: BSD-2-Clause-Patent // #define MAX_PRS_INT_BUF_SIZE (15*4) -#pragma pack(1) +// MU_CHANGE - [BEGIN] + +#if 0 + + #pragma pack(1) typedef struct { EFI_ACPI_DESCRIPTION_HEADER Header; @@ -89,7 +93,11 @@ typedef struct { UINT64 Lasa; // Optional } EFI_TPM2_ACPI_TABLE_V4; -#pragma pack() + #pragma pack() + +#endif + +// MU_CHANGE - [END] EFI_TPM2_ACPI_TABLE_V4 mTpm2AcpiTemplate = { { diff --git a/SecurityPkg/Tcg/Tcg2AcpiFfa/Tcg2AcpiFfa.c b/SecurityPkg/Tcg/Tcg2AcpiFfa/Tcg2AcpiFfa.c index c3ae72e6225..696128abf31 100644 --- a/SecurityPkg/Tcg/Tcg2AcpiFfa/Tcg2AcpiFfa.c +++ b/SecurityPkg/Tcg/Tcg2AcpiFfa/Tcg2AcpiFfa.c @@ -66,7 +66,11 @@ SPDX-License-Identifier: BSD-2-Clause-Patent // #define MAX_PRS_INT_BUF_SIZE (15*4) -#pragma pack(1) +// MU_CHANGE - [BEGIN] + +#if 0 + + #pragma pack(1) typedef struct { EFI_ACPI_DESCRIPTION_HEADER Header; @@ -81,7 +85,11 @@ typedef struct { UINT64 Lasa; // Optional } EFI_TPM2_ACPI_TABLE_V5; -#pragma pack() + #pragma pack() + +#endif + +// MU_CHANGE - [END] EFI_TPM2_ACPI_TABLE_V5 mTpm2AcpiTemplate = { { diff --git a/SecurityPkg/Tcg/Tcg2Dxe/README.md b/SecurityPkg/Tcg/Tcg2Dxe/README.md new file mode 100644 index 00000000000..6c1b3cab0ae --- /dev/null +++ b/SecurityPkg/Tcg/Tcg2Dxe/README.md @@ -0,0 +1,83 @@ +# Tcg2Dxe + +Tcg2Dxe is a DXE-phase UEFI driver that publishes the TCG2 protocol defined +by the [TCG EFI Protocol Specification](https://trustedcomputinggroup.org/resource/tcg-efi-protocol-specification/). +It's main responsibilites are to expose a standard interface to a TPM device, +measure components and events into PCRs, support measured boot, and enable +secure boot attestation. + +## Dynamic Event Log Scaling + +The TCG event log is initially allocated with a fixed size defined by a +PCD: PcdTcgLogAreaMinLen. As firmware components log measured boot +events the log fills up. Traditionally, when the log is full, subsequent events +are dropped and the log is marked as truncated. + +Tcg2Dxe extends this behavior with **dynamic scaling**: when the log is about +to overflow, the driver doubles its allocation, copies the existing log into +the new buffer, and frees the old one. This allows the log to grow as needed +and avoids losing events. + +### How It Works + +1. **Scaling check** — Before logging a TCG 2.0 event, + `TcgLogDynamicScalingNeeded` calculates whether the new event (plus a + reserved truncation marker) would exceed the current allocation + (`EventLogAreaStruct->Laml`). Space is reserved for the truncation marker + as long as the ACPI log has not yet been marked truncated. + +2. **Reallocation** — When scaling is needed, `TcgScaleEventLog` allocates a + new `EfiBootServicesData` region at twice the current size, copies the + existing log, updates the `Lasa`/`Laml` fields in the event log area + struct, and frees the old region. + +3. **Logging** — After scaling, the new event is logged into the resized buffer + via `TcgDxeLogEvent` inside a TPL-raised critical section. + +### Normal Log vs. ACPI Log vs. Final Events Log + +Tcg2Dxe maintains three distinct event log regions: + +| Log | Memory Type | Lifetime | Can Scale | +| --- | ----------- | -------- | --------- | +| **Normal log** | `EfiBootServicesData` | Available until `ExitBootServices` | Yes | +| **ACPI log** | `EfiACPIMemoryNVS` | Persistent | No | +| **Final Events log** | `EfiACPIMemoryNVS` | Persistent | No | + +- The **Normal log** is the main log copy which is returned via `GetEventLog`. + It can grow dynamically via scaling. Note that previous calls to `GetEventLog` + could contain stale data if the log was scaled after. It is recommended to + call `GetEventLog` each time access is required. +- The **ACPI log** is created at `ReadyToBoot` by `GenerateAcpiLog`. It + allocates an `EfiACPIMemoryNVS` region equal to the **Normal log** size at that + point, copies the log contents, and updates the TPM2 ACPI table's + `LAML`/`LASA` fields so the OS can find it. If the TPM2 table was already + installed via Tcg2Acpi/Tcg2AcpiFfa, `GenerateAcpiLog` will uninstall and + reinstall the ACPI table with the updated LAML and LASA pointing to the + newly allocated NVS region. +- The **Final Events log** (`EFI_TCG2_FINAL_EVENTS_TABLE`) records events + logged after `GetEventLog` has been called. It is installed as a UEFI + configuration table so the OS can discover events that occurred between its + call to `GetEventLog` and `ExitBootServices`. Because the **Final Events log** + does not scale, it can become truncated. + +After `ReadyToBoot`, every new event is also appended to the ACPI log. Because +the ACPI log's NVS allocation is fixed (the OS may already be referencing its +address), it cannot be reallocated. + +### Post-ReadyToBoot Truncation + +When dynamic scaling is triggered after `ReadyToBoot`: + +1. A `NO_ACTION` event with the payload `"TCG Event Log Truncated"` is + appended to the **ACPI log** to notify the OS that the ACPI-visible log is + now incomplete. +2. The ACPI log is marked truncated (`EventLogTruncated = TRUE`) so no further + events are written to it and no additional space is reserved for the + truncation marker. +3. The **normal log** is scaled as usual — it continues to grow and accept new + events. + +This means the normal log accessed via `GetEventLog` always has the complete +set of events, while the ACPI log visible to the OS may be truncated with a +marker at the end. diff --git a/SecurityPkg/Tcg/Tcg2Dxe/Tcg2Dxe.c b/SecurityPkg/Tcg/Tcg2Dxe/Tcg2Dxe.c index c9e292159e3..de2784aaa64 100644 --- a/SecurityPkg/Tcg/Tcg2Dxe/Tcg2Dxe.c +++ b/SecurityPkg/Tcg/Tcg2Dxe/Tcg2Dxe.c @@ -11,6 +11,7 @@ SPDX-License-Identifier: BSD-2-Clause-Patent #include #include #include +#include // MU_CHANGE #include #include @@ -28,6 +29,8 @@ SPDX-License-Identifier: BSD-2-Clause-Patent #include // MU_CHANGE - Add a new protocol to support Log-only events. #include #include +#include // MU_CHANGE +#include // MU_CHANGE #include #include @@ -132,7 +135,13 @@ VARIABLE_TYPE mVariableType[] = { { EFI_IMAGE_SECURITY_DATABASE1, &gEfiImageSecurityDatabaseGuid }, }; -EFI_HANDLE mImageHandle; +// MU_CHANGE - [BEGIN] + +EFI_HANDLE mImageHandle; +BOOLEAN mReadyToBoot = FALSE; +TCG_EVENT_LOG_AREA_STRUCT mAcpiEventLog; + +// MU_CHANGE - [END] /** Measure PE image into TPM log based on the authenticode image hashing in @@ -1002,6 +1011,31 @@ TcgDxeLogEvent ( EventLogAreaStruct->EventLogStarted = TRUE; } + // MU_CHANGE - [BEGIN] + + // + // Record to the ACPI event log + // + EventLogAreaStruct = &mAcpiEventLog; + + if (mReadyToBoot && !EventLogAreaStruct->EventLogTruncated) { + Status = TcgCommLogEvent ( + EventLogAreaStruct, + NewEventHdr, + NewEventHdrSize, + NewEventData, + NewEventSize + ); + + if (Status == EFI_OUT_OF_RESOURCES) { + EventLogAreaStruct->EventLogTruncated = TRUE; + } else if (Status == EFI_SUCCESS) { + EventLogAreaStruct->EventLogStarted = TRUE; + } + } + + // MU_CHANGE - [END] + // // If GetEventLog is called, record to FinalEventsTable, too. // @@ -1133,6 +1167,133 @@ CopyDigestListBinToBuffer ( return Buffer; } +// MU_CHANGE - [BEGIN] + +/** + Dynamically scale the TCG event log, this should only occur when the + log is filled/truncated. + + @param[in, out] EventLogAreaStruct The event log area data structure. + + @retval EFI_SUCCESS Log was successfully scaled. + @retval EFI_OUT_OF_RESOURCES Allocation failed. + +**/ +STATIC +EFI_STATUS +TcgScaleEventLog ( + IN OUT TCG_EVENT_LOG_AREA_STRUCT *EventLogAreaStruct + ) +{ + EFI_STATUS Status; + EFI_PHYSICAL_ADDRESS NewLasa; + UINT64 NewLaml; + EFI_PHYSICAL_ADDRESS OldLasa; + UINT64 OldLaml; + + // Make sure EventLogAreaStruct is valid. + if (EventLogAreaStruct == NULL) { + return EFI_INVALID_PARAMETER; + } + + // Double the length of the TCG log. + NewLaml = EventLogAreaStruct->Laml * 2; + Status = gBS->AllocatePages ( + AllocateAnyPages, + EfiBootServicesData, + EFI_SIZE_TO_PAGES ((UINTN)NewLaml), + &NewLasa + ); + + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "Failed to allocate new TCG event log\n")); + return EFI_OUT_OF_RESOURCES; + } + + // Copy the data from the old event log to the new event log. + CopyMem ((VOID *)(UINTN)NewLasa, (VOID *)(UINTN)EventLogAreaStruct->Lasa, EventLogAreaStruct->EventLogSize); + + // Store the old Lasa and Laml before updating. + OldLasa = EventLogAreaStruct->Lasa; + OldLaml = EventLogAreaStruct->Laml; + + DEBUG ((DEBUG_INFO, "OldLasa: 0x%lx, OldLaml: 0x%x\n", OldLasa, OldLaml)); + DEBUG ((DEBUG_INFO, "NewLasa: 0x%lx, NewLaml: 0x%x\n", NewLasa, NewLaml)); + + // Update the EventLogAreaStruct. + EventLogAreaStruct->Lasa = NewLasa; + EventLogAreaStruct->Laml = NewLaml; + + // Update the LastEvent pointer. LastEvent = Lasa + Offset. To calculate + // the offset we can do: Offset = LastEvent - Lasa. + EventLogAreaStruct->LastEvent = (UINT8 *)(UINTN)NewLasa + ((UINTN)EventLogAreaStruct->LastEvent - (UINTN)OldLasa); + + // Free the old log region. + gBS->FreePages (OldLasa, EFI_SIZE_TO_PAGES ((UINTN)OldLaml)); + + return Status; +} + +/** + Check if the TCG log needs to be dynamically scaled. + + @param[in] EventLogAreaStruct Pointer to the event log area structure. + @param[in] NewEventHdrSize New event header size. + @param[in] NewEventSize New event data size. + + @retval TRUE Dynamic scaling needed. + @retval FALSE Dynamic scaling not needed. + +**/ +STATIC +BOOLEAN +TcgLogDynamicScalingNeeded ( + IN TCG_EVENT_LOG_AREA_STRUCT *EventLogAreaStruct, + IN UINT32 NewEventHdrSize, + IN UINT32 NewEventSize + ) +{ + UINTN NewLogSize; + + // Make sure EventLogAreaStruct is valid. + if (EventLogAreaStruct == NULL) { + return FALSE; + } + + // Validate NewEventSize + NewEventHdrSize doesn't cause an overflow. + if (NewEventSize > MAX_ADDRESS - NewEventHdrSize) { + ASSERT (FALSE); + return FALSE; + } + + NewLogSize = NewEventHdrSize + NewEventSize; + + // Validate EventLogSize + NewLogSize doesn't cause an overflow. + if (NewLogSize > MAX_ADDRESS - EventLogAreaStruct->EventLogSize) { + ASSERT (FALSE); + return FALSE; + } + + // Determine if dynamic scaling is needed. + if (NewLogSize + EventLogAreaStruct->EventLogSize > EventLogAreaStruct->Laml) { + DEBUG ((DEBUG_INFO, " Laml - 0x%lx\n", EventLogAreaStruct->Laml)); + DEBUG ((DEBUG_INFO, " NewLogSize - 0x%lx\n", NewLogSize)); + DEBUG ((DEBUG_INFO, " LogSize - 0x%lx\n", EventLogAreaStruct->EventLogSize)); + DEBUG ((DEBUG_ERROR, "Dynamic scaling required! Recommended to update your TCG log size!\n")); + + // Log an error if we attempt to scale post ReadyToBoot. + if (mReadyToBoot) { + DEBUG ((DEBUG_ERROR, "Unexpected dynamic scaling occurring post ReadyToBoot!\n")); + } + + return TRUE; + } + + return FALSE; +} + +// MU_CHANGE - [END] + /** Add a new entry to the Event Log. @@ -1150,13 +1311,20 @@ TcgDxeLogHashEvent ( IN UINT8 *NewEventData ) { - EFI_STATUS Status; - EFI_TPL OldTpl; - UINTN Index; - EFI_STATUS RetStatus; - TCG_PCR_EVENT2 TcgPcrEvent2; - UINT8 *DigestBuffer; - UINT32 *EventSizePtr; + // MU_CHANGE - [BEGIN] + + EFI_STATUS Status; + EFI_TPL OldTpl; + UINTN Index; + EFI_STATUS RetStatus; + TCG_PCR_EVENT2 TcgPcrEvent2; + UINT8 *DigestBuffer; + UINT32 *EventSizePtr; + BOOLEAN DynamicScalingNeeded; + TCG_PCR_EVENT2_HDR NoActionEvent; + UINT32 EventHdrSize; + + // MU_CHANGE - [END] RetStatus = EFI_SUCCESS; for (Index = 0; Index < sizeof (mTcg2EventInfo)/sizeof (mTcg2EventInfo[0]); Index++) { @@ -1195,6 +1363,54 @@ TcgDxeLogHashEvent ( EventSizePtr = CopyDigestListToBuffer (DigestBuffer, DigestList, mTcgDxeData.BsCap.ActivePcrBanks); CopyMem (EventSizePtr, &NewEventHdr->EventSize, sizeof (NewEventHdr->EventSize)); + // MU_CHANGE - [BEGIN] + + // Need to dynamically scale the TCG log before we enter a critical region. + DynamicScalingNeeded = TcgLogDynamicScalingNeeded ( + &mTcgDxeData.EventLogAreaStruct[Index], + sizeof (TcgPcrEvent2.PCRIndex) + sizeof (TcgPcrEvent2.EventType) + GetDigestListBinSize (DigestBuffer) + sizeof (TcgPcrEvent2.EventSize), + NewEventHdr->EventSize + ); + + // If scaling is needed and the ACPI event log as been created. Log a + // NO_ACTION_EVENT with the truncation string to the end of it to mark that + // it is now truncated. + if (DynamicScalingNeeded && mReadyToBoot && !mAcpiEventLog.EventLogTruncated) { + InitNoActionEvent (&NoActionEvent, sizeof (TCG_LOG_TRUNCATION_EVENT_STRING)); + EventHdrSize = (UINT32)(sizeof (NoActionEvent.PCRIndex) + + sizeof (NoActionEvent.EventType) + + GetDigestListBinSize ((UINT8 *)&NoActionEvent.Digests) + + sizeof (NoActionEvent.EventSize)); + + OldTpl = gBS->RaiseTPL (TPL_HIGH_LEVEL); + Status = TcgCommLogEvent ( + &mAcpiEventLog, + &NoActionEvent, + EventHdrSize, + (UINT8 *)TCG_LOG_TRUNCATION_EVENT_STRING, + sizeof (TCG_LOG_TRUNCATION_EVENT_STRING) + ); + gBS->RestoreTPL (OldTpl); + + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "Failed to log truncation NO_ACTION_EVENT!\n")); + return Status; + } + + mAcpiEventLog.EventLogTruncated = TRUE; + } + + // Only scale when needed. + if (DynamicScalingNeeded) { + Status = TcgScaleEventLog (&mTcgDxeData.EventLogAreaStruct[Index]); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "Unable to scale the TCG event log!\n")); + return Status; + } + } + + // MU_CHANGE - [END] + // // Enter critical region // @@ -1702,21 +1918,18 @@ SetupEventLog ( for (Index = 0; Index < sizeof (mTcg2EventInfo)/sizeof (mTcg2EventInfo[0]); Index++) { if ((mTcgDxeData.BsCap.SupportedEventLogs & mTcg2EventInfo[Index].LogFormat) != 0) { mTcgDxeData.EventLogAreaStruct[Index].EventLogFormat = mTcg2EventInfo[Index].LogFormat; - if (PcdGet8 (PcdTpm2AcpiTableRev) >= 4) { - Status = gBS->AllocatePages ( - AllocateAnyPages, - EfiACPIMemoryNVS, - EFI_SIZE_TO_PAGES (PcdGet32 (PcdTcgLogAreaMinLen)), - &Lasa - ); - } else { - Status = gBS->AllocatePages ( - AllocateAnyPages, - EfiBootServicesData, - EFI_SIZE_TO_PAGES (PcdGet32 (PcdTcgLogAreaMinLen)), - &Lasa - ); - } + + // MU_CHANGE - [BEGIN] + + // Always allocate BootServicesData + Status = gBS->AllocatePages ( + AllocateAnyPages, + EfiBootServicesData, + EFI_SIZE_TO_PAGES (PcdGet32 (PcdTcgLogAreaMinLen)), + &Lasa + ); + + // MU_CHANGE - [END] if (EFI_ERROR (Status)) { return Status; @@ -2575,6 +2788,191 @@ MeasureSecureBootPolicy ( return; } +// MU_CHANGE - [BEGIN] + +/** + Find the installed TPM2 ACPI table, uninstall it, update LAML/LASA, + and reinstall the table. + + @retval EFI_SUCCESS Table updated successfully. + @retval EFI_NOT_FOUND TPM2 table not found or protocols unavailable. + @retval Other Uninstall or reinstall failed. +**/ +STATIC +EFI_STATUS +UpdateTpm2AcpiTable ( + VOID + ) +{ + EFI_STATUS Status; + EFI_ACPI_SDT_PROTOCOL *AcpiSdt; + EFI_ACPI_TABLE_PROTOCOL *AcpiTable; + UINTN Index; + EFI_ACPI_SDT_HEADER *SdtHeader; + EFI_ACPI_TABLE_VERSION Version; + UINTN TableKey; + EFI_TPM2_ACPI_TABLE_V4 *Tpm2Table; + EFI_TPM2_ACPI_TABLE_V4 *TableCopy; + + Status = gBS->LocateProtocol (&gEfiAcpiSdtProtocolGuid, NULL, (VOID **)&AcpiSdt); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_WARN, "%a: AcpiSdt protocol not found - %r\n", __func__, Status)); + return EFI_NOT_FOUND; + } + + Status = gBS->LocateProtocol (&gEfiAcpiTableProtocolGuid, NULL, (VOID **)&AcpiTable); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_WARN, "%a: AcpiTable protocol not found - %r\n", __func__, Status)); + return EFI_NOT_FOUND; + } + + // Walk installed ACPI tables looking for the TPM2 signature. + Index = 0; + while (TRUE) { + Status = AcpiSdt->GetAcpiTable (Index, &SdtHeader, &Version, &TableKey); + if (EFI_ERROR (Status)) { + // Reached the end of the table list without finding TPM2. + DEBUG ((DEBUG_WARN, "%a: TPM2 table not found\n", __func__)); + return EFI_NOT_FOUND; + } + + if (SdtHeader->Signature == EFI_ACPI_5_0_TRUSTED_COMPUTING_PLATFORM_2_TABLE_SIGNATURE) { + break; + } + + Index++; + } + + // Verify the table is large enough to contain LAML/LASA. + if (SdtHeader->Length < sizeof (EFI_TPM2_ACPI_TABLE_V4)) { + DEBUG ((DEBUG_WARN, "%a: TPM2 table too small for LAML/LASA\n", __func__)); + return EFI_NOT_FOUND; + } + + Tpm2Table = (EFI_TPM2_ACPI_TABLE_V4 *)SdtHeader; + + // Make a copy of the table. + TableCopy = AllocateCopyPool (Tpm2Table->Header.Length, Tpm2Table); + if (TableCopy == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + // Patch LAML/LASA in the copy. + TableCopy->Laml = PcdGet32 (PcdTpm2AcpiTableLaml); + TableCopy->Lasa = PcdGet64 (PcdTpm2AcpiTableLasa); + + // Uninstall the old table. + Status = AcpiTable->UninstallAcpiTable (AcpiTable, TableKey); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: UninstallAcpiTable failed - %r\n", __func__, Status)); + FreePool (TableCopy); + return Status; + } + + // Reinstall with updated values. + Status = AcpiTable->InstallAcpiTable ( + AcpiTable, + TableCopy, + TableCopy->Header.Length, + &TableKey + ); + + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: InstallAcpiTable failed - %r\n", __func__, Status)); + } else { + DEBUG ((DEBUG_INFO, "%a: TPM2 table updated (LAML=0x%x, LASA=0x%lx)\n", __func__, TableCopy->Laml, TableCopy->Lasa)); + } + + FreePool (TableCopy); + return Status; +} + +/** + Compute the full size of the truncation NO_ACTION event (header + payload). + + @retval Size in bytes of the truncation event. +**/ +STATIC +UINTN +GetTruncationEventSize ( + VOID + ) +{ + TCG_PCR_EVENT2_HDR NoActionEvent; + UINT32 EventHdrSize; + + InitNoActionEvent (&NoActionEvent, sizeof (TCG_LOG_TRUNCATION_EVENT_STRING)); + EventHdrSize = (UINT32)(sizeof (NoActionEvent.PCRIndex) + + sizeof (NoActionEvent.EventType) + + GetDigestListBinSize ((UINT8 *)&NoActionEvent.Digests) + + sizeof (NoActionEvent.EventSize)); + + return (UINTN)EventHdrSize + sizeof (TCG_LOG_TRUNCATION_EVENT_STRING); +} + +/** + Generate the ACPI TCG event log. + + @param[in, out] EventLogAreaStruct The event log area data structure. + + @retval EFI_SUCCESS Log was successfully allocated. + @retval EFI_OUT_OF_RESOURCES Allocation failed. + +**/ +STATIC +EFI_STATUS +GenerateAcpiLog ( + IN OUT TCG_EVENT_LOG_AREA_STRUCT *EventLogAreaStruct + ) +{ + EFI_STATUS Status; + EFI_PHYSICAL_ADDRESS AcpiLasa; + UINTN TruncationEventSize; + UINT64 AcpiLaml; + + // Compute the truncation event size so we can reserve space for it in the + // ACPI log. + TruncationEventSize = GetTruncationEventSize (); + AcpiLaml = EventLogAreaStruct->Laml + TruncationEventSize; + + // Allocate the NVS region for the ACPI log (with truncation event headroom). + Status = gBS->AllocatePages ( + AllocateAnyPages, + EfiACPIMemoryNVS, + EFI_SIZE_TO_PAGES ((UINTN)AcpiLaml), + &AcpiLasa + ); + + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "Failed to allocate NVS ACPI log region\n")); + return Status; + } + + // Copy the event log information. + mAcpiEventLog.EventLogFormat = EventLogAreaStruct->EventLogFormat; + mAcpiEventLog.Lasa = AcpiLasa; + mAcpiEventLog.Laml = AcpiLaml; + mAcpiEventLog.EventLogSize = EventLogAreaStruct->EventLogSize; + mAcpiEventLog.LastEvent = (UINT8 *)(UINTN)AcpiLasa + EventLogAreaStruct->EventLogSize; + mAcpiEventLog.EventLogStarted = EventLogAreaStruct->EventLogStarted; + mAcpiEventLog.EventLogTruncated = FALSE; + mAcpiEventLog.Next800155EventOffset = EventLogAreaStruct->Next800155EventOffset; + + // Copy the data to the ACPI log. + CopyMem ((VOID *)(UINTN)AcpiLasa, (VOID *)(UINTN)EventLogAreaStruct->Lasa, (UINTN)mAcpiEventLog.EventLogSize); + + // Update the PCDs. + PcdSet32S (PcdTpm2AcpiTableLaml, (UINT32)AcpiLaml); + PcdSet64S (PcdTpm2AcpiTableLasa, AcpiLasa); + + // Uninstall and reinstall the ACPI table with the updated LAML/LASA. + UpdateTpm2AcpiTable (); + + return Status; +} + +// MU_CHANGE - [END] + /** Ready to Boot Event notification handler. @@ -2593,9 +2991,33 @@ OnReadyToBoot ( { EFI_STATUS Status; TPM_PCRINDEX PcrIndex; + UINTN Index; // MU_CHANGE PERF_FUNCTION_BEGIN (); + // MU_CHANGE - [BEGIN] + + for (Index = 0; Index < ARRAY_SIZE (mTcg2EventInfo); Index++) { + if ((mTcgDxeData.BsCap.SupportedEventLogs & mTcg2EventInfo[Index].LogFormat) != 0) { + switch (mTcg2EventInfo[Index].LogFormat) { + case EFI_TCG2_EVENT_LOG_FORMAT_TCG_1_2: + // Do nothing for TCG1.2. + break; + case EFI_TCG2_EVENT_LOG_FORMAT_TCG_2: + // Only generate the ACPI log once. + if ((PcdGet8 (PcdTpm2AcpiTableRev) >= 4) && !mReadyToBoot) { + GenerateAcpiLog (&mTcgDxeData.EventLogAreaStruct[Index]); + } + + break; + } + } + } + + mReadyToBoot = TRUE; + + // MU_CHANGE - [END] + // MU_CHANGE_23086 // MU_CHANGE [BEGIN] - Call OEM init hook. Status = OemTpm2InitDxeReadyToBootEvent (mBootAttempts); diff --git a/SecurityPkg/Tcg/Tcg2Dxe/Tcg2Dxe.inf b/SecurityPkg/Tcg/Tcg2Dxe/Tcg2Dxe.inf index 74877e13dbe..d98d896b4ac 100644 --- a/SecurityPkg/Tcg/Tcg2Dxe/Tcg2Dxe.inf +++ b/SecurityPkg/Tcg/Tcg2Dxe/Tcg2Dxe.inf @@ -107,6 +107,8 @@ gEfiMpServiceProtocolGuid ## SOMETIMES_CONSUMES gEfiVariableWriteArchProtocolGuid ## NOTIFY gEfiResetNotificationProtocolGuid ## CONSUMES + gEfiAcpiTableProtocolGuid ## SOMETIMES_CONSUMES # MU_CHANGE + gEfiAcpiSdtProtocolGuid ## SOMETIMES_CONSUMES # MU_CHANGE [Pcd] gEfiSecurityPkgTokenSpaceGuid.PcdTpmPlatformClass ## SOMETIMES_CONSUMES diff --git a/SecurityPkg/Tcg/TcgLogTest/README.md b/SecurityPkg/Tcg/TcgLogTest/README.md new file mode 100644 index 00000000000..9e1a91cce07 --- /dev/null +++ b/SecurityPkg/Tcg/TcgLogTest/README.md @@ -0,0 +1,207 @@ +# TcgLogTest + +TcgLogTest validates the dynamic event log scaling functionality implemented +by `Tcg2Dxe`. It consists of a DXE driver (`TcgLogTestDxe`) and a UEFI shell +unit test application (`TcgLogTestApp`) that coordinate across multiple boots +to exercise scaling both before and after `ReadyToBoot`. + +## Components + +### TcgLogTestDxe (DXE_DRIVER) + +A DXE driver that runs pre-ReadyToBoot scaling tests on demand. It installs +the `TCG_LOG_TEST_PROTOCOL` which allows the test application to enable/disable +the tests and retrieve results. + +**Entry flow:** + +1. Installs the `TCG_LOG_TEST_PROTOCOL` on a new handle. +2. Checks the NV variable `TcgLogTestEnable` (existence-based: present = + enabled, absent = disabled). +3. If disabled: + - Returns immediately. The protocol is still available for the test app + to call `Enable` on. +4. If enabled: + - Deletes the enable variable. This makes it so the test only runs once. + - Locates `EFI_TCG2_PROTOCOL`. + - Runs `TestPreReadyToBootScaling`. + - Records results in an internal log buffer which can be acquired via + `GetLog`. + +#### Protocol + +The `TCG_LOG_TEST_PROTOCOL` provides the following function(s): + +| Function | Description | +| -------- | ----------- | +| `GetLog` | Returns a pointer to the DXE driver's internal ASCII log buffer and its size. Returns `EFI_NOT_STARTED` if the test did not run this boot. | +| `Enable` | Creates or deletes the `TcgLogTestEnable` NV variable to enable or disable the DXE test for the next boot. | + +The `TCG_LOG_TEST_PROTOCOL` GUID is defined in `TcgLogTest.h` and declared +in `SecurityPkg.dec`. + +```code +#define TCG_LOG_TEST_PROTOCOL_GUID \ + { 0xA3C12F80, 0x7D9E, 0x4B5A, { 0x91, 0xE4, 0x6C, 0xF8, 0x2D, 0xA1, 0xB7, 0x03 } } +``` + +#### NV Variable + +The enable/disable mechanism uses an NV variable rather than UnitTest saved +context because the DXE driver and the test application are separate binaries. +The DXE driver does not use `UnitTestLib` and cannot access the framework's +persisted state. An NV variable is the standard cross-module communication +channel in UEFI. + +| Attribute | Value | +| --------- | ----- | +| Name | `TcgLogTestEnable` | +| Vendor GUID | `gTcgLogTestProtocolGuid` | +| Attributes | `NV + BS` | +| Semantics | Existence-based: variable present = enabled, variable absent = disabled | + +#### Test: TestPreReadyToBootScaling + +Executed before `ReadyToBoot` when the NV variable is present indicating the +test was enabled. Exercises dynamic scaling before `ReadyToBoot` has fired. + +1. Calls `TcgLogTestLogEventsUntilScaled` to repeatedly log `EV_NO_ACTION` + events to PCR 8 until the event log base address changes (indicating + scaling occurred). +2. Calls `TcgLogTestDumpEventLog` to dump every event in the log and verify + the normal log is **not** truncated. +3. Writes `PASS` or `FAIL` (with details) to the internal log buffer. + +### TcgLogTestApp (UEFI_APPLICATION) + +A UnitTest framework shell application that runs post-ReadyToBoot scaling +tests and collects pre-ReadyToBoot results from the DXE driver. + +#### Test: TestPostReadyToBootScaling + +Executed after `ReadyToBoot` in the UEFI shell. Exercises dynamic scaling +after `ReadyToBoot` has fired. + +1. Retrieves the TPM2 ACPI table's `LAML`/`LASA` values via the ACPI SDT + protocol. +2. Calls `TcgLogTestLogEventsUntilScaled` to repeatedly log `EV_NO_ACTION` + events to PCR 8 until the event log base address changes (indicating + scaling occurred). +3. Calls `TcgLogTestDumpEventLog` to dump every event in the log and verify + the normal log is **not** truncated. +4. Calls `CheckTruncationEvent` to verify the `"TCG Event Log Truncated"` + `NO_ACTION` marker **is** present in the ACPI log (since the ACPI log + cannot scale and should have been marked truncated). + +#### Test: TestPreReadyToBootResults + +Verifies the DXE driver's pre-ReadyToBoot results. + +1. Locates `TCG_LOG_TEST_PROTOCOL` and calls `GetLog`. +2. Dumps the DXE log for visibility. +3. Asserts the log contains `"PASS"` and does not contain `"FAIL"`. + +## Three-Boot Reboot Flow + +The tests require three boots to complete because scaling must be tested in +two different phases of the boot process, and each phase requires a separate +boot. The final boot should guarantee that the TCG event log is not polluted +with the test `NO_ACTION_EVENT` events used to scale the log. + +```text +Boot 1 (TestApp Test) +├── TcgLogTestDxe: +│ ├── Installs TCG_LOG_TEST_PROTOCOL. +│ ├── NV variable absent → Test not enabled → SKIPPED. +├── TcgLogTestApp: +│ ├── Launched from UEFI shell. (UnitTest Framework) +│ ├── Test Prerequisites: +│ │ └── Calls LocateProtocols() to locate the TCG2 and TcgLogTest protocols. +│ ├── Calls TestPostReadyToBootScaling(): +│ │ ├── Calls TcgLogTestLogEventsUntilScaled() to scale the event log. +│ │ ├── Verifies the log was not truncated. +│ │ ├── Checks ACPI truncation event. +│ │ └── PASS. +│ └── Test Cleanup: +│ └── Calls EnableDxeTestAndReboot(). +│ ├── Calls Enable (TRUE) to create the NV variable. +│ └── Calls SaveAndReboot() to SaveFrameworkState + EfiResetCold. +│ +Boot 2 (DXE Driver Test) +├── TcgLogTestDxe: +│ ├── Installs TCG_LOG_TEST_PROTOCOL. +│ ├── NV variable present → Test enabled → Deletes the NV variable → Runs. +│ ├── Calls TestPreReadyToBootScaling(): +│ │ ├── Calls TcgLogTestLogEventsUntilScaled() to scale the event log. +│ │ ├── Verifies the log was not truncated. +│ │ ├── PASS. +│ │ └── Logs results into internal buffer for later access via GetLog(). +├── TcgLogTestApp: +│ ├── Resumes execution from UEFI shell. (UnitTest Framework) +│ ├── Test Prerequisite: +│ │ └── SKIPPED. +│ ├── Calls TestPostReadyToBootScaling(): +│ │ └── Already PASSED in saved state → SKIPPED. +│ ├── Calls TestPreReadyToBootResults(): +│ │ ├── Locates TcgLogTest protocol. +│ │ ├── Calls GetLog() to acquire the TcgLogTestDxe log. +│ │ ├── Verifies PASS in TcgLogTestDxe log. +│ │ └── PASS. +│ └── Test Cleanup: +│ └── Calls SaveAndReboot() to SaveFrameworkState + EfiResetCold. +│ +Boot 3 (Final Report/Results) +├── TcgLogTestDxe: +│ ├── Installs TCG_LOG_TEST_PROTOCOL. +│ ├── NV variable absent → Test not enabled → Exit. +├── TcgLogTestApp: +│ ├── Resumes execution from UEFI shell. (UnitTest Framework) +│ ├── Both tests already PASSED → SKIPPED. +│ └── Reports final results, cleans up framework state. +``` + +## Shared Code (TcgLogTestCommon) + +Common functions compiled into both binaries: + +| Function | Description | +| -------- | ----------- | +| `TcgLogTestAdvanceEvent` | Parses one TCG 2.0 event entry, advancing the pointer to the next event. Handles SHA-1/256/384/512/SM3 digest algorithms. | +| `TcgLogTestLogEventsUntilScaled` | Builds a test event and logs it repeatedly via `HashLogExtendEvent` until `GetEventLog` reports a different base address. | +| `TcgLogTestDumpEventLog` | Calls `GetEventLog`, walks the entire log (skipping the TCG 1.2 SpecID header), and prints each event's index, PCR index, event type, and event size via `DEBUG`. Returns the truncation status. | + +## Platform Integration + +### DSC + +Add both modules to the platform DSC under the `[Components]` section, +typically gated behind a TPM enable flag: + +```ini +!if $(TPM2_ENABLE) == TRUE + SecurityPkg/Tcg/TcgLogTest/TcgLogTestDxe.inf + SecurityPkg/Tcg/TcgLogTest/TcgLogTestApp.inf +!endif +``` + +### FDF + +Add both modules to the platform FDF so they are included in the firmware +volume, typically gated behind a TPM enable flag. The DXE driver must be in +the DXE FV so it loads during DXE dispatch. The test application can be in +the same FV or a separate one accessible from the UEFI shell: + +```ini +!if $(TPM2_ENABLE) == TRUE + INF SecurityPkg/Tcg/TcgLogTest/TcgLogTestDxe.inf + INF SecurityPkg/Tcg/TcgLogTest/TcgLogTestApp.inf +!endif +``` + +### Running the Test + +1. Boot to the UEFI shell. +2. Run the test application: `TcgLogTestApp.efi` +3. The system will automatically reboot twice more to complete the three-boot + flow. +4. On the third boot, the framework reports final results to the shell. diff --git a/SecurityPkg/Tcg/TcgLogTest/TcgLogTest.h b/SecurityPkg/Tcg/TcgLogTest/TcgLogTest.h new file mode 100644 index 00000000000..ee5795cc41c --- /dev/null +++ b/SecurityPkg/Tcg/TcgLogTest/TcgLogTest.h @@ -0,0 +1,67 @@ +/** @file + TCG Log Test protocol definition. + + Defines the protocol produced by TcgLogTestDxe that allows the TcgLogTestApp + to retrieve pre-ReadyToBoot test results and to enable/disable the DXE test + via an NV variable. + + Copyright (c), Microsoft Corporation. + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#ifndef TCG_LOG_TEST_H_ +#define TCG_LOG_TEST_H_ + +#include + +#define TCG_LOG_TEST_PROTOCOL_GUID \ + { 0xA3C12F80, 0x7D9E, 0x4B5A, { 0x91, 0xE4, 0x6C, 0xF8, 0x2D, 0xA1, 0xB7, 0x03 } } + +#define TCG_LOG_TEST_ENABLE_VARIABLE_NAME L"TcgLogTestEnable" + +typedef struct _TCG_LOG_TEST_PROTOCOL TCG_LOG_TEST_PROTOCOL; + +/** + Retrieve the pre-ReadyToBoot test log produced by TcgLogTestDxe. + + @param[in] This Protocol instance. + @param[out] LogBuffer Pointer to the internal log buffer (NULL-terminated). + @param[out] LogSize Number of valid bytes in LogBuffer (including NULL). + + @retval EFI_SUCCESS Log data returned. + @retval EFI_NOT_STARTED The DXE test did not run this boot. + @retval EFI_INVALID_PARAMETER NULL pointer supplied. +**/ +typedef +EFI_STATUS +(EFIAPI *TCG_LOG_TEST_GET_LOG)( + IN TCG_LOG_TEST_PROTOCOL *This, + OUT CHAR8 **LogBuffer, + OUT UINTN *LogSize + ); + +/** + Enable or disable the DXE pre-ReadyToBoot test for the next boot by + writing an NV variable. + + @param[in] This Protocol instance. + @param[in] Enable TRUE to enable the test on next boot, FALSE to disable. + + @retval EFI_SUCCESS Variable written successfully. + @retval Other SetVariable failure. +**/ +typedef +EFI_STATUS +(EFIAPI *TCG_LOG_TEST_ENABLE)( + IN TCG_LOG_TEST_PROTOCOL *This, + IN BOOLEAN Enable + ); + +struct _TCG_LOG_TEST_PROTOCOL { + TCG_LOG_TEST_GET_LOG GetLog; + TCG_LOG_TEST_ENABLE Enable; +}; + +extern EFI_GUID gTcgLogTestProtocolGuid; + +#endif // TCG_LOG_TEST_H_ diff --git a/SecurityPkg/Tcg/TcgLogTest/TcgLogTestApp.c b/SecurityPkg/Tcg/TcgLogTest/TcgLogTestApp.c new file mode 100644 index 00000000000..ec6b1ab6348 --- /dev/null +++ b/SecurityPkg/Tcg/TcgLogTest/TcgLogTestApp.c @@ -0,0 +1,369 @@ +/** @file + UEFI Shell UnitTest application that validates TCG2 event log dynamic + scaling after ReadyToBoot. + + This application locates the TcgLogTestProtocol produced by TcgLogTestDxe to + retrieve pre-ReadyToBoot test logs, then exercises post-ReadyToBoot scaling + and verifies the truncation marker is present in the ACPI event log. + + Copyright (c), Microsoft Corporation. + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "TcgLogTest.h" +#include "TcgLogTestCommon.h" + +#define UNIT_TEST_NAME "TCG Log Scaling Test" +#define UNIT_TEST_VERSION "1.0" + +STATIC EFI_TCG2_PROTOCOL *mTcg2Protocol = NULL; +STATIC TCG_LOG_TEST_PROTOCOL *mTcgLogTestProtocol = NULL; + +/** + Look up the installed TPM2 ACPI table and return its LAML/LASA values. + + @param[out] Laml Log Area Minimum Length from the ACPI table. + @param[out] Lasa Log Area Start Address from the ACPI table. + + @retval EFI_SUCCESS Table found and values returned. + @retval EFI_NOT_FOUND TPM2 table not installed or too small. + @retval EFI_INVALID_PARAMETER Protocol not installed. +**/ +STATIC +EFI_STATUS +GetTpm2AcpiTableLogData ( + OUT UINT32 *Laml, + OUT EFI_PHYSICAL_ADDRESS *Lasa + ) +{ + EFI_STATUS Status; + EFI_ACPI_SDT_PROTOCOL *AcpiSdt; + EFI_ACPI_SDT_HEADER *SdtHeader; + EFI_ACPI_TABLE_VERSION Version; + UINTN TableKey; + UINTN Index; + EFI_TPM2_ACPI_TABLE_V4 *Tpm2Table; + + Status = gBS->LocateProtocol (&gEfiAcpiSdtProtocolGuid, NULL, (VOID **)&AcpiSdt); + if (EFI_ERROR (Status)) { + return Status; + } + + // Walk the installed ACPI tables looking for the TPM2 signature. + Index = 0; + while (TRUE) { + Status = AcpiSdt->GetAcpiTable (Index, &SdtHeader, &Version, &TableKey); + if (EFI_ERROR (Status)) { + // Reached the end of the table list without finding TPM2. + DEBUG ((DEBUG_ERROR, "%a: TPM2 table not found\n", __func__)); + return EFI_NOT_FOUND; + } + + if (SdtHeader->Signature == EFI_ACPI_5_0_TRUSTED_COMPUTING_PLATFORM_2_TABLE_SIGNATURE) { + break; + } + + Index++; + } + + if (SdtHeader->Length < sizeof (EFI_TPM2_ACPI_TABLE_V4)) { + return EFI_NOT_FOUND; + } + + Tpm2Table = (EFI_TPM2_ACPI_TABLE_V4 *)SdtHeader; + *Laml = Tpm2Table->Laml; + *Lasa = (EFI_PHYSICAL_ADDRESS)Tpm2Table->Lasa; + + return EFI_SUCCESS; +} + +/** + Walk the ACPI event log and check whether it contains a NO_ACTION event + whose payload matches the given truncation string. + + The ACPI log starts with a TCG 1.2 format SpecID event (TCG_PCR_EVENT_HDR + with a fixed 20-byte SHA1 digest), followed by TCG 2.0 format events. + This function skips the SpecID header, then walks all TCG 2.0 events + looking for the truncation marker. + + @param[in] Lasa Log Area Start Address (ACPI log base). + @param[in] Laml Log Area Min Length (ACPI log size). + + @retval TRUE A matching NO_ACTION truncation event was found. + @retval FALSE No match or log could not be parsed. +**/ +BOOLEAN +CheckTruncationEvent ( + IN EFI_PHYSICAL_ADDRESS Lasa, + IN UINTN Laml + ) +{ + UINT8 *CurrentEvent; + UINT8 *EndOfLog; + UINT32 PcrIndex; + UINT32 EventType; + UINT32 EventSize; + UINT8 *EventData; + UINT32 EventDataLen; + UINT32 SpecIdEventSize; + CONST CHAR8 *TruncEventStr = TCG_LOG_TRUNCATION_EVENT_STRING; + + // Verify the input parameters. + if ((Lasa == 0) || (Laml == 0) || (TruncEventStr == NULL)) { + return FALSE; + } + + CurrentEvent = (UINT8 *)(UINTN)Lasa; + EndOfLog = CurrentEvent + Laml; + EventDataLen = (UINT32)AsciiStrSize (TruncEventStr); + + // Skip the first event which is the TCG 1.2 format SpecID event: + // PCRIndex (4) + EventType (4) + Digest (20 = SHA1) + EventSize (4) + Event[EventSize] + if ((UINTN)(EndOfLog - CurrentEvent) < sizeof (TCG_PCR_EVENT_HDR)) { + return FALSE; + } + + SpecIdEventSize = ((TCG_PCR_EVENT_HDR *)CurrentEvent)->EventSize; + if ((UINTN)(EndOfLog - CurrentEvent) < sizeof (TCG_PCR_EVENT_HDR) + SpecIdEventSize) { + return FALSE; + } + + CurrentEvent += sizeof (TCG_PCR_EVENT_HDR) + SpecIdEventSize; + + // Walk all TCG 2.0 events looking for the truncation marker. + while (TcgLogTestAdvanceEvent (&CurrentEvent, EndOfLog, &PcrIndex, &EventType, &EventSize, &EventData)) { + if ((EventType == EV_NO_ACTION) && (PcrIndex == 0) && (EventSize == EventDataLen)) { + if (CompareMem (TruncEventStr, EventData, EventDataLen) == 0) { + return TRUE; + } + } + } + + return FALSE; +} + +/** + Test that the DXE driver ran and its pre-ReadyToBoot log contains PASS. + + This runs on the second boot after TestPostReadyToBootScaling enabled the + DXE driver and rebooted. The DXE driver ran before ReadyToBoot on this + boot, so results are available via the protocol. + + @param[in] Context Unit test context (unused). + + @retval UNIT_TEST_PASSED Log contains PASS and no FAIL. + @retval UNIT_TEST_ERROR_TEST_FAILED Assertion failed. +**/ +UNIT_TEST_STATUS +EFIAPI +TestPreReadyToBootResults ( + IN UNIT_TEST_CONTEXT Context + ) +{ + EFI_STATUS Status; + CHAR8 *LogBuffer; + UINTN LogSize; + + // The prerequisite is skipped on resume from a reboot, so locate the + // protocol here if it was not already set. + if (mTcgLogTestProtocol == NULL) { + Status = gBS->LocateProtocol (&gTcgLogTestProtocolGuid, NULL, (VOID **)&mTcgLogTestProtocol); + UT_ASSERT_NOT_EFI_ERROR (Status); + } + + Status = mTcgLogTestProtocol->GetLog (mTcgLogTestProtocol, &LogBuffer, &LogSize); + if (EFI_ERROR (Status)) { + UT_LOG_ERROR ("GetLog failed: %r\n", Status); + } + + UT_ASSERT_NOT_EFI_ERROR (Status); + UT_ASSERT_NOT_NULL (LogBuffer); + UT_ASSERT_TRUE (LogSize > 1); + + // Dump the DXE driver's log for visibility. + UT_LOG_INFO ("TcgLogTestDxe Log (%u bytes):\n%a\n", LogSize, LogBuffer); + + // Verify the log contains "PASS". + UT_ASSERT_NOT_NULL (AsciiStrStr (LogBuffer, "PASS")); + + // Verify the log does not contain "FAIL". + UT_ASSERT_TRUE (AsciiStrStr (LogBuffer, "FAIL") == NULL); + + return UNIT_TEST_PASSED; +} + +/** + Test post-ReadyToBoot scaling: log events until the log scales, then verify + the truncation marker is present in the ACPI log region. + + @param[in] Context Unit test context (unused). + + @retval UNIT_TEST_PASSED Scaling and truncation marker verified. + @retval UNIT_TEST_ERROR_TEST_FAILED Assertion failed. +**/ +UNIT_TEST_STATUS +EFIAPI +TestPostReadyToBootScaling ( + IN UNIT_TEST_CONTEXT Context + ) +{ + EFI_STATUS Status; + BOOLEAN Scaled; + BOOLEAN Truncated; + EFI_PHYSICAL_ADDRESS AcpiLasa; + UINT32 AcpiLaml; + + Status = GetTpm2AcpiTableLogData (&AcpiLaml, &AcpiLasa); + UT_ASSERT_NOT_EFI_ERROR (Status); + UT_ASSERT_TRUE (AcpiLasa != 0); + UT_ASSERT_TRUE (AcpiLaml != 0); + + Status = TcgLogTestLogEventsUntilScaled (mTcg2Protocol, &Scaled); + UT_ASSERT_NOT_EFI_ERROR (Status); + UT_ASSERT_TRUE (Scaled); + + Status = TcgLogTestDumpEventLog (mTcg2Protocol, &Truncated); + UT_ASSERT_NOT_EFI_ERROR (Status); + + // The normal event log must not be truncated after scaling. + UT_ASSERT_FALSE (Truncated); + + UT_LOG_INFO ("Post-ReadyToBoot scaling succeeded\n"); + + // Verify truncation marker in ACPI log. + UT_ASSERT_TRUE (CheckTruncationEvent (AcpiLasa, (UINTN)AcpiLaml)); + + return UNIT_TEST_PASSED; +} + +/** + Save the unit test framework state and perform a cold reboot. + + @param[in] Context Unit test context (unused). +**/ +STATIC +VOID +EFIAPI +SaveAndReboot ( + IN UNIT_TEST_CONTEXT Context + ) +{ + SaveFrameworkState (NULL, 0); + gRT->ResetSystem (EfiResetCold, EFI_SUCCESS, 0, NULL); +} + +/** + Cleanup for TestPostReadyToBootScaling: enable the DXE pre-ReadyToBoot test + for the next boot, then save and reboot so the DXE driver runs before + ReadyToBoot on the second boot. + + @param[in] Context Unit test context (unused). +**/ +STATIC +VOID +EFIAPI +EnableDxeTestAndReboot ( + IN UNIT_TEST_CONTEXT Context + ) +{ + EFI_STATUS Status; + + if (mTcgLogTestProtocol != NULL) { + Status = mTcgLogTestProtocol->Enable (mTcgLogTestProtocol, TRUE); + DEBUG ((DEBUG_INFO, "%a: Enable (TRUE) - %r\n", __func__, Status)); + } else { + DEBUG ((DEBUG_ERROR, "%a: mTcgLogTestProtocol is NULL, cannot enable\n", __func__)); + } + + SaveAndReboot (Context); +} + +/** + Prerequisite: locate the TCG2 and TcgLogTest protocols. + + @param[in] Context Unit test context (unused). + + @retval UNIT_TEST_PASSED Protocols located. + @retval UNIT_TEST_ERROR_PREREQUISITE_NOT_MET Protocol not found. +**/ +UNIT_TEST_STATUS +EFIAPI +LocateProtocols ( + IN UNIT_TEST_CONTEXT Context + ) +{ + EFI_STATUS Status; + + Status = gBS->LocateProtocol (&gEfiTcg2ProtocolGuid, NULL, (VOID **)&mTcg2Protocol); + if (EFI_ERROR (Status)) { + return UNIT_TEST_ERROR_PREREQUISITE_NOT_MET; + } + + Status = gBS->LocateProtocol (&gTcgLogTestProtocolGuid, NULL, (VOID **)&mTcgLogTestProtocol); + if (EFI_ERROR (Status)) { + return UNIT_TEST_ERROR_PREREQUISITE_NOT_MET; + } + + return UNIT_TEST_PASSED; +} + +/** + Entry point for TcgLogTestApp. + + @param[in] ImageHandle Image handle. + @param[in] SystemTable Pointer to the System Table. + + @retval EFI_SUCCESS Tests dispatched and framework freed. + @retval Other Framework initialization failed. +**/ +EFI_STATUS +EFIAPI +TcgLogTestAppEntry ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable + ) +{ + EFI_STATUS Status; + UNIT_TEST_FRAMEWORK_HANDLE Framework; + UNIT_TEST_SUITE_HANDLE Suite; + + Framework = NULL; + + Status = InitUnitTestFramework (&Framework, UNIT_TEST_NAME, gEfiCallerBaseName, UNIT_TEST_VERSION); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: InitUnitTestFramework failed: %r\n", __func__, Status)); + return Status; + } + + Status = CreateUnitTestSuite (&Suite, Framework, "TCG Log Scaling Tests", "TcgLogTest", NULL, NULL); + if (EFI_ERROR (Status)) { + goto Done; + } + + AddTestCase (Suite, "Post-ReadyToBoot scaling produces truncation event", "PostRtbScaling", TestPostReadyToBootScaling, LocateProtocols, EnableDxeTestAndReboot, NULL); + AddTestCase (Suite, "Pre-ReadyToBoot DXE results contain PASS", "PreRtbResults", TestPreReadyToBootResults, LocateProtocols, SaveAndReboot, NULL); + + Status = RunAllTestSuites (Framework); + +Done: + if (Framework != NULL) { + FreeUnitTestFramework (Framework); + } + + return Status; +} diff --git a/SecurityPkg/Tcg/TcgLogTest/TcgLogTestApp.inf b/SecurityPkg/Tcg/TcgLogTest/TcgLogTestApp.inf new file mode 100644 index 00000000000..46cd39cdc4a --- /dev/null +++ b/SecurityPkg/Tcg/TcgLogTest/TcgLogTestApp.inf @@ -0,0 +1,43 @@ +## @file +# UEFI Shell application that validates TCG2 event log dynamic scaling +# after ReadyToBoot using the UnitTest framework. +# +# Copyright (c), Microsoft Corporation. +# SPDX-License-Identifier: BSD-2-Clause-Patent +## + +[Defines] + INF_VERSION = 0x00010006 + BASE_NAME = TcgLogTestApp + FILE_GUID = 1F3A9C52-6E8B-4D07-B2A3-8C4E7F1D5E90 + MODULE_TYPE = UEFI_APPLICATION + VERSION_STRING = 1.0 + ENTRY_POINT = TcgLogTestAppEntry + +[Sources] + TcgLogTestApp.c + TcgLogTestCommon.c + TcgLogTestCommon.h + TcgLogTest.h + +[Packages] + MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec + SecurityPkg/SecurityPkg.dec + UnitTestFrameworkPkg/UnitTestFrameworkPkg.dec + +[LibraryClasses] + BaseLib + BaseMemoryLib + DebugLib + MemoryAllocationLib + UefiApplicationEntryPoint + UefiBootServicesTableLib + UefiLib + UefiRuntimeServicesTableLib + UnitTestLib + +[Protocols] + gEfiTcg2ProtocolGuid ## CONSUMES + gEfiAcpiSdtProtocolGuid ## SOMETIMES_CONSUMES + gTcgLogTestProtocolGuid ## CONSUMES diff --git a/SecurityPkg/Tcg/TcgLogTest/TcgLogTestCommon.c b/SecurityPkg/Tcg/TcgLogTest/TcgLogTestCommon.c new file mode 100644 index 00000000000..e7166fdf877 --- /dev/null +++ b/SecurityPkg/Tcg/TcgLogTest/TcgLogTestCommon.c @@ -0,0 +1,499 @@ +/** @file + TCG Log Test common implementation shared by TcgLogTestDxe and TcgLogTestApp. + + Contains event log walking, truncation event checking, log-until-scaled + logic, and TPM2 ACPI table lookup. + + Copyright (c), Microsoft Corporation. + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include +#include +#include +#include +#include +#include +#include + +#include "TcgLogTestCommon.h" + +#define TCG_LOG_TEST_PCR_INDEX 8 +#define TCG_LOG_TEST_EVENT_TYPE EV_NO_ACTION +#define TCG_LOG_TEST_EVENT_PAYLOAD "TcgLogTestDxeEvent" + +STATIC CHAR8 mCommonEventPayload[] = TCG_LOG_TEST_EVENT_PAYLOAD; + +/** + Converts EventType to ASCII string. + + @param[in] EventType TCG_EVENTTYPE value. + + @retval ASCII string describing the event type. +**/ +STATIC +CHAR8 * +TcgEventTypeToString ( + IN UINT32 EventType + ) +{ + switch (EventType) { + case EV_PREBOOT_CERT: + return "EV_PREBOOT_CERT"; + case EV_POST_CODE: + return "EV_POST_CODE"; + case EV_NO_ACTION: + return "EV_NO_ACTION"; + case EV_SEPARATOR: + return "EV_SEPARATOR"; + case EV_ACTION: + return "EV_ACTION"; + case EV_EVENT_TAG: + return "EV_EVENT_TAG"; + case EV_S_CRTM_CONTENTS: + return "EV_S_CRTM_CONTENTS"; + case EV_S_CRTM_VERSION: + return "EV_S_CRTM_VERSION"; + case EV_CPU_MICROCODE: + return "EV_CPU_MICROCODE"; + case EV_PLATFORM_CONFIG_FLAGS: + return "EV_PLATFORM_CONFIG_FLAGS"; + case EV_TABLE_OF_DEVICES: + return "EV_TABLE_OF_DEVICES"; + case EV_COMPACT_HASH: + return "EV_COMPACT_HASH"; + case EV_NONHOST_CODE: + return "EV_NONHOST_CODE"; + case EV_NONHOST_CONFIG: + return "EV_NONHOST_CONFIG"; + case EV_NONHOST_INFO: + return "EV_NONHOST_INFO"; + case EV_OMIT_BOOT_DEVICE_EVENTS: + return "EV_OMIT_BOOT_DEVICE_EVENTS"; + case EV_EFI_VARIABLE_DRIVER_CONFIG: + return "EV_EFI_VARIABLE_DRIVER_CONFIG"; + case EV_EFI_VARIABLE_BOOT: + return "EV_EFI_VARIABLE_BOOT"; + case EV_EFI_BOOT_SERVICES_APPLICATION: + return "EV_EFI_BOOT_SERVICES_APPLICATION"; + case EV_EFI_BOOT_SERVICES_DRIVER: + return "EV_EFI_BOOT_SERVICES_DRIVER"; + case EV_EFI_RUNTIME_SERVICES_DRIVER: + return "EV_EFI_RUNTIME_SERVICES_DRIVER"; + case EV_EFI_GPT_EVENT: + return "EV_EFI_GPT_EVENT"; + case EV_EFI_ACTION: + return "EV_EFI_ACTION"; + case EV_EFI_PLATFORM_FIRMWARE_BLOB: + return "EV_EFI_PLATFORM_FIRMWARE_BLOB"; + case EV_EFI_HANDOFF_TABLES: + return "EV_EFI_HANDOFF_TABLES"; + case EV_EFI_PLATFORM_FIRMWARE_BLOB2: + return "EV_EFI_PLATFORM_FIRMWARE_BLOB2"; + case EV_EFI_HANDOFF_TABLES2: + return "EV_EFI_HANDOFF_TABLES2"; + case EV_EFI_HCRTM_EVENT: + return "EV_EFI_HCRTM_EVENT"; + case EV_EFI_VARIABLE_AUTHORITY: + return "EV_EFI_VARIABLE_AUTHORITY"; + case EV_EFI_SPDM_FIRMWARE_BLOB: + return "EV_EFI_SPDM_FIRMWARE_BLOB"; + case EV_EFI_SPDM_FIRMWARE_CONFIG: + return "EV_EFI_SPDM_FIRMWARE_CONFIG"; + case EV_EFI_SPDM_DEVICE_POLICY: + return "EV_EFI_SPDM_DEVICE_POLICY"; + case EV_EFI_SPDM_DEVICE_AUTHORITY: + return "EV_EFI_SPDM_DEVICE_AUTHORITY"; + default: + return "UNKNOWN"; + } +} + +/** + Allocate and initialize an EFI_TCG2_EVENT structure with a fixed payload. + + @param[out] Event On success, pointer to the allocated event. Caller must + free with FreePool(). + + @retval EFI_SUCCESS Event allocated and initialized. + @retval EFI_OUT_OF_RESOURCES Allocation failed. +**/ +STATIC +EFI_STATUS +TcgLogTestBuildEvent ( + OUT EFI_TCG2_EVENT **Event + ) +{ + UINT32 PayloadSize; + UINT32 TotalSize; + EFI_TCG2_EVENT *TestEvent; + + PayloadSize = (UINT32)AsciiStrSize (mCommonEventPayload); + TotalSize = (UINT32)(sizeof (EFI_TCG2_EVENT) - sizeof (TestEvent->Event) + PayloadSize); + + TestEvent = AllocateZeroPool (TotalSize); + if (TestEvent == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + TestEvent->Size = TotalSize; + TestEvent->Header.HeaderSize = sizeof (EFI_TCG2_EVENT_HEADER); + TestEvent->Header.HeaderVersion = EFI_TCG2_EVENT_HEADER_VERSION; + TestEvent->Header.PCRIndex = TCG_LOG_TEST_PCR_INDEX; + TestEvent->Header.EventType = TCG_LOG_TEST_EVENT_TYPE; + + CopyMem (TestEvent->Event, mCommonEventPayload, PayloadSize); + + *Event = TestEvent; + return EFI_SUCCESS; +} + +/** + Log a single event via the TCG2 protocol. + + @param[in] Tcg2Protocol TCG2 protocol instance. + @param[in] Event Pre-built TCG2 event structure. + + @retval EFI_SUCCESS Operation completed successfully. + @retval EFI_OUT_OF_RESOURCES No enough memory to log the new event. + @retval EFI_DEVICE_ERROR The command was unsuccessful. +**/ +STATIC +EFI_STATUS +TcgLogTestLogEvent ( + IN EFI_TCG2_PROTOCOL *Tcg2Protocol, + IN EFI_TCG2_EVENT *Event + ) +{ + return Tcg2Protocol->HashLogExtendEvent ( + Tcg2Protocol, + 0, + (EFI_PHYSICAL_ADDRESS)(UINTN)mCommonEventPayload, + AsciiStrSize (mCommonEventPayload), + Event + ); +} + +/** + Advance one entry in the event log. + + @param[in,out] CurrentEvent On entry, points to the start of the event (PCRIndex). + On success, updated to point to the next event. + @param[in] LogEnd One byte past the end of the log buffer. + @param[out] PcrIndex PCRIndex of the parsed event. + @param[out] EventType EventType of the parsed event. + @param[out] EventSize Size of the event data payload. + @param[out] EventData Pointer to the event data payload. + + @retval TRUE Event parsed successfully and CurrentEvent updated. + @retval FALSE Invalid pointers, buffer, or digest algorithm. +**/ +BOOLEAN +TcgLogTestAdvanceEvent ( + IN OUT UINT8 **CurrentEvent, + IN UINT8 *LogEnd, + OUT UINT32 *PcrIndex OPTIONAL, + OUT UINT32 *EventType OPTIONAL, + OUT UINT32 *EventSize OPTIONAL, + OUT UINT8 **EventData OPTIONAL + ) +{ + UINT32 DigestCount; + UINT32 DigestIndex; + UINT16 AlgId; + UINT32 DigestLen; + UINT32 Size; + UINT8 *EventPtr; + + // Verify the required pointers are valid + if ((CurrentEvent == NULL) || (LogEnd == NULL)) { + DEBUG ((DEBUG_ERROR, "%a: Invalid input parameters\n", __func__)); + return FALSE; + } + + // Start on the current event. + EventPtr = *CurrentEvent; + + // Verify there are 8 bytes (PCRIndex (4 bytes) + EventType (4 bytes)) + // in the log before attempting to read. + if ((UINTN)(LogEnd - EventPtr) < sizeof (UINT32) + sizeof (UINT32)) { + DEBUG ((DEBUG_ERROR, "%a: PCRIndex & EventType invalid\n", __func__)); + return FALSE; + } + + // Store the PCRIndex, if provided. + if (PcrIndex != NULL) { + *PcrIndex = *(UINT32 *)EventPtr; + } + + // Store the EventType, if provided. + if (EventType != NULL) { + *EventType = *(UINT32 *)(EventPtr + sizeof (UINT32)); + } + + // Move the pointer past the PcrIndex and EventType. + EventPtr += sizeof (UINT32) + sizeof (UINT32); + + // Verify there are 4 bytes (DigestCount (4 bytes)) in the log before + // attempting to read. + // TPML_DIGEST_VALUES = DigestCount followed by (AlgId + Digest) pairs. + if ((UINTN)(LogEnd - EventPtr) < sizeof (UINT32)) { + DEBUG ((DEBUG_ERROR, "%a: DigestCount invalid\n", __func__)); + return FALSE; + } + + // Acquire the DigestCount. + DigestCount = *(UINT32 *)EventPtr; + + // Move the pointer past the DigestCount. + EventPtr += sizeof (UINT32); + + // Loop through the number of digests. + for (DigestIndex = 0; DigestIndex < DigestCount; DigestIndex++) { + // Verify there are 2 bytes (AlgId (2 bytes)) in the log + // before attempting to read. + if ((UINTN)(LogEnd - EventPtr) < sizeof (UINT16)) { + DEBUG ((DEBUG_ERROR, "%a: AlgId invalid\n", __func__)); + return FALSE; + } + + // Acquire the AlgId. + AlgId = *(UINT16 *)EventPtr; + + // Move the pointer past the AlgId. + EventPtr += sizeof (UINT16); + + // DigestLen depends on the AlgId. + switch (AlgId) { + case TPM_ALG_SHA1: + DigestLen = SHA1_DIGEST_SIZE; + break; + case TPM_ALG_SHA256: + DigestLen = SHA256_DIGEST_SIZE; + break; + case TPM_ALG_SHA384: + DigestLen = SHA384_DIGEST_SIZE; + break; + case TPM_ALG_SHA512: + DigestLen = SHA512_DIGEST_SIZE; + break; + case TPM_ALG_SM3_256: + DigestLen = SM3_256_DIGEST_SIZE; + break; + default: + DEBUG ((DEBUG_ERROR, "%a: Unknown AlgId 0x%x\n", __func__, AlgId)); + return FALSE; + } + + // Verify there are DigestLen bytes in the log. + if ((UINTN)(LogEnd - EventPtr) < DigestLen) { + DEBUG ((DEBUG_ERROR, "%a: DigestLen invalid\n", __func__)); + return FALSE; + } + + // Move the pointer past the Digest based on the DigestLen. + EventPtr += DigestLen; + } + + // Verify there are 4 bytes (EventSize (4 bytes)) in the log + // before attempting to read. + if ((UINTN)(LogEnd - EventPtr) < sizeof (UINT32)) { + DEBUG ((DEBUG_ERROR, "%a: EventSize invalid\n", __func__)); + return FALSE; + } + + // Acquire the size of the event. + Size = *(UINT32 *)EventPtr; + + // Move the pointer past the event size + EventPtr += sizeof (UINT32); + + // Verify there are EventSize bytes in the log. + if ((UINTN)(LogEnd - EventPtr) < Size) { + DEBUG ((DEBUG_ERROR, "%a: Size of event invalid\n", __func__)); + return FALSE; + } + + // Store the EventSize, if provided. + if (EventSize != NULL) { + *EventSize = Size; + } + + // Store the EventData, if provided. + if (EventData != NULL) { + *EventData = EventPtr; + } + + // Move the pointer to next Event. + EventPtr += Size; + + // Update the current event pointer. + *CurrentEvent = EventPtr; + + return TRUE; +} + +/** + Log events until the event log base address changes (i.e. dynamic scaling + occurs) or an error is returned. + + @param[in] Tcg2Protocol TCG2 protocol instance. + @param[out] Scaled TRUE if scaling was detected. + + @retval EFI_SUCCESS Scaling detected and events logged successfully. + @retval EFI_INVALID_PARAMETER One or more invalid parameters. +**/ +EFI_STATUS +TcgLogTestLogEventsUntilScaled ( + IN EFI_TCG2_PROTOCOL *Tcg2Protocol, + OUT BOOLEAN *Scaled + ) +{ + EFI_STATUS Status; + EFI_PHYSICAL_ADDRESS LocationBefore; + EFI_PHYSICAL_ADDRESS LocationAfter; + EFI_PHYSICAL_ADDRESS LastEntry; + BOOLEAN Truncated; + EFI_TCG2_EVENT *Event; + + // Validate the input parameters. + if ((Tcg2Protocol == NULL) || (Scaled == NULL)) { + return EFI_INVALID_PARAMETER; + } + + *Scaled = FALSE; + + // Build a test event. + Status = TcgLogTestBuildEvent (&Event); + if (EFI_ERROR (Status)) { + return Status; + } + + // Get the event log before scaling. + Status = Tcg2Protocol->GetEventLog ( + Tcg2Protocol, + EFI_TCG2_EVENT_LOG_FORMAT_TCG_2, + &LocationBefore, + &LastEntry, + &Truncated + ); + + if (EFI_ERROR (Status)) { + FreePool (Event); + return Status; + } + + // Log multiple events until scaling occurs. + do { + Status = TcgLogTestLogEvent (Tcg2Protocol, Event); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: LogEvent failed - %r\n", __func__, Status)); + FreePool (Event); + return Status; + } + + Status = Tcg2Protocol->GetEventLog ( + Tcg2Protocol, + EFI_TCG2_EVENT_LOG_FORMAT_TCG_2, + &LocationAfter, + &LastEntry, + &Truncated + ); + + if (EFI_ERROR (Status)) { + FreePool (Event); + return Status; + } + } while (LocationBefore == LocationAfter); + + *Scaled = TRUE; + + DEBUG ((DEBUG_INFO, "%a: Log scaled (0x%lx -> 0x%lx)\n", __func__, LocationBefore, LocationAfter)); + + FreePool (Event); + return EFI_SUCCESS; +} + +/** + Dump the contents of a TCG 2.0 event log via DEBUG prints. + + Walks the event log skipping the TCG 1.2 SpecID header event. + Prints each event's index, PCRIndex, EventType, and EventSize. + + @param[in] Tcg2Protocol TCG2 protocol instance used to retrieve the event log. + @param[out] Truncated If the log is truncated. + + @retval EFI_SUCCESS Log dumped successfully. + @retval EFI_INVALID_PARAMETER One or more invalid parameters. +**/ +EFI_STATUS +TcgLogTestDumpEventLog ( + IN EFI_TCG2_PROTOCOL *Tcg2Protocol, + OUT BOOLEAN *Truncated + ) +{ + EFI_STATUS Status; + EFI_PHYSICAL_ADDRESS LogBase; + EFI_PHYSICAL_ADDRESS LastEntry; + BOOLEAN LogTruncated; + UINT8 *CurrentEvent; + UINT8 *EndOfLog; + UINT32 PcrIndex; + UINT32 EventType; + UINT32 EventSize; + UINTN Index; + + // Validate the input parameters. + if ((Tcg2Protocol == NULL) || (Truncated == NULL)) { + return EFI_INVALID_PARAMETER; + } + + Status = Tcg2Protocol->GetEventLog ( + Tcg2Protocol, + EFI_TCG2_EVENT_LOG_FORMAT_TCG_2, + &LogBase, + &LastEntry, + &LogTruncated + ); + + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: GetEventLog failed - %r\n", __func__, Status)); + return Status; + } + + // Check if the log is truncated. This should never happen. + if (Truncated != NULL) { + *Truncated = LogTruncated; + } + + // Check if the log is empty. + if (LastEntry < LogBase) { + DEBUG ((DEBUG_WARN, "%a: EventLog is empty\n", __func__)); + return EFI_SUCCESS; + } + + // Skip the TCG 1.2 SpecID header event. + CurrentEvent = (UINT8 *)(UINTN)LogBase; + CurrentEvent += sizeof (TCG_PCR_EVENT_HDR) + ((TCG_PCR_EVENT_HDR *)CurrentEvent)->EventSize; + + // Determine what the EndOfLog should be. + EndOfLog = (UINT8 *)(UINTN)LastEntry; + if (!TcgLogTestAdvanceEvent (&EndOfLog, EndOfLog + MAX_UINT16, NULL, NULL, NULL, NULL)) { + DEBUG ((DEBUG_WARN, "%a: Could not determine EndOfLog\n", __func__)); + return EFI_SUCCESS; + } + + // Dump the contents of the EventLog. + DEBUG ((DEBUG_INFO, "%a: Event log dump (base=0x%lx, truncated=%d):\n", __func__, LogBase, LogTruncated)); + DEBUG ((DEBUG_INFO, " %-6a %-10a %-34a %a\n", "Index", "PCRIndex", "EventType", "EventSize")); + DEBUG ((DEBUG_INFO, " %-6a %-10a %-34a %a\n", "-----", "--------", "---------", "---------")); + + Index = 0; + while (TcgLogTestAdvanceEvent (&CurrentEvent, EndOfLog, &PcrIndex, &EventType, &EventSize, NULL)) { + DEBUG ((DEBUG_INFO, " %-6u %-10u %-34a %u\n", Index, PcrIndex, TcgEventTypeToString (EventType), EventSize)); + Index++; + } + + DEBUG ((DEBUG_INFO, "%a: Total events: %u\n", __func__, Index)); + return EFI_SUCCESS; +} diff --git a/SecurityPkg/Tcg/TcgLogTest/TcgLogTestCommon.h b/SecurityPkg/Tcg/TcgLogTest/TcgLogTestCommon.h new file mode 100644 index 00000000000..2d602114d2d --- /dev/null +++ b/SecurityPkg/Tcg/TcgLogTest/TcgLogTestCommon.h @@ -0,0 +1,74 @@ +/** @file + TCG Log Test common function declarations shared by TcgLogTestDxe and + TcgLogTestApp. + + Copyright (c), Microsoft Corporation. + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#ifndef TCG_LOG_TEST_COMMON_H_ +#define TCG_LOG_TEST_COMMON_H_ + +#include +#include +#include + +/** + Advance one entry in the event log. + + @param[in,out] CurrentEvent On entry, points to the start of the event (PCRIndex). + On success, updated to point to the next event. + @param[in] LogEnd One byte past the end of the log buffer. + @param[out] PcrIndex PCRIndex of the parsed event. + @param[out] EventType EventType of the parsed event. + @param[out] EventSize Size of the event data payload. + @param[out] EventData Pointer to the event data payload. + + @retval TRUE Event parsed successfully and CurrentEvent updated. + @retval FALSE Invalid pointers, buffer, or digest algorithm. +**/ +BOOLEAN +TcgLogTestAdvanceEvent ( + IN OUT UINT8 **CurrentEvent, + IN UINT8 *LogEnd, + OUT UINT32 *PcrIndex OPTIONAL, + OUT UINT32 *EventType OPTIONAL, + OUT UINT32 *EventSize OPTIONAL, + OUT UINT8 **EventData OPTIONAL + ); + +/** + Log events via TCG2 until the event log base address changes (dynamic + scaling) or an error is returned. + + @param[in] Tcg2 TCG2 protocol instance. + @param[out] Scaled TRUE if scaling was detected. + + @retval EFI_SUCCESS Scaling detected. + @retval EFI_INVALID_PARAMETER NULL argument. +**/ +EFI_STATUS +TcgLogTestLogEventsUntilScaled ( + IN EFI_TCG2_PROTOCOL *Tcg2, + OUT BOOLEAN *Scaled + ); + +/** + Dump the contents of a TCG 2.0 event log via DEBUG prints. + + Walks the event log skipping the TCG 1.2 SpecID header event. + Prints each event's index, PCRIndex, EventType, and EventSize. + + @param[in] Tcg2 TCG2 protocol instance used to retrieve the event log. + @param[out] Truncated If the log is truncated. + + @retval EFI_SUCCESS Log dumped successfully. + @retval EFI_INVALID_PARAMETER One or more invalid parameters. +**/ +EFI_STATUS +TcgLogTestDumpEventLog ( + IN EFI_TCG2_PROTOCOL *Tcg2, + OUT BOOLEAN *Truncated + ); + +#endif // TCG_LOG_TEST_COMMON_H_ diff --git a/SecurityPkg/Tcg/TcgLogTest/TcgLogTestDxe.c b/SecurityPkg/Tcg/TcgLogTest/TcgLogTestDxe.c new file mode 100644 index 00000000000..9b627b30cfa --- /dev/null +++ b/SecurityPkg/Tcg/TcgLogTest/TcgLogTestDxe.c @@ -0,0 +1,268 @@ +/** @file + DXE driver that validates TCG2 event log dynamic scaling before ReadyToBoot. + + On entry the driver checks the NV variable TcgLogTestEnable. If not set, + the driver installs the protocol (with SetEnabled only) and returns without + running any tests. When the variable is set, the driver runs the + pre-ReadyToBoot scaling test, logs results into an internal buffer, clears + the variable, and installs the protocol so TcgLogTestApp can retrieve logs. + + Copyright (c), Microsoft Corporation. + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "TcgLogTest.h" +#include "TcgLogTestCommon.h" + +#define TCG_LOG_TEST_MAX_LOG_SIZE 4096 + +STATIC CHAR8 mLogBuffer[TCG_LOG_TEST_MAX_LOG_SIZE]; +STATIC UINTN mLogOffset = 0; +STATIC EFI_TCG2_PROTOCOL *mTcg2Protocol = NULL; +STATIC EFI_HANDLE mTcgLogTestHandle = NULL; + +/** + Append a formatted message to the internal log buffer. + + @param[in] Format Printf-style format string. + @param[in] ... Variable arguments for the format string. +**/ +STATIC +VOID +EFIAPI +LogAppend ( + IN CONST CHAR8 *Format, + ... + ) +{ + VA_LIST Args; + UINTN Remaining; + UINTN Written; + + if (mLogOffset >= TCG_LOG_TEST_MAX_LOG_SIZE - 1) { + return; + } + + Remaining = TCG_LOG_TEST_MAX_LOG_SIZE - mLogOffset - 1; + + VA_START (Args, Format); + Written = AsciiVSPrint (mLogBuffer + mLogOffset, Remaining, Format, Args); + VA_END (Args); + + mLogOffset += Written; +} + +/** + Protocol function: retrieve the pre-ReadyToBoot test log. + + @param[in] This Protocol instance. + @param[out] LogBuffer On success, pointer to the internal log buffer. + @param[out] LogSize On success, size of the log data in bytes. + + @retval EFI_SUCCESS Log retrieved. + @retval EFI_INVALID_PARAMETER LogBuffer or LogSize is NULL. + @retval EFI_NOT_STARTED The test has not run this boot. +**/ +STATIC +EFI_STATUS +EFIAPI +TcgLogTestGetLog ( + IN TCG_LOG_TEST_PROTOCOL *This, + OUT CHAR8 **LogBuffer, + OUT UINTN *LogSize + ) +{ + if ((LogBuffer == NULL) || (LogSize == NULL)) { + return EFI_INVALID_PARAMETER; + } + + *LogBuffer = mLogBuffer; + *LogSize = mLogOffset + 1; + return EFI_SUCCESS; +} + +/** + Protocol function: enable or disable the DXE test for the next boot. + + @param[in] This Protocol instance. + @param[in] Enable TRUE to enable, FALSE to disable. + + @retval EFI_SUCCESS Variable updated successfully. + @retval Other SetVariable failure. +**/ +STATIC +EFI_STATUS +EFIAPI +TcgLogTestEnable ( + IN TCG_LOG_TEST_PROTOCOL *This, + IN BOOLEAN Enable + ) +{ + BOOLEAN Dummy; + + if (Enable) { + Dummy = TRUE; + // Create the variable to signal the test should run. + return gRT->SetVariable ( + TCG_LOG_TEST_ENABLE_VARIABLE_NAME, + &gTcgLogTestProtocolGuid, + EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS, + sizeof (Dummy), + &Dummy + ); + } else { + // Delete the variable entirely. + return gRT->SetVariable ( + TCG_LOG_TEST_ENABLE_VARIABLE_NAME, + &gTcgLogTestProtocolGuid, + 0, + 0, + NULL + ); + } +} + +STATIC TCG_LOG_TEST_PROTOCOL mTcgLogTestProtocol = { + TcgLogTestGetLog, + TcgLogTestEnable +}; + +/** + Check whether the NV enable variable exists. + + @retval TRUE Variable exists (test is enabled). + @retval FALSE Variable absent. +**/ +STATIC +BOOLEAN +IsTestEnabled ( + VOID + ) +{ + EFI_STATUS Status; + BOOLEAN Value; + UINTN Size; + + Size = sizeof (Value); + Status = gRT->GetVariable ( + TCG_LOG_TEST_ENABLE_VARIABLE_NAME, + &gTcgLogTestProtocolGuid, + NULL, + &Size, + &Value + ); + + if (EFI_ERROR (Status)) { + return FALSE; + } + + return TRUE; +} + +/** + Exercise dynamic scaling before ReadyToBoot. Verifies that scaling occurs + (log base address changes) and the log is not truncated. +**/ +STATIC +VOID +TestPreReadyToBootScaling ( + VOID + ) +{ + EFI_STATUS Status; + BOOLEAN Scaled; + BOOLEAN Truncated; + + Status = TcgLogTestLogEventsUntilScaled (mTcg2Protocol, &Scaled); + if (EFI_ERROR (Status) || !Scaled) { + DEBUG ((DEBUG_ERROR, "%a: LogEventsUntilScaled failed - %r, Scaled=%d\n", __func__, Status, Scaled)); + LogAppend ("FAIL: Pre-ReadyToBoot: LogEventsUntilScaled - %r\n", Status); + return; + } + + // Dump the event log and check for truncation. + Status = TcgLogTestDumpEventLog (mTcg2Protocol, &Truncated); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: DumpEventLog failed - %r\n", __func__, Status)); + LogAppend ("FAIL: Pre-ReadyToBoot: DumpEventLog - %r\n", Status); + return; + } + + if (Truncated) { + DEBUG ((DEBUG_ERROR, "%a: Log truncated after scaling\n", __func__)); + LogAppend ("FAIL: Pre-ReadyToBoot: Log truncated\n"); + return; + } + + DEBUG ((DEBUG_INFO, "%a: Pre-ReadyToBoot scaling succeeded\n", __func__)); + LogAppend ("PASS: Pre-ReadyToBoot scaling succeeded\n"); +} + +/** + Entry point for TcgLogTestDxe. + + @param[in] ImageHandle Image handle. + @param[in] SystemTable Pointer to the System Table. + + @retval EFI_SUCCESS Driver initialized and protocol installed. +**/ +EFI_STATUS +EFIAPI +TcgLogTestDxeEntry ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable + ) +{ + EFI_STATUS Status; + + // Install the protocol so the TcgLogTestApp can enable the test and/or acquire the logs. + Status = gBS->InstallProtocolInterface ( + &mTcgLogTestHandle, + &gTcgLogTestProtocolGuid, + EFI_NATIVE_INTERFACE, + &mTcgLogTestProtocol + ); + + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: InstallProtocolInterface failed: %r\n", __func__, Status)); + return Status; + } + + DEBUG ((DEBUG_INFO, "%a: Protocol installed, checking enable state\n", __func__)); + + // Only run the test if it has been enabled. + if (!IsTestEnabled ()) { + DEBUG ((DEBUG_INFO, "%a: Test not enabled, skipping\n", __func__)); + return EFI_SUCCESS; + } + + DEBUG ((DEBUG_INFO, "%a: Test enabled, running pre-ReadyToBoot test\n", __func__)); + + // Clear the enable variable so we don't re-run on the next boot. + TcgLogTestEnable (&mTcgLogTestProtocol, FALSE); + + Status = gBS->LocateProtocol (&gEfiTcg2ProtocolGuid, NULL, (VOID **)&mTcg2Protocol); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: Failed to locate TCG2 protocol: %r\n", __func__, Status)); + LogAppend ("FAIL: TCG2 protocol not found - %r\n", Status); + return EFI_SUCCESS; + } + + // Run the pre-ReadyToBoot scaling test. + TestPreReadyToBootScaling (); + + return EFI_SUCCESS; +} diff --git a/SecurityPkg/Tcg/TcgLogTest/TcgLogTestDxe.inf b/SecurityPkg/Tcg/TcgLogTest/TcgLogTestDxe.inf new file mode 100644 index 00000000000..ab4a5054de9 --- /dev/null +++ b/SecurityPkg/Tcg/TcgLogTest/TcgLogTestDxe.inf @@ -0,0 +1,47 @@ +## @file +# DXE driver that validates TCG2 event log dynamic scaling before ReadyToBoot. +# +# Checks an NV variable to determine whether to run. If enabled, exercises +# pre-ReadyToBoot log scaling and records results via a local protocol that +# TcgLogTestApp retrieves post-boot. +# +# Copyright (c), Microsoft Corporation. +# SPDX-License-Identifier: BSD-2-Clause-Patent +## + +[Defines] + INF_VERSION = 0x00010006 + BASE_NAME = TcgLogTestDxe + FILE_GUID = B4D2F8A7-3E16-4C9B-A1F0-7D5E9B2C4A81 + MODULE_TYPE = DXE_DRIVER + VERSION_STRING = 1.0 + ENTRY_POINT = TcgLogTestDxeEntry + +[Sources] + TcgLogTestDxe.c + TcgLogTestCommon.c + TcgLogTestCommon.h + TcgLogTest.h + +[Packages] + MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec + SecurityPkg/SecurityPkg.dec + +[LibraryClasses] + BaseLib + BaseMemoryLib + DebugLib + MemoryAllocationLib + PrintLib + UefiDriverEntryPoint + UefiBootServicesTableLib + UefiLib + UefiRuntimeServicesTableLib + +[Protocols] + gEfiTcg2ProtocolGuid ## CONSUMES + gTcgLogTestProtocolGuid ## PRODUCES + +[Depex] + gEfiVariableArchProtocolGuid AND gEfiTcg2ProtocolGuid