From 6af79e3405504a853ed301b9712a1ca327f67217 Mon Sep 17 00:00:00 2001
From: ebrig <146060912+ebrig@users.noreply.github.com>
Date: Tue, 15 Sep 2026 07:11:27 -0400
Subject: [PATCH 1/2] efi: support AMD pre-OS measurements
Validate AMD TSME event data and HP pre-boot DMA configuration events, and derive their profile extensions from explicit platform configuration rather than copying event-log digests.
---
efi/amd_pre_os.go | 57 +++++++
efi/export_test.go | 2 +
efi/fw_load_handler.go | 126 ++++++++++++++-
efi/pcr2_vendor_event_test.go | 268 ++++++++++++++++++++++++++++++++
efi/pcr7_hp_dma_event_test.go | 212 +++++++++++++++++++++++++
internal/efi/tcg_events.go | 39 +++++
internal/efi/tcg_events_test.go | 65 ++++++++
7 files changed, 761 insertions(+), 8 deletions(-)
create mode 100644 efi/amd_pre_os.go
create mode 100644 efi/pcr2_vendor_event_test.go
create mode 100644 efi/pcr7_hp_dma_event_test.go
diff --git a/efi/amd_pre_os.go b/efi/amd_pre_os.go
new file mode 100644
index 00000000..73e264fe
--- /dev/null
+++ b/efi/amd_pre_os.go
@@ -0,0 +1,57 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2026 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package efi
+
+import internal_efi "github.com/snapcore/secboot/internal/efi"
+
+const (
+ amdTSMEEnabledParamKey loadParamsKey = "amd_tsme_enabled"
+ includeHPPreBootDMAConfigParamKey loadParamsKey = "include_hp_pre_boot_dma_config"
+)
+
+type amdPreOSMeasurementsOption struct {
+ tsmeEnabled bool
+ includeHPPreBootDMAConfig bool
+}
+
+func (o *amdPreOSMeasurementsOption) ApplyOptionTo(visitor internal_efi.PCRProfileOptionVisitor) error {
+ visitor.AddImageLoadParams(func(params ...loadParams) []loadParams {
+ out := make([]loadParams, 0, len(params))
+ for _, p := range params {
+ p = p.Clone()
+ p[amdTSMEEnabledParamKey] = o.tsmeEnabled
+ p[includeHPPreBootDMAConfigParamKey] = o.includeHPPreBootDMAConfig
+ out = append(out, p)
+ }
+ return out
+ })
+ return nil
+}
+
+// WithAMDPreOSMeasurements supplies independently detected AMD platform
+// configuration that affects pre-OS measurements. tsmeEnabled controls the
+// expected payload if AMD's vendor-defined TSME event is present. If
+// includeHPPreBootDMAConfig is true, the PCR7 profile includes HP's enabled
+// SVM and DMA protection configuration event.
+func WithAMDPreOSMeasurements(tsmeEnabled, includeHPPreBootDMAConfig bool) PCRProfileOption {
+ return &amdPreOSMeasurementsOption{
+ tsmeEnabled: tsmeEnabled,
+ includeHPPreBootDMAConfig: includeHPPreBootDMAConfig,
+ }
+}
diff --git a/efi/export_test.go b/efi/export_test.go
index 73c4ff71..38186a30 100644
--- a/efi/export_test.go
+++ b/efi/export_test.go
@@ -29,8 +29,10 @@ import (
// Export constants for testing
const (
+ AMDTSMEEnabledParamKey = amdTSMEEnabledParamKey
GrubChainloaderUsesShimProtocol = grubChainloaderUsesShimProtocol
KernelConfigPCR = kernelConfigPCR
+ IncludeHPPreBootDMAConfigParamKey = includeHPPreBootDMAConfigParamKey
KernelCommandlineParamKey = kernelCommandlineParamKey
ShimFixVariableAuthorityEventsMatchSpec = shimFixVariableAuthorityEventsMatchSpec
ShimHasSbatRevocationManagement = shimHasSbatRevocationManagement
diff --git a/efi/fw_load_handler.go b/efi/fw_load_handler.go
index 6cfa0fd1..90453fcd 100644
--- a/efi/fw_load_handler.go
+++ b/efi/fw_load_handler.go
@@ -148,6 +148,8 @@ func (h *fwLoadHandler) measureSecureBootPolicyPreOS(ctx pcrBranchContext) error
// EV_EFI_ACTION event in the profile if it is present.
includeThunderboltSecurityLevel0 := boolParamOrFalse(ctx.Params(), includeThunderboltSecurityLevel0ParamKey)
+ includeHPPreBootDMAConfig := boolParamOrFalse(ctx.Params(), includeHPPreBootDMAConfigParamKey)
+
// Wind the log forward to the first EV_EFI_VARIABLE_DRIVER_CONFIG event, including
// any EV_EFI_ACTION "DMA Protection Disabled" measurement before this in the target
// profile, if this measurement is encountered and the supplied options permit it.
@@ -157,6 +159,10 @@ func (h *fwLoadHandler) measureSecureBootPolicyPreOS(ctx pcrBranchContext) error
// enabled. A firmware debugger permits an adversary with local access to control
// firmware execution, bypassing any protections offered by measuredboot or verified
// boot, and the presence of one should prevent FDE from being enabled.
+ var (
+ measuredHPPreBootDMAConfig bool
+ measuredAMDTSMEConfig bool
+ )
for len(events) > 0 {
e := events[0]
events = events[1:]
@@ -166,15 +172,31 @@ func (h *fwLoadHandler) measureSecureBootPolicyPreOS(ctx pcrBranchContext) error
continue
}
+ if handled, err := h.measureAMDTSMEConfigEvent(ctx, e, &measuredAMDTSMEConfig); handled {
+ if err != nil {
+ return err
+ }
+ continue
+ }
+
if e.EventType == tcglog.EventTypeEFIVariableDriverConfig {
- // This is the first secure boot configuration measurement. In most
- // circumstances, this will be the first measurement to PCR7. Only
- // in the case where the first event is a EV_EFI_ACTION "DMA Protection
- // Disabled" event will this not be true.
+ // This is the first secure boot configuration measurement. It is
+ // generally the first measurement to PCR7, although supported
+ // pre-configuration action or vendor events may precede it.
+ if includeHPPreBootDMAConfig && !measuredHPPreBootDMAConfig {
+ return errors.New("missing expected HP pre-boot DMA configuration event")
+ }
break
}
switch {
+ case internal_efi.IsHPPreBootDMAConfigEvent(e) && includeHPPreBootDMAConfig && !measuredHPPreBootDMAConfig:
+ expectedDigest := tcglog.ComputeStringEventDigest(ctx.PCRAlg().GetHash(), internal_efi.HPPreBootDMAConfigEventData())
+ if !bytes.Equal(e.Digests[ctx.PCRAlg()], expectedDigest) {
+ return errors.New("invalid digest for HP pre-boot DMA configuration event")
+ }
+ ctx.ExtendPCR(internal_efi.SecureBootPolicyPCR, expectedDigest)
+ measuredHPPreBootDMAConfig = true
case e.EventType == tcglog.EventTypeEFIAction &&
(bytes.Equal(e.Data.Bytes(), []byte(dmaProtectionDisabled)) || bytes.Equal(e.Data.Bytes(), []byte(dmaProtectionDisabledNul))) &&
allowInsufficientDMAProtection:
@@ -259,10 +281,23 @@ func (h *fwLoadHandler) measureSecureBootPolicyPreOS(ctx pcrBranchContext) error
foundOsPresent := false // true when we enounter the first EV_SEPARATOR in PCRs 0-6.
measuredSecureBootSeparator := false // true when we encounter the EV_SEPARATOR in PCR7.
measuredPreOSVerification := false // true when we encounter a EV_EFI_VARIABLE_AUTHORITY event.
+ remainingSecureBootConfigEvents := 4 // PK, KEK, db and dbx (SecureBoot was consumed above).
for len(events) > 0 {
e := events[0]
events = events[1:]
+ if e.PCRIndex == internal_efi.SecureBootPolicyPCR {
+ if internal_efi.IsAMDTSMEConfigEvent(e) && remainingSecureBootConfigEvents > 0 {
+ return errors.New("unexpected AMD TSME configuration event whilst measuring secure boot configuration")
+ }
+ if handled, err := h.measureAMDTSMEConfigEvent(ctx, e, &measuredAMDTSMEConfig); handled {
+ if err != nil {
+ return err
+ }
+ continue
+ }
+ }
+
switch {
case e.PCRIndex < internal_efi.SecureBootPolicyPCR && e.EventType == tcglog.EventTypeSeparator:
// Pre-OS to OS-present signal. We abort this loop once we've seen
@@ -295,6 +330,9 @@ func (h *fwLoadHandler) measureSecureBootPolicyPreOS(ctx pcrBranchContext) error
// once we've encountered the first EV_EFI_VARIABLE_AUTHORITY event and
// we'll likely generate an invalid profile if we do. The preinstall
// checks will catch this.
+ if remainingSecureBootConfigEvents > 0 {
+ remainingSecureBootConfigEvents--
+ }
case e.PCRIndex == internal_efi.SecureBootPolicyPCR && e.EventType == tcglog.EventTypeEFIAction &&
(bytes.Equal(e.Data.Bytes(), []byte(dmaProtectionDisabled)) || bytes.Equal(e.Data.Bytes(), []byte(dmaProtectionDisabledNul))) &&
allowInsufficientDMAProtection:
@@ -364,6 +402,13 @@ func (h *fwLoadHandler) measureSecureBootPolicyPreOS(ctx pcrBranchContext) error
continue
}
+ if handled, err := h.measureAMDTSMEConfigEvent(ctx, e, &measuredAMDTSMEConfig); handled {
+ if err != nil {
+ return err
+ }
+ continue
+ }
+
if e.EventType == tcglog.EventTypeEFIVariableAuthority {
break
}
@@ -387,6 +432,33 @@ func boolParamOrFalse(loadParams loadParams, key loadParamsKey) bool {
return val.(bool)
}
+func (h *fwLoadHandler) measureAMDTSMEConfigEvent(ctx pcrBranchContext, event *tcglog.Event, seen *bool) (bool, error) {
+ if !internal_efi.IsAMDTSMEConfigEvent(event) {
+ return false, nil
+ }
+
+ value, ok := ctx.Params()[amdTSMEEnabledParamKey]
+ if !ok {
+ return true, errors.New("encountered AMD TSME configuration event without expected platform configuration")
+ }
+ if *seen {
+ return true, errors.New("encountered duplicate AMD TSME configuration event")
+ }
+
+ expectedData := internal_efi.AMDTSMEConfigEventData(value.(bool))
+ if !bytes.Equal(event.Data.Bytes(), expectedData) {
+ return true, fmt.Errorf("AMD TSME configuration event data does not match expected TSME status (expected %#x, got %#x)", expectedData, event.Data.Bytes())
+ }
+ expectedDigest := tcglog.ComputeEventDigest(ctx.PCRAlg().GetHash(), expectedData)
+ if !bytes.Equal(event.Digests[ctx.PCRAlg()], expectedDigest) {
+ return true, errors.New("invalid digest for AMD TSME configuration event")
+ }
+
+ ctx.ExtendPCR(event.PCRIndex, expectedDigest)
+ *seen = true
+ return true, nil
+}
+
func (h *fwLoadHandler) measurePlatformFirmware(ctx pcrBranchContext) error {
donePcrReset := false
@@ -423,18 +495,56 @@ func (h *fwLoadHandler) measurePlatformFirmware(ctx pcrBranchContext) error {
}
func (h *fwLoadHandler) measureDriversAndApps(ctx pcrBranchContext) error {
+ var (
+ seenSeparator bool
+ seenAMDTSMEConfig bool
+ )
+
for _, event := range h.log.Events {
+ // AMD firmware may extend the TSME configuration event into PCR2 after
+ // the separator but before authorizing or launching the initial OS
+ // loader. Retain that event, but do not copy any PCR2 measurements made
+ // once OS image processing has begun.
+ if seenSeparator &&
+ ((event.PCRIndex == internal_efi.SecureBootPolicyPCR &&
+ event.EventType == tcglog.EventTypeEFIVariableAuthority) ||
+ (event.PCRIndex == internal_efi.BootManagerCodePCR &&
+ event.EventType == tcglog.EventTypeEFIBootServicesApplication)) {
+ return nil
+ }
+
if event.PCRIndex != internal_efi.DriversAndAppsPCR {
continue
}
- if event.EventType == tcglog.EventTypeSeparator {
- return h.measureSeparator(ctx, internal_efi.DriversAndAppsPCR, event)
+ if handled, err := h.measureAMDTSMEConfigEvent(ctx, event, &seenAMDTSMEConfig); handled {
+ if err != nil {
+ return err
+ }
+ continue
}
- ctx.ExtendPCR(internal_efi.DriversAndAppsPCR, event.Digests[ctx.PCRAlg()])
+
+ if !seenSeparator {
+ if event.EventType == tcglog.EventTypeSeparator {
+ if err := h.measureSeparator(ctx, internal_efi.DriversAndAppsPCR, event); err != nil {
+ return err
+ }
+ seenSeparator = true
+ continue
+ }
+ ctx.ExtendPCR(internal_efi.DriversAndAppsPCR, event.Digests[ctx.PCRAlg()])
+ continue
+ }
+
+ return fmt.Errorf(
+ "unexpected post-separator event type %v found in PCR %d",
+ event.EventType, internal_efi.DriversAndAppsPCR)
}
- return errors.New("missing separator in log")
+ if !seenSeparator {
+ return errors.New("missing separator in log")
+ }
+ return errors.New("reached end of log before encountering initial OS authorization or launch")
}
func (h *fwLoadHandler) measureBootManagerCodePreOS(ctx pcrBranchContext) error {
diff --git a/efi/pcr2_vendor_event_test.go b/efi/pcr2_vendor_event_test.go
new file mode 100644
index 00000000..dca8bffe
--- /dev/null
+++ b/efi/pcr2_vendor_event_test.go
@@ -0,0 +1,268 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2026 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package efi_test
+
+import (
+ "github.com/canonical/go-tpm2"
+ "github.com/canonical/tcglog-parser"
+ . "github.com/snapcore/secboot/efi"
+ internal_efi "github.com/snapcore/secboot/internal/efi"
+ "github.com/snapcore/secboot/internal/efitest"
+ "github.com/snapcore/secboot/internal/testutil"
+ . "gopkg.in/check.v1"
+)
+
+func newLogWithPostSeparatorPCR2Event(c *C, insertedEvent *tcglog.Event) *tcglog.Log {
+ log := efitest.NewLog(c, &efitest.LogOptions{
+ Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256},
+ })
+
+ var events []*tcglog.Event
+ inserted := false
+ for _, event := range log.Events {
+ events = append(events, event)
+ if event.PCRIndex == internal_efi.PlatformManufacturerPCR &&
+ event.EventType == tcglog.EventTypeSeparator {
+ events = append(events, insertedEvent)
+ inserted = true
+ }
+ }
+ c.Assert(inserted, Equals, true)
+ log.Events = events
+ return log
+}
+
+func newLogWithPCR2EventAfterOSBoundary(c *C, opts *efitest.LogOptions, insertedEvent *tcglog.Event) *tcglog.Log {
+ log := efitest.NewLog(c, opts)
+
+ var events []*tcglog.Event
+ seenPCR2Separator := false
+ inserted := false
+ for _, event := range log.Events {
+ events = append(events, event)
+
+ if event.PCRIndex == internal_efi.DriversAndAppsPCR &&
+ event.EventType == tcglog.EventTypeSeparator {
+ seenPCR2Separator = true
+ continue
+ }
+ if !seenPCR2Separator || inserted {
+ continue
+ }
+
+ if (event.PCRIndex == internal_efi.SecureBootPolicyPCR &&
+ event.EventType == tcglog.EventTypeEFIVariableAuthority) ||
+ (event.PCRIndex == internal_efi.BootManagerCodePCR &&
+ event.EventType == tcglog.EventTypeEFIBootServicesApplication) {
+ events = append(events, insertedEvent)
+ inserted = true
+ }
+ }
+ c.Assert(inserted, Equals, true)
+ log.Events = events
+ return log
+}
+
+// TestMeasureImageStartDriversAndAppsIncludesPostSeparatorAMDTSMEEvent
+// reproduces the event ordering from the HP ZBook Ultra G1a firmware:
+//
+// PCR2 EV_SEPARATOR
+// ...
+// PCR2 vendor event 0x00008401 with data 0x01
+// ...
+// PCR7 EV_EFI_VARIABLE_AUTHORITY
+// PCR4 EV_EFI_BOOT_SERVICES_APPLICATION
+//
+// The event must be included in the generated PCR2 profile because the firmware
+// extends it into the TPM before launching the OS. Its expected digest is
+// derived from the independently detected TSME status.
+func (s *fwLoadHandlerSuite) TestMeasureImageStartDriversAndAppsIncludesPostSeparatorAMDTSMEEvent(c *C) {
+ vendorDigest := testutil.DecodeHexString(c, "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a")
+ log := newLogWithPostSeparatorPCR2Event(c, &tcglog.Event{
+ PCRIndex: internal_efi.DriversAndAppsPCR,
+ EventType: 0x00008401,
+ Digests: tcglog.DigestMap{
+ tpm2.HashAlgorithmSHA256: vendorDigest,
+ },
+ Data: tcglog.OpaqueEventData{0x01},
+ })
+
+ params := LoadParams{AMDTSMEEnabledParamKey: true}
+ s.testMeasureImageStart(c, &testFwMeasureImageStartData{
+ log: log,
+ alg: tpm2.HashAlgorithmSHA256,
+ pcrs: MakePcrFlags(internal_efi.DriversAndAppsPCR),
+ loadParams: ¶ms,
+ expectedEvents: []*mockPcrBranchEvent{
+ {pcr: 2, eventType: mockPcrBranchResetEvent},
+ {pcr: 2, eventType: mockPcrBranchExtendEvent, digest: testutil.DecodeHexString(c, "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119")},
+ {pcr: 2, eventType: mockPcrBranchExtendEvent, digest: vendorDigest},
+ },
+ })
+}
+
+func (s *fwLoadHandlerSuite) TestMeasureImageStartDriversAndAppsRejectsTSMEStatusMismatch(c *C) {
+ log := newLogWithPostSeparatorPCR2Event(c, &tcglog.Event{
+ PCRIndex: internal_efi.DriversAndAppsPCR,
+ EventType: internal_efi.AMDTSMEConfigEventType,
+ Digests: tcglog.DigestMap{
+ tpm2.HashAlgorithmSHA256: testutil.DecodeHexString(c, "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"),
+ },
+ Data: tcglog.OpaqueEventData{0x01},
+ })
+
+ collector := NewVariableSetCollector(efitest.NewMockHostEnvironment(nil, nil))
+ params := LoadParams{AMDTSMEEnabledParamKey: false}
+ ctx := newMockPcrBranchContext(&mockPcrProfileContext{
+ alg: tpm2.HashAlgorithmSHA256,
+ pcrs: MakePcrFlags(internal_efi.DriversAndAppsPCR),
+ }, ¶ms, collector.Next())
+
+ err := NewFwLoadHandler(log).MeasureImageStart(ctx)
+ c.Check(err, ErrorMatches, `cannot measure drivers and apps: AMD TSME configuration event data does not match expected TSME status \(expected 0x00, got 0x01\)`)
+}
+
+func (s *fwLoadHandlerSuite) TestMeasureImageStartDriversAndAppsRejectsUnknownPostSeparatorVendorEvent(c *C) {
+ log := newLogWithPostSeparatorPCR2Event(c, &tcglog.Event{
+ PCRIndex: internal_efi.DriversAndAppsPCR,
+ EventType: 0x00008402,
+ Digests: tcglog.DigestMap{
+ tpm2.HashAlgorithmSHA256: testutil.DecodeHexString(c, "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"),
+ },
+ Data: tcglog.OpaqueEventData{0x01},
+ })
+
+ collector := NewVariableSetCollector(efitest.NewMockHostEnvironment(nil, nil))
+ ctx := newMockPcrBranchContext(&mockPcrProfileContext{
+ alg: tpm2.HashAlgorithmSHA256,
+ pcrs: MakePcrFlags(internal_efi.DriversAndAppsPCR),
+ }, nil, collector.Next())
+
+ err := NewFwLoadHandler(log).MeasureImageStart(ctx)
+ c.Check(err, ErrorMatches, `cannot measure drivers and apps: unexpected post-separator event type 00008402 found in PCR 2`)
+}
+
+func (s *fwLoadHandlerSuite) TestMeasureImageStartDriversAndAppsRejectsPostSeparatorStandardEvent(c *C) {
+ log := newLogWithPostSeparatorPCR2Event(c, &tcglog.Event{
+ PCRIndex: internal_efi.DriversAndAppsPCR,
+ EventType: tcglog.EventTypeEFIAction,
+ Digests: tcglog.DigestMap{
+ tpm2.HashAlgorithmSHA256: testutil.DecodeHexString(c, "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"),
+ },
+ Data: tcglog.OpaqueEventData{0x01},
+ })
+
+ collector := NewVariableSetCollector(efitest.NewMockHostEnvironment(nil, nil))
+ ctx := newMockPcrBranchContext(&mockPcrProfileContext{
+ alg: tpm2.HashAlgorithmSHA256,
+ pcrs: MakePcrFlags(internal_efi.DriversAndAppsPCR),
+ }, nil, collector.Next())
+
+ handler := NewFwLoadHandler(log)
+ c.Check(
+ handler.MeasureImageStart(ctx),
+ ErrorMatches,
+ `cannot measure drivers and apps: unexpected post-separator event type EV_EFI_ACTION found in PCR 2`)
+}
+
+func (s *fwLoadHandlerSuite) TestMeasureImageStartDriversAndAppsRejectsMissingOSBoundary(c *C) {
+ log := newLogWithPostSeparatorPCR2Event(c, &tcglog.Event{
+ PCRIndex: internal_efi.DriversAndAppsPCR,
+ EventType: 0x00008401,
+ Digests: tcglog.DigestMap{
+ tpm2.HashAlgorithmSHA256: testutil.DecodeHexString(c, "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"),
+ },
+ Data: tcglog.OpaqueEventData{0x01},
+ })
+
+ truncated := false
+ for i, event := range log.Events {
+ if (event.PCRIndex == internal_efi.SecureBootPolicyPCR &&
+ event.EventType == tcglog.EventTypeEFIVariableAuthority) ||
+ (event.PCRIndex == internal_efi.BootManagerCodePCR &&
+ event.EventType == tcglog.EventTypeEFIBootServicesApplication) {
+ log.Events = log.Events[:i]
+ truncated = true
+ break
+ }
+ }
+ c.Assert(truncated, Equals, true)
+
+ collector := NewVariableSetCollector(efitest.NewMockHostEnvironment(nil, nil))
+ params := LoadParams{AMDTSMEEnabledParamKey: true}
+ ctx := newMockPcrBranchContext(&mockPcrProfileContext{
+ alg: tpm2.HashAlgorithmSHA256,
+ pcrs: MakePcrFlags(internal_efi.DriversAndAppsPCR),
+ }, ¶ms, collector.Next())
+
+ handler := NewFwLoadHandler(log)
+ c.Check(
+ handler.MeasureImageStart(ctx),
+ ErrorMatches,
+ `cannot measure drivers and apps: reached end of log before encountering initial OS authorization or launch`)
+}
+
+func (s *fwLoadHandlerSuite) TestMeasureImageStartDriversAndAppsStopsAtVariableAuthorityBoundary(c *C) {
+ vendorDigest := testutil.DecodeHexString(c, "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a")
+ log := newLogWithPCR2EventAfterOSBoundary(c, &efitest.LogOptions{
+ Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256},
+ }, &tcglog.Event{
+ PCRIndex: internal_efi.DriversAndAppsPCR,
+ EventType: 0x00008401,
+ Digests: tcglog.DigestMap{
+ tpm2.HashAlgorithmSHA256: vendorDigest,
+ },
+ Data: tcglog.OpaqueEventData{0x01},
+ })
+
+ s.testMeasureImageStart(c, &testFwMeasureImageStartData{
+ log: log,
+ alg: tpm2.HashAlgorithmSHA256,
+ pcrs: MakePcrFlags(internal_efi.DriversAndAppsPCR),
+ expectedEvents: []*mockPcrBranchEvent{
+ {pcr: 2, eventType: mockPcrBranchResetEvent},
+ {pcr: 2, eventType: mockPcrBranchExtendEvent, digest: testutil.DecodeHexString(c, "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119")},
+ },
+ })
+}
+
+func (s *fwLoadHandlerSuite) TestMeasureImageStartDriversAndAppsStopsAtImageLaunchBoundary(c *C) {
+ vendorDigest := testutil.DecodeHexString(c, "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a")
+ log := newLogWithPCR2EventAfterOSBoundary(c, &efitest.LogOptions{
+ Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256},
+ SecureBootDisabled: true,
+ }, &tcglog.Event{
+ PCRIndex: internal_efi.DriversAndAppsPCR,
+ EventType: 0x00008401,
+ Digests: tcglog.DigestMap{
+ tpm2.HashAlgorithmSHA256: vendorDigest,
+ },
+ Data: tcglog.OpaqueEventData{0x01},
+ })
+
+ s.testMeasureImageStart(c, &testFwMeasureImageStartData{
+ log: log,
+ alg: tpm2.HashAlgorithmSHA256,
+ pcrs: MakePcrFlags(internal_efi.DriversAndAppsPCR),
+ expectedEvents: []*mockPcrBranchEvent{
+ {pcr: 2, eventType: mockPcrBranchResetEvent},
+ {pcr: 2, eventType: mockPcrBranchExtendEvent, digest: testutil.DecodeHexString(c, "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119")},
+ },
+ })
+}
diff --git a/efi/pcr7_hp_dma_event_test.go b/efi/pcr7_hp_dma_event_test.go
new file mode 100644
index 00000000..2257e79b
--- /dev/null
+++ b/efi/pcr7_hp_dma_event_test.go
@@ -0,0 +1,212 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2026 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package efi_test
+
+import (
+ "crypto"
+
+ . "gopkg.in/check.v1"
+
+ efi "github.com/canonical/go-efilib"
+ "github.com/canonical/go-tpm2"
+ "github.com/canonical/tcglog-parser"
+
+ . "github.com/snapcore/secboot/efi"
+ internal_efi "github.com/snapcore/secboot/internal/efi"
+ "github.com/snapcore/secboot/internal/efitest"
+ "github.com/snapcore/secboot/internal/testutil"
+)
+
+const hpPreBootDMAConfigEventData = `"SVM CPU Virtualization":"Enable";"DMA protection":"Enable";"Pre-boot DMA protection":"All PCIe devices";`
+
+func newLogWithHPPreBootDMAConfigEvent(c *C) *tcglog.Log {
+ log := efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})
+ data := tcglog.StringEventData(hpPreBootDMAConfigEventData)
+ event := &tcglog.Event{
+ PCRIndex: internal_efi.SecureBootPolicyPCR,
+ EventType: tcglog.EventTypeEFIAction,
+ Digests: tcglog.DigestMap{
+ tpm2.HashAlgorithmSHA256: tcglog.ComputeStringEventDigest(crypto.SHA256, hpPreBootDMAConfigEventData),
+ },
+ Data: data,
+ }
+
+ var events []*tcglog.Event
+ added := false
+ for _, ev := range log.Events {
+ if ev.PCRIndex == internal_efi.SecureBootPolicyPCR &&
+ ev.EventType == tcglog.EventTypeEFIVariableDriverConfig &&
+ !added {
+ events = append(events, event)
+ added = true
+ }
+ events = append(events, ev)
+ }
+ c.Assert(added, testutil.IsTrue)
+ log.Events = events
+ return log
+}
+
+func newLogWithAMDTSMEPCR7Event(c *C, enabled bool) *tcglog.Log {
+ log := efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})
+ data := internal_efi.AMDTSMEConfigEventData(enabled)
+ event := &tcglog.Event{
+ PCRIndex: internal_efi.SecureBootPolicyPCR,
+ EventType: internal_efi.AMDTSMEConfigEventType,
+ Digests: tcglog.DigestMap{
+ tpm2.HashAlgorithmSHA256: tcglog.ComputeEventDigest(crypto.SHA256, data),
+ },
+ Data: tcglog.OpaqueEventData(data),
+ }
+
+ var events []*tcglog.Event
+ added := false
+ for _, ev := range log.Events {
+ if ev.PCRIndex == internal_efi.SecureBootPolicyPCR &&
+ ev.EventType == tcglog.EventTypeEFIVariableDriverConfig &&
+ !added {
+ events = append(events, event)
+ added = true
+ }
+ events = append(events, ev)
+ }
+ c.Assert(added, testutil.IsTrue)
+ log.Events = events
+ return log
+}
+
+func measureSecureBootPolicyProfileError(c *C, log *tcglog.Log) error {
+ return measureSecureBootPolicyProfileErrorWithHPConfig(c, log, true)
+}
+
+func measureSecureBootPolicyProfileErrorWithHPConfig(c *C, log *tcglog.Log, enabled bool) error {
+ collector := NewVariableSetCollector(efitest.NewMockHostEnvironment(makeMockVars(c, withMsSecureBootConfig()), nil))
+ params := LoadParams{IncludeHPPreBootDMAConfigParamKey: enabled}
+ ctx := newMockPcrBranchContext(&mockPcrProfileContext{
+ alg: tpm2.HashAlgorithmSHA256,
+ pcrs: MakePcrFlags(internal_efi.SecureBootPolicyPCR),
+ }, ¶ms, collector.Next())
+ return NewFwLoadHandler(log).MeasureImageStart(ctx)
+}
+
+func (s *fwLoadHandlerSuite) TestMeasureImageStartSecureBootPolicyProfileRejectsMissingExpectedHPPreBootDMAConfig(c *C) {
+ log := efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})
+ err := measureSecureBootPolicyProfileError(c, log)
+ c.Check(err, ErrorMatches, `cannot measure secure boot policy: missing expected HP pre-boot DMA configuration event`)
+}
+
+func (s *fwLoadHandlerSuite) TestMeasureImageStartSecureBootPolicyProfileRejectsUnexpectedHPPreBootDMAConfig(c *C) {
+ err := measureSecureBootPolicyProfileErrorWithHPConfig(c, newLogWithHPPreBootDMAConfigEvent(c), false)
+ c.Check(err, ErrorMatches, `cannot measure secure boot policy: unexpected event type \(EV_EFI_ACTION\) found in log, before config`)
+}
+
+func (s *fwLoadHandlerSuite) TestMeasureImageStartSecureBootPolicyProfileWithHPPreBootDMAConfig(c *C) {
+ vars := makeMockVars(c, withMsSecureBootConfig())
+ params := LoadParams{IncludeHPPreBootDMAConfigParamKey: true}
+ s.testMeasureImageStart(c, &testFwMeasureImageStartData{
+ vars: vars,
+ log: newLogWithHPPreBootDMAConfigEvent(c),
+ alg: tpm2.HashAlgorithmSHA256,
+ pcrs: MakePcrFlags(internal_efi.SecureBootPolicyPCR),
+ loadParams: ¶ms,
+ expectedEvents: []*mockPcrBranchEvent{
+ {pcr: 7, eventType: mockPcrBranchResetEvent},
+ {pcr: 7, eventType: mockPcrBranchExtendEvent, digest: testutil.DecodeHexString(c, "102a994acb0172f38fada0319cea1a2964ad15fcdb54216bcd3a0b821c8612ee")},
+ {pcr: 7, eventType: mockPcrBranchMeasureVariableEvent, varName: efi.VariableDescriptor{Name: "SecureBoot", GUID: efi.GlobalVariable}, varData: []byte{0x01}},
+ {pcr: 7, eventType: mockPcrBranchMeasureVariableEvent, varName: PK, varData: vars[PK].Payload},
+ {pcr: 7, eventType: mockPcrBranchMeasureVariableEvent, varName: KEK, varData: vars[KEK].Payload},
+ {pcr: 7, eventType: mockPcrBranchMeasureVariableEvent, varName: Db, varData: vars[Db].Payload},
+ {pcr: 7, eventType: mockPcrBranchMeasureVariableEvent, varName: Dbx, varData: vars[Dbx].Payload},
+ {pcr: 7, eventType: mockPcrBranchExtendEvent, digest: testutil.DecodeHexString(c, "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119")},
+ },
+ })
+}
+
+func (s *fwLoadHandlerSuite) TestMeasureImageStartSecureBootPolicyProfileWithDisabledTSME(c *C) {
+ collector := NewVariableSetCollector(efitest.NewMockHostEnvironment(makeMockVars(c, withMsSecureBootConfig()), nil))
+ params := LoadParams{AMDTSMEEnabledParamKey: false}
+ ctx := newMockPcrBranchContext(&mockPcrProfileContext{
+ alg: tpm2.HashAlgorithmSHA256,
+ pcrs: MakePcrFlags(internal_efi.SecureBootPolicyPCR),
+ }, ¶ms, collector.Next())
+
+ err := NewFwLoadHandler(newLogWithAMDTSMEPCR7Event(c, false)).MeasureImageStart(ctx)
+ c.Assert(err, IsNil)
+ c.Assert(ctx.events, HasLen, 8)
+ c.Check(ctx.events[1], DeepEquals, &mockPcrBranchEvent{
+ pcr: internal_efi.SecureBootPolicyPCR,
+ eventType: mockPcrBranchExtendEvent,
+ digest: tcglog.ComputeEventDigest(crypto.SHA256, []byte{0}),
+ })
+}
+
+func (s *fwLoadHandlerSuite) TestMeasureImageStartSecureBootPolicyProfileRejectsHPPreBootDMAConfigWithWrongDigest(c *C) {
+ log := newLogWithHPPreBootDMAConfigEvent(c)
+ for _, ev := range log.Events {
+ if internal_efi.IsHPPreBootDMAConfigEvent(ev) {
+ ev.Digests[tpm2.HashAlgorithmSHA256] = make(tpm2.Digest, tpm2.HashAlgorithmSHA256.Size())
+ }
+ }
+
+ err := measureSecureBootPolicyProfileError(c, log)
+ c.Check(err, ErrorMatches, `cannot measure secure boot policy: invalid digest for HP pre-boot DMA configuration event`)
+}
+
+func (s *fwLoadHandlerSuite) TestMeasureImageStartSecureBootPolicyProfileRejectsDuplicateHPPreBootDMAConfig(c *C) {
+ log := newLogWithHPPreBootDMAConfigEvent(c)
+ var events []*tcglog.Event
+ for _, ev := range log.Events {
+ events = append(events, ev)
+ if internal_efi.IsHPPreBootDMAConfigEvent(ev) {
+ events = append(events, ev)
+ }
+ }
+ log.Events = events
+
+ err := measureSecureBootPolicyProfileError(c, log)
+ c.Check(err, ErrorMatches, `cannot measure secure boot policy: unexpected event type \(EV_EFI_ACTION\) found in log, before config`)
+}
+
+func (s *fwLoadHandlerSuite) TestMeasureImageStartSecureBootPolicyProfileRejectsMisplacedHPPreBootDMAConfig(c *C) {
+ log := newLogWithHPPreBootDMAConfigEvent(c)
+ var (
+ events []*tcglog.Event
+ hpEvent *tcglog.Event
+ added bool
+ )
+ for _, ev := range log.Events {
+ if internal_efi.IsHPPreBootDMAConfigEvent(ev) {
+ hpEvent = ev
+ continue
+ }
+ events = append(events, ev)
+ if ev.PCRIndex == internal_efi.SecureBootPolicyPCR &&
+ ev.EventType == tcglog.EventTypeEFIVariableDriverConfig &&
+ !added {
+ events = append(events, hpEvent)
+ added = true
+ }
+ }
+ c.Assert(hpEvent, NotNil)
+ c.Assert(added, testutil.IsTrue)
+ log.Events = events
+
+ err := measureSecureBootPolicyProfileError(c, log)
+ c.Check(err, ErrorMatches, `cannot measure secure boot policy: missing expected HP pre-boot DMA configuration event`)
+}
diff --git a/internal/efi/tcg_events.go b/internal/efi/tcg_events.go
index b6f49e80..4151fb84 100644
--- a/internal/efi/tcg_events.go
+++ b/internal/efi/tcg_events.go
@@ -20,6 +20,7 @@
package efi
import (
+ "bytes"
"errors"
"fmt"
@@ -27,6 +28,12 @@ import (
"github.com/canonical/tcglog-parser"
)
+const hpPreBootDMAConfigEventData = `"SVM CPU Virtualization":"Enable";"DMA protection":"Enable";"Pre-boot DMA protection":"All PCIe devices";`
+
+// AMDTSMEConfigEventType is the vendor-defined event type used by AMD AGESA
+// to measure the TSME status.
+const AMDTSMEConfigEventType tcglog.EventType = 0x00008401
+
// IsVendorEventType indicates whether the supplied event type is vendor
// defined. Officially, this applies to any event type that is not within the
// range of TCG reserved types (0x00000000-0x0000ffff and 0x80000000-0x8000ffff),
@@ -42,6 +49,38 @@ func IsVendorEventType(t tcglog.EventType) bool {
}
}
+// AMDTSMEConfigEventData returns the event data measured by AMD AGESA for the
+// supplied TSME status.
+func AMDTSMEConfigEventData(enabled bool) []byte {
+ if enabled {
+ return []byte{1}
+ }
+ return []byte{0}
+}
+
+// IsAMDTSMEConfigEvent indicates whether the supplied event is an AMD AGESA
+// TSME status measurement. AGESA measures this to PCR2 and, when TSME is
+// disabled, may additionally measure it to PCR7.
+func IsAMDTSMEConfigEvent(ev *tcglog.Event) bool {
+ return (ev.PCRIndex == DriversAndAppsPCR || ev.PCRIndex == SecureBootPolicyPCR) &&
+ ev.EventType == AMDTSMEConfigEventType
+}
+
+// HPPreBootDMAConfigEventData returns the expected event data for HP's
+// enabled SVM and DMA protection configuration.
+func HPPreBootDMAConfigEventData() string {
+ return hpPreBootDMAConfigEventData
+}
+
+// IsHPPreBootDMAConfigEvent indicates whether the supplied event is the exact
+// virtualization and pre-boot DMA configuration measurement produced by HP
+// firmware when "Measure Additional DMA Settings" is directed to PCR7.
+func IsHPPreBootDMAConfigEvent(ev *tcglog.Event) bool {
+ return ev.PCRIndex == SecureBootPolicyPCR &&
+ ev.EventType == tcglog.EventTypeEFIAction &&
+ bytes.Equal(ev.Data.Bytes(), []byte(hpPreBootDMAConfigEventData))
+}
+
// IsLaunchedFromFirmwareVolume indicates that the supplied event is associated
// with an image launch from a firmware volume.
func IsLaunchedFromFirmwareVolume(ev *tcglog.Event) (yes bool, err error) {
diff --git a/internal/efi/tcg_events_test.go b/internal/efi/tcg_events_test.go
index 90bb6a26..40117c29 100644
--- a/internal/efi/tcg_events_test.go
+++ b/internal/efi/tcg_events_test.go
@@ -51,6 +51,71 @@ func (*tcgEventsSuite) TestIsVendorEventType(c *C) {
}
}
+func (*tcgEventsSuite) TestIsHPPreBootDMAConfigEvent(c *C) {
+ const hpData = `"SVM CPU Virtualization":"Enable";"DMA protection":"Enable";"Pre-boot DMA protection":"All PCIe devices";`
+
+ for _, params := range []struct {
+ event *tcglog.Event
+ expected bool
+ }{
+ {
+ event: &tcglog.Event{
+ PCRIndex: 7,
+ EventType: tcglog.EventTypeEFIAction,
+ Data: tcglog.StringEventData(hpData),
+ },
+ expected: true,
+ },
+ {
+ event: &tcglog.Event{
+ PCRIndex: 6,
+ EventType: tcglog.EventTypeEFIAction,
+ Data: tcglog.StringEventData(hpData),
+ },
+ },
+ {
+ event: &tcglog.Event{
+ PCRIndex: 7,
+ EventType: tcglog.EventTypeEFIPlatformFirmwareBlob,
+ Data: tcglog.StringEventData(hpData),
+ },
+ },
+ {
+ event: &tcglog.Event{
+ PCRIndex: 7,
+ EventType: tcglog.EventTypeEFIAction,
+ Data: tcglog.StringEventData("UEFI Debug Mode"),
+ },
+ },
+ {
+ event: &tcglog.Event{
+ PCRIndex: 7,
+ EventType: tcglog.EventTypeEFIAction,
+ Data: tcglog.StringEventData(hpData + "\x00"),
+ },
+ },
+ } {
+ c.Check(IsHPPreBootDMAConfigEvent(params.event), Equals, params.expected)
+ }
+}
+
+func (*tcgEventsSuite) TestAMDTSMEConfigEvent(c *C) {
+ c.Check(AMDTSMEConfigEventData(false), DeepEquals, []byte{0})
+ c.Check(AMDTSMEConfigEventData(true), DeepEquals, []byte{1})
+
+ for _, params := range []struct {
+ event *tcglog.Event
+ expected bool
+ }{
+ {event: &tcglog.Event{PCRIndex: DriversAndAppsPCR, EventType: AMDTSMEConfigEventType}, expected: true},
+ {event: &tcglog.Event{PCRIndex: SecureBootPolicyPCR, EventType: AMDTSMEConfigEventType}, expected: true},
+ {event: &tcglog.Event{PCRIndex: PlatformConfigPCR, EventType: AMDTSMEConfigEventType}},
+ {event: &tcglog.Event{PCRIndex: DriversAndAppsPCR, EventType: 0x00008402}},
+ } {
+ c.Check(IsAMDTSMEConfigEvent(params.event), Equals, params.expected)
+ }
+}
+
type invalidEventData struct {
err error
}
From a435b8f59b37af6122fa12525aca904b7cbea0b8 Mon Sep 17 00:00:00 2001
From: ebrig <146060912+ebrig@users.noreply.github.com>
Date: Tue, 15 Sep 2026 07:11:43 -0400
Subject: [PATCH 2/2] efi/preinstall: verify AMD pre-OS measurements
Detect TSME and HP DMA measurement configuration from sysfs, require the HP event when configured, validate event payloads and digests, and carry the independently detected values into automatic PCR profile generation.
---
efi/preinstall/check_amd_measurements.go | 53 +++
efi/preinstall/check_host_security_amd.go | 84 +++++
efi/preinstall/check_host_security_amd64.go | 41 ++-
.../check_host_security_amd64_test.go | 91 ++++++
efi/preinstall/check_host_security_null.go | 9 +
efi/preinstall/check_pcr2.go | 53 ++-
efi/preinstall/check_pcr2_test.go | 3 +-
efi/preinstall/check_pcr7.go | 49 ++-
efi/preinstall/check_pcr7_test.go | 3 +-
efi/preinstall/checks.go | 7 +-
efi/preinstall/export_test.go | 58 ++--
efi/preinstall/pcr2_vendor_event_test.go | 302 ++++++++++++++++++
efi/preinstall/pcr7_hp_dma_event_test.go | 229 +++++++++++++
efi/preinstall/profile.go | 5 +
efi/preinstall/profile_test.go | 20 ++
efi/preinstall/result.go | 39 ++-
efi/preinstall/result_test.go | 19 ++
17 files changed, 1000 insertions(+), 65 deletions(-)
create mode 100644 efi/preinstall/check_amd_measurements.go
create mode 100644 efi/preinstall/pcr2_vendor_event_test.go
create mode 100644 efi/preinstall/pcr7_hp_dma_event_test.go
diff --git a/efi/preinstall/check_amd_measurements.go b/efi/preinstall/check_amd_measurements.go
new file mode 100644
index 00000000..5b870555
--- /dev/null
+++ b/efi/preinstall/check_amd_measurements.go
@@ -0,0 +1,53 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2026 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package preinstall
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+
+ "github.com/canonical/go-tpm2"
+ "github.com/canonical/tcglog-parser"
+ internal_efi "github.com/snapcore/secboot/internal/efi"
+)
+
+func checkAMDTSMEConfigEvent(ev *tcglog.Event, pcrAlg tpm2.HashAlgorithmId, config *amdPreOSMeasurementConfig, seen *bool) (bool, error) {
+ if !internal_efi.IsAMDTSMEConfigEvent(ev) {
+ return false, nil
+ }
+ if config == nil {
+ return true, errors.New("encountered AMD TSME configuration event without independently detected platform configuration")
+ }
+ if *seen {
+ return true, errors.New("encountered duplicate AMD TSME configuration event")
+ }
+
+ expectedData := internal_efi.AMDTSMEConfigEventData(config.TSMEEnabled)
+ if !bytes.Equal(ev.Data.Bytes(), expectedData) {
+ return true, fmt.Errorf("AMD TSME configuration event data does not match detected TSME status (expected %#x, got %#x)", expectedData, ev.Data.Bytes())
+ }
+ expectedDigest := tcglog.ComputeEventDigest(pcrAlg.GetHash(), expectedData)
+ if !bytes.Equal(ev.Digests[pcrAlg], expectedDigest) {
+ return true, errors.New("invalid digest for AMD TSME configuration event")
+ }
+
+ *seen = true
+ return true, nil
+}
diff --git a/efi/preinstall/check_host_security_amd.go b/efi/preinstall/check_host_security_amd.go
index 2e88c2a6..43b98ad6 100644
--- a/efi/preinstall/check_host_security_amd.go
+++ b/efi/preinstall/check_host_security_amd.go
@@ -26,7 +26,9 @@ import (
"errors"
"fmt"
"io"
+ "path/filepath"
"strconv"
+ "strings"
"github.com/pilebones/go-udev/netlink"
internal_efi "github.com/snapcore/secboot/internal/efi"
@@ -47,6 +49,88 @@ func readAMDPSPBooleanAttribute(dev internal_efi.SysfsDevice, name string) (bool
return strconv.ParseBool(string(bytes.TrimSuffix(data, []byte("\n"))))
}
+func readSysfsDeviceStringAttribute(dev internal_efi.SysfsDevice, name string) (string, error) {
+ rc, err := dev.AttributeReader(name)
+ if err != nil {
+ return "", err
+ }
+ defer rc.Close()
+
+ data, err := io.ReadAll(rc)
+ if err != nil {
+ return "", err
+ }
+ return strings.TrimSuffix(string(data), "\n"), nil
+}
+
+func detectAMDPreOSMeasurementConfig(env internal_efi.HostEnvironment) (*amdPreOSMeasurementConfig, error) {
+ devices, err := env.EnumerateDevices(&netlink.RuleDefinition{
+ Env: map[string]string{
+ "SUBSYSTEM": "pci",
+ "DRIVER": "ccp",
+ },
+ })
+ if err != nil {
+ return nil, fmt.Errorf("cannot obtain PCI devices that are bound to the ccp driver: %w", err)
+ }
+ if len(devices) == 0 {
+ return nil, &UnsupportedPlatformError{errors.New("no PSP PCI device")}
+ }
+
+ tsmeEnabled, err := readAMDPSPBooleanAttribute(devices[0], "tsme_status")
+ if err != nil {
+ return nil, fmt.Errorf("cannot determine TSME status: %w", err)
+ }
+ // The ccp driver does not expose the HSTI TSME measurement-support bit,
+ // so the event log checks cannot determine whether the TSME event must be
+ // present. They can still validate its value against this status when it is
+ // present and policy generation can derive its digest from this status.
+ config := &amdPreOSMeasurementConfig{TSMEEnabled: tsmeEnabled}
+
+ devices, err = env.EnumerateDevices(&netlink.RuleDefinition{
+ Env: map[string]string{"SUBSYSTEM": "firmware-attributes"},
+ })
+ if err != nil {
+ return nil, fmt.Errorf("cannot enumerate firmware attributes devices: %w", err)
+ }
+ for _, device := range devices {
+ if filepath.Base(device.Path()) != "hp-bioscfg" {
+ continue
+ }
+
+ const attrPrefix = "attributes/"
+ measureTo, err := readSysfsDeviceStringAttribute(device, attrPrefix+"Measure Additional DMA Settings/current_value")
+ if err != nil {
+ return nil, fmt.Errorf("cannot read HP Measure Additional DMA Settings firmware attribute: %w", err)
+ }
+ if measureTo != "PCR7" {
+ return config, nil
+ }
+
+ for _, setting := range []struct {
+ name string
+ expected string
+ }{
+ {name: "SVM CPU Virtualization", expected: "Enable"},
+ {name: "DMA Protection", expected: "Enable"},
+ {name: "Pre-boot DMA Protection", expected: "All PCIe Devices"},
+ } {
+ value, err := readSysfsDeviceStringAttribute(device, attrPrefix+setting.name+"/current_value")
+ if err != nil {
+ return nil, fmt.Errorf("cannot read HP %s firmware attribute: %w", setting.name, err)
+ }
+ if value != setting.expected {
+ return nil, &UnsupportedPlatformError{fmt.Errorf("unsupported HP %s firmware setting %q", setting.name, value)}
+ }
+ }
+
+ config.HPPreBootDMAConfigEnabled = true
+ return config, nil
+ }
+
+ return config, nil
+}
+
func checkHostSecurityAMDPSP(env internal_efi.HostEnvironment) (platformFirmwareIntegrityConfig, error) {
// Enumerate the PCI devices that are bound to the ccp driver.
devices, err := env.EnumerateDevices(&netlink.RuleDefinition{
diff --git a/efi/preinstall/check_host_security_amd64.go b/efi/preinstall/check_host_security_amd64.go
index 9571ca12..c943511c 100644
--- a/efi/preinstall/check_host_security_amd64.go
+++ b/efi/preinstall/check_host_security_amd64.go
@@ -32,31 +32,46 @@ import (
// are returned immediately and without any wrapping. Errors that can be resolved and which shouldn't
// prevent further checks from running are returned wrapped in [joinError].
func checkHostSecurity(env internal_efi.HostEnvironment, log *tcglog.Log) (platformFirmwareIntegrityConfig, error) {
+ integrity, _, err := checkHostSecurityWithAMDPreOSMeasurements(env, log, false)
+ return integrity, err
+}
+
+// checkHostSecurityAndAMDPreOSMeasurements also returns AMD configuration that
+// determines platform-specific pre-OS measurements. On non-AMD systems, the
+// returned configuration is nil.
+func checkHostSecurityAndAMDPreOSMeasurements(env internal_efi.HostEnvironment, log *tcglog.Log) (platformFirmwareIntegrityConfig, *amdPreOSMeasurementConfig, error) {
+ return checkHostSecurityWithAMDPreOSMeasurements(env, log, true)
+}
+
+func checkHostSecurityWithAMDPreOSMeasurements(env internal_efi.HostEnvironment, log *tcglog.Log, detectMeasurements bool) (platformFirmwareIntegrityConfig, *amdPreOSMeasurementConfig, error) {
cpuVendor, err := determineCPUVendor(env)
if err != nil {
- return platformFirmwareIntegrityNone, &UnsupportedPlatformError{fmt.Errorf("cannot determine CPU vendor: %w", err)}
+ return platformFirmwareIntegrityNone, nil, &UnsupportedPlatformError{fmt.Errorf("cannot determine CPU vendor: %w", err)}
}
amd64Env, err := env.AMD64()
if err != nil {
- return platformFirmwareIntegrityNone, fmt.Errorf("cannot obtain AMD64 environment: %w", err)
+ return platformFirmwareIntegrityNone, nil, fmt.Errorf("cannot obtain AMD64 environment: %w", err)
}
var errs []error
- var integrity platformFirmwareIntegrityConfig
+ var (
+ integrity platformFirmwareIntegrityConfig
+ amdConfig *amdPreOSMeasurementConfig
+ )
switch cpuVendor {
case cpuVendorIntel:
if err := checkHostSecurityIntelBootGuard(env); err != nil {
var nohwrotErr *NoHardwareRootOfTrustError
ctxErr := fmt.Errorf("encountered an error when checking Intel BootGuard configuration: %w", err)
if !errors.As(err, &nohwrotErr) {
- return platformFirmwareIntegrityNone, ctxErr
+ return platformFirmwareIntegrityNone, nil, ctxErr
}
errs = append(errs, ctxErr)
}
if err := checkHostSecurityIntelCPUDebuggingLocked(amd64Env); err != nil {
- return platformFirmwareIntegrityNone, fmt.Errorf("encountered an error when checking Intel CPU debugging configuration: %w", err)
+ return platformFirmwareIntegrityNone, nil, fmt.Errorf("encountered an error when checking Intel CPU debugging configuration: %w", err)
}
if len(errs) == 0 {
integrity = platformFirmwareIntegrityVerified
@@ -67,10 +82,16 @@ func checkHostSecurity(env internal_efi.HostEnvironment, log *tcglog.Log) (platf
ctxErr := fmt.Errorf("encountered an error when checking the AMD PSP configuration: %w", err)
var nohwrotErr *NoHardwareRootOfTrustError
if !errors.As(err, &nohwrotErr) {
- return platformFirmwareIntegrityNone, ctxErr
+ return platformFirmwareIntegrityNone, nil, ctxErr
}
errs = append(errs, ctxErr)
}
+ if detectMeasurements {
+ amdConfig, err = detectAMDPreOSMeasurementConfig(env)
+ if err != nil {
+ return platformFirmwareIntegrityNone, nil, fmt.Errorf("cannot determine AMD pre-OS measurement configuration: %w", err)
+ }
+ }
default:
panic("not reached")
}
@@ -78,7 +99,7 @@ func checkHostSecurity(env internal_efi.HostEnvironment, log *tcglog.Log) (platf
if err := checkSecureBootPolicyPCRForDegradedFirmwareSettings(log); err != nil {
var ce CompoundError
if !errors.As(err, &ce) {
- return platformFirmwareIntegrityNone, fmt.Errorf("encountered an error whilst checking the TCG log for degraded firmware settings: %w", err)
+ return platformFirmwareIntegrityNone, nil, fmt.Errorf("encountered an error whilst checking the TCG log for degraded firmware settings: %w", err)
}
errs = append(errs, ce.Unwrap()...)
}
@@ -87,15 +108,15 @@ func checkHostSecurity(env internal_efi.HostEnvironment, log *tcglog.Log) (platf
case errors.Is(err, ErrNoKernelIOMMU):
errs = append(errs, err)
default:
- return platformFirmwareIntegrityNone, fmt.Errorf("encountered an error whilst checking sysfs to determine that kernel IOMMU support is enabled: %w", err)
+ return platformFirmwareIntegrityNone, nil, fmt.Errorf("encountered an error whilst checking sysfs to determine that kernel IOMMU support is enabled: %w", err)
}
}
if len(errs) > 0 {
- return platformFirmwareIntegrityNone, joinErrors(errs...)
+ return platformFirmwareIntegrityNone, amdConfig, joinErrors(errs...)
}
- return integrity, nil
+ return integrity, amdConfig, nil
}
// checkDiscreteTPMPartialResetAttackMitigationStatus determines whether a partial mitigation
diff --git a/efi/preinstall/check_host_security_amd64_test.go b/efi/preinstall/check_host_security_amd64_test.go
index 2ea4892e..d73f9a4d 100644
--- a/efi/preinstall/check_host_security_amd64_test.go
+++ b/efi/preinstall/check_host_security_amd64_test.go
@@ -428,3 +428,94 @@ func (s *hostSecurityAMD64Suite) TestCheckDiscreteTPMPartialResetAttackMitigatio
var upe *UnsupportedPlatformError
c.Check(errors.As(err, &upe), testutil.IsTrue)
}
+
+func (s *hostSecurityAMD64Suite) TestDetectAMDPreOSMeasurementConfig(c *C) {
+ devices := []internal_efi.SysfsDevice{
+ efitest.NewMockSysfsDevice(
+ "/sys/devices/pci0000:00/0000:00:08.1/0000:c1:00.2",
+ map[string]string{"DRIVER": "ccp"},
+ "pci",
+ map[string][]byte{"tsme_status": []byte("1\n")},
+ nil),
+ efitest.NewMockSysfsDevice(
+ "/sys/devices/platform/firmware-attributes/hp-bioscfg",
+ nil,
+ "firmware-attributes",
+ map[string][]byte{
+ "attributes/Measure Additional DMA Settings/current_value": []byte("PCR7\n"),
+ "attributes/SVM CPU Virtualization/current_value": []byte("Enable\n"),
+ "attributes/DMA Protection/current_value": []byte("Enable\n"),
+ "attributes/Pre-boot DMA Protection/current_value": []byte("All PCIe Devices\n"),
+ },
+ nil),
+ }
+ env := efitest.NewMockHostEnvironmentWithOpts(efitest.WithSysfsDevices(devices...))
+
+ config, err := DetectAMDPreOSMeasurementConfig(env)
+ c.Check(err, IsNil)
+ c.Check(config, DeepEquals, &AMDPreOSMeasurementConfig{
+ TSMEEnabled: true,
+ HPPreBootDMAConfigEnabled: true,
+ })
+}
+
+func (s *hostSecurityAMD64Suite) TestDetectAMDPreOSMeasurementConfigWithoutHPMeasurement(c *C) {
+ devices := []internal_efi.SysfsDevice{
+ efitest.NewMockSysfsDevice(
+ "/sys/devices/pci0000:00/0000:00:08.1/0000:c1:00.2",
+ map[string]string{"DRIVER": "ccp"},
+ "pci",
+ map[string][]byte{"tsme_status": []byte("0\n")},
+ nil),
+ efitest.NewMockSysfsDevice(
+ "/sys/devices/platform/firmware-attributes/hp-bioscfg",
+ nil,
+ "firmware-attributes",
+ map[string][]byte{
+ "attributes/Measure Additional DMA Settings/current_value": []byte("Do not measure\n"),
+ },
+ nil),
+ }
+ env := efitest.NewMockHostEnvironmentWithOpts(efitest.WithSysfsDevices(devices...))
+
+ config, err := DetectAMDPreOSMeasurementConfig(env)
+ c.Check(err, IsNil)
+ c.Check(config, DeepEquals, &AMDPreOSMeasurementConfig{})
+}
+
+func (s *hostSecurityAMD64Suite) TestDetectAMDPreOSMeasurementConfigMissingTSMEStatus(c *C) {
+ device := efitest.NewMockSysfsDevice(
+ "/sys/devices/pci0000:00/0000:00:08.1/0000:c1:00.2",
+ map[string]string{"DRIVER": "ccp"},
+ "pci",
+ nil,
+ nil)
+ env := efitest.NewMockHostEnvironmentWithOpts(efitest.WithSysfsDevices(device))
+
+ _, err := DetectAMDPreOSMeasurementConfig(env)
+ c.Check(err, ErrorMatches, `cannot determine TSME status: device attribute does not exist`)
+}
+
+func (s *hostSecurityAMD64Suite) TestDetectAMDPreOSMeasurementConfigRejectsUnsupportedMeasuredHPConfig(c *C) {
+ devices := []internal_efi.SysfsDevice{
+ efitest.NewMockSysfsDevice(
+ "/sys/devices/pci0000:00/0000:00:08.1/0000:c1:00.2",
+ map[string]string{"DRIVER": "ccp"},
+ "pci",
+ map[string][]byte{"tsme_status": []byte("1\n")},
+ nil),
+ efitest.NewMockSysfsDevice(
+ "/sys/devices/platform/firmware-attributes/hp-bioscfg",
+ nil,
+ "firmware-attributes",
+ map[string][]byte{
+ "attributes/Measure Additional DMA Settings/current_value": []byte("PCR7\n"),
+ "attributes/SVM CPU Virtualization/current_value": []byte("Disable\n"),
+ },
+ nil),
+ }
+ env := efitest.NewMockHostEnvironmentWithOpts(efitest.WithSysfsDevices(devices...))
+
+ _, err := DetectAMDPreOSMeasurementConfig(env)
+ c.Check(err, ErrorMatches, `unsupported platform: unsupported HP SVM CPU Virtualization firmware setting "Disable"`)
+}
diff --git a/efi/preinstall/check_host_security_null.go b/efi/preinstall/check_host_security_null.go
index ecaca284..d08da31c 100644
--- a/efi/preinstall/check_host_security_null.go
+++ b/efi/preinstall/check_host_security_null.go
@@ -34,6 +34,15 @@ func checkHostSecurity(env internal_efi.HostEnvironment, log *tcglog.Log) (platf
return platformFirmwareIntegrityNone, &UnsupportedPlatformError{fmt.Errorf("checking host security is not implemented on %s", runtime.GOARCH)}
}
+func checkHostSecurityAndAMDPreOSMeasurements(env internal_efi.HostEnvironment, log *tcglog.Log) (platformFirmwareIntegrityConfig, *amdPreOSMeasurementConfig, error) {
+ integrity, err := checkHostSecurity(env, log)
+ return integrity, nil, err
+}
+
+func detectAMDPreOSMeasurementConfig(env internal_efi.HostEnvironment) (*amdPreOSMeasurementConfig, error) {
+ return nil, &UnsupportedPlatformError{fmt.Errorf("checking AMD pre-OS measurement configuration is not implemented on %s", runtime.GOARCH)}
+}
+
func checkDiscreteTPMPartialResetAttackMitigationStatus(env internal_efi.HostEnvironment, logResults *pcrBankResults) (discreteTPMPartialResetAttackMitigationStatus, error) {
return dtpmPartialResetAttackMitigationNotRequired, nil
}
diff --git a/efi/preinstall/check_pcr2.go b/efi/preinstall/check_pcr2.go
index a62bff89..93b76cfa 100644
--- a/efi/preinstall/check_pcr2.go
+++ b/efi/preinstall/check_pcr2.go
@@ -46,6 +46,10 @@ import (
// This function expects that the supplied log has already been tested to be valid (eg,
// with checkFirmwareLogAndChoosePCRBank), and will panic if it isn't
func checkDriversAndAppsMeasurements(ctx context.Context, env internal_efi.HostEnvironment, log *tcglog.Log, pcrAlg tpm2.HashAlgorithmId) ([]*LoadedImageInfo, error) {
+ return checkDriversAndAppsMeasurementsWithConfig(ctx, env, log, pcrAlg, nil)
+}
+
+func checkDriversAndAppsMeasurementsWithConfig(ctx context.Context, env internal_efi.HostEnvironment, log *tcglog.Log, pcrAlg tpm2.HashAlgorithmId, amdConfig *amdPreOSMeasurementConfig) ([]*LoadedImageInfo, error) {
varCtx := env.VarContext(ctx)
// Obtain the list of DriverXXXX load options for drivers that are started
@@ -56,18 +60,56 @@ func checkDriversAndAppsMeasurements(ctx context.Context, env internal_efi.HostE
}
var addonDrivers []*LoadedImageInfo
+ var seenAMDTSMEConfig bool
- // Iterate over the log until OS-present and check if there are any
- // drivers or applications loaded
+ // Iterate over the log through the initial OS authorization or launch
+ // boundary and check if there are any drivers or applications loaded.
phaseTracker := newTcgLogPhaseTracker()
for _, ev := range log.Events {
+ wasTransitioningToOSPresent := phaseTracker.phase == tcglogPhaseTransitioningToOSPresent
phase, err := phaseTracker.processEvent(ev)
if err != nil {
return nil, err
}
- if phase >= tcglogPhaseTransitioningToOSPresent {
- return addonDrivers, nil
+ if ev.PCRIndex == internal_efi.DriversAndAppsPCR {
+ if handled, err := checkAMDTSMEConfigEvent(ev, pcrAlg, amdConfig, &seenAMDTSMEConfig); handled {
+ if err != nil {
+ return nil, err
+ }
+ continue
+ }
+ }
+
+ switch phase {
+ case tcglogPhaseTransitioningToOSPresent:
+ // The phase tracker validates that this consists only of the
+ // remaining separators.
+ continue
+ case tcglogPhaseOSPresent:
+ if wasTransitioningToOSPresent {
+ // processEvent returns the new phase. The event that
+ // completes the transition is still one of the required
+ // separators, regardless of which PCR it belongs to.
+ continue
+ }
+
+ // AMD firmware may extend its independently verifiable TSME status
+ // into PCR2 after the separators but before authorizing or launching
+ // the initial OS loader. The event is handled above.
+ if (ev.PCRIndex == internal_efi.SecureBootPolicyPCR &&
+ ev.EventType == tcglog.EventTypeEFIVariableAuthority) ||
+ (ev.PCRIndex == internal_efi.BootManagerCodePCR &&
+ ev.EventType == tcglog.EventTypeEFIBootServicesApplication) {
+ return addonDrivers, nil
+ }
+
+ if ev.PCRIndex == internal_efi.DriversAndAppsPCR {
+ return nil, fmt.Errorf(
+ "unexpected post-separator event type %v found in PCR %d",
+ ev.EventType, internal_efi.DriversAndAppsPCR)
+ }
+ continue
}
if ev.PCRIndex != internal_efi.DriversAndAppsPCR {
@@ -126,5 +168,8 @@ func checkDriversAndAppsMeasurements(ctx context.Context, env internal_efi.HostE
}
}
+ if phaseTracker.reachedOSPresent() {
+ return nil, errors.New("reached end of log before encountering initial OS authorization or launch")
+ }
return nil, errors.New("reached end of log before encountering transition to OS-present")
}
diff --git a/efi/preinstall/check_pcr2_test.go b/efi/preinstall/check_pcr2_test.go
index 51320d13..9a5b8e52 100644
--- a/efi/preinstall/check_pcr2_test.go
+++ b/efi/preinstall/check_pcr2_test.go
@@ -40,6 +40,7 @@ var _ = Suite(&pcr2Suite{})
type testCheckDriversAndAppsMeasurementsParams struct {
env internal_efi.HostEnvironment
pcrAlg tpm2.HashAlgorithmId
+ amdConfig *AMDPreOSMeasurementConfig
expectedResult []*LoadedImageInfo
}
@@ -47,7 +48,7 @@ func (s *pcr2Suite) testCheckDriversAndAppsMeasurements(c *C, params *testCheckD
log, err := params.env.ReadEventLog()
c.Assert(err, IsNil)
- result, err := CheckDriversAndAppsMeasurements(context.Background(), params.env, log, params.pcrAlg)
+ result, err := CheckDriversAndAppsMeasurementsWithConfig(context.Background(), params.env, log, params.pcrAlg, params.amdConfig)
if err != nil {
return err
}
diff --git a/efi/preinstall/check_pcr7.go b/efi/preinstall/check_pcr7.go
index 1b67ef9c..568e5abd 100644
--- a/efi/preinstall/check_pcr7.go
+++ b/efi/preinstall/check_pcr7.go
@@ -362,6 +362,10 @@ type secureBootPolicyResult struct {
//
// If the end of the log is reached without encountering the launch of the initial boot loader, an error is returned.
func checkSecureBootPolicyMeasurementsAndObtainAuthorities(ctx context.Context, env internal_efi.HostEnvironment, log *tcglog.Log, pcrAlg tpm2.HashAlgorithmId, iblImage secboot_efi.Image, permitDMAProtectionDisabledEvent bool) (result *secureBootPolicyResult, err error) {
+ return checkSecureBootPolicyMeasurementsAndObtainAuthoritiesWithConfig(ctx, env, log, pcrAlg, iblImage, permitDMAProtectionDisabledEvent, nil)
+}
+
+func checkSecureBootPolicyMeasurementsAndObtainAuthoritiesWithConfig(ctx context.Context, env internal_efi.HostEnvironment, log *tcglog.Log, pcrAlg tpm2.HashAlgorithmId, iblImage secboot_efi.Image, permitDMAProtectionDisabledEvent bool, amdConfig *amdPreOSMeasurementConfig) (result *secureBootPolicyResult, err error) {
if iblImage == nil {
return nil, errors.New("must supply the initial boot loader image")
}
@@ -429,9 +433,11 @@ func checkSecureBootPolicyMeasurementsAndObtainAuthorities(ctx context.Context,
}
var (
- db efi.SignatureDatabase // The authorized signature database from the TCG log.
- measuredSignatures tpm2.DigestList // The verification event digests measured by the firmware
- seenIBLLoadEvent bool // Whether we've seen the launch event for the OS initial boot loader
+ db efi.SignatureDatabase // The authorized signature database from the TCG log.
+ measuredSignatures tpm2.DigestList // The verification event digests measured by the firmware
+ seenIBLLoadEvent bool // Whether we've seen the launch event for the OS initial boot loader
+ seenHPPreBootDMAConfig bool // Whether we've seen HP's additional pre-boot DMA configuration event
+ seenAMDTSMEConfig bool // Whether we've seen AMD's TSME status measurement
)
phaseTracker := newTcgLogPhaseTracker()
@@ -441,6 +447,20 @@ NextEvent:
if err != nil {
return nil, err
}
+ if phase != tcglogPhaseFirmwareLaunch && amdConfig != nil && amdConfig.HPPreBootDMAConfigEnabled && !seenHPPreBootDMAConfig {
+ return nil, errors.New("missing expected HP pre-boot DMA configuration event")
+ }
+ if ev.PCRIndex == internal_efi.SecureBootPolicyPCR {
+ if internal_efi.IsAMDTSMEConfigEvent(ev) && phase == tcglogPhaseMeasuringSecureBootConfig && len(configs) > 0 {
+ return nil, errors.New("unexpected AMD TSME configuration event whilst measuring secure boot configuration")
+ }
+ if handled, err := checkAMDTSMEConfigEvent(ev, pcrAlg, amdConfig, &seenAMDTSMEConfig); handled {
+ if err != nil {
+ return nil, err
+ }
+ continue NextEvent
+ }
+ }
switch phase {
case tcglogPhaseFirmwareLaunch:
@@ -452,7 +472,8 @@ NextEvent:
switch ev.EventType {
case tcglog.EventTypeEFIAction:
// An EV_EFI_ACTION event measured to PCR7 may indicate some degraded condition
- // that weakens device security. 2 known ones are:
+ // that weakens device security, or may record a security-relevant
+ // platform configuration. 3 known ones are:
// - "UEFI Debug Mode", which indicates the presence of a debugging endpoint.
// The TCG PC Client PFP spec says this goes before the secure boot config
// is measured.
@@ -463,15 +484,25 @@ NextEvent:
// generate a policy that includes it. However, the tianocore documentation
// doesn't specify event ordering, so we need to accommodate any possible
// ordering of events.
+ // - HP's exact additional DMA settings event, which records enabled SVM,
+ // DMA protection and pre-boot DMA protection for all PCIe devices. This
+ // is accepted once before the secure boot configuration and its digest
+ // is validated below.
//
- // The presence of an EV_EFI_ACTION event other than "DMA Protection Disabled"
- // will result in WithSecureBootPolicyProfile() creating an invalid policy,
- // because it generally doesn't emit these measurements. Just return an error
- // here to prevent the use of WithSecureBootPolicyProfile() unless it is a
- // "DMA Protection Disabled" event and it is permitted.
+ // Other EV_EFI_ACTION events will result in
+ // WithSecureBootPolicyProfile() creating an invalid policy because it
+ // generally doesn't emit these measurements. Reject them here.
//
// Note that "UEFI Debug Mode" and "DMA Protection Disabled" events are both
// caught by the host security checks, which run before this.
+ if internal_efi.IsHPPreBootDMAConfigEvent(ev) && amdConfig != nil && amdConfig.HPPreBootDMAConfigEnabled && !seenHPPreBootDMAConfig {
+ expectedDigest := tcglog.ComputeStringEventDigest(pcrAlg.GetHash(), internal_efi.HPPreBootDMAConfigEventData())
+ if !bytes.Equal(ev.Digests[pcrAlg], expectedDigest) {
+ return nil, errors.New("invalid digest for HP pre-boot DMA configuration event")
+ }
+ seenHPPreBootDMAConfig = true
+ continue NextEvent
+ }
if permitDMAProtectionDisabledEvent && (bytes.Equal(ev.Data.Bytes(), []byte(tcglog.DMAProtectionDisabled)) ||
bytes.Equal(ev.Data.Bytes(), append([]byte(tcglog.DMAProtectionDisabled), 0x00))) {
// This event is detected by the host security checks which will result in a flag
diff --git a/efi/preinstall/check_pcr7_test.go b/efi/preinstall/check_pcr7_test.go
index 5a6ce330..92371565 100644
--- a/efi/preinstall/check_pcr7_test.go
+++ b/efi/preinstall/check_pcr7_test.go
@@ -56,6 +56,7 @@ type testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams struct {
pcrAlg tpm2.HashAlgorithmId
iblImage secboot_efi.Image
permitDMAProtectionDisabled bool
+ amdConfig *AMDPreOSMeasurementConfig
expectedFlags SecureBootPolicyResultFlags
expectedUsedAuthorities []*X509CertificateID
@@ -95,7 +96,7 @@ func (s *pcr7Suite) testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c
log, err := params.env.ReadEventLog()
c.Assert(err, IsNil)
- result, err := CheckSecureBootPolicyMeasurementsAndObtainAuthorities(context.Background(), params.env, log, params.pcrAlg, params.iblImage, params.permitDMAProtectionDisabled)
+ result, err := CheckSecureBootPolicyMeasurementsAndObtainAuthoritiesWithConfig(context.Background(), params.env, log, params.pcrAlg, params.iblImage, params.permitDMAProtectionDisabled, params.amdConfig)
if err != nil {
return err
}
diff --git a/efi/preinstall/checks.go b/efi/preinstall/checks.go
index c114e096..a0f4e5ae 100644
--- a/efi/preinstall/checks.go
+++ b/efi/preinstall/checks.go
@@ -311,7 +311,8 @@ func RunChecks(ctx context.Context, flags CheckFlags, loadedImages []secboot_efi
if virtMode == detectVirtNone {
// Only run host security checks if we are not in a VM
- fwIntegrity, err := checkHostSecurity(runChecksEnv, log)
+ fwIntegrity, amdConfig, err := checkHostSecurityAndAMDPreOSMeasurements(runChecksEnv, log)
+ result.amdPreOSMeasurementConfig = amdConfig
if err != nil {
// Either a simple error or a compound error
@@ -369,7 +370,7 @@ func RunChecks(ctx context.Context, flags CheckFlags, loadedImages []secboot_efi
}
// Check PCR2 for addon drivers.
- switch addonDrivers, err := checkDriversAndAppsMeasurements(ctx, runChecksEnv, log, result.PCRAlg); {
+ switch addonDrivers, err := checkDriversAndAppsMeasurementsWithConfig(ctx, runChecksEnv, log, result.PCRAlg, result.amdPreOSMeasurementConfig); {
case err != nil && !logResults.Lookup(internal_efi.DriversAndAppsPCR).Ok():
// Don't record another error for this PCR
case err != nil && flags&PermitNoDriversAndAppsProfileSupport == 0:
@@ -430,7 +431,7 @@ func RunChecks(ctx context.Context, flags CheckFlags, loadedImages []secboot_efi
iblImage = loadedImages[0]
}
permitDMAProtectionDisabledEvent := flags&PermitInsufficientDMAProtection > 0
- pcr7Result, err := checkSecureBootPolicyMeasurementsAndObtainAuthorities(ctx, runChecksEnv, log, result.PCRAlg, iblImage, permitDMAProtectionDisabledEvent)
+ pcr7Result, err := checkSecureBootPolicyMeasurementsAndObtainAuthoritiesWithConfig(ctx, runChecksEnv, log, result.PCRAlg, iblImage, permitDMAProtectionDisabledEvent, result.amdPreOSMeasurementConfig)
switch {
case isEFIVariableAccessError(err):
// Always return EFI variable access errors.
diff --git a/efi/preinstall/export_test.go b/efi/preinstall/export_test.go
index 753f468e..4dff0e2c 100644
--- a/efi/preinstall/export_test.go
+++ b/efi/preinstall/export_test.go
@@ -31,6 +31,7 @@ import (
)
type (
+ AMDPreOSMeasurementConfig = amdPreOSMeasurementConfig
AuthorityTrustFlags = authorityTrustFlags
AuthorityTrustData = authorityTrustData
AuthorityTrustDataSet = authorityTrustDataSet
@@ -65,31 +66,34 @@ const (
)
var (
- CheckBootManagerCodeMeasurements = checkBootManagerCodeMeasurements
- CheckDiscreteTPMPartialResetAttackMitigationStatus = checkDiscreteTPMPartialResetAttackMitigationStatus
- CheckDriversAndAppsMeasurements = checkDriversAndAppsMeasurements
- CheckFirmwareLogAndChoosePCRBank = checkFirmwareLogAndChoosePCRBank
- CheckForKernelIOMMU = checkForKernelIOMMU
- CheckHostSecurity = checkHostSecurity
- CheckSecureBootPolicyMeasurementsAndObtainAuthorities = checkSecureBootPolicyMeasurementsAndObtainAuthorities
- CheckSecureBootPolicyPCRForDegradedFirmwareSettings = checkSecureBootPolicyPCRForDegradedFirmwareSettings
- CheckSystemIsEFI = checkSystemIsEFI
- CheckTPM2ForRequiredPCClientFeatures = checkTPM2ForRequiredPCClientFeatures
- ClearTPM = clearTPM
- DetectVirtualization = detectVirtualization
- ErrInvalidLockoutAuthValueSupplied = errInvalidLockoutAuthValueSupplied
- InsertActionProceed = insertActionProceed
- IsLaunchedFromLoadOption = isLaunchedFromLoadOption
- IsPPIActionAvailable = isPPIActionAvailable
- IsTPMDiscrete = isTPMDiscrete
- JoinErrors = joinErrors
- MatchLaunchToLoadOption = matchLaunchToLoadOption
- NewX509CertificateID = newX509CertificateID
- OpenAndCheckTPM2Device = openAndCheckTPM2Device
- ReadOrderedLoadOptionVariables = readOrderedLoadOptionVariables
- RestrictedTPMLocalitiesIntel = restrictedTPMLocalitiesIntel
- RunPPIAction = runPPIAction
- UnwrapCompoundError = unwrapCompoundError
+ CheckBootManagerCodeMeasurements = checkBootManagerCodeMeasurements
+ CheckDiscreteTPMPartialResetAttackMitigationStatus = checkDiscreteTPMPartialResetAttackMitigationStatus
+ CheckDriversAndAppsMeasurements = checkDriversAndAppsMeasurements
+ CheckDriversAndAppsMeasurementsWithConfig = checkDriversAndAppsMeasurementsWithConfig
+ CheckFirmwareLogAndChoosePCRBank = checkFirmwareLogAndChoosePCRBank
+ CheckForKernelIOMMU = checkForKernelIOMMU
+ CheckHostSecurity = checkHostSecurity
+ CheckSecureBootPolicyMeasurementsAndObtainAuthorities = checkSecureBootPolicyMeasurementsAndObtainAuthorities
+ CheckSecureBootPolicyMeasurementsAndObtainAuthoritiesWithConfig = checkSecureBootPolicyMeasurementsAndObtainAuthoritiesWithConfig
+ DetectAMDPreOSMeasurementConfig = detectAMDPreOSMeasurementConfig
+ CheckSecureBootPolicyPCRForDegradedFirmwareSettings = checkSecureBootPolicyPCRForDegradedFirmwareSettings
+ CheckSystemIsEFI = checkSystemIsEFI
+ CheckTPM2ForRequiredPCClientFeatures = checkTPM2ForRequiredPCClientFeatures
+ ClearTPM = clearTPM
+ DetectVirtualization = detectVirtualization
+ ErrInvalidLockoutAuthValueSupplied = errInvalidLockoutAuthValueSupplied
+ InsertActionProceed = insertActionProceed
+ IsLaunchedFromLoadOption = isLaunchedFromLoadOption
+ IsPPIActionAvailable = isPPIActionAvailable
+ IsTPMDiscrete = isTPMDiscrete
+ JoinErrors = joinErrors
+ MatchLaunchToLoadOption = matchLaunchToLoadOption
+ NewX509CertificateID = newX509CertificateID
+ OpenAndCheckTPM2Device = openAndCheckTPM2Device
+ ReadOrderedLoadOptionVariables = readOrderedLoadOptionVariables
+ RestrictedTPMLocalitiesIntel = restrictedTPMLocalitiesIntel
+ RunPPIAction = runPPIAction
+ UnwrapCompoundError = unwrapCompoundError
)
func MockEfiComputePeImageDigest(fn func(crypto.Hash, io.ReaderAt, int64) ([]byte, error)) (restore func()) {
@@ -151,6 +155,10 @@ func MakePCRResults(mandatory bool, initialVal, logVal, pcrVal tpm2.Digest, err
}
}
+func SetAMDPreOSMeasurementConfigForTest(result *CheckResult, config *AMDPreOSMeasurementConfig) {
+ result.amdPreOSMeasurementConfig = config
+}
+
func NewPCRBankResults(alg tpm2.HashAlgorithmId, sl uint8, pcrs [8]PcrResults) *pcrBankResults {
return &pcrBankResults{
Alg: alg,
diff --git a/efi/preinstall/pcr2_vendor_event_test.go b/efi/preinstall/pcr2_vendor_event_test.go
new file mode 100644
index 00000000..2bfbfd18
--- /dev/null
+++ b/efi/preinstall/pcr2_vendor_event_test.go
@@ -0,0 +1,302 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2026 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package preinstall_test
+
+import (
+ "github.com/canonical/go-tpm2"
+ "github.com/canonical/tcglog-parser"
+ . "github.com/snapcore/secboot/efi/preinstall"
+ internal_efi "github.com/snapcore/secboot/internal/efi"
+ "github.com/snapcore/secboot/internal/efitest"
+ "github.com/snapcore/secboot/internal/testutil"
+ . "gopkg.in/check.v1"
+)
+
+func newLogWithPostSeparatorPCR2Event(c *C, insertedEvent *tcglog.Event) *tcglog.Log {
+ log := efitest.NewLog(c, &efitest.LogOptions{
+ Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256},
+ })
+
+ var events []*tcglog.Event
+ inserted := false
+ for _, event := range log.Events {
+ events = append(events, event)
+ if event.PCRIndex == internal_efi.PlatformManufacturerPCR &&
+ event.EventType == tcglog.EventTypeSeparator {
+ events = append(events, insertedEvent)
+ inserted = true
+ }
+ }
+ c.Assert(inserted, Equals, true)
+ log.Events = events
+ return log
+}
+
+func newLogWithPCR2EventAfterOSBoundary(c *C, insertedEvent *tcglog.Event) *tcglog.Log {
+ log := efitest.NewLog(c, &efitest.LogOptions{
+ Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256},
+ })
+
+ var events []*tcglog.Event
+ seenPCR2Separator := false
+ inserted := false
+ for _, event := range log.Events {
+ events = append(events, event)
+
+ if event.PCRIndex == internal_efi.DriversAndAppsPCR &&
+ event.EventType == tcglog.EventTypeSeparator {
+ seenPCR2Separator = true
+ continue
+ }
+ if !seenPCR2Separator || inserted {
+ continue
+ }
+ if event.PCRIndex == internal_efi.SecureBootPolicyPCR &&
+ event.EventType == tcglog.EventTypeEFIVariableAuthority {
+ events = append(events, insertedEvent)
+ inserted = true
+ }
+ }
+ c.Assert(inserted, Equals, true)
+ log.Events = events
+ return log
+}
+
+func newLogWithPCR2AsFinalSeparator(c *C, insertedEvent *tcglog.Event) *tcglog.Log {
+ log := efitest.NewLog(c, &efitest.LogOptions{
+ Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256},
+ })
+
+ var (
+ events []*tcglog.Event
+ pcr2Separator *tcglog.Event
+ inserted bool
+ )
+ for _, event := range log.Events {
+ if event.PCRIndex == internal_efi.DriversAndAppsPCR &&
+ event.EventType == tcglog.EventTypeSeparator {
+ pcr2Separator = event
+ continue
+ }
+
+ events = append(events, event)
+ if event.PCRIndex == internal_efi.PlatformManufacturerPCR &&
+ event.EventType == tcglog.EventTypeSeparator {
+ c.Assert(pcr2Separator, NotNil)
+ events = append(events, pcr2Separator, insertedEvent)
+ inserted = true
+ }
+ }
+ c.Assert(inserted, Equals, true)
+ log.Events = events
+ return log
+}
+
+// TestCheckDriversAndAppsMeasurementsAcceptsPostSeparatorAMDTSMEEvent confirms
+// that the PCR2 preinstall check accepts the AMD TSME event before the OS launch
+// boundary when it matches the independently detected status.
+func (s *pcr2Suite) TestCheckDriversAndAppsMeasurementsAcceptsPostSeparatorAMDTSMEEvent(c *C) {
+ log := newLogWithPostSeparatorPCR2Event(c, &tcglog.Event{
+ PCRIndex: internal_efi.DriversAndAppsPCR,
+ EventType: 0x00008401,
+ Digests: tcglog.DigestMap{
+ tpm2.HashAlgorithmSHA256: testutil.DecodeHexString(c, "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"),
+ },
+ Data: tcglog.OpaqueEventData{0x01},
+ })
+
+ err := s.testCheckDriversAndAppsMeasurements(c, &testCheckDriversAndAppsMeasurementsParams{
+ env: efitest.NewMockHostEnvironmentWithOpts(
+ efitest.WithMockVars(efitest.MockVars{}),
+ efitest.WithLog(log),
+ ),
+ pcrAlg: tpm2.HashAlgorithmSHA256,
+ amdConfig: &AMDPreOSMeasurementConfig{TSMEEnabled: true},
+ })
+ c.Check(err, IsNil)
+}
+
+func (s *pcr2Suite) TestCheckDriversAndAppsMeasurementsIgnoresPCR7AMDTSMEEvent(c *C) {
+ err := s.testCheckDriversAndAppsMeasurements(c, &testCheckDriversAndAppsMeasurementsParams{
+ env: efitest.NewMockHostEnvironmentWithOpts(
+ efitest.WithMockVars(efitest.MockVars{}),
+ efitest.WithLog(newLogWithAMDTSMEPCR7Event(c, false)),
+ ),
+ pcrAlg: tpm2.HashAlgorithmSHA256,
+ amdConfig: &AMDPreOSMeasurementConfig{TSMEEnabled: false},
+ })
+ c.Check(err, IsNil)
+}
+
+func (s *pcr2Suite) TestCheckDriversAndAppsMeasurementsRejectsTSMEStatusMismatch(c *C) {
+ log := newLogWithPostSeparatorPCR2Event(c, &tcglog.Event{
+ PCRIndex: internal_efi.DriversAndAppsPCR,
+ EventType: internal_efi.AMDTSMEConfigEventType,
+ Digests: tcglog.DigestMap{
+ tpm2.HashAlgorithmSHA256: testutil.DecodeHexString(c, "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"),
+ },
+ Data: tcglog.OpaqueEventData{0x01},
+ })
+
+ err := s.testCheckDriversAndAppsMeasurements(c, &testCheckDriversAndAppsMeasurementsParams{
+ env: efitest.NewMockHostEnvironmentWithOpts(
+ efitest.WithMockVars(efitest.MockVars{}),
+ efitest.WithLog(log),
+ ),
+ pcrAlg: tpm2.HashAlgorithmSHA256,
+ amdConfig: &AMDPreOSMeasurementConfig{TSMEEnabled: false},
+ })
+ c.Check(err, ErrorMatches, `AMD TSME configuration event data does not match detected TSME status \(expected 0x00, got 0x01\)`)
+}
+
+func (s *pcr2Suite) TestCheckDriversAndAppsMeasurementsRejectsUnknownPostSeparatorVendorEvent(c *C) {
+ log := newLogWithPostSeparatorPCR2Event(c, &tcglog.Event{
+ PCRIndex: internal_efi.DriversAndAppsPCR,
+ EventType: 0x00008402,
+ Digests: tcglog.DigestMap{
+ tpm2.HashAlgorithmSHA256: testutil.DecodeHexString(c, "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"),
+ },
+ Data: tcglog.OpaqueEventData{0x01},
+ })
+
+ err := s.testCheckDriversAndAppsMeasurements(c, &testCheckDriversAndAppsMeasurementsParams{
+ env: efitest.NewMockHostEnvironmentWithOpts(
+ efitest.WithMockVars(efitest.MockVars{}),
+ efitest.WithLog(log),
+ ),
+ pcrAlg: tpm2.HashAlgorithmSHA256,
+ })
+ c.Check(err, ErrorMatches, `unexpected post-separator event type 00008402 found in PCR 2`)
+}
+
+func (s *pcr2Suite) TestCheckDriversAndAppsMeasurementsAcceptsPCR2AsFinalSeparator(c *C) {
+ log := newLogWithPCR2AsFinalSeparator(c, &tcglog.Event{
+ PCRIndex: internal_efi.DriversAndAppsPCR,
+ EventType: 0x00008401,
+ Digests: tcglog.DigestMap{
+ tpm2.HashAlgorithmSHA256: testutil.DecodeHexString(c, "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"),
+ },
+ Data: tcglog.OpaqueEventData{0x01},
+ })
+
+ err := s.testCheckDriversAndAppsMeasurements(c, &testCheckDriversAndAppsMeasurementsParams{
+ env: efitest.NewMockHostEnvironmentWithOpts(
+ efitest.WithMockVars(efitest.MockVars{}),
+ efitest.WithLog(log),
+ ),
+ pcrAlg: tpm2.HashAlgorithmSHA256,
+ amdConfig: &AMDPreOSMeasurementConfig{TSMEEnabled: true},
+ })
+ c.Check(err, IsNil)
+}
+
+func (s *pcr2Suite) TestCheckDriversAndAppsMeasurementsRejectsLatePCR2Separator(c *C) {
+ log := newLogWithPostSeparatorPCR2Event(c, &tcglog.Event{
+ PCRIndex: internal_efi.DriversAndAppsPCR,
+ EventType: tcglog.EventTypeSeparator,
+ Digests: tcglog.DigestMap{
+ tpm2.HashAlgorithmSHA256: testutil.DecodeHexString(c, "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119"),
+ },
+ Data: &tcglog.SeparatorEventData{Value: tcglog.SeparatorEventNormalValue},
+ })
+
+ err := s.testCheckDriversAndAppsMeasurements(c, &testCheckDriversAndAppsMeasurementsParams{
+ env: efitest.NewMockHostEnvironmentWithOpts(
+ efitest.WithMockVars(efitest.MockVars{}),
+ efitest.WithLog(log),
+ ),
+ pcrAlg: tpm2.HashAlgorithmSHA256,
+ amdConfig: &AMDPreOSMeasurementConfig{TSMEEnabled: true},
+ })
+ c.Check(err, ErrorMatches, `unexpected post-separator event type EV_SEPARATOR found in PCR 2`)
+}
+
+func (s *pcr2Suite) TestCheckDriversAndAppsMeasurementsRejectsMissingOSBoundary(c *C) {
+ log := newLogWithPostSeparatorPCR2Event(c, &tcglog.Event{
+ PCRIndex: internal_efi.DriversAndAppsPCR,
+ EventType: 0x00008401,
+ Digests: tcglog.DigestMap{
+ tpm2.HashAlgorithmSHA256: testutil.DecodeHexString(c, "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"),
+ },
+ Data: tcglog.OpaqueEventData{0x01},
+ })
+
+ truncated := false
+ for i, event := range log.Events {
+ if (event.PCRIndex == internal_efi.SecureBootPolicyPCR &&
+ event.EventType == tcglog.EventTypeEFIVariableAuthority) ||
+ (event.PCRIndex == internal_efi.BootManagerCodePCR &&
+ event.EventType == tcglog.EventTypeEFIBootServicesApplication) {
+ log.Events = log.Events[:i]
+ truncated = true
+ break
+ }
+ }
+ c.Assert(truncated, Equals, true)
+
+ err := s.testCheckDriversAndAppsMeasurements(c, &testCheckDriversAndAppsMeasurementsParams{
+ env: efitest.NewMockHostEnvironmentWithOpts(
+ efitest.WithMockVars(efitest.MockVars{}),
+ efitest.WithLog(log),
+ ),
+ pcrAlg: tpm2.HashAlgorithmSHA256,
+ amdConfig: &AMDPreOSMeasurementConfig{TSMEEnabled: true},
+ })
+ c.Check(err, ErrorMatches, `reached end of log before encountering initial OS authorization or launch`)
+}
+
+func (s *pcr2Suite) TestCheckDriversAndAppsMeasurementsRejectsPostSeparatorStandardEvent(c *C) {
+ log := newLogWithPostSeparatorPCR2Event(c, &tcglog.Event{
+ PCRIndex: internal_efi.DriversAndAppsPCR,
+ EventType: tcglog.EventTypeEFIAction,
+ Digests: tcglog.DigestMap{
+ tpm2.HashAlgorithmSHA256: testutil.DecodeHexString(c, "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"),
+ },
+ Data: tcglog.OpaqueEventData{0x01},
+ })
+
+ err := s.testCheckDriversAndAppsMeasurements(c, &testCheckDriversAndAppsMeasurementsParams{
+ env: efitest.NewMockHostEnvironmentWithOpts(
+ efitest.WithMockVars(efitest.MockVars{}),
+ efitest.WithLog(log),
+ ),
+ pcrAlg: tpm2.HashAlgorithmSHA256,
+ })
+ c.Check(err, ErrorMatches, `unexpected post-separator event type EV_EFI_ACTION found in PCR 2`)
+}
+
+func (s *pcr2Suite) TestCheckDriversAndAppsMeasurementsStopsAtOSBoundary(c *C) {
+ log := newLogWithPCR2EventAfterOSBoundary(c, &tcglog.Event{
+ PCRIndex: internal_efi.DriversAndAppsPCR,
+ EventType: tcglog.EventTypeEFIAction,
+ Digests: tcglog.DigestMap{
+ tpm2.HashAlgorithmSHA256: testutil.DecodeHexString(c, "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"),
+ },
+ Data: tcglog.OpaqueEventData{0x01},
+ })
+
+ err := s.testCheckDriversAndAppsMeasurements(c, &testCheckDriversAndAppsMeasurementsParams{
+ env: efitest.NewMockHostEnvironmentWithOpts(
+ efitest.WithMockVars(efitest.MockVars{}),
+ efitest.WithLog(log),
+ ),
+ pcrAlg: tpm2.HashAlgorithmSHA256,
+ })
+ c.Check(err, IsNil)
+}
diff --git a/efi/preinstall/pcr7_hp_dma_event_test.go b/efi/preinstall/pcr7_hp_dma_event_test.go
new file mode 100644
index 00000000..d39314ce
--- /dev/null
+++ b/efi/preinstall/pcr7_hp_dma_event_test.go
@@ -0,0 +1,229 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2026 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package preinstall_test
+
+import (
+ "crypto"
+
+ efi "github.com/canonical/go-efilib"
+ "github.com/canonical/go-tpm2"
+ "github.com/canonical/tcglog-parser"
+ secboot_efi "github.com/snapcore/secboot/efi"
+ . "github.com/snapcore/secboot/efi/preinstall"
+ internal_efi "github.com/snapcore/secboot/internal/efi"
+ "github.com/snapcore/secboot/internal/efitest"
+ "github.com/snapcore/secboot/internal/testutil"
+ . "gopkg.in/check.v1"
+)
+
+const hpPreBootDMAConfigEventData = `"SVM CPU Virtualization":"Enable";"DMA protection":"Enable";"Pre-boot DMA protection":"All PCIe devices";`
+
+func newLogWithHPPreBootDMAConfigEvent(c *C) *tcglog.Log {
+ log := efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})
+ event := &tcglog.Event{
+ PCRIndex: internal_efi.SecureBootPolicyPCR,
+ EventType: tcglog.EventTypeEFIAction,
+ Digests: tcglog.DigestMap{
+ tpm2.HashAlgorithmSHA256: tcglog.ComputeStringEventDigest(crypto.SHA256, hpPreBootDMAConfigEventData),
+ },
+ Data: tcglog.StringEventData(hpPreBootDMAConfigEventData),
+ }
+
+ var events []*tcglog.Event
+ added := false
+ for _, ev := range log.Events {
+ if ev.PCRIndex == internal_efi.SecureBootPolicyPCR &&
+ ev.EventType == tcglog.EventTypeEFIVariableDriverConfig &&
+ !added {
+ events = append(events, event)
+ added = true
+ }
+ events = append(events, ev)
+ }
+ c.Assert(added, testutil.IsTrue)
+ log.Events = events
+ return log
+}
+
+func newLogWithAMDTSMEPCR7Event(c *C, enabled bool) *tcglog.Log {
+ log := efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})
+ data := internal_efi.AMDTSMEConfigEventData(enabled)
+ event := &tcglog.Event{
+ PCRIndex: internal_efi.SecureBootPolicyPCR,
+ EventType: internal_efi.AMDTSMEConfigEventType,
+ Digests: tcglog.DigestMap{
+ tpm2.HashAlgorithmSHA256: tcglog.ComputeEventDigest(crypto.SHA256, data),
+ },
+ Data: tcglog.OpaqueEventData(data),
+ }
+
+ var events []*tcglog.Event
+ added := false
+ for _, ev := range log.Events {
+ if ev.PCRIndex == internal_efi.SecureBootPolicyPCR &&
+ ev.EventType == tcglog.EventTypeEFIVariableDriverConfig &&
+ !added {
+ events = append(events, event)
+ added = true
+ }
+ events = append(events, ev)
+ }
+ c.Assert(added, testutil.IsTrue)
+ log.Events = events
+ return log
+}
+
+func hpPreBootDMAConfigTestVars(c *C) efitest.MockVars {
+ return efitest.MockVars{
+ {Name: "AuditMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}},
+ {Name: "DeployedMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x1}},
+ {Name: "SetupMode", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x0}},
+ {Name: "OsIndicationsSupported", GUID: efi.GlobalVariable}: &efitest.VarEntry{Attrs: efi.AttributeNonVolatile | efi.AttributeBootserviceAccess | efi.AttributeRuntimeAccess, Payload: []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
+ }.SetSecureBoot(true).SetPK(c, efitest.NewSignatureListX509(c, snakeoilCert, efi.MakeGUID(0x03f66fa4, 0x5eee, 0x479c, 0xa408, [...]uint8{0xc4, 0xdc, 0x0a, 0x33, 0xfc, 0xde})))
+}
+
+func hpPreBootDMAConfigTestIBL(c *C) secboot_efi.Image {
+ return &mockImage{
+ signatures: []*efi.WinCertificateAuthenticode{
+ efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig4),
+ },
+ digest: testutil.DecodeHexString(c, "25e1b08db2f31ff5f5d2ea53e1a1e8fda6e1d81af4f26a7908071f1dec8611b7"),
+ }
+}
+
+func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesGoodWithHPPreBootDMAConfig(c *C) {
+ err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{
+ env: efitest.NewMockHostEnvironmentWithOpts(
+ efitest.WithMockVars(hpPreBootDMAConfigTestVars(c)),
+ efitest.WithLog(newLogWithHPPreBootDMAConfigEvent(c)),
+ ),
+ pcrAlg: tpm2.HashAlgorithmSHA256,
+ amdConfig: &AMDPreOSMeasurementConfig{TSMEEnabled: true, HPPreBootDMAConfigEnabled: true},
+ iblImage: hpPreBootDMAConfigTestIBL(c),
+ expectedFlags: SecureBootPolicyResultFlags(0),
+ expectedUsedAuthorities: []*X509CertificateID{
+ NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert)),
+ },
+ })
+ c.Check(err, IsNil)
+}
+
+func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesGoodWithDisabledTSME(c *C) {
+ err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{
+ env: efitest.NewMockHostEnvironmentWithOpts(
+ efitest.WithMockVars(hpPreBootDMAConfigTestVars(c)),
+ efitest.WithLog(newLogWithAMDTSMEPCR7Event(c, false)),
+ ),
+ pcrAlg: tpm2.HashAlgorithmSHA256,
+ amdConfig: &AMDPreOSMeasurementConfig{TSMEEnabled: false},
+ iblImage: hpPreBootDMAConfigTestIBL(c),
+ expectedUsedAuthorities: []*X509CertificateID{
+ NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert)),
+ },
+ })
+ c.Check(err, IsNil)
+}
+
+func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesRejectsMissingExpectedHPPreBootDMAConfig(c *C) {
+ log := efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}})
+ err := s.checkHPPreBootDMAConfigLog(c, log)
+ c.Check(err, ErrorMatches, `missing expected HP pre-boot DMA configuration event`)
+}
+
+func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesRejectsUnexpectedHPPreBootDMAConfig(c *C) {
+ err := s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{
+ env: efitest.NewMockHostEnvironmentWithOpts(
+ efitest.WithMockVars(hpPreBootDMAConfigTestVars(c)),
+ efitest.WithLog(newLogWithHPPreBootDMAConfigEvent(c)),
+ ),
+ pcrAlg: tpm2.HashAlgorithmSHA256,
+ iblImage: hpPreBootDMAConfigTestIBL(c),
+ })
+ c.Check(err, ErrorMatches, `unexpected EV_EFI_ACTION event .* before config`)
+}
+
+func (s *pcr7Suite) checkHPPreBootDMAConfigLog(c *C, log *tcglog.Log) error {
+ return s.testCheckSecureBootPolicyMeasurementsAndObtainAuthorities(c, &testCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesParams{
+ env: efitest.NewMockHostEnvironmentWithOpts(
+ efitest.WithMockVars(hpPreBootDMAConfigTestVars(c)),
+ efitest.WithLog(log),
+ ),
+ pcrAlg: tpm2.HashAlgorithmSHA256,
+ amdConfig: &AMDPreOSMeasurementConfig{TSMEEnabled: true, HPPreBootDMAConfigEnabled: true},
+ iblImage: hpPreBootDMAConfigTestIBL(c),
+ expectedUsedAuthorities: []*X509CertificateID{
+ NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert)),
+ },
+ })
+}
+
+func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesRejectsHPPreBootDMAConfigWithWrongDigest(c *C) {
+ log := newLogWithHPPreBootDMAConfigEvent(c)
+ for _, ev := range log.Events {
+ if internal_efi.IsHPPreBootDMAConfigEvent(ev) {
+ ev.Digests[tpm2.HashAlgorithmSHA256] = make(tpm2.Digest, tpm2.HashAlgorithmSHA256.Size())
+ }
+ }
+
+ err := s.checkHPPreBootDMAConfigLog(c, log)
+ c.Check(err, ErrorMatches, `invalid digest for HP pre-boot DMA configuration event`)
+}
+
+func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesRejectsDuplicateHPPreBootDMAConfig(c *C) {
+ log := newLogWithHPPreBootDMAConfigEvent(c)
+ var events []*tcglog.Event
+ for _, ev := range log.Events {
+ events = append(events, ev)
+ if internal_efi.IsHPPreBootDMAConfigEvent(ev) {
+ events = append(events, ev)
+ }
+ }
+ log.Events = events
+
+ err := s.checkHPPreBootDMAConfigLog(c, log)
+ c.Check(err, ErrorMatches, `unexpected EV_EFI_ACTION event .* before config`)
+}
+
+func (s *pcr7Suite) TestCheckSecureBootPolicyMeasurementsAndObtainAuthoritiesRejectsMisplacedHPPreBootDMAConfig(c *C) {
+ log := newLogWithHPPreBootDMAConfigEvent(c)
+ var (
+ events []*tcglog.Event
+ hpEvent *tcglog.Event
+ added bool
+ )
+ for _, ev := range log.Events {
+ if internal_efi.IsHPPreBootDMAConfigEvent(ev) {
+ hpEvent = ev
+ continue
+ }
+ events = append(events, ev)
+ if ev.PCRIndex == internal_efi.SecureBootPolicyPCR &&
+ ev.EventType == tcglog.EventTypeEFIVariableDriverConfig &&
+ !added {
+ events = append(events, hpEvent)
+ added = true
+ }
+ }
+ c.Assert(hpEvent, NotNil)
+ c.Assert(added, testutil.IsTrue)
+ log.Events = events
+
+ err := s.checkHPPreBootDMAConfigLog(c, log)
+ c.Check(err, ErrorMatches, `missing expected HP pre-boot DMA configuration event`)
+}
diff --git a/efi/preinstall/profile.go b/efi/preinstall/profile.go
index 2a342b77..a18e2924 100644
--- a/efi/preinstall/profile.go
+++ b/efi/preinstall/profile.go
@@ -495,6 +495,11 @@ func (o *pcrProfileAutoSetPcrsOption) ApplyOptionTo(visitor internal_efi.PCRProf
return fmt.Errorf("cannot add PCR profile option %d: %w", i, err)
}
}
+ if config := o.result.amdPreOSMeasurementConfig; config != nil {
+ if err := secboot_efi.WithAMDPreOSMeasurements(config.TSMEEnabled, config.HPPreBootDMAConfigEnabled).ApplyOptionTo(visitor); err != nil {
+ return fmt.Errorf("cannot add AMD pre-OS measurements profile option: %w", err)
+ }
+ }
if _, permitted := o.result.AcceptedErrors[ErrorKindInsufficientDMAProtection]; permitted {
if err := secboot_efi.WithAllowInsufficientDmaProtection().ApplyOptionTo(visitor); err != nil {
return fmt.Errorf("cannot add DMA allow insufficient protection profile option: %w", err)
diff --git a/efi/preinstall/profile_test.go b/efi/preinstall/profile_test.go
index 53713ac9..07cc2b9a 100644
--- a/efi/preinstall/profile_test.go
+++ b/efi/preinstall/profile_test.go
@@ -592,3 +592,23 @@ func (s *profileSuite) TestWithAutoTCGPCRInvalidSecureBootMode(c *C) {
},
})
}
+
+func (s *profileSuite) TestWithAutoTCGPCRAMDPreOSMeasurements(c *C) {
+ result := &CheckResult{
+ UsedSecureBootCAs: []*X509CertificateID{NewX509CertificateID(testutil.ParseCertificate(c, msUefiCACert))},
+ }
+ SetAMDPreOSMeasurementConfigForTest(result, &AMDPreOSMeasurementConfig{
+ TSMEEnabled: true,
+ HPPreBootDMAConfigEnabled: true,
+ })
+ profile := WithAutoTCGPCRProfile(result, PCRProfileOptionsDefault)
+
+ visitor := &mockPcrProfileOptionVisitor{
+ imageLoadParams: []internal_efi.LoadParams{{}},
+ }
+ c.Check(profile.ApplyOptionTo(visitor), IsNil)
+ c.Check(visitor.imageLoadParams, DeepEquals, []internal_efi.LoadParams{{
+ "amd_tsme_enabled": true,
+ "include_hp_pre_boot_dma_config": true,
+ }})
+}
diff --git a/efi/preinstall/result.go b/efi/preinstall/result.go
index 853e096e..1f2446ba 100644
--- a/efi/preinstall/result.go
+++ b/efi/preinstall/result.go
@@ -214,10 +214,21 @@ func (f CheckResultFlags) String() string {
}
type checkResultJSON struct {
- PCRAlg hashAlgorithmId `json:"pcr-alg"`
- UsedSecureBootCAs []*X509CertificateID `json:"used-secure-boot-cas"`
- Flags CheckResultFlags `json:"flags"`
- AcceptedErrors map[ErrorKind]json.RawMessage `json:"accepted-errors,omitempty"`
+ PCRAlg hashAlgorithmId `json:"pcr-alg"`
+ UsedSecureBootCAs []*X509CertificateID `json:"used-secure-boot-cas"`
+ Flags CheckResultFlags `json:"flags"`
+ AcceptedErrors map[ErrorKind]json.RawMessage `json:"accepted-errors,omitempty"`
+ AMDPreOSMeasurementConfig *amdPreOSMeasurementConfig `json:"amd-pre-os-measurement-config,omitempty"`
+}
+
+// amdPreOSMeasurementConfig contains platform configuration obtained from
+// sources that are independent of the TCG event log.
+type amdPreOSMeasurementConfig struct {
+ // Linux exposes the TSME status, but not the HSTI bit that indicates
+ // whether firmware measures it. This supplies the expected payload when
+ // the event is present; its presence cannot currently be required.
+ TSMEEnabled bool `json:"tsme-enabled"`
+ HPPreBootDMAConfigEnabled bool `json:"hp-pre-boot-dma-config-enabled,omitempty"`
}
// CheckResult is returned from [RunChecks] when it completes successfully.
@@ -240,6 +251,8 @@ type CheckResult struct {
// for future proofing.
AcceptedErrors map[ErrorKind]json.RawMessage
+ amdPreOSMeasurementConfig *amdPreOSMeasurementConfig
+
// Warnings contains any non-fatal errors that were detected when running the tests
// on the current platform with the specified configuration. Note that this field is
// not serialized.
@@ -249,10 +262,11 @@ type CheckResult struct {
// MarshalJSON implements [json.Marshaler].
func (r CheckResult) MarshalJSON() ([]byte, error) {
res := &checkResultJSON{
- PCRAlg: hashAlgorithmId(r.PCRAlg),
- UsedSecureBootCAs: r.UsedSecureBootCAs,
- Flags: r.Flags,
- AcceptedErrors: r.AcceptedErrors,
+ PCRAlg: hashAlgorithmId(r.PCRAlg),
+ UsedSecureBootCAs: r.UsedSecureBootCAs,
+ Flags: r.Flags,
+ AcceptedErrors: r.AcceptedErrors,
+ AMDPreOSMeasurementConfig: r.amdPreOSMeasurementConfig,
}
return json.Marshal(res)
}
@@ -265,10 +279,11 @@ func (r *CheckResult) UnmarshalJSON(data []byte) error {
}
*r = CheckResult{
- PCRAlg: tpm2.HashAlgorithmId(res.PCRAlg),
- UsedSecureBootCAs: res.UsedSecureBootCAs,
- Flags: res.Flags,
- AcceptedErrors: res.AcceptedErrors,
+ PCRAlg: tpm2.HashAlgorithmId(res.PCRAlg),
+ UsedSecureBootCAs: res.UsedSecureBootCAs,
+ Flags: res.Flags,
+ AcceptedErrors: res.AcceptedErrors,
+ amdPreOSMeasurementConfig: res.AMDPreOSMeasurementConfig,
}
if r.Flags&insufficientDMAProtectionDetected > 0 {
if r.AcceptedErrors == nil {
diff --git a/efi/preinstall/result_test.go b/efi/preinstall/result_test.go
index 26f1c590..9f98fe10 100644
--- a/efi/preinstall/result_test.go
+++ b/efi/preinstall/result_test.go
@@ -122,6 +122,25 @@ func (s *resultSuite) TestCheckResultMarshalJSON(c *C) {
c.Check(data, DeepEquals, []byte("{\"pcr-alg\":\"sha256\",\"used-secure-boot-cas\":[{\"subject\":\"MIGBMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSswKQYDVQQDEyJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVUVGSSBDQSAyMDEx\",\"subject-key-id\":\"E62/Qwm9gnCcjNVPMW7VIpiKG9Q=\",\"pubkey-algorithm\":\"RSA\",\"issuer\":\"MIGRMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTswOQYDVQQDEzJNaWNyb3NvZnQgQ29ycG9yYXRpb24gVGhpcmQgUGFydHkgTWFya2V0cGxhY2UgUm9vdA==\",\"authority-key-id\":\"RWZSQ+F+WBG/1k6eI1UIOzoiaqg=\",\"signature-algorithm\":\"SHA256-RSA\"}],\"flags\":[\"no-platform-config-profile-support\",\"no-drivers-and-apps-config-profile-support\",\"no-boot-manager-config-profile-support\"]}"))
}
+func (s *resultSuite) TestCheckResultAMDPreOSMeasurementConfigJSONRoundTrip(c *C) {
+ result := &CheckResult{
+ PCRAlg: tpm2.HashAlgorithmSHA256,
+ UsedSecureBootCAs: []*X509CertificateID{},
+ }
+ SetAMDPreOSMeasurementConfigForTest(result, &AMDPreOSMeasurementConfig{
+ TSMEEnabled: true,
+ HPPreBootDMAConfigEnabled: true,
+ })
+
+ data, err := json.Marshal(result)
+ c.Assert(err, IsNil)
+ c.Check(string(data), Equals, `{"pcr-alg":"sha256","used-secure-boot-cas":[],"flags":[],"amd-pre-os-measurement-config":{"tsme-enabled":true,"hp-pre-boot-dma-config-enabled":true}}`)
+
+ var recovered *CheckResult
+ c.Assert(json.Unmarshal(data, &recovered), IsNil)
+ c.Check(recovered, DeepEquals, result)
+}
+
func (s *resultSuite) TestCheckResultMarshalJSONSHA384(c *C) {
result := CheckResult{
PCRAlg: tpm2.HashAlgorithmSHA384,