From 821844b79448d636c3aabd7f1f48e0ff13178946 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Wed, 8 Apr 2026 14:35:54 +0100 Subject: [PATCH 1/2] efi/preinstall: Fall back to checking the BootGuard status MSR in HAP mode Access to the HFSTS registers via the HECI is not possible on systems that use Intel's High Assurance Platform mode. This means that we can't check the BootGuard policy. However, the startup ACM mirrors some BootGuard policy settings to a MSR, so we can check this as a fallback in this case. There are some limitations here - it's not possible to check some bits that indicate the platform has properly transitioned out of manufacturing mode, and the error enforcement bits of the BootGuard policy are not available either. However, there are no combinations of the other policy bits that are duplicated with different error enforcement bits for any of the recognized profiles, so this doesn't matter. Note that the reporter of this issue also noted that turning on HAP mode resulted in an unexpected EV_EFI_ACTION event being measured to PCR7 on their system. This doesn't fix that yet because I'm still waiting on a response from the reporter. --- efi/preinstall/check_host_security_intel.go | 20 +++- .../check_host_security_intel_btgmsr.go | 53 +++++++++++ .../check_host_security_intel_btgmsr_test.go | 64 +++++++++++++ .../check_host_security_intel_csme11.go | 1 + .../check_host_security_intel_csme18.go | 1 + .../check_host_security_intel_test.go | 72 ++++++++++----- efi/preinstall/check_tpm_intel.go | 27 +----- efi/preinstall/export_amd64_test.go | 1 + efi/preinstall/intel_util.go | 91 +++++++++++++++++++ 9 files changed, 277 insertions(+), 53 deletions(-) create mode 100644 efi/preinstall/check_host_security_intel_btgmsr.go create mode 100644 efi/preinstall/check_host_security_intel_btgmsr_test.go create mode 100644 efi/preinstall/intel_util.go diff --git a/efi/preinstall/check_host_security_intel.go b/efi/preinstall/check_host_security_intel.go index fb80293b..bedd4f92 100644 --- a/efi/preinstall/check_host_security_intel.go +++ b/efi/preinstall/check_host_security_intel.go @@ -279,9 +279,23 @@ func checkHostSecurityIntelBootGuard(env internal_efi.HostEnvironment) error { return fmt.Errorf("cannot enumerate PCI devices with MEI class: %w", err) } if len(devices) == 0 { - // We didn't find the PCI device, so indicate that this platform - // isn't supported. - return &UnsupportedPlatformError{errors.New("no MEI PCI device")} + // We didn't find the PCI device. This could be an Intel platform that + // is configured in High Assurance mode, so try this fallback using the + // BootGuard status MSR instead. + amd64Env, err := env.AMD64() + if err != nil { + return err + } + status, err := readIntelBootGuardStatus(amd64Env) + switch { + case errors.Is(err, internal_efi.ErrNoKernelMSRSupport): + return MissingKernelModuleError("msr") + case errors.Is(err, internal_efi.ErrNoMSRSupport): + return &UnsupportedPlatformError{errors.New("no MEI PCI device or BootGuard status MSR")} + case err != nil: + return fmt.Errorf("cannot read BootGuard status MSR: %w", err) + } + return checkHostSecurityIntelBootGuardMSR(status) } // We did find the PCI device, so indicate that we need the mei_me module diff --git a/efi/preinstall/check_host_security_intel_btgmsr.go b/efi/preinstall/check_host_security_intel_btgmsr.go new file mode 100644 index 00000000..d64a65bb --- /dev/null +++ b/efi/preinstall/check_host_security_intel_btgmsr.go @@ -0,0 +1,53 @@ +//go:build amd64 + +// -*- 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 ( + "errors" + "fmt" +) + +// checkHostSecurityIntelBootGuardMSR checks the BootGuard configuration using the BootGuard status +// MSR rather than the HFSTS registers. The MSR is mirrored by the startup ACM - it will contain +// all zeroes if this didn't execute, in which case, BootGuard is not active. +// +// This has some limitations compared with using the HFSTS registers. Eg, it's not possible to +// ensure that the system has properly transitioned out of manufacturing mode. +func checkHostSecurityIntelBootGuardMSR(status bootGuardStatus) error { + if status&bootGuardCapability == 0 { + return &NoHardwareRootOfTrustError{errors.New("BootGuard ACM is not active")} + } + + // Check the BootGuard profile. + profile, err := status.btgProfile() + if err != nil { + return &NoHardwareRootOfTrustError{fmt.Errorf("cannot determine BootGuard profile: %w", err)} + } + switch profile { + case btgProfileFVE, btgProfileFVME: + // We require verified boot, so the 2 profiles with forced + // verification are ok. + return nil + default: + return &NoHardwareRootOfTrustError{errors.New("unsupported BootGuard profile")} + } +} diff --git a/efi/preinstall/check_host_security_intel_btgmsr_test.go b/efi/preinstall/check_host_security_intel_btgmsr_test.go new file mode 100644 index 00000000..0025edf7 --- /dev/null +++ b/efi/preinstall/check_host_security_intel_btgmsr_test.go @@ -0,0 +1,64 @@ +//go:build amd64 + +// -*- 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 ( + . "gopkg.in/check.v1" + + . "github.com/snapcore/secboot/efi/preinstall" +) + +type hostSecurityIntelBtgMSRSuite struct{} + +var _ = Suite(&hostSecurityIntelBtgMSRSuite{}) + +func (*hostSecurityIntelBtgMSRSuite) TestCheckHostSecurityIntelBootGuardMSRGoodFVMEProfile(c *C) { + c.Check(CheckHostSecurityIntelBootGuardMSR(0x000000030000007d), IsNil) +} + +func (*hostSecurityIntelBtgMSRSuite) TestCheckHostSecurityIntelBootGuardMSRGoodFVEProfile(c *C) { + c.Check(CheckHostSecurityIntelBootGuardMSR(0x000000030000005d), IsNil) +} + +func (*hostSecurityIntelBtgMSRSuite) TestCheckHostSecurityIntelBootGuardMSRErrInvalidProfile(c *C) { + err := CheckHostSecurityIntelBootGuardMSR(0x000000030000007c) + c.Check(err, ErrorMatches, `no hardware root-of-trust properly configured: cannot determine BootGuard profile: invalid profile`) + c.Check(err, FitsTypeOf, &NoHardwareRootOfTrustError{}) +} + +func (*hostSecurityIntelBtgMSRSuite) TestCheckHostSecurityIntelBootGuardMSRErrUnsupportedNoFVMEProfile(c *C) { + err := CheckHostSecurityIntelBootGuardMSR(0x000000030000000c) + c.Check(err, ErrorMatches, `no hardware root-of-trust properly configured: unsupported BootGuard profile`) + c.Check(err, FitsTypeOf, &NoHardwareRootOfTrustError{}) +} + +func (*hostSecurityIntelBtgMSRSuite) TestCheckHostSecurityIntelBootGuardMSRErrUnsupportedVMProfile(c *C) { + err := CheckHostSecurityIntelBootGuardMSR(0x000000030000006d) + c.Check(err, ErrorMatches, `no hardware root-of-trust properly configured: unsupported BootGuard profile`) + c.Check(err, FitsTypeOf, &NoHardwareRootOfTrustError{}) +} + +func (*hostSecurityIntelBtgMSRSuite) TestCheckHostSecurityIntelBootGuardMSRErrNoBtg(c *C) { + err := CheckHostSecurityIntelBootGuardMSR(0) + c.Check(err, ErrorMatches, `no hardware root-of-trust properly configured: BootGuard ACM is not active`) + c.Check(err, FitsTypeOf, &NoHardwareRootOfTrustError{}) +} diff --git a/efi/preinstall/check_host_security_intel_csme11.go b/efi/preinstall/check_host_security_intel_csme11.go index 94ece910..806cd6bb 100644 --- a/efi/preinstall/check_host_security_intel_csme11.go +++ b/efi/preinstall/check_host_security_intel_csme11.go @@ -123,6 +123,7 @@ func isInManufacturingModeCSME11(vers meVersion, regs hfstsRegistersCsme11) bool return false } +// checkHostSecurityIntelBootGuardCSME11 checks the BootGuard configuration CSME versions 11 to 17. func checkHostSecurityIntelBootGuardCSME11(vers meVersion, regs hfstsRegistersCsme11) error { // These checks are based on the HSI checks performed in the pci-mei // plugin in fwupd. diff --git a/efi/preinstall/check_host_security_intel_csme18.go b/efi/preinstall/check_host_security_intel_csme18.go index 69f144e9..4693e4e4 100644 --- a/efi/preinstall/check_host_security_intel_csme18.go +++ b/efi/preinstall/check_host_security_intel_csme18.go @@ -88,6 +88,7 @@ func isInManufacturingModeCSME18(regs hfstsRegistersCsme18) bool { return false } +// checkHostSecurityIntelBootGuardCSME18 checks the BootGuard configuration CSME versions 18 onwards. func checkHostSecurityIntelBootGuardCSME18(regs hfstsRegistersCsme18) error { // These checks are based on the HSI checks performed in the pci-mei // plugin in fwupd. diff --git a/efi/preinstall/check_host_security_intel_test.go b/efi/preinstall/check_host_security_intel_test.go index 27cedcda..81eb007e 100644 --- a/efi/preinstall/check_host_security_intel_test.go +++ b/efi/preinstall/check_host_security_intel_test.go @@ -378,6 +378,41 @@ func (s *hostSecurityIntelSuite) TestCheckHostSecurityIntelBootGuardGoodFVMECSME c.Check(err, IsNil) } +func (s *hostSecurityIntelSuite) TestCheckHostSecurityIntelBootGuardNoMEDevice1(c *C) { + // Test the case where there are no mei devices, but we can + // fallback to using the bootguard status MSR. + env := efitest.NewMockHostEnvironmentWithOpts( + efitest.WithSysfsDevices(), + efitest.WithAMD64Environment("GenuineIntel", 0x6, nil, 4, map[uint32]uint64{0x13a: 0x000000030000007d}), + ) + c.Check(CheckHostSecurityIntelBootGuard(env), IsNil) +} + +func (s *hostSecurityIntelSuite) TestCheckHostSecurityIntelBootGuardMEDevice2(c *C) { + // Test the case where the only mei device is not the required one. + device := efitest.NewMockSysfsDevice("/sys/devices/platform/intel_vsc/mei/mei0", map[string]string{"DEVNAME": "mei0"}, "mei", nil, efitest.NewMockSysfsDevice( + "/sys/devices/platform", map[string]string{"DRIVER": "intel_vsc"}, "platform", nil, nil, + )) + env := efitest.NewMockHostEnvironmentWithOpts( + efitest.WithSysfsDevices(device), + efitest.WithAMD64Environment("GenuineIntel", 0x6, nil, 4, map[uint32]uint64{0x13a: 0x000000030000007d}), + ) + c.Check(CheckHostSecurityIntelBootGuard(env), IsNil) +} + +func (s *hostSecurityIntelSuite) TestCheckHostSecurityIntelBootGuardNoMEDevice3(c *C) { + // Test the case where there is a PCI mei device that isn't bound to the mei_me + // module. This probably isn't a realistic test case. + device := efitest.NewMockSysfsDevice("/sys/devices/pci0000:00/0000:00:16.0/mei/mei0", map[string]string{"DEVNAME": "mei0"}, "mei", nil, efitest.NewMockSysfsDevice( + "/sys/devices/pci0000:00:16:0", nil, "pci", nil, nil, + )) + env := efitest.NewMockHostEnvironmentWithOpts( + efitest.WithSysfsDevices(device), + efitest.WithAMD64Environment("GenuineIntel", 0x6, nil, 4, map[uint32]uint64{0x13a: 0x000000030000007d}), + ) + c.Check(CheckHostSecurityIntelBootGuard(env), IsNil) +} + func (s *hostSecurityIntelSuite) TestCheckHostSecurityIntelBootGuardErrNoDevices(c *C) { env := efitest.NewMockHostEnvironmentWithOpts() err := CheckHostSecurityIntelBootGuard(env) @@ -393,34 +428,23 @@ func (s *hostSecurityIntelSuite) TestCheckHostSecurityIntelBootGuardErrNoMEModul c.Check(err, Equals, MissingKernelModuleError("mei_me")) } -func (s *hostSecurityIntelSuite) TestCheckHostSecurityIntelBootGuardErrNoMEDevice1(c *C) { - // Test the case where there are no mei devices. - env := efitest.NewMockHostEnvironmentWithOpts(efitest.WithSysfsDevices()) - err := CheckHostSecurityIntelBootGuard(env) - c.Check(err, ErrorMatches, `unsupported platform: no MEI PCI device`) - c.Check(err, FitsTypeOf, &UnsupportedPlatformError{}) -} - -func (s *hostSecurityIntelSuite) TestCheckHostSecurityIntelBootGuardErrNoMEDevice2(c *C) { - // Test the case where the only mei device is not the required one. - device := efitest.NewMockSysfsDevice("/sys/devices/platform/intel_vsc/mei/mei0", map[string]string{"DEVNAME": "mei0"}, "mei", nil, efitest.NewMockSysfsDevice( - "/sys/devices/platform", map[string]string{"DRIVER": "intel_vsc"}, "platform", nil, nil, - )) - env := efitest.NewMockHostEnvironmentWithOpts(efitest.WithSysfsDevices(device)) +func (s *hostSecurityIntelSuite) TestCheckHostSecurityIntelBootGuardNoMEDeviceAndNoMSRModule(c *C) { + env := efitest.NewMockHostEnvironmentWithOpts( + efitest.WithSysfsDevices(), + efitest.WithAMD64Environment("GenuineIntel", 0x6, nil, 4, nil), + ) err := CheckHostSecurityIntelBootGuard(env) - c.Check(err, ErrorMatches, `unsupported platform: no MEI PCI device`) - c.Check(err, FitsTypeOf, &UnsupportedPlatformError{}) + c.Check(err, ErrorMatches, `the kernel module "msr" must be loaded`) + c.Check(err, Equals, MissingKernelModuleError("msr")) } -func (s *hostSecurityIntelSuite) TestCheckHostSecurityIntelBootGuardErrNoMEDevice3(c *C) { - // Test the case where there is a PCI mei device that isn't bound to the mei_me - // module. This probably isn't a realistic test case. - device := efitest.NewMockSysfsDevice("/sys/devices/pci0000:00/0000:00:16.0/mei/mei0", map[string]string{"DEVNAME": "mei0"}, "mei", nil, efitest.NewMockSysfsDevice( - "/sys/devices/pci0000:00:16:0", nil, "pci", nil, nil, - )) - env := efitest.NewMockHostEnvironmentWithOpts(efitest.WithSysfsDevices(device)) +func (s *hostSecurityIntelSuite) TestCheckHostSecurityIntelBootGuardNoMEDeviceAndNoBtgMSR(c *C) { + env := efitest.NewMockHostEnvironmentWithOpts( + efitest.WithSysfsDevices(), + efitest.WithAMD64Environment("GenuineIntel", 0x6, nil, 4, map[uint32]uint64{}), + ) err := CheckHostSecurityIntelBootGuard(env) - c.Check(err, ErrorMatches, `unsupported platform: no MEI PCI device`) + c.Check(err, ErrorMatches, `unsupported platform: no MEI PCI device or BootGuard status MSR`) c.Check(err, FitsTypeOf, &UnsupportedPlatformError{}) } diff --git a/efi/preinstall/check_tpm_intel.go b/efi/preinstall/check_tpm_intel.go index cda0a42d..1a5d9fe0 100644 --- a/efi/preinstall/check_tpm_intel.go +++ b/efi/preinstall/check_tpm_intel.go @@ -28,30 +28,8 @@ import ( internal_efi "github.com/snapcore/secboot/internal/efi" ) -const bootGuardStatusMsr = 0x13a - -type bootGuardStatus uint64 - -const ( - bootGuardStatusTPMShift = 1 - bootGuardStatusTPMMask bootGuardStatus = (3 << bootGuardStatusTPMShift) -) - -type bootGuardTPMStatus uint64 - -const ( - bootGuardTPMStatusNone bootGuardTPMStatus = 0 - bootGuardTPMStatus12 bootGuardTPMStatus = 1 - bootGuardTPMStatus2 bootGuardTPMStatus = 2 - bootGuardTPMStatusPTT bootGuardTPMStatus = 3 -) - -func (s bootGuardStatus) tpmStatus() bootGuardTPMStatus { - return bootGuardTPMStatus(s&bootGuardStatusTPMMask) >> bootGuardStatusTPMShift -} - func isTPMDiscreteFromIntelBootGuard(env internal_efi.HostEnvironmentAMD64) (bool, error) { - msrValue, err := env.ReadMSRs(bootGuardStatusMsr) + status, err := readIntelBootGuardStatus(env) switch { case errors.Is(err, internal_efi.ErrNoKernelMSRSupport): return false, MissingKernelModuleError("msr") @@ -59,9 +37,6 @@ func isTPMDiscreteFromIntelBootGuard(env internal_efi.HostEnvironmentAMD64) (boo return false, fmt.Errorf("cannot read BootGuard status MSR: %w", err) } - status := bootGuardStatus(msrValue[0]) - - // NOTE: bootGuardStatus[0] is fine because BootGuard status MSR has the same value across all CPUs switch status.tpmStatus() { case bootGuardTPMStatusNone, bootGuardTPMStatus12: // System has no TPM or unsupported TPM 1.2 device diff --git a/efi/preinstall/export_amd64_test.go b/efi/preinstall/export_amd64_test.go index 73903a4b..03de4c17 100644 --- a/efi/preinstall/export_amd64_test.go +++ b/efi/preinstall/export_amd64_test.go @@ -45,6 +45,7 @@ var ( CheckHostSecurityIntelBootGuard = checkHostSecurityIntelBootGuard CheckHostSecurityIntelBootGuardCSME11 = checkHostSecurityIntelBootGuardCSME11 CheckHostSecurityIntelBootGuardCSME18 = checkHostSecurityIntelBootGuardCSME18 + CheckHostSecurityIntelBootGuardMSR = checkHostSecurityIntelBootGuardMSR CheckHostSecurityIntelCPUDebuggingLocked = checkHostSecurityIntelCPUDebuggingLocked DetermineCPUVendor = determineCPUVendor IsTPMDiscreteFromIntelBootGuard = isTPMDiscreteFromIntelBootGuard diff --git a/efi/preinstall/intel_util.go b/efi/preinstall/intel_util.go new file mode 100644 index 00000000..880ce056 --- /dev/null +++ b/efi/preinstall/intel_util.go @@ -0,0 +1,91 @@ +//go:build amd64 + +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2024 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 ( + "errors" + + internal_efi "github.com/snapcore/secboot/internal/efi" +) + +const bootGuardStatusMsr = 0x13a + +type bootGuardStatus uint64 + +const ( + bootGuardNEM bootGuardStatus = 1 << 0 + bootGuardFACB bootGuardStatus = 1 << 4 + bootGuardMeasuredBoot bootGuardStatus = 1 << 5 + bootGuardVerifiedBoot bootGuardStatus = 1 << 6 + bootGuardCapability bootGuardStatus = 1 << 32 + + bootGuardStatusTPMShift = 1 + bootGuardStatusTPMMask bootGuardStatus = (3 << bootGuardStatusTPMShift) +) + +type bootGuardTPMStatus uint64 + +const ( + bootGuardTPMStatusNone bootGuardTPMStatus = 0 + bootGuardTPMStatus12 bootGuardTPMStatus = 1 + bootGuardTPMStatus2 bootGuardTPMStatus = 2 + bootGuardTPMStatusPTT bootGuardTPMStatus = 3 +) + +func (s bootGuardStatus) tpmStatus() bootGuardTPMStatus { + return bootGuardTPMStatus(s&bootGuardStatusTPMMask) >> bootGuardStatusTPMShift +} + +func (s bootGuardStatus) btgProfile() (btgProfile, error) { + // We can't check the error enforcement policy here, either because it's + // not mirrored into the MSR, or it is but we don't know which 2 bits to + // use. We just interpret the other bits into one of the well known No_FVME, + // VM, FVE and FVME profiles. This is ok because there are no known profiles + // where these bits are reused with different error enforcement policies. + f := s&bootGuardFACB > 0 + v := s&bootGuardVerifiedBoot > 0 + m := s&bootGuardMeasuredBoot > 0 + p := s&bootGuardNEM > 0 + + switch { + case !f && !v && !m && !p: + return btgProfileNoFVME, nil + case !f && v && m && p: + return btgProfileVM, nil + case f && v && !m && p: + return btgProfileFVE, nil + case f && v && m && p: + return btgProfileFVME, nil + default: + return 0, errors.New("invalid profile") + } +} + +func readIntelBootGuardStatus(env internal_efi.HostEnvironmentAMD64) (bootGuardStatus, error) { + msrValue, err := env.ReadMSRs(bootGuardStatusMsr) + if err != nil { + return 0, err + } + + // NOTE: msrValue[0] is fine because BootGuard status MSR has the same value across all CPUs + return bootGuardStatus(msrValue[0]), nil +} From 7bdbc7d0bf2d8430e559d9555e36c1076d36f145 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Thu, 9 Apr 2026 12:16:10 +0100 Subject: [PATCH 2/2] efi/preinstall: update copyright --- efi/preinstall/intel_util.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/efi/preinstall/intel_util.go b/efi/preinstall/intel_util.go index 880ce056..f044d1b9 100644 --- a/efi/preinstall/intel_util.go +++ b/efi/preinstall/intel_util.go @@ -3,7 +3,7 @@ // -*- Mode: Go; indent-tabs-mode: t -*- /* - * Copyright (C) 2024 Canonical Ltd + * 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