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..f044d1b9 --- /dev/null +++ b/efi/preinstall/intel_util.go @@ -0,0 +1,91 @@ +//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" + + 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 +}