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/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,
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
}