Skip to content
Open
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
5 changes: 4 additions & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ on:
branches: [ "master" ]
jobs:
tests:
runs-on: ubuntu-24.04
runs-on: ${{ matrix.runner }}
strategy:
matrix:
runner:
- ubuntu-24.04
- ubuntu-24.04-arm
goversion:
- 1.18
# The unit tests currently fail against the new stable go
Expand Down
73 changes: 73 additions & 0 deletions efi/preinstall/check_host_security.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ func checkHostSecurity(env internal_efi.HostEnvironment, log *tcglog.Log) (platf
switch runtimeGOARCH {
case "amd64":
return checkHostSecurityAMD64(env, log)
case "arm64":
return checkHostSecurityARM64(env, log)
default:
return platformFirmwareIntegrityNone, &UnsupportedPlatformError{fmt.Errorf("checking host security is not implemented on %s", runtimeGOARCH)}
}
Expand All @@ -178,6 +180,8 @@ func checkDiscreteTPMPartialResetAttackMitigationStatus(env internal_efi.HostEnv
switch runtimeGOARCH {
case "amd64":
return checkDiscreteTPMPartialResetAttackMitigationStatusAMD64(env, logResults)
case "arm64":
return checkDiscreteTPMPartialResetAttackMitigationStatusARM64(env, logResults)
default:
return dtpmPartialResetAttackMitigationNotRequired, nil
}
Expand Down Expand Up @@ -295,3 +299,72 @@ func checkDiscreteTPMPartialResetAttackMitigationStatusAMD64(env internal_efi.Ho
// PCR0 can be recreated from the OS.
return dtpmPartialResetAttackMitigationUnavailable, nil
}

// checkHostSecurityARM64Platform selects the platform-specific firmware
// integrity check. Tests replace this to supply synthetic platforms.
var checkHostSecurityARM64Platform = func(env internal_efi.HostEnvironmentARM64, cpuManufacturer string) (platformFirmwareIntegrityConfig, error) {
return platformFirmwareIntegrityNone, &UnsupportedPlatformError{fmt.Errorf("unsupported CPU manufacturer: %s", cpuManufacturer)}
}

func checkHostSecurityARM64(env internal_efi.HostEnvironment, log *tcglog.Log) (platformFirmwareIntegrityConfig, error) {
arm64Env, err := env.ARM64()
if err != nil {
return platformFirmwareIntegrityNone, &UnsupportedPlatformError{fmt.Errorf("cannot obtain ARM64 environment: %w", err)}
}

cpuManufacturer, err := arm64Env.CPUManufacturer()
if err != nil {
return platformFirmwareIntegrityNone, &UnsupportedPlatformError{fmt.Errorf("cannot determine CPU manufacturer: %w", err)}
}

integrity, err := checkHostSecurityARM64Platform(arm64Env, cpuManufacturer)
if err != nil {
return platformFirmwareIntegrityNone, err
}

return checkHostSecurityARM64Generic(env, log, integrity)
}

func checkHostSecurityARM64Generic(env internal_efi.HostEnvironment, log *tcglog.Log, integrity platformFirmwareIntegrityConfig) (platformFirmwareIntegrityConfig, error) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This does not look very Arm64 specific and duplicates code from checkHostSecurityAMD64.
Could both be merged? (does it make sense?)

Or do you intend to make them diverge in the next PR?

var errs []error

if err := checkSecureBootPolicyPCRForDegradedFirmwareSettings(log); err != nil {
var ce CompoundError
if !errors.As(err, &ce) {
return platformFirmwareIntegrityNone, fmt.Errorf("encountered an error whilst checking the TCG log for degraded firmware settings: %w", err)
}
errs = append(errs, ce.Unwrap()...)
}

if err := checkForKernelIOMMU(env); err != nil {
switch {
case errors.Is(err, ErrNoKernelIOMMU):
errs = append(errs, err)
default:
return platformFirmwareIntegrityNone, fmt.Errorf("encountered an error whilst checking sysfs to determine that kernel IOMMU support is enabled: %w", err)
}
}

if len(errs) > 0 {
return integrity, joinErrors(errs...)
}

return integrity, nil
}

// checkDiscreteTPMPartialResetAttackMitigationStatusARM64 determines whether a partial mitigation
// against discrete TPM reset attacks should be enabled.
func checkDiscreteTPMPartialResetAttackMitigationStatusARM64(env internal_efi.HostEnvironment, _ *pcrBankResults) (discreteTPMPartialResetAttackMitigationStatus, error) {
discreteTPM, err := isTPMDiscrete(env)
if err != nil {
return dtpmPartialResetAttackMitigationUnknown, &TPM2DeviceError{err}
}
if !discreteTPM {
return dtpmPartialResetAttackMitigationNotRequired, nil
}

// ARM64 has no generic mechanism to establish that the TPM startup locality
// is protected by the hardware root of trust, so PCR0 binding cannot be relied
// on to mitigate an independent reset of a discrete TPM.
return dtpmPartialResetAttackMitigationUnavailable, nil
}
148 changes: 148 additions & 0 deletions efi/preinstall/check_host_security_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,154 @@ func (s *hostSecurityAMD64Suite) TestCheckDiscreteTPMPartialResetAttackMitigatio
c.Check(errors.As(err, &upe), testutil.IsTrue)
}

