Skip to content

Commit

Permalink
preinstall: Add checks for platform firmware protections
Browse files Browse the repository at this point in the history
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
chrisccoulson committed Jul 10, 2024
1 parent a82643d commit 8b04c23
Show file tree
Hide file tree
Showing 10 changed files with 1,772 additions and 5 deletions.
116 changes: 116 additions & 0 deletions efi/preinstall/check_fw_protections.go
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")
}
144 changes: 144 additions & 0 deletions efi/preinstall/check_fw_protections_amd64.go
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
}
Loading

0 comments on commit 8b04c23

Please sign in to comment.