diff --git a/efi/fw_load_handler.go b/efi/fw_load_handler.go index 3cef06c5..6cfa0fd1 100644 --- a/efi/fw_load_handler.go +++ b/efi/fw_load_handler.go @@ -33,9 +33,10 @@ import ( ) const ( - sbStateName = "SecureBoot" // Unicode variable name for the EFI secure boot configuration (enabled/disabled) - dmaProtectionDisabled = "DMA Protection Disabled" // ASCII string measured to PCR7 if DMA remapping is disabled in the pre-OS environment - dmaProtectionDisabledNul = "DMA Protection Disabled\x00" // TCG PC Client Profile spec says no NUL-terminator, but some firmware is buggy + sbStateName = "SecureBoot" // Unicode variable name for the EFI secure boot configuration (enabled/disabled) + dmaProtectionDisabled = "DMA Protection Disabled" // ASCII string measured to PCR7 if DMA remapping is disabled in the pre-OS environment + dmaProtectionDisabledNul = "DMA Protection Disabled\x00" // TCG PC Client Profile spec says no NUL-terminator, but some firmware is buggy + thunderboltSecurityLevel0 = "Security Level is Downgraded to 0" // ASCII string measured to PCR7 if Thunderbolt security level is set to zero in the pre-OS ) // fwLoadHandler is an implementation of imageLoadHandler that measures firmware @@ -137,6 +138,16 @@ func (h *fwLoadHandler) measureSecureBootPolicyPreOS(ctx pcrBranchContext) error // EV_EFI_ACTION event in the profile if it is present. includeInsufficientDMAProtection := boolParamOrFalse(ctx.Params(), includeInsufficientDMAProtectionParamKey) + // allowThunderboltSecurityLevel0 indicates that we should permit generating profiles + // that are compatible with PCR7 even if there is an event ThunderboltSecurityLevel0. + // We consider that we cannot have both allowInsufficientDMAProtection and allowThunderboltSecurityLevel0. + allowThunderboltSecurityLevel0 := boolParamOrFalse(ctx.Params(), allowThunderboltSecurityLevel0ParamKey) + + // includeThunderboltSecurityLevel0 indicates that where allowThunderboltSecurityLevel0 + // is set to true, this branch should be a branch that includes the corresponding + // EV_EFI_ACTION event in the profile if it is present. + includeThunderboltSecurityLevel0 := boolParamOrFalse(ctx.Params(), includeThunderboltSecurityLevel0ParamKey) + // 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. @@ -180,6 +191,7 @@ func (h *fwLoadHandler) measureSecureBootPolicyPreOS(ctx pcrBranchContext) error ctx.ExtendPCR(internal_efi.SecureBootPolicyPCR, e.Digests[ctx.PCRAlg()]) } allowInsufficientDMAProtection = false // Only allow this event to appear once + allowThunderboltSecurityLevel0 = false // Also forbid this one case internal_efi.IsVendorEventType(e.EventType): ctx.ExtendPCR(internal_efi.SecureBootPolicyPCR, e.Digests[ctx.PCRAlg()]) default: @@ -302,6 +314,25 @@ func (h *fwLoadHandler) measureSecureBootPolicyPreOS(ctx pcrBranchContext) error ctx.ExtendPCR(internal_efi.SecureBootPolicyPCR, e.Digests[ctx.PCRAlg()]) } allowInsufficientDMAProtection = false // Only allow this event to appear once + allowThunderboltSecurityLevel0 = false // Also forbid this one + case e.PCRIndex == internal_efi.SecureBootPolicyPCR && e.EventType == tcglog.EventTypeEFIAction && + bytes.Equal(e.Data.Bytes(), []byte(thunderboltSecurityLevel0)) && + allowThunderboltSecurityLevel0: + // This is a EV_EFI_ACTION "Security Level is Downgraded to 0" measurement and is + // allowed to appear in PCR7. In this case, it is after the secure + // boot configuration measurements, and after the separator. + // + // Now we use the includeThunderboltSecurityLevel0 flag to determine if + // this run of fwLoadHandler should really measure it to this profile + // branch as well. Including a branch in the profile that skips this + // measurement makes it possible for the firmware setting to be corrected + // without invalidating the profile. + // Future runs can then drop the option to permit it. + if includeThunderboltSecurityLevel0 { + ctx.ExtendPCR(internal_efi.SecureBootPolicyPCR, e.Digests[ctx.PCRAlg()]) + } + allowThunderboltSecurityLevel0 = false // Only allow this event to appear once + allowInsufficientDMAProtection = false // Also forbid this one case e.PCRIndex == internal_efi.SecureBootPolicyPCR && internal_efi.IsVendorEventType(e.EventType): ctx.ExtendPCR(internal_efi.SecureBootPolicyPCR, e.Digests[ctx.PCRAlg()]) case e.PCRIndex == internal_efi.SecureBootPolicyPCR: diff --git a/efi/fw_load_handler_test.go b/efi/fw_load_handler_test.go index f5afed21..fdb4a3ff 100644 --- a/efi/fw_load_handler_test.go +++ b/efi/fw_load_handler_test.go @@ -521,6 +521,61 @@ func (s *fwLoadHandlerSuite) TestMeasureImageStartSecureBootPolicyProfileInclude }) } +func (s *fwLoadHandlerSuite) TestMeasureImageStartSecureBootPolicyProfileAllowThunderboltSecurityLevel0(c *C) { + // Verify that the EV_EFI_ACTION "Security Level is Downgraded to 0" event is permitted. + vars := makeMockVars(c, withMsSecureBootConfig()) + s.testMeasureImageStart(c, &testFwMeasureImageStartData{ + vars: vars, + logOptions: &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256, tpm2.HashAlgorithmSHA1}, + ThunderboltSecurityLevel0: true, + }, + alg: tpm2.HashAlgorithmSHA256, + pcrs: MakePcrFlags(internal_efi.SecureBootPolicyPCR), + loadParams: &LoadParams{ + "allow_thunderbolt_security_level_0": true, + "include_thunderbolt_security_level_0": false, + }, + expectedEvents: []*mockPcrBranchEvent{ + {pcr: 7, eventType: mockPcrBranchResetEvent}, + {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")}, // EV_SEPARATOR + }, + }) +} + +func (s *fwLoadHandlerSuite) TestMeasureImageStartSecureBootPolicyProfileIncludeThunderboltSecurityLevel0(c *C) { + // Verify that the EV_EFI_ACTION "Security Level is Downgraded to 0" event is permitted and included. + vars := makeMockVars(c, withMsSecureBootConfig()) + s.testMeasureImageStart(c, &testFwMeasureImageStartData{ + vars: vars, + logOptions: &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256, tpm2.HashAlgorithmSHA1}, + ThunderboltSecurityLevel0: true, + }, + alg: tpm2.HashAlgorithmSHA256, + pcrs: MakePcrFlags(internal_efi.SecureBootPolicyPCR), + loadParams: &LoadParams{ + "allow_thunderbolt_security_level_0": true, + "include_thunderbolt_security_level_0": true, + }, + expectedEvents: []*mockPcrBranchEvent{ + {pcr: 7, eventType: mockPcrBranchResetEvent}, + {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")}, // EV_SEPARATOR + {pcr: 7, eventType: mockPcrBranchExtendEvent, digest: testutil.DecodeHexString(c, "bceb62feef69604bb510dd066dc494813716b7087bf11ee311e88c73ac5ba04e")}, // "Security Level is Downgraded to 0" + }, + }) +} + func (s *fwLoadHandlerSuite) TestMeasureImageStartSecureBootPolicyProfileWithVendorEventBeforeConfig(c *C) { log := efitest.NewLog(c, &efitest.LogOptions{Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256}}) var eventsCopy []*tcglog.Event @@ -997,6 +1052,46 @@ func (s *fwLoadHandlerSuite) TestMeasureImageStartErrDisallowDMAProtectionDisabl c.Check(handler.MeasureImageStart(ctx), ErrorMatches, `cannot measure secure boot policy: unexpected event type \(EV_EFI_ACTION\) found in log, before config`) } +func (s *fwLoadHandlerSuite) TestMeasureImageStartErrDisallowThunderboltSecurityLevel0(c *C) { + // Verify that the EV_EFI_ACTION "Security Level is Downgraded to 0" event raises an error. + collector := NewVariableSetCollector(efitest.NewMockHostEnvironment(makeMockVars(c, withMsSecureBootConfig()), nil)) + ctx := newMockPcrBranchContext(&mockPcrProfileContext{ + alg: tpm2.HashAlgorithmSHA256, + pcrs: MakePcrFlags(internal_efi.SecureBootPolicyPCR)}, nil, collector.Next()) + + log := efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256, tpm2.HashAlgorithmSHA1}, + ThunderboltSecurityLevel0: true, + }) + + handler := NewFwLoadHandler(log) + c.Check(handler.MeasureImageStart(ctx), ErrorMatches, `cannot measure secure boot policy: unexpected event type \(EV_EFI_ACTION\) found in log`) +} + +func (s *fwLoadHandlerSuite) TestMeasureImageStartErrAllowThunderboltSecurityLevel0WithInsufficientDMAProtection(c *C) { + // Verify that an allowed event "Security Level is Downgraded to 0" together with an allowed event + // "DMA Protection Disabled" raise an error (as they are mutually exclusive). + collector := NewVariableSetCollector(efitest.NewMockHostEnvironment(makeMockVars(c, withMsSecureBootConfig()), nil)) + loadParams := LoadParams{ + "allow_insufficient_dma_protection": true, + "allow_thunderbolt_security_level_0": true, + } + ctx := newMockPcrBranchContext(&mockPcrProfileContext{ + alg: tpm2.HashAlgorithmSHA256, + pcrs: MakePcrFlags(internal_efi.SecureBootPolicyPCR)}, + &loadParams, + collector.Next()) + + log := efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256, tpm2.HashAlgorithmSHA1}, + DMAProtection: efitest.DMAProtectionDisabled, + ThunderboltSecurityLevel0: true, + }) + + handler := NewFwLoadHandler(log) + c.Check(handler.MeasureImageStart(ctx), ErrorMatches, `cannot measure secure boot policy: unexpected event type \(EV_EFI_ACTION\) found in log`) +} + func (s *fwLoadHandlerSuite) TestMeasureImageStartErrBadLogPCR7_1(c *C) { // Insert a second EV_SEPARATOR event into PCR7 collector := NewVariableSetCollector(efitest.NewMockHostEnvironment(makeMockVars(c, withMsSecureBootConfig()), nil)) diff --git a/efi/pcr_profile_test.go b/efi/pcr_profile_test.go index 27e0033c..5f9f8564 100644 --- a/efi/pcr_profile_test.go +++ b/efi/pcr_profile_test.go @@ -1378,6 +1378,72 @@ func (s *pcrProfileSuite) TestAddPCRProfileUC20WithDbxUpdateWithAllowInsufficien c.Check(err, IsNil) } +func (s *pcrProfileSuite) TestAddPCRProfileUC20WithAllowThunderboltSecurityLevel0(c *C) { + // Test with a standard UC20 profile without recovery kernel + shim := newMockUbuntuShimImage15_7(c) + grub := newMockUbuntuGrubImage3(c) + runKernel := newMockUbuntuKernelImage3(c) + + err := s.testAddPCRProfile(c, &testAddPCRProfileData{ + vars: makeMockVars(c, withMsSecureBootConfig(), withSbatLevel([]byte("sbat,1,2022052400\ngrub,2\n"))), + log: efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256, tpm2.HashAlgorithmSHA1}, + ThunderboltSecurityLevel0: true, + }), + alg: tpm2.HashAlgorithmSHA256, + loadSequences: NewImageLoadSequences( + SnapModelParams(testutil.MakeMockCore20ModelAssertion(c, map[string]interface{}{ + "authority-id": "fake-brand", + "series": "16", + "brand-id": "fake-brand", + "model": "fake-model", + "grade": "secured", + }, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")), + ).Append( + NewImageLoadActivity(shim).Loads( + NewImageLoadActivity(grub, KernelCommandlineParams("console=ttyS0 console=tty1 panic=-1 systemd.gpt_auto=0 snapd_recovery_mode=recover")).Loads( + NewImageLoadActivity(grub, KernelCommandlineParams("console=ttyS0 console=tty1 panic=-1 systemd.gpt_auto=0 snapd_recovery_mode=run")).Loads( + NewImageLoadActivity(runKernel), + ), + ), + ), + ), + expected: []tpm2.PCRValues{ + // Not including Thunderbolt Security Level downgraded string + { + tpm2.HashAlgorithmSHA256: { + 4: testutil.DecodeHexString(c, "bec6121586508581e08a41244944292ef452879f8e19c7f93d166e912c6aac5e"), + 7: testutil.DecodeHexString(c, "3d65dbe406e9427d402488ea4f87e07e8b584c79c578a735d48d21a6405fc8bb"), + 12: testutil.DecodeHexString(c, "fd1000c6f691c3054e2ff5cfacb39305820c9f3534ba67d7894cb753aa85074b"), + }, + }, + // Including Thunderbolt Security Level downgraded string + { + tpm2.HashAlgorithmSHA256: { + 4: testutil.DecodeHexString(c, "bec6121586508581e08a41244944292ef452879f8e19c7f93d166e912c6aac5e"), + 7: testutil.DecodeHexString(c, "a2e75770cb3dc606de7a535af63f2c78ecad9159e69eb2ffa39b8b443d75c954"), + 12: testutil.DecodeHexString(c, "fd1000c6f691c3054e2ff5cfacb39305820c9f3534ba67d7894cb753aa85074b"), + }, + }, + }, + }, WithSecureBootPolicyProfile(), WithBootManagerCodeProfile(), WithKernelConfigProfile(), WithAllowThunderboltSecurityLevel0()) + c.Check(err, IsNil) +} + +func (s *pcrProfileSuite) TestAddPCRProfileThunderboltSecurityLevel0(c *C) { + // Test with an unexpected ThunderboltSecurityLevel0 event + err := s.testAddPCRProfile(c, &testAddPCRProfileData{ + vars: makeMockVars(c, withMsSecureBootConfig(), withSbatLevel([]byte("sbat,1,2022052400\ngrub,2\n"))), + log: efitest.NewLog(c, &efitest.LogOptions{ + Algorithms: []tpm2.HashAlgorithmId{tpm2.HashAlgorithmSHA256, tpm2.HashAlgorithmSHA1}, + ThunderboltSecurityLevel0: true, + }), + alg: tpm2.HashAlgorithmSHA256, + loadSequences: NewImageLoadSequences(), + }, WithSecureBootPolicyProfile(), WithBootManagerCodeProfile(), WithKernelConfigProfile()) + c.Check(err, ErrorMatches, `cannot measure pre-OS: cannot measure secure boot policy: unexpected event type \(EV_EFI_ACTION\) found in log`) +} + func (s *pcrProfileSuite) TestAddPCRProfileUC20WithAllowSecureBootUserMode(c *C) { shim := newMockUbuntuShimImage15_7(c) grub := newMockUbuntuGrubImage3(c) diff --git a/efi/thunderbolt_security_level_0.go b/efi/thunderbolt_security_level_0.go new file mode 100644 index 00000000..e36c9b46 --- /dev/null +++ b/efi/thunderbolt_security_level_0.go @@ -0,0 +1,79 @@ +// -*- 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 ( + // allowThunderboltSecurityLevel0ParamKey is used to allow for the "Security Level is Downgraded to 0" + // string in PCR7. + allowThunderboltSecurityLevel0ParamKey loadParamsKey = "allow_thunderbolt_security_level_0" + + // includeThunderboltSecurityLevel0ParamKey is used to signal whether the "Security Level is Downgraded to 0" + // string should be reflected in the produced PCR profile. + // this is ignored if allowThunderboltSecurityLevel0 is false, as the presence of the event + // will lead to an error in that case. + includeThunderboltSecurityLevel0ParamKey = "include_thunderbolt_security_level_0" +) + +type allowThunderboltSecurityLevel0Option struct{} + +func (o allowThunderboltSecurityLevel0Option) ApplyOptionTo(visitor internal_efi.PCRProfileOptionVisitor) error { + visitor.AddImageLoadParams(func(params ...loadParams) []loadParams { + var out []loadParams + for _, v := range []bool{false, true} { + var newParams []loadParams + for _, p := range params { + newParams = append(newParams, p.Clone()) + } + for _, p := range newParams { + p[allowThunderboltSecurityLevel0ParamKey] = true + p[includeThunderboltSecurityLevel0ParamKey] = v + } + out = append(out, newParams...) + } + return out + }) + return nil +} + +// WithAllowThunderboltSecurityLevel0 can be supplied to AddPCRProfile to allow for +// PCR7 including the "Security Level is Downgraded to 0" event. While this reduces security, +// it is required on some devices. +// If this string is present in the event log, this option results in a creation of a +// branched PCR profile that has two branches at the Firmware load stage one including +// the event with the string, the another not. +// +// Rationale and context: +// Some old (2021) BIOS firmware on NUC8v5PNB devices measure an event of type EV_EFI_ACTION +// saying "Security Level is Downgraded to 0" to PCR7. +// By default this event makes secboot raise an error when computing the PCR policy, and this +// is relevant as this event denotes a situation where the platform may have a security issue. +// Moreover, the user who reported this issue said that this could be fixed either by +// configuring the BIOS to the most secure option for Thunderbolt, or by updating the firmware. +// This event should therefore not be allowed by default. +// +// That being said, there may be situations where users have this event and still want to use +// TPM-backed disk encryption. This is why we provide this option. +func WithAllowThunderboltSecurityLevel0() PCRProfileOption { + return allowThunderboltSecurityLevel0Option{} +} diff --git a/internal/efitest/log.go b/internal/efitest/log.go index 2a15d488..d9d59a11 100644 --- a/internal/efitest/log.go +++ b/internal/efitest/log.go @@ -153,6 +153,17 @@ func maybeMeasureDMAProtectionDisabledEvent(c *C, builder *logBuilder, opts *Log data: data}) } +func maybeMeasureThunderboltSecurityLevel0Event(c *C, builder *logBuilder, opts *LogOptions) { + if opts.ThunderboltSecurityLevel0 { + var data tcglog.EventData + data = tcglog.StringEventData("Security Level is Downgraded to 0") + builder.hashLogExtendEvent(c, data, &logEvent{ + pcrIndex: 7, + eventType: tcglog.EventTypeEFIAction, + data: data}) + } +} + // SecureBootSeparatorOrder specifies when the EV_SEPARATOR event in PCR7 // should be emitted. type SecureBootSeparatorOrder int @@ -169,6 +180,7 @@ type LogOptions struct { StartupLocality uint8 // specify a startup locality other than 0 FirmwareDebugger bool // indicate a firmware debugger endpoint is enabled DMAProtection DMAProtectionDisabledEventFlags // whether DMA protection is disabled + ThunderboltSecurityLevel0 bool // whether Thunderbolt security level is zero SecureBootDisabled bool // Whether secure boot is disabled SecureBootSeparatorOrder SecureBootSeparatorOrder // when to emit the EV_SEPARATOR event for PCR7 IncludeDriverLaunch bool // include a driver launch from a PCI device in the log @@ -402,6 +414,7 @@ func NewLog(c *C, opts *LogOptions) *tcglog.Log { eventType: tcglog.EventTypeSeparator, data: data}) maybeMeasureDMAProtectionDisabledEvent(c, builder, opts, DMAProtectionDisabledEventOrderAfterSeparator) + maybeMeasureThunderboltSecurityLevel0Event(c, builder, opts) } // Mock EFI driver launch