diff --git a/SecurityPkg/Include/Guid/Tcg2EventLogScaled.h b/SecurityPkg/Include/Guid/Tcg2EventLogScaled.h new file mode 100644 index 0000000000..283450bf42 --- /dev/null +++ b/SecurityPkg/Include/Guid/Tcg2EventLogScaled.h @@ -0,0 +1,17 @@ +/** @file + Defines the GUID used to signal that the TCG event log has been dynamically + scaled. Consumers may register a notification callback on this event group + to react to the scaling event. + + Copyright (c), Microsoft Corporation. + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#pragma once + +#define TCG2_EVENT_LOG_SCALED_GUID \ + { \ + 0x9b4f7c2a, 0x1d3e, 0x4a8b, { 0x9c, 0x6f, 0x5e, 0x8d, 0x2a, 0x1b, 0x4c, 0x7f } \ + } + +extern EFI_GUID gTcg2EventLogScaledGuid; diff --git a/SecurityPkg/SecurityPkg.dec b/SecurityPkg/SecurityPkg.dec index d21a9bcabb..77c5815574 100644 --- a/SecurityPkg/SecurityPkg.dec +++ b/SecurityPkg/SecurityPkg.dec @@ -164,6 +164,10 @@ ## Include/Guid/TcgEventHob.h gTcg800155PlatformIdEventHobGuid = { 0xe2c3bc69, 0x615c, 0x4b5b, { 0x8e, 0x5c, 0xa0, 0x33, 0xa9, 0xc2, 0x5e, 0xd6 }} + ## GUID used to signal dynamic scaling of the TCG event log. + ## Include/Guid/Tcg2EventLogScaled.h + gTcg2EventLogScaledGuid = { 0x9b4f7c2a, 0x1d3e, 0x4a8b, { 0x9c, 0x6f, 0x5e, 0x8d, 0x2a, 0x1b, 0x4c, 0x7f }} + ## HOB GUID used to pass all PEI measured FV info to DXE Driver. # Include/Guid/MeasuredFvHob.h gMeasuredFvHobGuid = { 0xb2360b42, 0x7173, 0x420a, { 0x86, 0x96, 0x46, 0xca, 0x6b, 0xab, 0x10, 0x60 }} @@ -255,6 +259,12 @@ # Include/Guid/Tpm2ServiceFfa.h gTpm2ServiceFfaGuid = { 0x17b862a4, 0x1806, 0x4faf, { 0x86, 0xb3, 0x08, 0x9a, 0x58, 0x35, 0x38, 0x61 } } +[Protocols] + ## Protocol used to test dynamic TCG log scaling functionality. This is a private protocol with visibility to + ## only the TestApp and DXE driver. + # Tcg/TcgLogTest/TcgLogTest.h + gTcgLogTestProtocolGuid = {0xa3c12f80, 0x7d9e, 0x4b5a, { 0x91, 0xe4, 0x6c, 0xf8, 0x2d, 0xa1, 0xb7, 0x03 }} + [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 9d4fab48eb..fcb4c04973 100644 --- a/SecurityPkg/SecurityPkg.dsc +++ b/SecurityPkg/SecurityPkg.dsc @@ -254,6 +254,14 @@ SecurityPkg/Library/PeiDxeTpmPlatformHierarchyLib/PeiDxeTpmPlatformHierarchyLib.inf SecurityPkg/Library/PeiDxeTpmPlatformHierarchyLibNull/PeiDxeTpmPlatformHierarchyLib.inf + 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/UnitTestResultReportLibConOut.inf + } + # # TCG Storage. # diff --git a/SecurityPkg/Tcg/Tcg2Dxe/README.md b/SecurityPkg/Tcg/Tcg2Dxe/README.md new file mode 100644 index 0000000000..d7f962ac20 --- /dev/null +++ b/SecurityPkg/Tcg/Tcg2Dxe/README.md @@ -0,0 +1,148 @@ +# 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/). +Its 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 would exceed + the current allocation (`EventLogAreaStruct->Laml`). + +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. + +### NormalEventLog vs. FinalEventLog + +Tcg2Dxe maintains two distinct event log regions: + +| Log | Memory Type | Lifetime | Can Scale | +| --- | ----------- | -------- | --------- | +| **Normal log** | `EfiBootServicesData` | Available until `ExitBootServices` | Yes | +| **Final Events log** | `EfiACPIMemoryNVS` | Persistent | No | +| **ACPI event 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 **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. +- The **ACPI event log** is a fixed-size mirror of the normal log, allocated + in `EfiACPIMemoryNVS` and sized from `PcdTcgLogAreaMinLen`. Because this region + does not scale, it can become truncated. Its address and length are published + to `PcdTpm2AcpiTableLasa` and `PcdTpm2AcpiTableLaml` so `Tcg2Acpi` picks them + up when populating the TPM2 ACPI table. + +### Scale Limit + +The number of times the normal event log region may be dynamically scaled is +capped by `TCG_EVENT_LOG_MAX_SCALE_COUNT`. Each successful scale doubles the +allocation, so this caps total growth at `PcdTcgLogAreaMinLen << TCG_EVENT_LOG_MAX_SCALE_COUNT`. +Once the limit is reached: + +1. `TcgScaleEventLog` refuses to scale further and returns + `EFI_OUT_OF_RESOURCES`. +2. `EventLogAreaStruct->EventLogTruncated` is set to `TRUE`, so subsequent + `GetEventLog` callers see `EventLogTruncated == TRUE`. +3. `HashLogExtendEvent` returns `EFI_VOLUME_FULL` for events that would have + triggered the refused scale. + +### Scaling Notification (`gTcg2EventLogScaledGuid`) + +Each time the normal log is successfully resized, `TcgScaleEventLog` calls +`EfiEventGroupSignal (&gTcg2EventLogScaledGuid)` to notify interested parties +that the log moved in memory. + +Consumers that cache the log base address returned by `GetEventLog` (for +example, parsers walking the log incrementally) must invalidate their cache +on this signal and call `GetEventLog` again to get the current `Lasa`/last +entry. A typical consumer: + +```c +gBS->CreateEventEx ( + EVT_NOTIFY_SIGNAL, + TPL_CALLBACK, + OnTcgEventLogScaled, + Context, + &gTcg2EventLogScaledGuid, + &Event + ); +``` + +The event is declared in `gTcg2EventLogScaledGuid` (see +`SecurityPkg/Include/Guid/Tcg2EventLogScaled.h`) and listed in `Tcg2Dxe.inf`. + +## FinalEventLog Truncation Marker + +Because the **FinalEventLog** is fixed-size and cannot scale, it can fill +up before `ExitBootServices`. When the next event would overflow the log, +`TcgDxeLogEvent` calls `AppendTruncationMarker` which writes a final +`EV_NO_ACTION` event whose payload is the ASCII string +`TCG_LOG_TRUNCATION_EVENT_STRING`. The `NumberOfEvents` counter in +`EFI_TCG2_FINAL_EVENTS_TABLE` is incremented to include the marker, and +`EventLogTruncated` is set so subsequent attempts return `EFI_VOLUME_FULL` +without re-appending the marker. + +To guarantee the marker always fits, FinalEventLog initialization in +`SetupEventLog` subtracts `GetTruncationEventSize()` from the usable `Laml`: + +```c +mTcgDxeData.FinalEventLogAreaStruct[Index].Laml = + PcdGet32 (PcdTcg2FinalLogAreaLen) + - sizeof (EFI_TCG2_FINAL_EVENTS_TABLE) + - GetTruncationEventSize (); +``` + +`AppendTruncationMarker` temporarily restores this reserved space so +`TcgCommLogEvent` will accept the marker write. + +OS-side and pre-OS consumers can detect FinalEventLog truncation by: + +- walking `EFI_TCG2_FINAL_EVENTS_TABLE` and inspecting the last entry for an + `EV_NO_ACTION` event whose payload begins with `"TCG Event Log Truncated"`. + +## ACPI Event Log Truncation Marker + +The **ACPI event log** uses the same truncation-marker mechanism as +FinalEventLog. Because it is fixed-size and does not scale, `TcgDxeLogEvent` +appends an `EV_NO_ACTION` truncation event whose payload is +`TCG_LOG_TRUNCATION_EVENT_STRING` when the next event would overflow the +region. `EventLogTruncated` is then set so subsequent writes silently skip the +ACPI region without re-appending the marker. Unlike FinalEventLog, the +truncated ACPI region does **not** cause `HashLogExtendEvent` to return +`EFI_VOLUME_FULL`. + +To guarantee the marker always fits, the ACPI event log initialization in +`SetupEventLog` subtracts `GetTruncationEventSize()` from the usable `Laml`: + +```c +mTcgDxeData.AcpiEventLogAreaStruct[Index].Laml = + PcdGet32 (PcdTcgLogAreaMinLen) - GetTruncationEventSize (); +``` + +Truncation can be detected by walking the region and inspecting the last +entry for the `"TCG Event Log Truncated"` payload. diff --git a/SecurityPkg/Tcg/Tcg2Dxe/Tcg2Dxe.c b/SecurityPkg/Tcg/Tcg2Dxe/Tcg2Dxe.c index 096e3ef6eb..b0383fac47 100644 --- a/SecurityPkg/Tcg/Tcg2Dxe/Tcg2Dxe.c +++ b/SecurityPkg/Tcg/Tcg2Dxe/Tcg2Dxe.c @@ -15,6 +15,7 @@ SPDX-License-Identifier: BSD-2-Clause-Patent #include #include #include +#include #include #include #include @@ -67,6 +68,13 @@ TCG2_EVENT_INFO_STRUCT mTcg2EventInfo[] = { #define TCG_EVENT_LOG_AREA_COUNT_MAX 2 +// Maximum number of times the TCG event log can be dynamically scaled +// before the log is considered truncated. +#define TCG_EVENT_LOG_MAX_SCALE_COUNT 4 + +// Payload written as the last event of the FinalEventLog when it becomes truncated. +#define TCG_LOG_TRUNCATION_EVENT_STRING "TCG Event Log Truncated" + typedef struct { EFI_TCG2_EVENT_LOG_FORMAT EventLogFormat; EFI_PHYSICAL_ADDRESS Lasa; @@ -76,6 +84,7 @@ typedef struct { BOOLEAN EventLogStarted; BOOLEAN EventLogTruncated; UINTN Next800155EventOffset; + UINTN ScaleCount; } TCG_EVENT_LOG_AREA_STRUCT; // Mapping of TPM return status to BIOS/OS TPM support and related flags (TPMPresentFlag, TpmUpdateFlag) @@ -93,6 +102,7 @@ typedef struct _TCG_DXE_DATA { BOOLEAN GetEventLogCalled[TCG_EVENT_LOG_AREA_COUNT_MAX]; TCG_EVENT_LOG_AREA_STRUCT FinalEventLogAreaStruct[TCG_EVENT_LOG_AREA_COUNT_MAX]; EFI_TCG2_FINAL_EVENTS_TABLE *FinalEventsTable[TCG_EVENT_LOG_AREA_COUNT_MAX]; + TCG_EVENT_LOG_AREA_STRUCT AcpiEventLogAreaStruct[TCG_EVENT_LOG_AREA_COUNT_MAX]; } TCG_DXE_DATA; TCG_DXE_DATA mTcgDxeData = { @@ -124,6 +134,7 @@ VARIABLE_TYPE mVariableType[] = { }; EFI_HANDLE mImageHandle; +BOOLEAN mReadyToBoot = FALSE; /** Measure PE image into TPM log based on the authenticode image hashing in @@ -930,6 +941,128 @@ TcgCommLogEvent ( return EFI_SUCCESS; } +/** + Get TPML_DIGEST_VALUES compact binary buffer size. + + @param[in] DigestListBin TPML_DIGEST_VALUES compact binary buffer. + + @return TPML_DIGEST_VALUES compact binary buffer size. +**/ +UINT32 +GetDigestListBinSize ( + IN VOID *DigestListBin + ) +{ + UINTN Index; + UINT16 DigestSize; + UINT32 TotalSize; + UINT32 Count; + TPMI_ALG_HASH HashAlg; + UINT8 *Current; + + Current = (UINT8 *)DigestListBin; + Count = ReadUnaligned32 ((UINT32 *)Current); + TotalSize = sizeof (Count); + Current += sizeof (Count); + for (Index = 0; Index < Count; Index++) { + HashAlg = ReadUnaligned16 ((UINT16 *)Current); + TotalSize += sizeof (HashAlg); + Current += sizeof (HashAlg); + + DigestSize = GetHashSizeFromAlgo (HashAlg); + TotalSize += DigestSize; + Current += DigestSize; + } + + return TotalSize; +} + +/** + Build the EV_NO_ACTION truncation marker event. + + @param[out] EventHdr TCG_PCR_EVENT2 header initialized for the + truncation marker. + @param[out] EventHdrSize Size in bytes of the serialized header + (excluding the payload string). +**/ +STATIC +VOID +BuildTruncationEvent ( + OUT TCG_PCR_EVENT2_HDR *EventHdr, + OUT UINT32 *EventHdrSize + ) +{ + InitNoActionEvent (EventHdr, sizeof (TCG_LOG_TRUNCATION_EVENT_STRING)); + *EventHdrSize = (UINT32)(sizeof (EventHdr->PCRIndex) + + sizeof (EventHdr->EventType) + + GetDigestListBinSize (&EventHdr->Digests) + + sizeof (EventHdr->EventSize)); +} + +/** + Compute the total size in bytes of the EV_NO_ACTION truncation marker event + (header for the active PCR banks + truncation payload). + + Used to reserve headroom in the Final Events log so the marker is always + guaranteed to fit when truncation occurs. + + @return Size in bytes of the truncation marker event. +**/ +STATIC +UINTN +GetTruncationEventSize ( + VOID + ) +{ + TCG_PCR_EVENT2_HDR NoActionEvent; + UINT32 EventHdrSize; + + BuildTruncationEvent (&NoActionEvent, &EventHdrSize); + + return EventHdrSize + sizeof (TCG_LOG_TRUNCATION_EVENT_STRING); +} + +/** + Append an EV_NO_ACTION truncation marker as the final entry of the Final + Events log. The marker is written into the headroom reserved at log + initialization (see Final log Laml setup in SetupEventLog) so it is always + guaranteed to fit even when the log is otherwise full. + + @param[in,out] EventLogAreaStruct Final Events log area. + + @retval EFI_SUCCESS The truncation marker was logged. + @retval Other TcgCommLogEvent failed; nothing was logged. +**/ +STATIC +EFI_STATUS +AppendTruncationMarker ( + IN OUT TCG_EVENT_LOG_AREA_STRUCT *EventLogAreaStruct + ) +{ + EFI_STATUS Status; + TCG_PCR_EVENT2_HDR TruncationHdr; + UINT32 TruncationHdrSize; + + BuildTruncationEvent (&TruncationHdr, &TruncationHdrSize); + + // Restore the reserved space so TcgCommLogEvent accepts the marker. + EventLogAreaStruct->Laml += TruncationHdrSize + sizeof (TCG_LOG_TRUNCATION_EVENT_STRING); + + Status = TcgCommLogEvent ( + EventLogAreaStruct, + &TruncationHdr, + TruncationHdrSize, + (UINT8 *)TCG_LOG_TRUNCATION_EVENT_STRING, + sizeof (TCG_LOG_TRUNCATION_EVENT_STRING) + ); + + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: Failed to log truncation marker - %r\n", __func__, Status)); + } + + return Status; +} + /** Add a new entry to the Event Log. @@ -1015,6 +1148,11 @@ TcgDxeLogEvent ( NewEventSize ); if (Status == EFI_OUT_OF_RESOURCES) { + Status = AppendTruncationMarker (EventLogAreaStruct); + if (!EFI_ERROR (Status)) { + (mTcgDxeData.FinalEventsTable[Index])->NumberOfEvents++; + } + EventLogAreaStruct->EventLogTruncated = TRUE; return EFI_VOLUME_FULL; } else if (Status == EFI_SUCCESS) { @@ -1028,41 +1166,34 @@ TcgDxeLogEvent ( } } - return Status; -} + // + // Also record to the fixed-size ACPI event log region. + // + EventLogAreaStruct = &mTcgDxeData.AcpiEventLogAreaStruct[Index]; -/** - Get TPML_DIGEST_VALUES compact binary buffer size. - - @param[in] DigestListBin TPML_DIGEST_VALUES compact binary buffer. - - @return TPML_DIGEST_VALUES compact binary buffer size. -**/ -UINT32 -GetDigestListBinSize ( - IN VOID *DigestListBin - ) -{ - UINTN Index; - UINT16 DigestSize; - UINT32 TotalSize; - UINT32 Count; - TPMI_ALG_HASH HashAlg; - - Count = ReadUnaligned32 (DigestListBin); - TotalSize = sizeof (Count); - DigestListBin = (UINT8 *)DigestListBin + sizeof (Count); - for (Index = 0; Index < Count; Index++) { - HashAlg = ReadUnaligned16 (DigestListBin); - TotalSize += sizeof (HashAlg); - DigestListBin = (UINT8 *)DigestListBin + sizeof (HashAlg); - - DigestSize = Tpm2GetHashSizeFromAlgo (HashAlg); - TotalSize += DigestSize; - DigestListBin = (UINT8 *)DigestListBin + DigestSize; + if (EventLogAreaStruct->Lasa == 0) { + // No need to handle ACPI event log region. + return EFI_SUCCESS; } - return TotalSize; + if (!EventLogAreaStruct->EventLogTruncated) { + Status = TcgCommLogEvent ( + EventLogAreaStruct, + NewEventHdr, + NewEventHdrSize, + NewEventData, + NewEventSize + ); + if (Status == EFI_OUT_OF_RESOURCES) { + AppendTruncationMarker (EventLogAreaStruct); + EventLogAreaStruct->EventLogTruncated = TRUE; + Status = EFI_SUCCESS; + } else if (Status == EFI_SUCCESS) { + EventLogAreaStruct->EventLogStarted = TRUE; + } + } + + return Status; } /** @@ -1121,6 +1252,153 @@ CopyDigestListBinToBuffer ( return Buffer; } +/** + 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. + @retval EFI_VOLUME_FULL EventLog truncated. + +**/ +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; + EFI_TPL OldTpl; + + // Make sure EventLogAreaStruct is valid. + if (EventLogAreaStruct == NULL) { + return EFI_INVALID_PARAMETER; + } + + // If the log was scaled the maximum number of times, mark it as truncated. + if (EventLogAreaStruct->ScaleCount >= TCG_EVENT_LOG_MAX_SCALE_COUNT) { + DEBUG ((DEBUG_ERROR, "%a: Scale limit reached (%u)\n", __func__, TCG_EVENT_LOG_MAX_SCALE_COUNT)); + return EFI_VOLUME_FULL; + } + + NewLaml = EventLogAreaStruct->Laml * 2; + if (NewLaml <= EventLogAreaStruct->Laml) { + DEBUG ((DEBUG_ERROR, "%a: Laml overflow (0x%lx * 2)\n", __func__, EventLogAreaStruct->Laml)); + return EFI_OUT_OF_RESOURCES; + } + + 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; + } + + // Enter a critical section, we do not want to be interrupted while copying. + OldTpl = gBS->RaiseTPL (TPL_HIGH_LEVEL); + + // 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%lx\n", OldLasa, OldLaml)); + DEBUG ((DEBUG_INFO, "NewLasa: 0x%lx, NewLaml: 0x%lx\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); + + // Track the number of times we've scaled. + EventLogAreaStruct->ScaleCount++; + + // Exit the critical section once we finish copying/updated the struct. + gBS->RestoreTPL (OldTpl); + + // Free the old log region. + gBS->FreePages (OldLasa, EFI_SIZE_TO_PAGES ((UINTN)OldLaml)); + + // Notify that the event log was scaled. + EfiEventGroupSignal (&gTcg2EventLogScaledGuid); + + 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; +} + /** Add a new entry to the Event Log. @@ -1145,6 +1423,7 @@ TcgDxeLogHashEvent ( TCG_PCR_EVENT2 TcgPcrEvent2; UINT8 *DigestBuffer; UINT32 *EventSizePtr; + BOOLEAN DynamicScalingNeeded; RetStatus = EFI_SUCCESS; for (Index = 0; Index < sizeof (mTcg2EventInfo)/sizeof (mTcg2EventInfo[0]); Index++) { @@ -1183,6 +1462,27 @@ TcgDxeLogHashEvent ( EventSizePtr = Tpm2CopyDigestListToBuffer (DigestBuffer, DigestList, mTcgDxeData.BsCap.ActivePcrBanks); CopyMem (EventSizePtr, &NewEventHdr->EventSize, sizeof (NewEventHdr->EventSize)); + // Continually scale until we have enough space to log. We need to dynamically + // scale the TCG log before we enter a critical region. + while (TRUE) { + DynamicScalingNeeded = TcgLogDynamicScalingNeeded ( + &mTcgDxeData.EventLogAreaStruct[Index], + sizeof (TcgPcrEvent2.PCRIndex) + sizeof (TcgPcrEvent2.EventType) + GetDigestListBinSize (DigestBuffer) + sizeof (TcgPcrEvent2.EventSize), + NewEventHdr->EventSize + ); + + if (!DynamicScalingNeeded) { + break; + } + + Status = TcgScaleEventLog (&mTcgDxeData.EventLogAreaStruct[Index]); + if (EFI_ERROR (Status)) { + mTcgDxeData.EventLogAreaStruct[Index].EventLogTruncated = TRUE; + DEBUG ((DEBUG_ERROR, "Unable to scale the TCG event log!\n")); + return Status; + } + } + // // Enter critical region // @@ -1622,26 +1922,71 @@ SetupEventLog ( DEBUG ((DEBUG_INFO, "SetupEventLog\n")); // - // 1. Create Log Area + // 0. Create ACPI Log Area. // 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) { + if ((PcdGet8 (PcdTpm2AcpiTableRev) >= 4) && + (mTcg2EventInfo[Index].LogFormat == EFI_TCG2_EVENT_LOG_FORMAT_TCG_2)) + { + // PcdTcgLogAreaMinLen is platform-configurable; ensure it has room for the + // EV_NO_ACTION truncation marker event. + if (PcdGet32 (PcdTcgLogAreaMinLen) < GetTruncationEventSize ()) { + DEBUG ((DEBUG_ERROR, "Insufficient AcpiEventLogArea length\n")); + return EFI_INVALID_PARAMETER; + } + Status = gBS->AllocatePages ( AllocateAnyPages, EfiACPIMemoryNVS, EFI_SIZE_TO_PAGES (PcdGet32 (PcdTcgLogAreaMinLen)), &Lasa ); + + if (EFI_ERROR (Status)) { + return Status; + } + + SetMem ((VOID *)(UINTN)Lasa, PcdGet32 (PcdTcgLogAreaMinLen), 0xFF); + + mTcgDxeData.AcpiEventLogAreaStruct[Index].EventLogFormat = mTcg2EventInfo[Index].LogFormat; + mTcgDxeData.AcpiEventLogAreaStruct[Index].Lasa = Lasa; + mTcgDxeData.AcpiEventLogAreaStruct[Index].Laml = PcdGet32 (PcdTcgLogAreaMinLen) - GetTruncationEventSize (); + mTcgDxeData.AcpiEventLogAreaStruct[Index].EventLogSize = 0; + mTcgDxeData.AcpiEventLogAreaStruct[Index].LastEvent = (VOID *)(UINTN)Lasa; + mTcgDxeData.AcpiEventLogAreaStruct[Index].EventLogStarted = FALSE; + mTcgDxeData.AcpiEventLogAreaStruct[Index].EventLogTruncated = FALSE; + mTcgDxeData.AcpiEventLogAreaStruct[Index].Next800155EventOffset = 0; + + PcdSet32S (PcdTpm2AcpiTableLaml, PcdGet32 (PcdTcgLogAreaMinLen)); + PcdSet64S (PcdTpm2AcpiTableLasa, mTcgDxeData.AcpiEventLogAreaStruct[Index].Lasa); } else { - Status = gBS->AllocatePages ( - AllocateAnyPages, - EfiBootServicesData, - EFI_SIZE_TO_PAGES (PcdGet32 (PcdTcgLogAreaMinLen)), - &Lasa - ); + // No need to handle EFI_TCG2_EVENT_LOG_FORMAT_TCG_1_2 or if PcdTpm2AcpiTableRev < 4 + mTcgDxeData.AcpiEventLogAreaStruct[Index].EventLogFormat = mTcg2EventInfo[Index].LogFormat; + mTcgDxeData.AcpiEventLogAreaStruct[Index].Lasa = 0; + mTcgDxeData.AcpiEventLogAreaStruct[Index].Laml = 0; + mTcgDxeData.AcpiEventLogAreaStruct[Index].EventLogSize = 0; + mTcgDxeData.AcpiEventLogAreaStruct[Index].LastEvent = 0; + mTcgDxeData.AcpiEventLogAreaStruct[Index].EventLogStarted = FALSE; + mTcgDxeData.AcpiEventLogAreaStruct[Index].EventLogTruncated = FALSE; + mTcgDxeData.AcpiEventLogAreaStruct[Index].Next800155EventOffset = 0; } + } + } + + // + // 1. Create Log Area + // + 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; + + Status = gBS->AllocatePages ( + AllocateAnyPages, + EfiBootServicesData, + EFI_SIZE_TO_PAGES (PcdGet32 (PcdTcgLogAreaMinLen)), + &Lasa + ); if (EFI_ERROR (Status)) { return Status; @@ -1650,17 +1995,7 @@ SetupEventLog ( mTcgDxeData.EventLogAreaStruct[Index].Lasa = Lasa; mTcgDxeData.EventLogAreaStruct[Index].Laml = PcdGet32 (PcdTcgLogAreaMinLen); mTcgDxeData.EventLogAreaStruct[Index].Next800155EventOffset = 0; - - if ((PcdGet8 (PcdTpm2AcpiTableRev) >= 4) || - (mTcg2EventInfo[Index].LogFormat == EFI_TCG2_EVENT_LOG_FORMAT_TCG_2)) - { - // - // Report TCG2 event log address and length, so that they can be reported in TPM2 ACPI table. - // Ignore the return status, because those fields are optional. - // - PcdSet32S (PcdTpm2AcpiTableLaml, (UINT32)mTcgDxeData.EventLogAreaStruct[Index].Laml); - PcdSet64S (PcdTpm2AcpiTableLasa, mTcgDxeData.EventLogAreaStruct[Index].Lasa); - } + mTcgDxeData.EventLogAreaStruct[Index].ScaleCount = 0; // // To initialize them as 0xFF is recommended @@ -1753,6 +2088,9 @@ SetupEventLog ( mTcgDxeData.EventLogAreaStruct[Index].Next800155EventOffset = \ mTcgDxeData.EventLogAreaStruct[Index].EventLogSize; + mTcgDxeData.AcpiEventLogAreaStruct[Index].Next800155EventOffset = \ + mTcgDxeData.AcpiEventLogAreaStruct[Index].EventLogSize; + // // Tcg800155PlatformIdEvent. Event format is TCG_PCR_EVENT2 // @@ -1811,6 +2149,13 @@ SetupEventLog ( for (Index = 0; Index < sizeof (mTcg2EventInfo)/sizeof (mTcg2EventInfo[0]); Index++) { if ((mTcgDxeData.BsCap.SupportedEventLogs & mTcg2EventInfo[Index].LogFormat) != 0) { if (mTcg2EventInfo[Index].LogFormat == EFI_TCG2_EVENT_LOG_FORMAT_TCG_2) { + // PcdTcg2FinalLogAreaLen is platform-configurable; ensure it has room for both the + // EFI_TCG2_FINAL_EVENTS_TABLE header and the EV_NO_ACTION truncation marker event. + if (PcdGet32 (PcdTcg2FinalLogAreaLen) < sizeof (EFI_TCG2_FINAL_EVENTS_TABLE) + GetTruncationEventSize ()) { + DEBUG ((DEBUG_ERROR, "Insufficient FinalEventLogArea length\n")); + return EFI_INVALID_PARAMETER; + } + Status = gBS->AllocatePages ( AllocateAnyPages, EfiACPIMemoryNVS, @@ -1832,7 +2177,7 @@ SetupEventLog ( mTcgDxeData.FinalEventLogAreaStruct[Index].EventLogFormat = mTcg2EventInfo[Index].LogFormat; mTcgDxeData.FinalEventLogAreaStruct[Index].Lasa = Lasa + sizeof (EFI_TCG2_FINAL_EVENTS_TABLE); - mTcgDxeData.FinalEventLogAreaStruct[Index].Laml = PcdGet32 (PcdTcg2FinalLogAreaLen) - sizeof (EFI_TCG2_FINAL_EVENTS_TABLE); + mTcgDxeData.FinalEventLogAreaStruct[Index].Laml = PcdGet32 (PcdTcg2FinalLogAreaLen) - sizeof (EFI_TCG2_FINAL_EVENTS_TABLE) - GetTruncationEventSize (); mTcgDxeData.FinalEventLogAreaStruct[Index].EventLogSize = 0; mTcgDxeData.FinalEventLogAreaStruct[Index].LastEvent = (VOID *)(UINTN)mTcgDxeData.FinalEventLogAreaStruct[Index].Lasa; mTcgDxeData.FinalEventLogAreaStruct[Index].EventLogStarted = FALSE; @@ -2502,6 +2847,9 @@ OnReadyToBoot ( TPM_PCRINDEX PcrIndex; PERF_FUNCTION_BEGIN (); + + mReadyToBoot = TRUE; + if (mBootAttempts == 0) { // // Measure handoff tables. diff --git a/SecurityPkg/Tcg/Tcg2Dxe/Tcg2Dxe.inf b/SecurityPkg/Tcg/Tcg2Dxe/Tcg2Dxe.inf index d291c03254..dc367e6301 100644 --- a/SecurityPkg/Tcg/Tcg2Dxe/Tcg2Dxe.inf +++ b/SecurityPkg/Tcg/Tcg2Dxe/Tcg2Dxe.inf @@ -88,6 +88,7 @@ gTpm2StartupLocalityHobGuid ## SOMETIMES_CONSUMES ## HOB gTcg800155PlatformIdEventHobGuid ## SOMETIMES_CONSUMES ## HOB gEfiDeviceSignatureDatabaseGuid + gTcg2EventLogScaledGuid ## SOMETIMES_PRODUCES ## Event [Protocols] gEfiTcg2ProtocolGuid ## PRODUCES diff --git a/SecurityPkg/Tcg/TcgLogTest/README.md b/SecurityPkg/Tcg/TcgLogTest/README.md new file mode 100644 index 0000000000..c5387aa6a5 --- /dev/null +++ b/SecurityPkg/Tcg/TcgLogTest/README.md @@ -0,0 +1,279 @@ +# 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. Internally, the helper registers a notification callback + on `gTcg2EventLogScaledGuid` and stops as soon as `Tcg2Dxe` signals that + GUID, indicating the event log was dynamically scaled. +2. 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. + +The suite registers five test cases. Each case shares a `LocateProtocols` +prerequisite that resolves `EFI_TCG2_PROTOCOL` and `TCG_LOG_TEST_PROTOCOL`. + +| # | Test Case (Class Name) | Function | Cleanup | +| - | ---------------------- | -------- | ------- | +| 1 | `PostRtbScaling` | `TestPostReadyToBootScaling` | none | +| 2 | `ScaleLimitTruncates` | `TestScaleLimitTruncatesLog` | `EnableDxeTestAndReboot` | +| 3 | `SnapshotPlusFinalMatches` | `TestSnapshotPlusFinalMatchesEventLog` | none | +| 4 | `FinalEventLogTruncates` | `TestFinalEventLogTruncationMarker` | none | +| 5 | `PreRtbResults` | `TestPreReadyToBootScaling` | `SaveAndReboot` | + +#### Test: TestPostReadyToBootScaling + +Executed after `ReadyToBoot` in the UEFI shell. Exercises dynamic scaling +after `ReadyToBoot` has fired. + +1. Calls `TcgLogTestLogEventsUntilScaled` to repeatedly log `EV_NO_ACTION` + events to PCR 8. Internally, the helper registers a notification callback + on `gTcg2EventLogScaledGuid` and stops as soon as `Tcg2Dxe` signals that + GUID, indicating the event log was dynamically scaled. + +#### Test: TestScaleLimitTruncatesLog + +Verifies that the normal event log can only be dynamically scaled up to +`TCG_EVENT_LOG_MAX_SCALE_COUNT` times, after which scaling is refused and +`GetEventLog` reports the log as truncated. + +1. `TestPostReadyToBootScaling` already consumed one scale, so this test + calls `TcgLogTestLogEventsUntilScaled` repeatedly to exhaust the remaining + `TCG_EVENT_LOG_MAX_SCALE_COUNT - 1` scales. +2. Calls `TcgLogTestLogEventsUntilScaled` once more and asserts it returns + an error and reports `Scaled = FALSE`. +3. Calls `GetEventLog` and asserts the `Truncated` flag is `TRUE`. + +#### Test: TestSnapshotPlusFinalMatchesEventLog + +Validates the relationship between the normal event log and the +`FinalEventLog`: after the first `GetEventLog` call activates FinalEventLog +logging, every newly logged event must be appended to **both** the normal +log and the FinalEventLog. Therefore: + +```text +Snapshot1 + FinalEventLog_entries == Snapshot2 +``` + +1. Calls `GetEventLog` to capture `Snapshot1` (and activate FinalEventLog + logging). +2. Calls `TcgLogTestLogSingleEvent` `MAX_NUM_EXTRA_EVENTS` times so the new + entries are small enough to avoid triggering scaling. +3. Locates `EFI_TCG2_FINAL_EVENTS_TABLE` from the system configuration table + and asserts `NumberOfEvents == MAX_NUM_EXTRA_EVENTS`. +4. Walks the FinalEventLog entries to compute their combined byte size. +5. Calls `GetEventLog` again to capture `Snapshot2`. +6. Asserts `Snapshot2 == Snapshot1 || FinalEventLog_entries` byte-for-byte. + +#### Test: TestFinalEventLogTruncationMarker + +Verifies that when the `FinalEventLog` fills up, `Tcg2Dxe` appends a final +`EV_NO_ACTION` event carrying the `TCG_LOG_TRUNCATION_EVENT_STRING` marker +(`"TCG Event Log Truncated"`) so consumers can recognise the truncated state. + +1. Calls `GetEventLog` to activate FinalEventLog logging and assert it is + not yet truncated. +2. Calls `TcgLogTestLogEventsUntilScaled` in a loop until `HashLogExtendEvent` + returns `EFI_VOLUME_FULL`, signalling FinalEventLog truncation. (The normal + log can still scale; the FinalEventLog cannot.) +3. Locates `EFI_TCG2_FINAL_EVENTS_TABLE` and walks all `NumberOfEvents` entries + to land on the last entry. +4. Asserts the last entry is `EV_NO_ACTION` and its payload begins with the + ASCII bytes of `TCG_LOG_TRUNCATION_EVENT_STRING`. + +#### Test: TestPreReadyToBootScaling Results + +Verifies the DXE driver's pre-ReadyToBoot results. + +1. Locates `TCG_LOG_TEST_PROTOCOL` and calls `GetLog`. +2. Dumps the DXE driver's 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 (Post-ReadyToBoot scaling + truncation) +├── 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. +│ ├── TestPostReadyToBootScaling(): +│ │ ├── Calls TcgLogTestLogEventsUntilScaled() to scale the event log once. +│ │ └── PASS. +│ ├── TestScaleLimitTruncatesLog(): +│ │ ├── Scales the remaining (TCG_EVENT_LOG_MAX_SCALE_COUNT - 1) times. +│ │ ├── Attempts one more scale and asserts it fails. +│ │ ├── Calls GetEventLog() and asserts Truncated == TRUE. +│ │ └── PASS. +│ └── Test Cleanup (for TestScaleLimitTruncatesLog): +│ └── Calls EnableDxeTestAndReboot(). +│ ├── Calls Enable (TRUE) to create the NV variable. +│ └── Calls SaveAndReboot() to SaveFrameworkState + EfiResetCold. +│ +Boot 2 (DXE pre-ReadyToBoot test + FinalEventLog tests + DXE results) +├── 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. +│ │ ├── PASS. +│ │ └── Logs results into internal buffer for later access via GetLog(). +├── TcgLogTestApp: +│ ├── Resumes execution from UEFI shell. (UnitTest Framework) +│ ├── TestPostReadyToBootScaling() → already PASSED → SKIPPED. +│ ├── TestScaleLimitTruncatesLog() → already PASSED → SKIPPED. +│ ├── TestSnapshotPlusFinalMatchesEventLog(): +│ │ ├── Captures Snapshot1 of the normal log and activates FinalEventLog. +│ │ ├── Logs MAX_NUM_EXTRA_EVENTS individual events. +│ │ ├── Reads FinalEventsTable and asserts NumberOfEvents matches. +│ │ ├── Captures Snapshot2 and asserts Snapshot1 || FinalEntries == Snapshot2. +│ │ └── PASS. +│ ├── TestFinalEventLogTruncationMarker(): +│ │ ├── Logs events until HashLogExtendEvent returns EFI_VOLUME_FULL. +│ │ ├── Walks FinalEventsTable to the last entry. +│ │ ├── Asserts the entry is EV_NO_ACTION carrying TCG_LOG_TRUNCATION_EVENT_STRING. +│ │ └── PASS. +│ ├── TestPreReadyToBootScaling() (DXE results): +│ │ ├── Calls GetLog() to acquire the TcgLogTestDxe log. +│ │ ├── Verifies PASS in TcgLogTestDxe log. +│ │ └── PASS. +│ └── Test Cleanup (for TestPreReadyToBootScaling): +│ └── 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) +│ ├── All 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 `Tcg2Dxe` signals `gTcg2EventLogScaledGuid` (indicating the event log was dynamically scaled) or `HashLogExtendEvent` returns an error. | +| `TcgLogTestLogSingleEvent` | Builds and logs exactly one fixed test event via `HashLogExtendEvent`. Used by tests that need to add a small, deterministic number of events without triggering scaling. | + +## Truncation Marker Event + +When the `FinalEventLog` fills up and `Tcg2Dxe` can no longer append new +entries, it writes a final `EV_NO_ACTION` event whose data payload is the +ASCII string `"TCG Event Log Truncated"` (`TCG_LOG_TRUNCATION_EVENT_STRING`). +`TestFinalEventLogTruncationMarker` exercises this code path and verifies the +marker is present as the last entry of the table. The string constant in the +test sources must stay in sync with the definition in `Tcg2Dxe.c`. + +## 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 0000000000..7fe3040714 --- /dev/null +++ b/SecurityPkg/Tcg/TcgLogTest/TcgLogTest.h @@ -0,0 +1,64 @@ +/** @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 +**/ + +#pragma once + +#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; diff --git a/SecurityPkg/Tcg/TcgLogTest/TcgLogTestApp.c b/SecurityPkg/Tcg/TcgLogTest/TcgLogTestApp.c new file mode 100644 index 0000000000..199180dafc --- /dev/null +++ b/SecurityPkg/Tcg/TcgLogTest/TcgLogTestApp.c @@ -0,0 +1,758 @@ +/** @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. + + 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 "TcgLogTest.h" +#include "TcgLogTestCommon.h" + +#define UNIT_TEST_NAME "TCG Log Scaling Test" +#define UNIT_TEST_VERSION "1.0" + +// Must match TCG_EVENT_LOG_MAX_SCALE_COUNT in Tcg2Dxe.c. +#define TCG_EVENT_LOG_MAX_SCALE_COUNT 4 + +// Must match TCG_LOG_TRUNCATION_EVENT_STRING in Tcg2Dxe.c. +#define TCG_LOG_TRUNCATION_EVENT_STRING "TCG Event Log Truncated" + +// Number of extra events to log +#define MAX_NUM_EXTRA_EVENTS 8 + +STATIC EFI_TCG2_PROTOCOL *mTcg2Protocol = NULL; +STATIC TCG_LOG_TEST_PROTOCOL *mTcgLogTestProtocol = NULL; + +/** + Locate the fixed-size ACPI TCG event log region published by Tcg2Dxe via + the TPM2 ACPI table LAML/LASA fields. + + The fields only exist for revision >= 4; the Header.Length must include + them or they are considered absent. + + @param[out] Lasa On success, physical address of the ACPI event log region. + @param[out] Laml On success, length in bytes of the ACPI event log region. + + @retval EFI_SUCCESS ACPI event log located. + @retval EFI_INVALID_PARAMETER NULL argument. + @retval EFI_NOT_FOUND TPM2 ACPI table not present, revision < 4, + header length excludes LAML/LASA, or the + fields are zero. +**/ +STATIC +EFI_STATUS +GetAcpiEventLog ( + OUT EFI_PHYSICAL_ADDRESS *Lasa, + OUT UINT32 *Laml + ) +{ + EFI_ACPI_DESCRIPTION_HEADER *Header; + EFI_TPM2_ACPI_TABLE_V4 *TableV4; + EFI_TPM2_ACPI_TABLE_V5 *TableV5; + UINT32 FoundLaml; + UINT64 FoundLasa; + + if ((Lasa == NULL) || (Laml == NULL)) { + return EFI_INVALID_PARAMETER; + } + + Header = (EFI_ACPI_DESCRIPTION_HEADER *)EfiLocateFirstAcpiTable ( + EFI_ACPI_5_0_TRUSTED_COMPUTING_PLATFORM_2_TABLE_SIGNATURE + ); + if (Header == NULL) { + DEBUG ((DEBUG_ERROR, "%a: TPM2 ACPI table not found\n", __func__)); + return EFI_NOT_FOUND; + } + + // LAML/LASA are optional and only exist for revision 4 and above. Confirm + // the table's Header.Length is large enough to include those trailing fields + // before dereferencing them. + switch (Header->Revision) { + case EFI_TPM2_ACPI_TABLE_REVISION_4: + if (Header->Length < sizeof (EFI_TPM2_ACPI_TABLE_V4)) { + DEBUG ((DEBUG_ERROR, "%a: TPM2 ACPI rev4 length %u < %u\n", __func__, Header->Length, (UINT32)sizeof (EFI_TPM2_ACPI_TABLE_V4))); + return EFI_NOT_FOUND; + } + + TableV4 = (EFI_TPM2_ACPI_TABLE_V4 *)Header; + FoundLaml = TableV4->Laml; + FoundLasa = TableV4->Lasa; + break; + + case EFI_TPM2_ACPI_TABLE_REVISION_5: + if (Header->Length < sizeof (EFI_TPM2_ACPI_TABLE_V5)) { + DEBUG ((DEBUG_ERROR, "%a: TPM2 ACPI rev5 length %u < %u\n", __func__, Header->Length, (UINT32)sizeof (EFI_TPM2_ACPI_TABLE_V5))); + return EFI_NOT_FOUND; + } + + TableV5 = (EFI_TPM2_ACPI_TABLE_V5 *)Header; + FoundLaml = TableV5->Laml; + FoundLasa = TableV5->Lasa; + break; + + default: + DEBUG ((DEBUG_ERROR, "%a: TPM2 ACPI revision %u does not carry LAML/LASA\n", __func__, Header->Revision)); + return EFI_NOT_FOUND; + } + + if ((FoundLaml == 0) || (FoundLasa == 0)) { + DEBUG ((DEBUG_ERROR, "%a: TPM2 ACPI LAML/LASA are zero\n", __func__)); + return EFI_NOT_FOUND; + } + + *Lasa = (EFI_PHYSICAL_ADDRESS)FoundLasa; + *Laml = FoundLaml; + + return EFI_SUCCESS; +} + +/** + 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 +TestPreReadyToBootScaling ( + 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); + return UNIT_TEST_ERROR_TEST_FAILED; + } + + // 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. + + @param[in] Context Unit test context (unused). + + @retval UNIT_TEST_PASSED Scaling verified. + @retval UNIT_TEST_ERROR_TEST_FAILED Assertion failed. +**/ +UNIT_TEST_STATUS +EFIAPI +TestPostReadyToBootScaling ( + IN UNIT_TEST_CONTEXT Context + ) +{ + EFI_STATUS Status; + BOOLEAN Scaled; + + Status = TcgLogTestLogEventsUntilScaled (mTcg2Protocol, &Scaled); + UT_ASSERT_NOT_EFI_ERROR (Status); + UT_ASSERT_TRUE (Scaled); + + UT_LOG_INFO ("Post-ReadyToBoot scaling succeeded\n"); + + return UNIT_TEST_PASSED; +} + +/** + Test that the TCG event log can only be dynamically scaled up to + TCG_EVENT_LOG_MAX_SCALE_COUNT times, after which the log is marked as + truncated. + + TestPostReadyToBootScaling already consumed one scale in this boot, so this + test exhausts the remaining (TCG_EVENT_LOG_MAX_SCALE_COUNT - 1) scales and + then verifies the next attempt fails and the log is marked truncated. + + @param[in] Context Unit test context (unused). + + @retval UNIT_TEST_PASSED Scaling limit and truncation verified. + @retval UNIT_TEST_ERROR_TEST_FAILED Assertion failed. +**/ +UNIT_TEST_STATUS +EFIAPI +TestScaleLimitTruncatesLog ( + IN UNIT_TEST_CONTEXT Context + ) +{ + EFI_STATUS Status; + BOOLEAN Scaled; + BOOLEAN Truncated; + UINTN ScaleCount; + EFI_PHYSICAL_ADDRESS LogBase; + EFI_PHYSICAL_ADDRESS LastEntry; + + // TestPostReadyToBootScaling scaled once, scale the remaining amount. + for (ScaleCount = 1; ScaleCount < TCG_EVENT_LOG_MAX_SCALE_COUNT; ScaleCount++) { + Scaled = FALSE; + Status = TcgLogTestLogEventsUntilScaled (mTcg2Protocol, &Scaled); + UT_LOG_INFO ("Scale %u/%u: status=%r scaled=%d\n", ScaleCount + 1, TCG_EVENT_LOG_MAX_SCALE_COUNT, Status, Scaled); + UT_ASSERT_NOT_EFI_ERROR (Status); + UT_ASSERT_TRUE (Scaled); + } + + // Attempt to scale once more which should fail. + Scaled = FALSE; + Status = TcgLogTestLogEventsUntilScaled (mTcg2Protocol, &Scaled); + UT_LOG_INFO ("Scale beyond limit: status=%r scaled=%d\n", Status, Scaled); + UT_ASSERT_TRUE (EFI_ERROR (Status)); + UT_ASSERT_FALSE (Scaled); + + // Verify GetEventLog reports the log as truncated. + Status = mTcg2Protocol->GetEventLog ( + mTcg2Protocol, + EFI_TCG2_EVENT_LOG_FORMAT_TCG_2, + &LogBase, + &LastEntry, + &Truncated + ); + + UT_ASSERT_NOT_EFI_ERROR (Status); + UT_ASSERT_TRUE (Truncated); + + UT_LOG_INFO ("Test scaling limit succeeded\n"); + + return UNIT_TEST_PASSED; +} + +/** + Test that an EV_NO_ACTION truncation marker is appended to the + FinalEventLog when it becomes truncated. + + Activates FinalEventLog via GetEventLog, then logs events until + HashLogExtendEvent reports an error. Because the FinalEventLog does not scale + it is possible to truncate at which point the truncation marker is appended + as the final entry. The test then walks the table and verifies the last entry + is an EV_NO_ACTION carrying the truncation marker string. + + @param[in] Context Unit test context (unused). + + @retval UNIT_TEST_PASSED Truncation marker located and verified. + @retval UNIT_TEST_ERROR_TEST_FAILED Assertion failed. +**/ +UNIT_TEST_STATUS +EFIAPI +TestFinalEventLogTruncationMarker ( + IN UNIT_TEST_CONTEXT Context + ) +{ + EFI_STATUS Status; + EFI_PHYSICAL_ADDRESS LogBase; + EFI_PHYSICAL_ADDRESS LastEntry; + BOOLEAN Truncated; + BOOLEAN Scaled; + EFI_TCG2_FINAL_EVENTS_TABLE *FinalTable; + UINT8 *CurrentEvent; + UINT32 EventType; + UINT32 EventSize; + UINT8 *EventData; + UINT64 Index; + + // Activate FinalEventLog logging. + Status = mTcg2Protocol->GetEventLog ( + mTcg2Protocol, + EFI_TCG2_EVENT_LOG_FORMAT_TCG_2, + &LogBase, + &LastEntry, + &Truncated + ); + + UT_ASSERT_NOT_EFI_ERROR (Status); + UT_ASSERT_FALSE (Truncated); + + // Log events until HashLogExtendEvent reports an error. When the + // FinalEventLog becomes truncated, HashLogExtendEvent will return + // EFI_VOLUME_FULL. + Scaled = FALSE; + do { + Status = TcgLogTestLogEventsUntilScaled (mTcg2Protocol, &Scaled); + } while (!EFI_ERROR (Status)); + + UT_LOG_INFO ("Log events stopped: status=%r", Status); + UT_ASSERT_TRUE (Status == EFI_VOLUME_FULL); + + // Walk the FinalEventsTable and verify the last entry is the truncation marker. + FinalTable = NULL; + Status = EfiGetSystemConfigurationTable (&gEfiTcg2FinalEventsTableGuid, (VOID **)&FinalTable); + UT_ASSERT_NOT_EFI_ERROR (Status); + UT_ASSERT_NOT_NULL (FinalTable); + + UT_LOG_INFO ("FinalEventsTable: Version=%lu NumberOfEvents=%lu\n", FinalTable->Version, FinalTable->NumberOfEvents); + UT_ASSERT_TRUE (FinalTable->NumberOfEvents > 0); + + CurrentEvent = (UINT8 *)(FinalTable + 1); + EventType = 0; + EventSize = 0; + EventData = NULL; + + for (Index = 0; Index < FinalTable->NumberOfEvents; Index++) { + if (!TcgLogTestAdvanceEvent ( + &CurrentEvent, + (UINT8 *)(UINTN)MAX_ADDRESS, + NULL, + &EventType, + &EventSize, + &EventData + )) + { + UT_LOG_ERROR ("AdvanceEvent failed at index %lu\n", Index); + return UNIT_TEST_ERROR_TEST_FAILED; + } + } + + UT_LOG_INFO ("Last FinalEventLog entry: type=0x%x size=%u\n", EventType, EventSize); + UT_ASSERT_EQUAL (EventType, EV_NO_ACTION); + UT_ASSERT_NOT_NULL (EventData); + UT_ASSERT_TRUE (EventSize >= sizeof (TCG_LOG_TRUNCATION_EVENT_STRING)); + UT_ASSERT_MEM_EQUAL (EventData, TCG_LOG_TRUNCATION_EVENT_STRING, sizeof (TCG_LOG_TRUNCATION_EVENT_STRING)); + + return UNIT_TEST_PASSED; +} + +/** + Test that a snapshot of the event log plus the FinalEventLog entries + exactly reconstructs a later snapshot of the event log. + + After Tcg2Dxe begins maintaining the FinalEventLog (triggered by the first + call to GetEventLog), every subsequent event is appended to both the normal + event log and the FinalEventLog. Therefore: + + Snapshot1 + FinalEventLog_entries == Snapshot2 + + where Snapshot1 is the normal log captured immediately after the first + GetEventLog call and Snapshot2 is the normal log captured after additional + events have been logged. + + @param[in] Context Unit test context (unused). + + @retval UNIT_TEST_PASSED Reconstruction matches. + @retval UNIT_TEST_ERROR_TEST_FAILED Assertion failed. +**/ +UNIT_TEST_STATUS +EFIAPI +TestSnapshotPlusFinalMatchesEventLog ( + IN UNIT_TEST_CONTEXT Context + ) +{ + EFI_STATUS Status; + EFI_PHYSICAL_ADDRESS LogLocation1; + EFI_PHYSICAL_ADDRESS LastEntry1; + EFI_PHYSICAL_ADDRESS LogLocation2; + EFI_PHYSICAL_ADDRESS LastEntry2; + BOOLEAN Truncated; + EFI_TCG2_FINAL_EVENTS_TABLE *FinalTable; + UINT8 *Snapshot; + UINTN SnapshotSize1; + UINTN SnapshotSize2; + UINTN FinalEntriesSize; + UINT8 *FinalEntriesStart; + UINT8 *LogPtr; + UINT64 Index; + + // First snapshot. Also activates FinalEventLog logging. + Status = mTcg2Protocol->GetEventLog ( + mTcg2Protocol, + EFI_TCG2_EVENT_LOG_FORMAT_TCG_2, + &LogLocation1, + &LastEntry1, + &Truncated + ); + + UT_ASSERT_NOT_EFI_ERROR (Status); + UT_ASSERT_FALSE (Truncated); + + // Snapshot length runs from LogLocation1 to one byte past LastEntry1. + LogPtr = (UINT8 *)(UINTN)LastEntry1; + if (!TcgLogTestAdvanceEvent ( + &LogPtr, + (UINT8 *)(UINTN)MAX_ADDRESS, + NULL, + NULL, + NULL, + NULL + )) + { + UT_LOG_ERROR ("AdvanceEvent failed past LastEntry1\n"); + return UNIT_TEST_ERROR_TEST_FAILED; + } + + SnapshotSize1 = (UINTN)LogPtr - (UINTN)LogLocation1; + UT_LOG_INFO ("Snapshot1: base=0x%lx size=%u\n", LogLocation1, SnapshotSize1); + + Snapshot = AllocateCopyPool (SnapshotSize1, (VOID *)(UINTN)LogLocation1); + UT_ASSERT_NOT_NULL (Snapshot); + + // Log a small number of additional events. Few enough not to trigger + // scaling, but enough to make the test meaningful. + for (Index = 0; Index < MAX_NUM_EXTRA_EVENTS; Index++) { + Status = TcgLogTestLogSingleEvent (mTcg2Protocol); + if (EFI_ERROR (Status)) { + UT_LOG_ERROR ("LogSingleEvent %u failed: %r\n", Index, Status); + FreePool (Snapshot); + return UNIT_TEST_ERROR_TEST_FAILED; + } + } + + // Retrieve the FinalEventLog from the system configuration table. + FinalTable = NULL; + Status = EfiGetSystemConfigurationTable (&gEfiTcg2FinalEventsTableGuid, (VOID **)&FinalTable); + if (EFI_ERROR (Status) || (FinalTable == NULL)) { + UT_LOG_ERROR ("FinalEventsTable lookup failed: %r\n", Status); + FreePool (Snapshot); + return UNIT_TEST_ERROR_TEST_FAILED; + } + + UT_LOG_INFO ("FinalEventsTable: NumberOfEvents=%lu\n", FinalTable->NumberOfEvents); + UT_ASSERT_EQUAL (FinalTable->NumberOfEvents, MAX_NUM_EXTRA_EVENTS); + + // Walk all FinalEventLog entries to compute their combined byte size. + FinalEntriesStart = (UINT8 *)(FinalTable + 1); + LogPtr = FinalEntriesStart; + for (Index = 0; Index < FinalTable->NumberOfEvents; Index++) { + if (!TcgLogTestAdvanceEvent ( + &LogPtr, + (UINT8 *)(UINTN)MAX_ADDRESS, + NULL, + NULL, + NULL, + NULL + )) + { + UT_LOG_ERROR ("AdvanceEvent failed at FinalEventLog index %lu\n", Index); + FreePool (Snapshot); + return UNIT_TEST_ERROR_TEST_FAILED; + } + } + + FinalEntriesSize = (UINTN)LogPtr - (UINTN)FinalEntriesStart; + UT_LOG_INFO ("FinalEventLog entries total bytes: %u\n", FinalEntriesSize); + + // Second snapshot. + Status = mTcg2Protocol->GetEventLog ( + mTcg2Protocol, + EFI_TCG2_EVENT_LOG_FORMAT_TCG_2, + &LogLocation2, + &LastEntry2, + &Truncated + ); + + UT_ASSERT_NOT_EFI_ERROR (Status); + UT_ASSERT_FALSE (Truncated); + + LogPtr = (UINT8 *)(UINTN)LastEntry2; + if (!TcgLogTestAdvanceEvent ( + &LogPtr, + (UINT8 *)(UINTN)MAX_ADDRESS, + NULL, + NULL, + NULL, + NULL + )) + { + UT_LOG_ERROR ("AdvanceEvent failed past LastEntry2\n"); + FreePool (Snapshot); + return UNIT_TEST_ERROR_TEST_FAILED; + } + + SnapshotSize2 = (UINTN)LogPtr - (UINTN)LogLocation2; + UT_LOG_INFO ("Snapshot2: base=0x%lx size=%u\n", LogLocation2, SnapshotSize2); + + // Snapshot2 must be exactly Snapshot1 followed by the FinalEventLog entries. + UT_ASSERT_EQUAL (SnapshotSize2, SnapshotSize1 + FinalEntriesSize); + UT_ASSERT_MEM_EQUAL ((VOID *)(UINTN)LogLocation2, Snapshot, SnapshotSize1); + UT_ASSERT_MEM_EQUAL ((VOID *)((UINTN)LogLocation2 + SnapshotSize1), FinalEntriesStart, FinalEntriesSize); + + FreePool (Snapshot); + return UNIT_TEST_PASSED; +} + +/** + Verify the ACPI event log region contains same events as the normal event log. + + @param[in] Context Unit test context (unused). + + @retval UNIT_TEST_PASSED Contents match. + @retval UNIT_TEST_ERROR_TEST_FAILED Assertion failed. +**/ +UNIT_TEST_STATUS +EFIAPI +TestAcpiEventLogMirrorsNormalLog ( + IN UNIT_TEST_CONTEXT Context + ) +{ + EFI_STATUS Status; + EFI_PHYSICAL_ADDRESS AcpiLasa; + UINT32 AcpiLaml; + EFI_PHYSICAL_ADDRESS LogLocation; + EFI_PHYSICAL_ADDRESS LastEntry; + BOOLEAN Truncated; + UINT8 *LogPtr; + UINTN NormalSize; + + Status = GetAcpiEventLog (&AcpiLasa, &AcpiLaml); + UT_ASSERT_NOT_EFI_ERROR (Status); + + Status = mTcg2Protocol->GetEventLog ( + mTcg2Protocol, + EFI_TCG2_EVENT_LOG_FORMAT_TCG_2, + &LogLocation, + &LastEntry, + &Truncated + ); + + UT_ASSERT_NOT_EFI_ERROR (Status); + UT_ASSERT_FALSE (Truncated); + + LogPtr = (UINT8 *)(UINTN)LastEntry; + if (!TcgLogTestAdvanceEvent ( + &LogPtr, + (UINT8 *)(UINTN)MAX_ADDRESS, + NULL, + NULL, + NULL, + NULL + )) + { + UT_LOG_ERROR ("AdvanceEvent past LastEntry failed\n"); + return UNIT_TEST_ERROR_TEST_FAILED; + } + + NormalSize = (UINTN)LogPtr - (UINTN)LogLocation; + UT_LOG_INFO ("Normal log: base=0x%lx size=%u; ACPI region: base=0x%lx size=%u\n", LogLocation, NormalSize, AcpiLasa, AcpiLaml); + + // The normal log must fit within the ACPI region for the mirror test to be + // meaningful; otherwise the ACPI region has already truncated and this test + // cannot make a byte-for-byte comparison. + UT_ASSERT_TRUE (NormalSize <= AcpiLaml); + UT_ASSERT_MEM_EQUAL ((VOID *)(UINTN)AcpiLasa, (VOID *)(UINTN)LogLocation, NormalSize); + + return UNIT_TEST_PASSED; +} + +/** + Verify that when the normal event log scales the ACPI region receives + an EV_NO_ACTION truncation marker as its final event. Walks the ACPI + region event-by-event, tracks the last valid event, and asserts that + it is EV_NO_ACTION carrying truncation event marker. + + @param[in] Context Unit test context (unused). + + @retval UNIT_TEST_PASSED Truncation marker located. + @retval UNIT_TEST_ERROR_TEST_FAILED Assertion failed. +**/ +UNIT_TEST_STATUS +EFIAPI +TestAcpiEventLogTruncationMarker ( + IN UNIT_TEST_CONTEXT Context + ) +{ + EFI_STATUS Status; + EFI_PHYSICAL_ADDRESS AcpiLasa; + UINT32 AcpiLaml; + UINT8 *EventPtr; + UINT8 *RegionEnd; + UINT32 PcrIndex; + UINT32 EventType; + UINT32 EventSize; + UINT8 *EventData; + UINT32 LastType; + UINT32 LastSize; + UINT8 *LastData; + UINTN EventCount; + + Status = GetAcpiEventLog (&AcpiLasa, &AcpiLaml); + UT_ASSERT_NOT_EFI_ERROR (Status); + + EventPtr = (UINT8 *)(UINTN)AcpiLasa; + RegionEnd = EventPtr + AcpiLaml; + LastType = 0; + LastSize = 0; + LastData = NULL; + EventCount = 0; + + while (TcgLogTestAdvanceEvent (&EventPtr, RegionEnd, &PcrIndex, &EventType, &EventSize, &EventData)) { + LastType = EventType; + LastSize = EventSize; + LastData = EventData; + EventCount++; + + if (EventPtr >= RegionEnd) { + break; + } + } + + UT_LOG_INFO ("ACPI region events walked: %u, last type=0x%x size=%u\n", EventCount, LastType, LastSize); + UT_ASSERT_TRUE (EventCount > 0); + UT_ASSERT_NOT_NULL (LastData); + + // The last valid event in the ACPI region must be the truncation marker. + UT_ASSERT_EQUAL (LastType, EV_NO_ACTION); + UT_ASSERT_TRUE (LastSize >= sizeof (TCG_LOG_TRUNCATION_EVENT_STRING)); + UT_ASSERT_MEM_EQUAL (LastData, TCG_LOG_TRUNCATION_EVENT_STRING, sizeof (TCG_LOG_TRUNCATION_EVENT_STRING)); + + 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, "ACPI event log mirrors normal log", "AcpiLogMirrors", TestAcpiEventLogMirrorsNormalLog, LocateProtocols, SaveAndReboot, NULL); + AddTestCase (Suite, "Post-ReadyToBoot scaling succeeds", "PostRtbScaling", TestPostReadyToBootScaling, LocateProtocols, NULL, NULL); + AddTestCase (Suite, "ACPI event log gets truncation marker", "AcpiLogTruncates", TestAcpiEventLogTruncationMarker, LocateProtocols, NULL, NULL); + AddTestCase (Suite, "Scaling occurs X times before truncating", "ScaleLimitTruncates", TestScaleLimitTruncatesLog, LocateProtocols, EnableDxeTestAndReboot, NULL); + AddTestCase (Suite, "Snapshot plus FinalEventLog matches full log", "SnapshotPlusFinalMatches", TestSnapshotPlusFinalMatchesEventLog, LocateProtocols, NULL, NULL); + AddTestCase (Suite, "FinalEventLog gets truncation marker", "FinalEventLogTruncates", TestFinalEventLogTruncationMarker, LocateProtocols, NULL, NULL); + AddTestCase (Suite, "Pre-ReadyToBoot DXE results contain PASS", "PreRtbResults", TestPreReadyToBootScaling, 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 0000000000..96bcd9c38e --- /dev/null +++ b/SecurityPkg/Tcg/TcgLogTest/TcgLogTestApp.inf @@ -0,0 +1,46 @@ +## @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 + gTcgLogTestProtocolGuid ## CONSUMES + gEfiTcg2FinalEventsTableGuid ## CONSUMES ## SystemTable + +[Guids] + gTcg2EventLogScaledGuid ## CONSUMES ## Event diff --git a/SecurityPkg/Tcg/TcgLogTest/TcgLogTestCommon.c b/SecurityPkg/Tcg/TcgLogTest/TcgLogTestCommon.c new file mode 100644 index 0000000000..60f44bec6e --- /dev/null +++ b/SecurityPkg/Tcg/TcgLogTest/TcgLogTestCommon.c @@ -0,0 +1,372 @@ +/** @file + TCG Log Test common implementation shared by TcgLogTestDxe and TcgLogTestApp. + + Copyright (c), Microsoft Corporation. + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include +#include +#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" + +/** + 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)sizeof (TCG_LOG_TEST_EVENT_PAYLOAD); + 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, TCG_LOG_TEST_EVENT_PAYLOAD, 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)(CONST CHAR8 *)TCG_LOG_TEST_EVENT_PAYLOAD, + sizeof (TCG_LOG_TEST_EVENT_PAYLOAD), + 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 = ReadUnaligned32 ((UINT32 *)EventPtr); + } + + // Store the EventType, if provided. + if (EventType != NULL) { + *EventType = ReadUnaligned32 ((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 = ReadUnaligned32 ((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 = ReadUnaligned16 ((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 = ReadUnaligned32 ((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; +} + +/** + Notification callback registered on gTcg2EventLogScaledGuid. Sets the BOOLEAN + pointed to by Context to TRUE. + + @param[in] Event Event handle (unused). + @param[in] Context Pointer to a BOOLEAN to flip when the event fires. +**/ +STATIC +VOID +EFIAPI +TcgLogTestScaleNotify ( + IN EFI_EVENT Event, + IN VOID *Context + ) +{ + if (Context != NULL) { + *(BOOLEAN *)Context = TRUE; + } +} + +/** + Log events until Tcg2Dxe signals that the event log was dynamically scaled + or until 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_EVENT ScaleEvent; + EFI_TCG2_EVENT *Event; + BOOLEAN LogScaled; + + // Validate the input parameters. + if ((Tcg2Protocol == NULL) || (Scaled == NULL)) { + return EFI_INVALID_PARAMETER; + } + + *Scaled = FALSE; + LogScaled = FALSE; + Event = NULL; + + // Build a test event. + Status = TcgLogTestBuildEvent (&Event); + if (EFI_ERROR (Status)) { + return Status; + } + + // Register a notification callback for when the TCG log gets scaled. + Status = gBS->CreateEventEx ( + EVT_NOTIFY_SIGNAL, + TPL_CALLBACK, + TcgLogTestScaleNotify, + &LogScaled, + &gTcg2EventLogScaledGuid, + &ScaleEvent + ); + + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: CreateEventEx failed - %r\n", __func__, Status)); + goto Exit; + } + + // Log events until the scale callback fires or LogEvent returns an error. + while (!LogScaled) { + Status = TcgLogTestLogEvent (Tcg2Protocol, Event); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: LogEvent failed - %r\n", __func__, Status)); + break; + } + } + + gBS->CloseEvent (ScaleEvent); + + if (LogScaled) { + *Scaled = TRUE; + DEBUG ((DEBUG_INFO, "%a: Log scaled\n", __func__)); + Status = EFI_SUCCESS; + } + +Exit: + FreePool (Event); + return Status; +} + +/** + Log a single fixed test event via the TCG2 protocol. + + @param[in] Tcg2Protocol TCG2 protocol instance. + + @retval EFI_SUCCESS Event logged. + @retval EFI_INVALID_PARAMETER NULL argument. + @retval EFI_OUT_OF_RESOURCES Allocation failed. + @retval Other HashLogExtendEvent failure. +**/ +EFI_STATUS +TcgLogTestLogSingleEvent ( + IN EFI_TCG2_PROTOCOL *Tcg2Protocol + ) +{ + EFI_STATUS Status; + EFI_TCG2_EVENT *Event; + + if (Tcg2Protocol == NULL) { + return EFI_INVALID_PARAMETER; + } + + Event = NULL; + Status = TcgLogTestBuildEvent (&Event); + if (EFI_ERROR (Status)) { + return Status; + } + + Status = TcgLogTestLogEvent (Tcg2Protocol, Event); + FreePool (Event); + return Status; +} diff --git a/SecurityPkg/Tcg/TcgLogTest/TcgLogTestCommon.h b/SecurityPkg/Tcg/TcgLogTest/TcgLogTestCommon.h new file mode 100644 index 0000000000..a712849ef5 --- /dev/null +++ b/SecurityPkg/Tcg/TcgLogTest/TcgLogTestCommon.h @@ -0,0 +1,67 @@ +/** @file + TCG Log Test common function declarations shared by TcgLogTestDxe and + TcgLogTestApp. + + Copyright (c), Microsoft Corporation. + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#pragma once + +#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 + ); + +/** + Log a single fixed test event via the TCG2 protocol. + + @param[in] Tcg2 TCG2 protocol instance. + + @retval EFI_SUCCESS Event logged. + @retval EFI_INVALID_PARAMETER NULL argument. + @retval Other HashLogExtendEvent failure. +**/ +EFI_STATUS +TcgLogTestLogSingleEvent ( + IN EFI_TCG2_PROTOCOL *Tcg2 + ); diff --git a/SecurityPkg/Tcg/TcgLogTest/TcgLogTestDxe.c b/SecurityPkg/Tcg/TcgLogTest/TcgLogTestDxe.c new file mode 100644 index 0000000000..f27be3672c --- /dev/null +++ b/SecurityPkg/Tcg/TcgLogTest/TcgLogTestDxe.c @@ -0,0 +1,261 @@ +/** @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; + } + + if (mLogOffset == 0) { + DEBUG ((DEBUG_ERROR, "%a: Log is empty.\n", __func__)); + return EFI_NOT_STARTED; + } + + *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 + (Tcg2Dxe signals gTcg2EventLogScaledGuid). +**/ +STATIC +VOID +TestPreReadyToBootScaling ( + VOID + ) +{ + EFI_STATUS Status; + BOOLEAN Scaled; + + 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; + } + + 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. + Status = TcgLogTestEnable (&mTcgLogTestProtocol, FALSE); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: Failed to clear enable variable: %r\n", __func__, Status)); + } + + 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 0000000000..ead24bcd12 --- /dev/null +++ b/SecurityPkg/Tcg/TcgLogTest/TcgLogTestDxe.inf @@ -0,0 +1,50 @@ +## @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 + +[Guids] + gTcg2EventLogScaledGuid ## CONSUMES ## Event + +[Depex] + gEfiVariableArchProtocolGuid AND gEfiTcg2ProtocolGuid