From 65754303574b6b5b8530a20dc3ff7bb4e98276b0 Mon Sep 17 00:00:00 2001 From: Constantine Peresypkin Date: Wed, 9 Sep 2026 17:32:27 -0700 Subject: [PATCH] efi: copy PCR2 events measured after the EV_SEPARATOR event The drivers and apps profile (PCR2) is a verbatim copy of the events in the TCG log rather than a prediction, but measureDriversAndApps stopped copying at the EV_SEPARATOR event and dropped anything measured after it. Some firmware measures vendor-defined events to PCR2 after the separators, as part of the transition to OS-present. AMD AGESA (StrixKrackanPI-FP8, module AmdPspDxeV2StxKrk) measures the TSME status from its ReadyToBoot handler as a 1-byte event with the vendor event type 0x8401 to PCR2, gated on the PSP firmware reporting support for the measurement. On builds where that handler runs after the Tcg2Dxe separators (observed on a Lenovo ThinkPad P14s Gen 6 AMD with BIOS 1.19 and 1.20, with both the discrete TPM and the Pluton fTPM selected), the event lands after the EV_SEPARATOR event. Because the profile omitted the event, the sealed PCR2 value could never match the real PCR value and every boot fell back to the recovery key. Resealing did not help because the same truncated profile was generated each time. The preinstall checks did not detect the problem either, because the log replays correctly and events in the OS-present phase are ignored by the phase tracker. Keep copying PCR2 events after the separator, and reject logs with more than one separator in PCR2. Add a mock log option and a test that exercises a post-separator vendor event. When TSME is disabled the same AGESA code additionally measures the event to PCR7, which the secure boot policy profile predicts rather than copies; that case is not addressed here. --- efi/fw_load_handler.go | 19 +++++++++++++++++-- efi/fw_load_handler_test.go | 13 +++++++++++++ internal/efitest/log.go | 10 ++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/efi/fw_load_handler.go b/efi/fw_load_handler.go index 6cfa0fd1..86c63f61 100644 --- a/efi/fw_load_handler.go +++ b/efi/fw_load_handler.go @@ -423,18 +423,33 @@ func (h *fwLoadHandler) measurePlatformFirmware(ctx pcrBranchContext) error { } func (h *fwLoadHandler) measureDriversAndApps(ctx pcrBranchContext) error { + seenSeparator := false + for _, event := range h.log.Events { if event.PCRIndex != internal_efi.DriversAndAppsPCR { continue } if event.EventType == tcglog.EventTypeSeparator { - return h.measureSeparator(ctx, internal_efi.DriversAndAppsPCR, event) + if seenSeparator { + return errors.New("more than one separator in log") + } + if err := h.measureSeparator(ctx, internal_efi.DriversAndAppsPCR, event); err != nil { + return err + } + seenSeparator = true + continue } + + // Some firmware measures vendor events here after the separator, + // and this profile is a copy of the log, so keep copying. ctx.ExtendPCR(internal_efi.DriversAndAppsPCR, event.Digests[ctx.PCRAlg()]) } - return errors.New("missing separator in log") + if !seenSeparator { + return errors.New("missing separator in log") + } + return nil } func (h *fwLoadHandler) measureBootManagerCodePreOS(ctx pcrBranchContext) error { diff --git a/efi/fw_load_handler_test.go b/efi/fw_load_handler_test.go index fdb4a3ff..aa04dccd 100644 --- a/efi/fw_load_handler_test.go +++ b/efi/fw_load_handler_test.go @@ -992,6 +992,19 @@ func (s *fwLoadHandlerSuite) TestMeasureImageStartDriversAndAppsProfile2(c *C) { }) } +func (s *fwLoadHandlerSuite) TestMeasureImageStartDriversAndAppsProfileVendorEventAfterSeparator(c *C) { + s.testMeasureImageStart(c, &testFwMeasureImageStartData{ + logOptions: &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256, tpm2.HashAlgorithmSHA1}, IncludeVendorEventAfterSeparator: true}, + alg: tpm2.HashAlgorithmSHA256, + pcrs: MakePcrFlags(internal_efi.DriversAndAppsPCR), + expectedEvents: []*mockPcrBranchEvent{ + {pcr: 2, eventType: mockPcrBranchResetEvent}, + {pcr: 2, eventType: mockPcrBranchExtendEvent, digest: testutil.DecodeHexString(c, "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119")}, // EV_SEPARATOR + {pcr: 2, eventType: mockPcrBranchExtendEvent, digest: testutil.DecodeHexString(c, "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a")}, // vendor event, SHA-256 of 0x01 + }, + }) +} + func (s *fwLoadHandlerSuite) TestMeasureImageStartErrDisallowDMAProtectionDisabled(c *C) { collector := NewVariableSetCollector(efitest.NewMockHostEnvironment(makeMockVars(c, withMsSecureBootConfig()), nil)) ctx := newMockPcrBranchContext(&mockPcrProfileContext{ diff --git a/internal/efitest/log.go b/internal/efitest/log.go index d9d59a11..f13cc511 100644 --- a/internal/efitest/log.go +++ b/internal/efitest/log.go @@ -191,6 +191,7 @@ type LogOptions struct { NoSBAT bool // omit the SbatLevel measurement to mimic older versions of shim PreOSVerificationUsesDigests crypto.Hash // Whether Driver or SysPrep launches are verified using a digest DisableDeployedMode bool // Whether deployed/audit modes are disabled and we have UEFI 2.5 + IncludeVendorEventAfterSeparator bool // include a vendor-defined event in PCR2 after the pre-OS to OS-present transition } // NewLog creates a mock TCG log for testing. The log will look like a standard @@ -666,6 +667,15 @@ func NewLog(c *C, opts *LogOptions) *tcglog.Log { maybeMeasureDMAProtectionDisabledEvent(c, builder, opts, DMAProtectionDisabledEventOrderAfterSeparator) } + // Mock a vendor-defined event measured to PCR2 after the transition to OS-present. + if opts.IncludeVendorEventAfterSeparator { + data := tcglog.OpaqueEventData([]byte{0x01}) + builder.hashLogExtendEvent(c, data, &logEvent{ + pcrIndex: 2, + eventType: tcglog.EventType(0x8401), + data: data}) + } + // Mock firmware application launch if opts.IncludeOSPresentFirmwareAppLaunch != zeroGuid { pe := bytesHashData(opts.IncludeOSPresentFirmwareAppLaunch[:])