type hostSecurityARM64Suite struct {
snapd_testutil.BaseTest
}

func (s *hostSecurityARM64Suite) SetUpTest(c *C) {
s.BaseTest.SetUpTest(c)
s.AddCleanup(MockRuntimeGOARCH("arm64"))
}

var _ = Suite(&hostSecurityARM64Suite{})

type hostSecurityARM64ErrorEnv struct {
cpuManufacturer string
cpuVersion string
}

func (e *hostSecurityARM64ErrorEnv) CPUManufacturer() (string, error) {
return e.cpuManufacturer, nil
}

func (e *hostSecurityARM64ErrorEnv) CPUVersion() (string, error) {
return e.cpuVersion, nil
}

func makeArm64IOMMUDevices() []internal_efi.SysfsDevice {
return []internal_efi.SysfsDevice{
efitest.NewMockSysfsDevice("/sys/devices/platform/soc@0/8000000.iommu", nil, "iommu", nil, nil),
}
}

func makeArm64PCRResults(c *C) *PCRBankResults {
return NewPCRBankResults(tpm2.HashAlgorithmSHA256, 0, [8]PcrResults{
MakePCRResults(
false,
make(tpm2.Digest, 32),
testutil.DecodeHexString(c, "a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5"),
testutil.DecodeHexString(c, "a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5"),
nil,
),
})
}

func (s *hostSecurityARM64Suite) TestCheckHostSecurityErrNotARM64(c *C) {
env := efitest.NewMockHostEnvironmentWithOpts()

_, err := CheckHostSecurity(env, nil)
c.Check(err, ErrorMatches, `unsupported platform: cannot obtain ARM64 environment: not a ARM64 host`)
var upe *UnsupportedPlatformError
c.Check(errors.As(err, &upe), testutil.IsTrue)
}

func (s *hostSecurityARM64Suite) TestCheckHostSecurityErrUnknownCPUManufacturer(c *C) {
env := efitest.NewMockHostEnvironmentWithOpts(efitest.WithARM64Environment("ACME", exampleARM64CPUVersion))

_, err := CheckHostSecurity(env, nil)
c.Check(err, ErrorMatches, `unsupported platform: unsupported CPU manufacturer: ACME`)
var upe *UnsupportedPlatformError
c.Check(errors.As(err, &upe), testutil.IsTrue)
}

