SecurityPkg: Introduce Dynamic TCG Log Scaling

Implemented dynamic TCG log scaling in Tcg2Dxe. When the log would become
truncated it instead now dynamically scales doubling the size each time.
An ERROR log is reported that an increase to your base log size should
occur such that scaling is not necessary. This is a precaution against
platforms that log a lot and the addition of new hashing algorithms for
PQC. The log is allocated in BootServices memory. Tests were added via
TcgLogTest which includes a DXE driver and a UEFI shell UnitTest app. The
DXE driver handles pre-ReadyToBoot tests while the TestApp handles
post-ReadyToBoot tests as well as gathering the test results from the DXE
driver. Markdown documents were created to detail the changes.

Added the Truncation event marker to the end of the FinalEventLog when it
becomes truncated. Added a event signal for when scaling occurs on the
normal event log. Consumers can trigger callbacks on this event; the test
app uses this to know when scaling occurs.

Added an ACPI log region for the ACPI table LAML/LASA. This region does
not scale and can become truncated indicated by the Truncation event
marker.

Signed-off-by: Raymond Diaz <raymonddiaz@microsoft.com>
This commit is contained in:
rdiaz 2026-07-14 02:40:34 +00:00 committed by mergify[bot]
parent ef437b30ad
commit ffaaf0f14c
14 changed files with 2482 additions and 53 deletions

View file

@ -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;

View file

@ -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

View file

@ -254,6 +254,14 @@
SecurityPkg/Library/PeiDxeTpmPlatformHierarchyLib/PeiDxeTpmPlatformHierarchyLib.inf
SecurityPkg/Library/PeiDxeTpmPlatformHierarchyLibNull/PeiDxeTpmPlatformHierarchyLib.inf
SecurityPkg/Tcg/TcgLogTest/TcgLogTestDxe.inf
SecurityPkg/Tcg/TcgLogTest/TcgLogTestApp.inf {
<LibraryClasses>
UnitTestLib|UnitTestFrameworkPkg/Library/UnitTestLib/UnitTestLib.inf
UnitTestPersistenceLib|UnitTestFrameworkPkg/Library/UnitTestPersistenceLibNull/UnitTestPersistenceLibNull.inf
UnitTestResultReportLib|UnitTestFrameworkPkg/Library/UnitTestResultReportLib/UnitTestResultReportLibConOut.inf
}
#
# TCG Storage.
#

View file

@ -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.

View file

@ -15,6 +15,7 @@ SPDX-License-Identifier: BSD-2-Clause-Patent
#include <Guid/GlobalVariable.h>
#include <Guid/HobList.h>
#include <Guid/TcgEventHob.h>
#include <Guid/Tcg2EventLogScaled.h>
#include <Guid/EventGroup.h>
#include <Guid/EventExitBootServiceFailed.h>
#include <Guid/ImageAuthentication.h>
@ -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.

View file

@ -88,6 +88,7 @@
gTpm2StartupLocalityHobGuid ## SOMETIMES_CONSUMES ## HOB
gTcg800155PlatformIdEventHobGuid ## SOMETIMES_CONSUMES ## HOB
gEfiDeviceSignatureDatabaseGuid
gTcg2EventLogScaledGuid ## SOMETIMES_PRODUCES ## Event
[Protocols]
gEfiTcg2ProtocolGuid ## PRODUCES

View file

@ -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.

View file

@ -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 <Uefi.h>
#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;

View file

@ -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 <Uefi.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/UefiApplicationEntryPoint.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/UnitTestLib.h>
#include <Protocol/Tcg2Protocol.h>
#include <IndustryStandard/Acpi.h>
#include <IndustryStandard/Tpm2Acpi.h>
#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;
}

View file

@ -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

View file

@ -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 <Uefi.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Protocol/Tcg2Protocol.h>
#include <Guid/Tcg2EventLogScaled.h>
#include <IndustryStandard/UefiTcgPlatform.h>
#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;
}

View file

@ -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 <Uefi.h>
#include <Protocol/Tcg2Protocol.h>
#include <IndustryStandard/UefiTcgPlatform.h>
/**
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
);

View file

@ -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 <Uefi.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/PrintLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiDriverEntryPoint.h>
#include <Library/UefiLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Protocol/Tcg2Protocol.h>
#include <IndustryStandard/UefiTcgPlatform.h>
#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;
}

View file

@ -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