-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
preinstall: Add checks for platform firmware protections
This adds checks for how the platform firmware is protected, with specific tests for whether CPU silicon debugging features are enabled or not locked off, whether a firmware debugging endpoint (such as UDK) is enabled, and whether a IOMMU is available. Some of these checks are based on the HSI checks in fwupd with some minor adaptions, particularly for the BootGuard checks where there are some tests in fwupd that disagree with other publicly available documentation which better aligns with hardware that is assumed to be good (note that Intel do not publicly document any of this). This has a significant limitation today - it only works on Intel based systems. AMD support will happen when we figure out how the platform firmware is protected on AMD platforms with the dedicated PSP. The main API (checkPlatformFirmwareProtections) doesn't return an error in all cases (eg, such as lack of IOMMU, and the presence of debugging capabilities). Whilst the main RunChecks API will return an error by default, it will support some flags for limited customization in order to bypass these checks, although the documentation makes it clear that this is obviously not recommended for any platform with debugging features enabled. These may be useful when bringing up a new platform.
- Loading branch information
1 parent
a82643d
commit 8b04c23
Showing
10 changed files
with
1,772 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
// -*- 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 <http://www.gnu.org/licenses/>. | ||
* | ||
*/ | ||
|
||
package preinstall | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/canonical/tcglog-parser" | ||
internal_efi "github.com/snapcore/secboot/internal/efi" | ||
) | ||
|
||
// NoHardwareRootOfTrustError is returned wrapped from [RunChecks] if the platform | ||
// firmware is not protected by a hardware root-of-trust. This won't be returned if | ||
// the PermitVirtualMachine flag is supplied to RunChecks and the current | ||
// environment is a virtual machine. | ||
type NoHardwareRootOfTrustError struct { | ||
err error | ||
} | ||
|
||
func (e *NoHardwareRootOfTrustError) Error() string { | ||
return "no hardware root-of-trust properly configured: " + e.err.Error() | ||
} | ||
|
||
func (e *NoHardwareRootOfTrustError) Unwrap() error { | ||
return e.err | ||
} | ||
|
||
type firmwareProtectionResultFlags int | ||
|
||
const ( | ||
firmwareProtectionCPUDebugEnabled firmwareProtectionResultFlags = 1 << iota | ||
firmwareProtectionCPUDebugAvailable | ||
firmwareProtectionDebuggingEnabled | ||
firmwareProtectionNoIOMMU | ||
) | ||
|
||
// checkForIOMMU checks for the presence of an IOMMU. The check is quite simple, | ||
// and based on the fwupd HSI checks. | ||
// XXX: Figure out whether this is genuinely sufficient, eg: | ||
// - are addon devices with embedded controllers behind a IOMMU? | ||
// - are external ports protected by a IOMMU? | ||
func checkForIOMMU(env internal_efi.HostEnvironment) (present bool, err error) { | ||
devices, err := env.DevicesForClass("iommu") | ||
switch { | ||
case err != nil: | ||
return false, err | ||
case len(devices) == 0: | ||
return false, nil | ||
} | ||
|
||
for _, device := range devices { | ||
if device.Subsystem() == "iommu" { | ||
// The kernel says we have an IOMMU. I'm not sure if there | ||
// are any other checks we should do here, eg, making sure | ||
// externally accessible ports are protected or that it protects | ||
// internal devices with embedded controllers, but fwupd's HSI | ||
// checks don't do anything else here either. | ||
return true, nil | ||
} | ||
} | ||
return false, nil | ||
} | ||
|
||
// checkFirmwareDebuggingEnabed determines whether the TCG log indidcates whether the firmware | ||
// has a debugging endpoint enabled. | ||
func checkFirmwareDebuggingEnabled(log *tcglog.Log) (enabled bool, err error) { | ||
events := log.Events | ||
for len(events) > 0 { | ||
// Pop next event | ||
event := events[0] | ||
events = events[1:] | ||
|
||
if event.PCRIndex != tcglog.PCRIndex(internal_efi.SecureBootPolicyPCR) { | ||
continue | ||
} | ||
|
||
// This loop always returns after this point | ||
|
||
switch event.EventType { | ||
case tcglog.EventTypeEFIAction: | ||
if event.Data == tcglog.FirmwareDebuggerEvent { | ||
// Debugger enabled | ||
return true, nil | ||
} | ||
// Unexpected data | ||
return false, fmt.Errorf("unexpected event data in first PCR7 event: %v", event.Data) | ||
case tcglog.EventTypeEFIVariableDriverConfig: | ||
// No debugger enabled | ||
return false, nil | ||
default: | ||
// Unexpected event type | ||
return false, fmt.Errorf("unexpected event type (%v) in PCR7", event.EventType) | ||
} | ||
} | ||
|
||
// This could only happen if there are no events in PCR7, but checkFirmwareLogAndChoosePCRBank | ||
// verifies that there is a separator in all TCG defined PCRs. | ||
panic("not reached") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
// -*- 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 <http://www.gnu.org/licenses/>. | ||
* | ||
*/ | ||
|
||
package preinstall | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
|
||
"github.com/canonical/tcglog-parser" | ||
"github.com/intel-go/cpuid" | ||
internal_efi "github.com/snapcore/secboot/internal/efi" | ||
) | ||
|
||
const ( | ||
ia32DebugInterfaceMSR = 0xc80 | ||
|
||
ia32DebugEnable uint64 = 1 << 0 | ||
ia32DebugLock uint64 = 1 << 30 | ||
) | ||
|
||
type cpuDebuggingResultFlags int | ||
|
||
const ( | ||
cpuDebuggingEnabled cpuDebuggingResultFlags = 1 << iota | ||
cpuDebuggingAvailable | ||
) | ||
|
||
func checkCPUDebuggingConfigMSR(env internal_efi.HostEnvironmentAMD64) (result cpuDebuggingResultFlags, err error) { | ||
// Check for "Silicon Debug Interface", returned in bit 11 of %ecx when calling | ||
// cpuid with %eax=1. | ||
debugSupported := env.HasCPUIDFeature(cpuid.SDBG) | ||
if !debugSupported { | ||
return 0, nil | ||
} | ||
|
||
vals, err := env.ReadMSRs(ia32DebugInterfaceMSR) | ||
if err != nil { | ||
return 0, err | ||
} | ||
if len(vals) == 0 { | ||
return 0, errors.New("no MSR values returned") | ||
} | ||
|
||
for _, val := range vals { | ||
if val&ia32DebugEnable > 0 { | ||
result |= cpuDebuggingEnabled | cpuDebuggingAvailable | ||
} | ||
if val&ia32DebugLock == 0 { | ||
result |= cpuDebuggingAvailable | ||
} | ||
} | ||
|
||
return result, nil | ||
} | ||
|
||
type cpuVendor int | ||
|
||
const ( | ||
cpuVendorUnknown cpuVendor = iota | ||
cpuVendorIntel | ||
cpuVendorAMD | ||
) | ||
|
||
func determineCPUVendor(env internal_efi.HostEnvironmentAMD64) (cpuVendor, error) { | ||
switch env.CPUVendorIdentificator() { | ||
case "GenuineIntel": | ||
return cpuVendorIntel, nil | ||
case "AuthenticAMD": | ||
return cpuVendorAMD, nil | ||
default: | ||
return cpuVendorUnknown, fmt.Errorf("unknown CPU vendor: %s", env.CPUVendorIdentificator()) | ||
} | ||
} | ||
|
||
func checkPlatformFirmwareProtections(env internal_efi.HostEnvironment, log *tcglog.Log) (result firmwareProtectionResultFlags, err error) { | ||
amd64Env, err := env.AMD64() | ||
if err != nil { | ||
return 0, fmt.Errorf("cannot obtain AMD64 environment: %w", err) | ||
} | ||
|
||
cpuVendor, err := determineCPUVendor(amd64Env) | ||
if err != nil { | ||
return 0, fmt.Errorf("cannot determine CPU vendor: %w", err) | ||
} | ||
|
||
switch cpuVendor { | ||
case cpuVendorIntel: | ||
if err := checkPlatformFirmwareProtectionsIntelMEI(env); err != nil { | ||
return 0, fmt.Errorf("encountered an error when determining platform firmware protections using Intel MEI: %w", err) | ||
} | ||
case cpuVendorAMD: | ||
return 0, errors.New("TODO: checking platform firmware protections is not yet implemented for AMD") | ||
default: | ||
panic("not reached") | ||
} | ||
|
||
present, err := checkForIOMMU(env) | ||
if err != nil { | ||
return 0, fmt.Errorf("encountered an error whilst checking sysfs for IOMMU support: %w", err) | ||
} | ||
if !present { | ||
result |= firmwareProtectionNoIOMMU | ||
} | ||
|
||
cpuDebugging, err := checkCPUDebuggingConfigMSR(amd64Env) | ||
if err != nil { | ||
return 0, fmt.Errorf("encountered an error when determining CPU debugging configuration from MSRs: %w", err) | ||
} | ||
if cpuDebugging&cpuDebuggingEnabled > 0 { | ||
result |= firmwareProtectionCPUDebugEnabled | ||
} | ||
if cpuDebugging&cpuDebuggingAvailable > 0 { | ||
result |= firmwareProtectionCPUDebugAvailable | ||
} | ||
|
||
enabled, err := checkFirmwareDebuggingEnabled(log) | ||
if err != nil { | ||
return 0, fmt.Errorf("encountered an error when determining if firmware debugging is enabled: %w", err) | ||
} | ||
if enabled { | ||
result |= firmwareProtectionDebuggingEnabled | ||
} | ||
|
||
// TODO: Maybe check for pre-boot DMA protection | ||
|
||
return result, nil | ||
} |
Oops, something went wrong.