func (s *hostSecurityARM64Suite) TestCheckHostSecurityUEFIDebuggerFinding(c *C) {
env := efitest.NewMockHostEnvironmentWithOpts(
efitest.WithARM64Environment(exampleARM64CPUManufacturer, exampleARM64CPUVersion),
efitest.WithSysfsDevices(makeArm64IOMMUDevices()...),
)
log := efitest.NewLog(c, &efitest.LogOptions{FirmwareDebugger: true})

integrity, err := CheckHostSecurity(env, log)
c.Check(integrity, Equals, PlatformFirmwareIntegrityMeasured)
c.Check(err, ErrorMatches, `the platform firmware contains a debugging endpoint enabled`)
var tmpl CompoundError
c.Assert(err, Implements, &tmpl)
c.Check(err.(CompoundError).Unwrap(), DeepEquals, []error{ErrUEFIDebuggingEnabled})
}

func (s *hostSecurityARM64Suite) TestCheckHostSecurityInsufficientDMAProtection(c *C) {
env := efitest.NewMockHostEnvironmentWithOpts(
efitest.WithARM64Environment(exampleARM64CPUManufacturer, exampleARM64CPUVersion),
efitest.WithSysfsDevices(makeArm64IOMMUDevices()...),
)
log := efitest.NewLog(c, &efitest.LogOptions{DMAProtection: efitest.DMAProtectionDisabled})

integrity, err := CheckHostSecurity(env, log)
c.Check(integrity, Equals, PlatformFirmwareIntegrityMeasured)
c.Check(err, ErrorMatches, `the platform firmware indicates that DMA protections are insufficient`)
var tmpl CompoundError
c.Assert(err, Implements, &tmpl)
c.Check(err.(CompoundError).Unwrap(), DeepEquals, []error{ErrInsufficientDMAProtection})
}

func (s *hostSecurityARM64Suite) TestCheckHostSecurityNoIOMMU(c *C) {
env := efitest.NewMockHostEnvironmentWithOpts(
efitest.WithARM64Environment(exampleARM64CPUManufacturer, exampleARM64CPUVersion),
efitest.WithSysfsDevices(),
)
log := efitest.NewLog(c, &efitest.LogOptions{})

integrity, err := CheckHostSecurity(env, log)
c.Check(integrity, Equals, PlatformFirmwareIntegrityMeasured)
c.Check(err, ErrorMatches, `no kernel IOMMU support was detected`)
var tmpl CompoundError
c.Assert(err, Implements, &tmpl)
c.Check(err.(CompoundError).Unwrap(), DeepEquals, []error{ErrNoKernelIOMMU})
}

func (s *hostSecurityARM64Suite) TestCheckHostSecurityMultipleRecoverableErrors(c *C) {
env := efitest.NewMockHostEnvironmentWithOpts(
efitest.WithARM64Environment(exampleARM64CPUManufacturer, exampleARM64CPUVersion),
efitest.WithSysfsDevices(),
)
log := efitest.NewLog(c, &efitest.LogOptions{FirmwareDebugger: true})

integrity, err := CheckHostSecurity(env, log)
c.Check(integrity, Equals, PlatformFirmwareIntegrityMeasured)
c.Check(err, ErrorMatches, `2 errors detected:
- the platform firmware contains a debugging endpoint enabled
- no kernel IOMMU support was detected
`)
var tmpl CompoundError
c.Assert(err, Implements, &tmpl)
c.Check(err.(CompoundError).Unwrap(), DeepEquals, []error{ErrUEFIDebuggingEnabled, ErrNoKernelIOMMU})
}

func (s *hostSecurityARM64Suite) TestCheckDiscreteTPMPartialResetAttackMitigationStatusUnknownForUnsupportedCPUManufacturer(c *C) {
env := efitest.NewMockHostEnvironmentWithOpts(
efitest.WithARM64Environment(exampleARM64CPUManufacturer, exampleARM64CPUVersion),
efitest.WithSysfsDevices(makeArm64TPMDevice("tpm_crb")),
)

status, err := CheckDiscreteTPMPartialResetAttackMitigationStatus(env, makeArm64PCRResults(c))
c.Check(status, Equals, DtpmPartialResetAttackMitigationUnknown)
c.Check(err, ErrorMatches, `error with TPM2 device: unsupported platform: unsupported CPU manufacturer: `+exampleARM64CPUManufacturer)
var tpmErr *TPM2DeviceError
c.Check(errors.As(err, &tpmErr), testutil.IsTrue)
c.Check(status, Equals, DtpmPartialResetAttackMitigationUnknown)
}

