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 }