Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions efi/preinstall/check_host_security_intel.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
53 changes: 53 additions & 0 deletions efi/preinstall/check_host_security_intel_btgmsr.go
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*
*/

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")}
}
}
64 changes: 64 additions & 0 deletions efi/preinstall/check_host_security_intel_btgmsr_test.go
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*
*/

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{})
}
1 change: 1 addition & 0 deletions efi/preinstall/check_host_security_intel_csme11.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions efi/preinstall/check_host_security_intel_csme18.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
72 changes: 48 additions & 24 deletions efi/preinstall/check_host_security_intel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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{})
}

Expand Down
27 changes: 1 addition & 26 deletions efi/preinstall/check_tpm_intel.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,40 +28,15 @@ 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")
case err != nil:
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
Expand Down
1 change: 1 addition & 0 deletions efi/preinstall/export_amd64_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ var (
CheckHostSecurityIntelBootGuard = checkHostSecurityIntelBootGuard
CheckHostSecurityIntelBootGuardCSME11 = checkHostSecurityIntelBootGuardCSME11
CheckHostSecurityIntelBootGuardCSME18 = checkHostSecurityIntelBootGuardCSME18
CheckHostSecurityIntelBootGuardMSR = checkHostSecurityIntelBootGuardMSR
CheckHostSecurityIntelCPUDebuggingLocked = checkHostSecurityIntelCPUDebuggingLocked
DetermineCPUVendor = determineCPUVendor
IsTPMDiscreteFromIntelBootGuard = isTPMDiscreteFromIntelBootGuard
Expand Down
91 changes: 91 additions & 0 deletions efi/preinstall/intel_util.go
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*
*/

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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose this measured boot has nothing to do with TPM, or does it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It implies that the ACM is the root of trust for measurement rather than the EFI firmware measuring itself.

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
}
Loading