func (s *hostSecurityARM64Suite) TestCheckDiscreteTPMPartialResetAttackMitigationStatusNotRequiredForOPTEE(c *C) {
env := efitest.NewMockHostEnvironmentWithOpts(
efitest.WithARM64Environment(exampleARM64CPUManufacturer, exampleARM64CPUVersion),
efitest.WithSysfsDevices(makeArm64TPMDevice("optee-ftpm")),
)

status, err := CheckDiscreteTPMPartialResetAttackMitigationStatus(env, makeArm64PCRResults(c))
c.Check(err, IsNil)
c.Check(status, Equals, DtpmPartialResetAttackMitigationNotRequired)
}

func (s *hostSecuritySuite) TestCheckHostSecurityUnsupportedArchitecture(c *C) {
restore := MockRuntimeGOARCH("ppc64le")
defer restore()
Expand Down
60 changes: 60 additions & 0 deletions efi/preinstall/check_tpm.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"fmt"

"github.com/canonical/go-tpm2"
"github.com/pilebones/go-udev/netlink"
internal_efi "github.com/snapcore/secboot/internal/efi"
)

Expand Down Expand Up @@ -478,6 +479,8 @@ func isTPMDiscrete(env internal_efi.HostEnvironment) (bool, error) {
switch runtimeGOARCH {
case "amd64":
return isTPMDiscreteAMD64(env)
case "arm64":
return isTPMDiscreteARM64(env)
default:
return false, &UnsupportedPlatformError{fmt.Errorf("checking for TPM discreteness is not implemented on %s", runtimeGOARCH)}
}
Expand Down Expand Up @@ -507,3 +510,60 @@ func isTPMDiscreteAMD64(env internal_efi.HostEnvironment) (bool, error) {
panic("not reached")
}
}

// isTPMDiscreteARM64 determines whether the default TPM is discrete. OP-TEE firmware
// TPMs are identified by their backing kernel driver. Other implementations use
// platform-specific knowledge.
func isTPMDiscreteARM64(env internal_efi.HostEnvironment) (bool, error) {
isOpteefTPM, err := isTPMFirmwareOptee(env)
if err != nil {
return false, err
}
if isOpteefTPM {
return false, nil
}

arm64Env, err := env.ARM64()
if err != nil {
return false, err
}

cpuManufacturer, err := arm64Env.CPUManufacturer()
if err != nil {
return false, &UnsupportedPlatformError{fmt.Errorf("cannot determine CPU manufacturer: %w", err)}
}

return false, &UnsupportedPlatformError{fmt.Errorf("unsupported CPU manufacturer: %s", cpuManufacturer)}
}

// isTPMFirmwareOptee determines whether the default TPM is an OP-TEE firmware TPM,
// as identified by its backing kernel driver
func isTPMFirmwareOptee(env internal_efi.HostEnvironment) (bool, error) {
devices, err := env.EnumerateDevices(&netlink.RuleDefinition{
Env: map[string]string{
"SUBSYSTEM": "tpm",
"DEVNAME": "tpm0",
},
})
if err != nil {
return false, fmt.Errorf("cannot enumerate TPM devices: %w", err)
}
if len(devices) != 1 {
return false, fmt.Errorf("internal error: expected one tpm0 device, found %d", len(devices))
}

parent, err := devices[0].Parent()
if err != nil {
return false, fmt.Errorf("cannot obtain parent of tpm0 device: %w", err)
}
if parent == nil {
return false, fmt.Errorf("internal error: tpm0 device has no parent")
}

switch parent.Properties()["DRIVER"] {
case "optee-ftpm", "ftpm-tee":
return true, nil
default:
return false, nil
}
}
Loading
Loading