From 1bc68cff688568d0d1461cdf9911756b1a207f21 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Wed, 26 Jun 2024 00:31:11 +0100 Subject: [PATCH 1/4] internal/efi: Add HostEnvironment bits for efi/preinstall This extends the HostEnvironment interface to accommodate efi/preinstall, to make it a superset of the one that's already exposed by the efi package. The intention is that this superset won't be exposed for now, other than for unit testing - the efi package still only exposes the existing subset. The defaultEnvImpl in has been updated to provide a default implementation of it, and the internal/efitest package has been updated to be able to mock the new parts of it. --- efi/efi_test.go | 2 +- efi/env.go | 16 +- efi/fw_load_handler_test.go | 6 +- efi/pcr_profile.go | 3 +- efi/shim_test.go | 4 +- go.mod | 3 +- go.sum | 6 + internal/efi/default_env.go | 160 +++++++++++++- internal/efi/default_env_amd64.go | 111 ++++++++++ internal/efi/default_env_amd64_test.go | 119 +++++++++++ internal/efi/default_env_not_amd64.go | 27 +++ internal/efi/default_env_not_amd64_test.go | 36 ++++ internal/efi/default_env_test.go | 189 ++++++++++++++++- internal/efi/env.go | 100 ++++++++- internal/efi/export_amd64_test.go | 38 ++++ internal/efi/export_test.go | 34 +++ internal/efi/options.go | 2 +- internal/efi/testdata/sys.tar | Bin 0 -> 20480 bytes internal/efitest/hostenv.go | 234 ++++++++++++++++++++- tpm2/tpm_test.go | 33 ++- 20 files changed, 1075 insertions(+), 48 deletions(-) create mode 100644 internal/efi/default_env_amd64.go create mode 100644 internal/efi/default_env_amd64_test.go create mode 100644 internal/efi/default_env_not_amd64.go create mode 100644 internal/efi/default_env_not_amd64_test.go create mode 100644 internal/efi/export_amd64_test.go create mode 100644 internal/efi/testdata/sys.tar diff --git a/efi/efi_test.go b/efi/efi_test.go index fd444057..4b6d9487 100644 --- a/efi/efi_test.go +++ b/efi/efi_test.go @@ -764,7 +764,7 @@ func (v *mockPcrProfileOptionVisitor) AddPCRs(pcrs ...tpm2.Handle) { v.pcrs = append(v.pcrs, pcrs...) } -func (v *mockPcrProfileOptionVisitor) SetEnvironment(env internal_efi.HostEnvironment) { +func (v *mockPcrProfileOptionVisitor) SetEnvironment(env HostEnvironment) { v.env = env } diff --git a/efi/env.go b/efi/env.go index 3afbf00a..c690f599 100644 --- a/efi/env.go +++ b/efi/env.go @@ -29,7 +29,6 @@ import ( "sort" efi "github.com/canonical/go-efilib" - "github.com/canonical/tcglog-parser" internal_efi "github.com/snapcore/secboot/internal/efi" "golang.org/x/xerrors" ) @@ -37,20 +36,7 @@ import ( // HostEnvironment is an interface that abstracts out an EFI environment, so that // consumers of the API can provide a custom mechanism to read EFI variables or parse // the TCG event log. -type HostEnvironment interface { - // VarContext returns a copy of parent containing a VarsBackend, keyed by efi.VarsBackendKey, - // for interacting with EFI variables via go-efilib. This context can be passed to any - // go-efilib function that interacts with EFI variables. Right now, go-efilib doesn't - // support any other uses of the context such as cancelation or deadlines. The efivarfs - // backend will support this eventually for variable writes because it currently implements - // a retry loop that has a potential to race with other processes. Cancelation and / or - // deadlines for sections of code that performs multiple reads using efivarfs may be useful - // in the future because the kernel rate-limits reads per-user. - VarContext(parent context.Context) context.Context - - // ReadEventLog reads the TCG event log - ReadEventLog() (*tcglog.Log, error) -} +type HostEnvironment = internal_efi.HostEnvironmentEFI type hostEnvironmentOption struct { HostEnvironment diff --git a/efi/fw_load_handler_test.go b/efi/fw_load_handler_test.go index 96864f07..f5d1fd4c 100644 --- a/efi/fw_load_handler_test.go +++ b/efi/fw_load_handler_test.go @@ -539,7 +539,7 @@ func (s *fwLoadHandlerSuite) TestMeasureImageStartErrBadLogPCR4_2(c *C) { func (s *fwLoadHandlerSuite) testMeasureImageStartErrBadLogSeparatorError(c *C, pcr tpm2.Handle) error { // Insert an invalid error separator event into the log for the specified pcr - collector := NewVariableSetCollector(efitest.NewMockHostEnvironment(nil, nil)) + collector := NewVariableSetCollector(efitest.NewMockHostEnvironment(efitest.MockVars{}, nil)) ctx := newMockPcrBranchContext(&mockPcrProfileContext{ alg: tpm2.HashAlgorithmSHA256, pcrs: MakePcrFlags(pcr)}, nil, collector.Next()) @@ -580,7 +580,7 @@ func (s *fwLoadHandlerSuite) TestMeasureImageStartErrBadLogSeparatorErrorPCR7(c func (s *fwLoadHandlerSuite) testMeasureImageStartErrBadLogInvalidSeparator(c *C, pcr tpm2.Handle) error { // Insert an invalid separator event into the log for the specified PCR - collector := NewVariableSetCollector(efitest.NewMockHostEnvironment(nil, nil)) + collector := NewVariableSetCollector(efitest.NewMockHostEnvironment(efitest.MockVars{}, nil)) ctx := newMockPcrBranchContext(&mockPcrProfileContext{ alg: tpm2.HashAlgorithmSHA256, pcrs: MakePcrFlags(pcr)}, nil, collector.Next()) @@ -621,7 +621,7 @@ func (s *fwLoadHandlerSuite) TestMeasureImageStartErrBadLogInvalidSeparatorPCR7( func (s *fwLoadHandlerSuite) testMeasureImageStartErrBadLogMissingSeparator(c *C, pcr tpm2.Handle) error { // Remove the separator from the specified PCR - collector := NewVariableSetCollector(efitest.NewMockHostEnvironment(nil, nil)) + collector := NewVariableSetCollector(efitest.NewMockHostEnvironment(efitest.MockVars{}, nil)) ctx := newMockPcrBranchContext(&mockPcrProfileContext{ alg: tpm2.HashAlgorithmSHA256, pcrs: MakePcrFlags(pcr)}, nil, collector.Next()) diff --git a/efi/pcr_profile.go b/efi/pcr_profile.go index ce988349..b20ace63 100644 --- a/efi/pcr_profile.go +++ b/efi/pcr_profile.go @@ -327,10 +327,11 @@ func (g *pcrProfileGenerator) AddPCRs(pcrs ...tpm2.Handle) { } // SetEnvironment implements [internal_efi.PCRProfileOptionVisitor.SetEnvironment] -func (g *pcrProfileGenerator) SetEnvironment(env internal_efi.HostEnvironment) { +func (g *pcrProfileGenerator) SetEnvironment(env HostEnvironment) { g.env = env } +// AddInitialVariablesModifier implements [internal_efi.PCRProfileOptionVisitor.AddInitialVariablesModifier] func (g *pcrProfileGenerator) AddInitialVariablesModifier(fn internal_efi.InitialVariablesModifier) { g.varModifiers = append(g.varModifiers, fn) } diff --git a/efi/shim_test.go b/efi/shim_test.go index f232850c..406f4b80 100644 --- a/efi/shim_test.go +++ b/efi/shim_test.go @@ -58,7 +58,7 @@ func (s *shimSuite) TestReadShimSbatPolicyLatest(c *C) { } func (s *shimSuite) TestReadShimSbatPolicyNotExist(c *C) { - env := efitest.NewMockHostEnvironment(nil, nil) + env := efitest.NewMockHostEnvironment(efitest.MockVars{}, nil) policy, err := ReadShimSbatPolicy(newMockVarReader(env)) c.Check(err, IsNil) c.Check(policy, Equals, ShimSbatPolicyPrevious) @@ -400,7 +400,7 @@ func (s *shimSuite) TestShimSbatPolicyLatestUnset(c *C) { c.Assert(visitor.varModifiers, HasLen, 1) - collector := NewVariableSetCollector(efitest.NewMockHostEnvironment(nil, nil)) + collector := NewVariableSetCollector(efitest.NewMockHostEnvironment(efitest.MockVars{}, nil)) c.Check(visitor.varModifiers[0](collector.PeekAll()[0]), IsNil) c.Assert(collector.More(), testutil.IsTrue) diff --git a/go.mod b/go.mod index 86d54cc9..2237bd6b 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/canonical/go-efilib v1.2.0 github.com/canonical/go-sp800.108-kdf v0.0.0-20210315104021-ead800bbf9a0 github.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3 - github.com/canonical/go-tpm2 v1.3.0 + github.com/canonical/go-tpm2 v1.6.2 github.com/canonical/tcglog-parser v0.0.0-20240502135731-7e805de2ca0d github.com/snapcore/snapd v0.0.0-20220714152900-4a1f4c93fc85 golang.org/x/crypto v0.9.0 @@ -19,6 +19,7 @@ require ( require ( github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect + github.com/intel-go/cpuid v0.0.0-20220614022739-219e067757cb // indirect github.com/kr/pretty v0.2.2-0.20200810074440-814ac30b4b18 // indirect github.com/kr/text v0.1.0 // indirect github.com/snapcore/go-gettext v0.0.0-20191107141714-82bbea49e785 // indirect diff --git a/go.sum b/go.sum index e9a0914d..954540ff 100644 --- a/go.sum +++ b/go.sum @@ -29,6 +29,10 @@ github.com/canonical/go-tpm2 v1.1.0 h1:i3YeiYTWciamtbUpKZC9FIGhCP9rWSu1AZpYnWUO9 github.com/canonical/go-tpm2 v1.1.0/go.mod h1:kLkR1//7ocrPDl6LZfijTKEoPGxRIZSbb8GuWaO1JM8= github.com/canonical/go-tpm2 v1.3.0 h1:+xc2++IM4kaMCJruFzlgtYgQyV5Q0EReaP++z8VTqJk= github.com/canonical/go-tpm2 v1.3.0/go.mod h1:kLkR1//7ocrPDl6LZfijTKEoPGxRIZSbb8GuWaO1JM8= +github.com/canonical/go-tpm2 v1.4.0 h1:qdOqD2tpww/8TeKdj86GatKcGrIjRl0wSwcWdqtuE6I= +github.com/canonical/go-tpm2 v1.4.0/go.mod h1:Dz0PQRmoYrmk/4BLILjRA+SFzuqEo1etAvYeAJiMhYU= +github.com/canonical/go-tpm2 v1.6.2 h1:hLGwI7rSSwExQZkyPF7jtAfEfH0tykxCf+O2SjXdxac= +github.com/canonical/go-tpm2 v1.6.2/go.mod h1:Dz0PQRmoYrmk/4BLILjRA+SFzuqEo1etAvYeAJiMhYU= github.com/canonical/tcglog-parser v0.0.0-20210824131805-69fa1e9f0ad2/go.mod h1:QoW2apR2tBl6T/4czdND/EHjL1Ia9cCmQnIj9Xe0Kt8= github.com/canonical/tcglog-parser v0.0.0-20230929123437-16b3d8d08691 h1:EMZbYZXGGmtSaS2+DIza1gZ54+KVjzsw/NEUAY8me1E= github.com/canonical/tcglog-parser v0.0.0-20230929123437-16b3d8d08691/go.mod h1:EPlw+kpcTgSHXkLiUP/Jqp4CmkNPyVnJLAk4oSjNFrQ= @@ -45,6 +49,8 @@ github.com/google/go-cmp v0.2.1-0.20190312032427-6f77996f0c42/go.mod h1:8QqcDgzr github.com/gorilla/mux v1.7.4-0.20190701202633-d83b6ffe499a/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gvalkov/golang-evdev v0.0.0-20191114124502-287e62b94bcb/go.mod h1:SAzVFKCRezozJTGavF3GX8MBUruETCqzivVLYiywouA= +github.com/intel-go/cpuid v0.0.0-20220614022739-219e067757cb h1:Fg0Y/RDZ6UPwl3o7/IzPbneDq8g9+gH6DPs42KFUsy8= +github.com/intel-go/cpuid v0.0.0-20220614022739-219e067757cb/go.mod h1:RmeVYf9XrPRbRc3XIx0gLYA8qOFvNoPOfaEZduRlEp4= github.com/jessevdk/go-flags v1.4.1-0.20180927143258-7309ec74f752/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/juju/ratelimit v1.0.1/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= diff --git a/internal/efi/default_env.go b/internal/efi/default_env.go index ca4e1b04..acddf272 100644 --- a/internal/efi/default_env.go +++ b/internal/efi/default_env.go @@ -20,26 +20,38 @@ package efi import ( + "bytes" "context" + "errors" + "fmt" + "io" "os" + "os/exec" + "path/filepath" efi "github.com/canonical/go-efilib" + "github.com/canonical/go-tpm2" + "github.com/canonical/go-tpm2/linux" "github.com/canonical/tcglog-parser" ) var ( + linuxDefaultTPM2Device = linux.DefaultTPM2Device + linuxRawDeviceResourceManagedDevice = (*linux.RawDevice).ResourceManagedDevice + eventLogPath = "/sys/kernel/security/tpm0/binary_bios_measurements" // Path of the TCG event log for the default TPM, in binary form + sysfsPath = "/sys" ) type defaultEnvImpl struct{} -// VarContext implements [HostEnvironment.VarContext]. -func (e defaultEnvImpl) VarContext(parent context.Context) context.Context { +// VarContext implements [HostEnvironmentEFI.VarContext]. +func (defaultEnvImpl) VarContext(parent context.Context) context.Context { return efi.WithDefaultVarsBackend(parent) } -// ReadEventLog implements [HostEnvironment.ReadEventLog]. -func (e defaultEnvImpl) ReadEventLog() (*tcglog.Log, error) { +// ReadEventLog implements [HostEnvironmentEFI.ReadEventLog]. +func (defaultEnvImpl) ReadEventLog() (*tcglog.Log, error) { f, err := os.Open(eventLogPath) if err != nil { return nil, err @@ -49,6 +61,146 @@ func (e defaultEnvImpl) ReadEventLog() (*tcglog.Log, error) { return tcglog.ReadLog(f, &tcglog.LogOptions{}) } +// TPMDevice implements [HostEnvironment.TPMDevice]. +func (defaultEnvImpl) TPMDevice() (tpm2.TPMDevice, error) { + device, err := linuxDefaultTPM2Device() + switch { + case errors.Is(err, linux.ErrNoTPMDevices) || errors.Is(err, linux.ErrDefaultNotTPM2Device): + return nil, ErrNoTPM2Device + case err != nil: + return nil, err + } + + rmDevice, err := linuxRawDeviceResourceManagedDevice(device) + switch { + case errors.Is(err, linux.ErrNoResourceManagedDevice): + // Return the raw device. This can only be open once, so can block and may block other users. + return device, nil + case err != nil: + return nil, err + default: + // Return the resource managed device. There is no limit as to how may of these can be opened, + // although note that they can't be opened if the raw device is opened so this can still block + // if something else has the raw device open and might block other raw device users. + return rmDevice, nil + } +} + +// DetectVirtMode implements [HostEnvironment.DetectVirtMode]. +func (defaultEnvImpl) DetectVirtMode(mode DetectVirtMode) (string, error) { + var extraArgs []string + switch mode { + case DetectVirtModeAll: + // no extra args + case DetectVirtModeContainer: + extraArgs = []string{"--container"} + case DetectVirtModeVM: + extraArgs = []string{"--vm"} + default: + panic("not reached") + } + + output, err := exec.Command("systemd-detect-virt", extraArgs...).Output() + virt := string(bytes.TrimSpace(output)) // The stdout is newline terminated + if err != nil { + if _, ok := err.(*exec.ExitError); ok && virt == VirtModeNone { + // systemd-detect-virt returns non zero exit code if no virtualization is detected + return virt, nil + } + return "", err + } + return virt, nil +} + +type defaultEnvSysfsDevice struct { + name string + path string + subsystem string +} + +// Name implements [SysfsDevice.Name]. +func (d *defaultEnvSysfsDevice) Name() string { + return d.name +} + +// Path implements [SysfsDevice.Path]. +func (d *defaultEnvSysfsDevice) Path() string { + return d.path +} + +// Subsystem implements [SysfsDevice.Subsystem]. +func (d *defaultEnvSysfsDevice) Subsystem() string { + return d.subsystem +} + +// AttributeReader implements [SysfsDevice.AttributeReader]. +func (d *defaultEnvSysfsDevice) AttributeReader(attr string) (rc io.ReadCloser, err error) { + if attr == "uevent" { + return nil, ErrNoDeviceAttribute + } + + f, err := os.Open(filepath.Join(d.path, attr)) + switch { + case os.IsNotExist(err): + return nil, ErrNoDeviceAttribute + case err != nil: + return nil, err + } + defer func() { + if err == nil { + return + } + f.Close() + }() + + fi, err := f.Stat() + if err != nil { + return nil, err + } + if !fi.Mode().IsRegular() { + return nil, ErrNoDeviceAttribute + } + + return f, nil +} + +// DeviceForClass implements [HostEnvironment.DevicesForClass]. +func (defaultEnvImpl) DevicesForClass(class string) ([]SysfsDevice, error) { + classPath := filepath.Join(sysfsPath, "class", class) + f, err := os.Open(classPath) + switch { + case os.IsNotExist(err): + // it's ok to have no devices for the specified class + return nil, nil + case err != nil: + return nil, err + } + defer f.Close() + + entries, err := f.ReadDir(-1) + if err != nil { + return nil, err + } + + var out []SysfsDevice + for _, entry := range entries { + path, err := filepath.EvalSymlinks(filepath.Join(classPath, entry.Name())) + if err != nil { + return nil, fmt.Errorf("cannot resolve path for %s: %w", entry.Name(), err) + } + subsystem, err := filepath.EvalSymlinks(filepath.Join(path, "subsystem")) + if err != nil { + return nil, fmt.Errorf("cannot resolve subsystem for %s: %w", entry.Name(), err) + } + out = append(out, &defaultEnvSysfsDevice{ + name: entry.Name(), + path: path, + subsystem: filepath.Base(subsystem), + }) + } + return out, nil +} + // DefaultEnv corresponds to the environment associated with the host // machine. var DefaultEnv = defaultEnvImpl{} diff --git a/internal/efi/default_env_amd64.go b/internal/efi/default_env_amd64.go new file mode 100644 index 00000000..cb510b58 --- /dev/null +++ b/internal/efi/default_env_amd64.go @@ -0,0 +1,111 @@ +// -*- 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 . + * + */ + +package efi + +import ( + "encoding/binary" + "errors" + "fmt" + "os" + "path/filepath" + "strconv" + "syscall" + + "github.com/intel-go/cpuid" +) + +var ( + cpuidHasFeature = cpuid.HasFeature + devcpuPath = "/dev/cpu" +) + +type defaultEnvAMD64Impl struct{} + +// CPUVendorIdentificator implements [HostEnvironmentAMD64.CPUVendorIdentificator]. +func (defaultEnvAMD64Impl) CPUVendorIdentificator() string { + return cpuid.VendorIdentificatorString +} + +// HasCPUIDFeature implements [HostEnvironmentAMD64.HasCPUIDFeature]. +func (defaultEnvAMD64Impl) HasCPUIDFeature(feature uint64) bool { + return cpuidHasFeature(feature) +} + +// ReadMSR implements [HostEnvironmentAMD64.ReadMSRs]. +func (defaultEnvAMD64Impl) ReadMSRs(msr uint32) (map[uint32]uint64, error) { + dir, err := os.Open(devcpuPath) + switch { + case os.IsNotExist(err): + return nil, ErrNoKernelMSRSupport + case err != nil: + return nil, err + } + defer dir.Close() + + entries, err := dir.ReadDir(-1) + if err != nil { + return nil, err + } + + out := make(map[uint32]uint64) + + for _, entry := range entries { + cpuNo, err := strconv.ParseUint(entry.Name(), 10, 32) + if err != nil { + return nil, fmt.Errorf("invalid CPU number for name %s: %w", entry.Name(), err) + } + + val, err := func(name string) (uint64, error) { + f, err := os.Open(filepath.Join(dir.Name(), name, "msr")) + switch { + case os.IsNotExist(err): + return 0, ErrNoKernelMSRSupport + case errors.Is(err, syscall.EIO): + return 0, ErrNoMSRSupport + case err != nil: + return 0, err + } + defer f.Close() + + var data [8]byte + _, err = f.ReadAt(data[:], int64(msr)) + switch { + case errors.Is(err, syscall.EIO): // I think the kernel returns -EIO if the MSR is not supported, but this is poorly documented. + return 0, ErrNoMSRSupport + case err != nil: + return 0, fmt.Errorf("cannot read from MSR device: %w", err) + } + + return binary.LittleEndian.Uint64(data[:]), nil + }(entry.Name()) + if err != nil { + return nil, fmt.Errorf("cannot read value for CPU %s: %w", entry.Name(), err) + } + + out[uint32(cpuNo)] = val + } + + return out, nil +} + +// AMD64 implements [HostEnvironment.AMD64]. +func (defaultEnvImpl) AMD64() (HostEnvironmentAMD64, error) { + return defaultEnvAMD64Impl{}, nil +} diff --git a/internal/efi/default_env_amd64_test.go b/internal/efi/default_env_amd64_test.go new file mode 100644 index 00000000..e7b8f22a --- /dev/null +++ b/internal/efi/default_env_amd64_test.go @@ -0,0 +1,119 @@ +//go:build amd64 + +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2021-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 . + * + */ + +package efi_test + +import ( + "encoding/binary" + "os" + "path/filepath" + + "github.com/intel-go/cpuid" + . "github.com/snapcore/secboot/internal/efi" + "github.com/snapcore/secboot/internal/testutil" + . "gopkg.in/check.v1" +) + +type defaultEnvAMD64Suite struct{} + +var _ = Suite(&defaultEnvAMD64Suite{}) + +func (s *defaultEnvAMD64Suite) TestCPUVendorIdentificatorIntel(c *C) { + orig := cpuid.VendorIdentificatorString + cpuid.VendorIdentificatorString = "GenuineIntel" + defer func() { cpuid.VendorIdentificatorString = orig }() + + amd64, err := DefaultEnv.AMD64() + c.Assert(err, IsNil) + c.Check(amd64.CPUVendorIdentificator(), Equals, "GenuineIntel") +} + +func (s *defaultEnvAMD64Suite) TestCPUVendorIdentificatorAMD(c *C) { + orig := cpuid.VendorIdentificatorString + cpuid.VendorIdentificatorString = "AuthenticAMD" + defer func() { cpuid.VendorIdentificatorString = orig }() + + amd64, err := DefaultEnv.AMD64() + c.Assert(err, IsNil) + c.Check(amd64.CPUVendorIdentificator(), Equals, "AuthenticAMD") +} + +func (s *defaultEnvAMD64Suite) TestCPUIDHasFeatureSDBGTrue(c *C) { + restore := MockCPUIDHasFeature(func(feature uint64) bool { + c.Check(feature, Equals, cpuid.SDBG) + return true + }) + defer restore() + + amd64, err := DefaultEnv.AMD64() + c.Assert(err, IsNil) + c.Check(amd64.HasCPUIDFeature(cpuid.SDBG), testutil.IsTrue) +} + +func (s *defaultEnvAMD64Suite) TestCPUIDHasFeatureSDBGFalse(c *C) { + restore := MockCPUIDHasFeature(func(feature uint64) bool { + c.Check(feature, Equals, cpuid.SDBG) + return false + }) + defer restore() + + amd64, err := DefaultEnv.AMD64() + c.Assert(err, IsNil) + c.Check(amd64.HasCPUIDFeature(cpuid.SDBG), testutil.IsFalse) +} + +func (s *defaultEnvAMD64Suite) TestCPUIDHasFeatureSSE3True(c *C) { + restore := MockCPUIDHasFeature(func(feature uint64) bool { + c.Check(feature, Equals, cpuid.SSE3) + return true + }) + defer restore() + + amd64, err := DefaultEnv.AMD64() + c.Assert(err, IsNil) + c.Check(amd64.HasCPUIDFeature(cpuid.SSE3), testutil.IsTrue) +} + +func (s *defaultEnvAMD64Suite) TestReadMSR(c *C) { + dir := c.MkDir() + restore := MockDevcpuPath(dir) + defer restore() + + c.Assert(os.Mkdir(filepath.Join(dir, "0"), 0755), IsNil) + c.Assert(os.Mkdir(filepath.Join(dir, "1"), 0755), IsNil) + + data := make([]byte, 0xc80) + var data8 [8]byte + binary.LittleEndian.PutUint64(data8[:], 0x40000000) + data = append(data, data8[:]...) + + c.Assert(os.WriteFile(filepath.Join(dir, "0/msr"), data, 0644), IsNil) + c.Assert(os.WriteFile(filepath.Join(dir, "1/msr"), data, 0644), IsNil) + + amd64, err := DefaultEnv.AMD64() + c.Assert(err, IsNil) + vals, err := amd64.ReadMSRs(0xc80) + c.Assert(err, IsNil) + c.Check(vals, DeepEquals, map[uint32]uint64{ + 0: 0x40000000, + 1: 0x40000000, + }) +} diff --git a/internal/efi/default_env_not_amd64.go b/internal/efi/default_env_not_amd64.go new file mode 100644 index 00000000..dbea4dc4 --- /dev/null +++ b/internal/efi/default_env_not_amd64.go @@ -0,0 +1,27 @@ +//go:build !amd64 + +// -*- 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 . + * + */ + +package efi + +// AMD64 implements [HostEnvironment.AMD64]. +func (defaultEnvImpl) AMD64() (HostEnvironmentAMD64, error) { + return nil, ErrNotAMD64Host +} diff --git a/internal/efi/default_env_not_amd64_test.go b/internal/efi/default_env_not_amd64_test.go new file mode 100644 index 00000000..316544c5 --- /dev/null +++ b/internal/efi/default_env_not_amd64_test.go @@ -0,0 +1,36 @@ +//go:build !amd64 + +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2021-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 . + * + */ + +package efi_test + +import ( + . "github.com/snapcore/secboot/internal/efi" + . "gopkg.in/check.v1" +) + +type defaultEnvAMD64Suite struct{} + +var _ = Suite(&defaultEnvAMD64Suite{}) + +func (s *defaultEnvAMD64Suite) TestNotAMD64Host(c *C) { + _, err := DefaultEnv.AMD64() + c.Check(err, Equals, ErrNotAMD64Host) +} diff --git a/internal/efi/default_env_test.go b/internal/efi/default_env_test.go index a034d465..63fb8a2d 100644 --- a/internal/efi/default_env_test.go +++ b/internal/efi/default_env_test.go @@ -1,7 +1,7 @@ // -*- Mode: Go; indent-tabs-mode: t -*- /* - * Copyright (C) 2021 Canonical Ltd + * Copyright (C) 2021-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 @@ -21,23 +21,35 @@ package efi_test import ( "context" - _ "embed" + "errors" "io" "os" + "os/exec" "path/filepath" efi "github.com/canonical/go-efilib" "github.com/canonical/go-tpm2" + "github.com/canonical/go-tpm2/linux" "github.com/canonical/tcglog-parser" . "github.com/snapcore/secboot/internal/efi" "github.com/snapcore/secboot/internal/efitest" "github.com/snapcore/secboot/internal/testutil" + snapd_testutil "github.com/snapcore/snapd/testutil" . "gopkg.in/check.v1" ) type defaultEnvSuite struct{} +func (s *defaultEnvSuite) mockSysfsPath(c *C, path string) (mockedPath string, restore func()) { + dir := c.MkDir() + + cmd := exec.Command("tar", "xaf", path, "-C", dir) + c.Assert(cmd.Run(), IsNil) + + return dir, MockSysfsPath(dir) +} + var _ = Suite(&defaultEnvSuite{}) type testKey struct{} @@ -94,3 +106,176 @@ func (s *defaultEnvSuite) TestReadEventLog2(c *C) { SecureBootDisabled: true, }) } + +func (s *defaultEnvSuite) TestTPMDeviceRM(c *C) { + rawDev := new(linux.RawDevice) + restore := MockLinuxDefaultTPM2Device(rawDev, nil) + defer restore() + + rmDev := new(linux.RMDevice) + restore = MockLinuxRawDeviceResourceManagedDevice(c, rawDev, rmDev, nil) + defer restore() + + dev, err := DefaultEnv.TPMDevice() + c.Check(err, IsNil) + c.Check(dev, Equals, rmDev) +} + +func (s *defaultEnvSuite) TestTPMDeviceRaw(c *C) { + rawDev := new(linux.RawDevice) + restore := MockLinuxDefaultTPM2Device(rawDev, nil) + defer restore() + + restore = MockLinuxRawDeviceResourceManagedDevice(c, rawDev, nil, linux.ErrNoResourceManagedDevice) + defer restore() + + dev, err := DefaultEnv.TPMDevice() + c.Check(err, IsNil) + c.Check(dev, Equals, rawDev) +} + +func (s *defaultEnvSuite) TestTPMDeviceNoDevicesErr(c *C) { + restore := MockLinuxDefaultTPM2Device(nil, linux.ErrNoTPMDevices) + defer restore() + + _, err := DefaultEnv.TPMDevice() + c.Check(err, Equals, ErrNoTPM2Device) +} + +func (s *defaultEnvSuite) TestTPMDeviceNoDevicesOtherErr(c *C) { + restore := MockLinuxDefaultTPM2Device(nil, errors.New("some error")) + defer restore() + + _, err := DefaultEnv.TPMDevice() + c.Check(err, ErrorMatches, `some error`) +} + +func (s *defaultEnvSuite) TestTPMDeviceRMOtherErr(c *C) { + rawDev := new(linux.RawDevice) + restore := MockLinuxDefaultTPM2Device(rawDev, nil) + defer restore() + + restore = MockLinuxRawDeviceResourceManagedDevice(c, rawDev, nil, errors.New("some error")) + defer restore() + + _, err := DefaultEnv.TPMDevice() + c.Check(err, ErrorMatches, `some error`) +} + +func (s *defaultEnvSuite) TestDetectVirtModeNoneAny(c *C) { + cmd := snapd_testutil.MockCommand(c, "systemd-detect-virt", `echo none; exit 1`) + defer cmd.Restore() + + virt, err := DefaultEnv.DetectVirtMode(DetectVirtModeAll) + c.Check(err, IsNil) + c.Check(virt, Equals, VirtModeNone) + + c.Check(cmd.Calls(), HasLen, 1) + c.Check(cmd.Calls()[0], DeepEquals, []string{"systemd-detect-virt"}) +} + +func (s *defaultEnvSuite) TestDetectVirtModeKVMAny(c *C) { + cmd := snapd_testutil.MockCommand(c, "systemd-detect-virt", `echo kvm`) + defer cmd.Restore() + + virt, err := DefaultEnv.DetectVirtMode(DetectVirtModeAll) + c.Check(err, IsNil) + c.Check(virt, Equals, "kvm") + + c.Check(cmd.Calls(), HasLen, 1) + c.Check(cmd.Calls()[0], DeepEquals, []string{"systemd-detect-virt"}) +} + +func (s *defaultEnvSuite) TestDetectVirtModeNoneContainer(c *C) { + cmd := snapd_testutil.MockCommand(c, "systemd-detect-virt", `echo none; exit 1`) + defer cmd.Restore() + + virt, err := DefaultEnv.DetectVirtMode(DetectVirtModeContainer) + c.Check(err, IsNil) + c.Check(virt, Equals, VirtModeNone) + + c.Check(cmd.Calls(), HasLen, 1) + c.Check(cmd.Calls()[0], DeepEquals, []string{"systemd-detect-virt", "--container"}) +} + +func (s *defaultEnvSuite) TestDetectVirtModeLXCContainer(c *C) { + cmd := snapd_testutil.MockCommand(c, "systemd-detect-virt", `echo lxc`) + defer cmd.Restore() + + virt, err := DefaultEnv.DetectVirtMode(DetectVirtModeContainer) + c.Check(err, IsNil) + c.Check(virt, Equals, "lxc") + + c.Check(cmd.Calls(), HasLen, 1) + c.Check(cmd.Calls()[0], DeepEquals, []string{"systemd-detect-virt", "--container"}) +} + +func (s *defaultEnvSuite) TestDetectVirtModeNoneVM(c *C) { + cmd := snapd_testutil.MockCommand(c, "systemd-detect-virt", `echo none; exit 1`) + defer cmd.Restore() + + virt, err := DefaultEnv.DetectVirtMode(DetectVirtModeVM) + c.Check(err, IsNil) + c.Check(virt, Equals, VirtModeNone) + + c.Check(cmd.Calls(), HasLen, 1) + c.Check(cmd.Calls()[0], DeepEquals, []string{"systemd-detect-virt", "--vm"}) +} + +func (s *defaultEnvSuite) TestDetectVirtModeKVMVM(c *C) { + cmd := snapd_testutil.MockCommand(c, "systemd-detect-virt", `echo kvm`) + defer cmd.Restore() + + virt, err := DefaultEnv.DetectVirtMode(DetectVirtModeVM) + c.Check(err, IsNil) + c.Check(virt, Equals, "kvm") + + c.Check(cmd.Calls(), HasLen, 1) + c.Check(cmd.Calls()[0], DeepEquals, []string{"systemd-detect-virt", "--vm"}) +} + +func (s *defaultEnvSuite) TestDetectVirtModeErr(c *C) { + cmd := snapd_testutil.MockCommand(c, "systemd-detect-virt", `echo kvm; exit 1`) + defer cmd.Restore() + + _, err := DefaultEnv.DetectVirtMode(DetectVirtModeAll) + c.Check(err, ErrorMatches, `exit status 1`) + + c.Check(cmd.Calls(), HasLen, 1) + c.Check(cmd.Calls()[0], DeepEquals, []string{"systemd-detect-virt"}) +} + +func (s *defaultEnvSuite) TestDevicesForClassMEI(c *C) { + path, restore := s.mockSysfsPath(c, "testdata/sys.tar") + defer restore() + + devices, err := DefaultEnv.DevicesForClass("mei") + c.Check(err, IsNil) + c.Assert(devices, HasLen, 1) + c.Check(devices[0].Name(), Equals, "mei0") + c.Check(devices[0].Path(), Equals, filepath.Join(path, "devices/pci00000:00/0000:00:16.0/mei/mei0")) + c.Check(devices[0].Subsystem(), Equals, "mei") + + rc, err := devices[0].AttributeReader("fw_ver") + c.Assert(err, IsNil) + defer rc.Close() + + data, err := io.ReadAll(rc) + c.Check(err, IsNil) + c.Check(data, DeepEquals, []byte(`0:16.1.27.2176 +0:16.1.27.2176 +0:16.0.15.1624 +`)) +} + +func (s *defaultEnvSuite) TestDevicesForClassIOMMU(c *C) { + path, restore := s.mockSysfsPath(c, "testdata/sys.tar") + defer restore() + + devices, err := DefaultEnv.DevicesForClass("iommu") + c.Check(err, IsNil) + c.Assert(devices, HasLen, 1) + c.Check(devices[0].Name(), Equals, "dmar0") + c.Check(devices[0].Path(), Equals, filepath.Join(path, "devices/virtual/iommu/dmar0")) + c.Check(devices[0].Subsystem(), Equals, "iommu") +} diff --git a/internal/efi/env.go b/internal/efi/env.go index d6404a73..96cade63 100644 --- a/internal/efi/env.go +++ b/internal/efi/env.go @@ -21,14 +21,21 @@ package efi import ( "context" + "errors" + "io" + "github.com/canonical/go-tpm2" "github.com/canonical/tcglog-parser" ) -// HostEnvironment is an interface that abstracts out an EFI environment, so that +// XXX: Some of the interfaces here are really public, but they are here because they are shared by +// the public efi and efi/preinstall packages. I wonder if there needs to be a public efi/common +// package for these interfaces to live in. + +// HostEnvironmentEFI is an interface that abstracts out an EFI environment, so that // consumers of the API can provide a custom mechanism to read EFI variables or parse -// the TCG event log. This needs to be kept in sync with [efi.HostEnvironment]. -type HostEnvironment interface { +// the TCG event log. +type HostEnvironmentEFI interface { // VarContext returns a copy of parent containing a VarsBackend, keyed by efi.VarsBackendKey, // for interacting with EFI variables via go-efilib. This context can be passed to any // go-efilib function that interacts with EFI variables. Right now, go-efilib doesn't @@ -42,3 +49,90 @@ type HostEnvironment interface { // ReadEventLog reads the TCG event log ReadEventLog() (*tcglog.Log, error) } + +// SysfsDevice corresponds to a device in the sysfs tree. +type SysfsDevice interface { + Name() string // the name of the device + Path() string // the fully evaluated sysfs path for the device + Subsystem() string // the device subsystem name + + // AttributeReader returns an io.ReadCloser to read the specified + // attribute for the device. The caller should call Close when + // finished. + AttributeReader(attr string) (io.ReadCloser, error) +} + +// HostEnvironmentAMD64 is an interface that abstracts out a host environment specific +// to AMD64 platforms. +type HostEnvironmentAMD64 interface { + // CPUVendorIdentificator returns the CPU vendor. + CPUVendorIdentificator() string + + // HasCPUIDFeature returns if feature from FeatureNames map in the + // github.com/intel-go/cpuid package is available. + HasCPUIDFeature(feature uint64) bool + + // ReadMSRs reads the value of the specified MSR for all CPUs, + // returning a map of the result for all CPU numbers + ReadMSRs(msr uint32) (map[uint32]uint64, error) +} + +// DetectVirtMode controls what type of virtualization to test for. +type DetectVirtMode int + +const ( + // DetectVirtModeAll detects for all types of virtualization. + DetectVirtModeAll DetectVirtMode = iota + + // DetectVirtModeContainer detects for container types of virtualization. + DetectVirtModeContainer + + // DetectVirtModeVM detects for fully virtualized types of environments. + DetectVirtModeVM +) + +// VirtModeNone corresponds to no virtualization. +const VirtModeNone = "none" + +var ( + // ErrNoTPM2Device is returned from HostEnvironment.TPMDevice if no TPM2 + // device is available. + ErrNoTPM2Device = errors.New("no TPM2 device is available") + + // ErrNoDeviceAttribute is returned from SysfsDevice.Attribute if the supplied attribute + // does not exist. + ErrNoDeviceAttribute = errors.New("device attribute does not exist") + + // ErrNotAMD64Host is returned from HostEnvironment.AMD64 on environments that + // are not AMD64. + ErrNotAMD64Host = errors.New("not a AMD64 host") + + // ErrNoKernelMSRSupport is returned from HostEnvironmentAMD64.ReadMSRs if there is + // no support for reading MSRs. + ErrNoKernelMSRSupport = errors.New("missing kernel support for reading MSRs") + + // ErrNoMSRSupport is returned from HostEnvironmentAMD64.ReadMSRs if there is + // no MSR support or the specified MSR cannot be read. + ErrNoMSRSupport = errors.New("missing MSR support") +) + +// HostEnvironment is an interface that abstracts out a host environment, so that +// consumers of the API can provide ways to mock parts of an environment. +type HostEnvironment interface { + HostEnvironmentEFI + + // TPMDevice returns a TPMDevice that can be used to open a tpm2.TPMContext. + TPMDevice() (tpm2.TPMDevice, error) + + // DetectVirtMode returns whether the environment is virtualized. If not, it returns + // (VirtModeNone, nil). The mode can be used to choose what type of virtualization to + // test for. + DetectVirtMode(mode DetectVirtMode) (string, error) + + // DevicesForClass returns a list of devices with the specified class. + DevicesForClass(class string) ([]SysfsDevice, error) + + // AMD64 returns an interface that can be used to mock some parts of an AMD64 platform. + // This will return ErrNotAMD64CPU on non-AMD64 platforms. + AMD64() (HostEnvironmentAMD64, error) +} diff --git a/internal/efi/export_amd64_test.go b/internal/efi/export_amd64_test.go new file mode 100644 index 00000000..d75b464d --- /dev/null +++ b/internal/efi/export_amd64_test.go @@ -0,0 +1,38 @@ +//go:build amd64 + +// -*- 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 . + * + */ + +package efi + +func MockDevcpuPath(path string) (restore func()) { + orig := devcpuPath + devcpuPath = path + return func() { + devcpuPath = orig + } +} + +func MockCPUIDHasFeature(fn func(uint64) bool) (restore func()) { + orig := cpuidHasFeature + cpuidHasFeature = fn + return func() { + cpuidHasFeature = orig + } +} diff --git a/internal/efi/export_test.go b/internal/efi/export_test.go index d37b2909..3c3e949e 100644 --- a/internal/efi/export_test.go +++ b/internal/efi/export_test.go @@ -19,6 +19,11 @@ package efi +import ( + "github.com/canonical/go-tpm2/linux" + . "gopkg.in/check.v1" +) + func MockEventLogPath(path string) (restore func()) { origPath := eventLogPath eventLogPath = path @@ -26,3 +31,32 @@ func MockEventLogPath(path string) (restore func()) { eventLogPath = origPath } } + +func MockLinuxDefaultTPM2Device(dev *linux.RawDevice, err error) (restore func()) { + orig := linuxDefaultTPM2Device + linuxDefaultTPM2Device = func() (*linux.RawDevice, error) { + return dev, err + } + return func() { + linuxDefaultTPM2Device = orig + } +} + +func MockLinuxRawDeviceResourceManagedDevice(c *C, expectedDev *linux.RawDevice, dev *linux.RMDevice, err error) (restore func()) { + orig := linuxRawDeviceResourceManagedDevice + linuxRawDeviceResourceManagedDevice = func(device *linux.RawDevice) (*linux.RMDevice, error) { + c.Check(device, Equals, expectedDev) + return dev, err + } + return func() { + linuxRawDeviceResourceManagedDevice = orig + } +} + +func MockSysfsPath(path string) (restore func()) { + orig := sysfsPath + sysfsPath = path + return func() { + sysfsPath = orig + } +} diff --git a/internal/efi/options.go b/internal/efi/options.go index 679ca1fc..939075cf 100644 --- a/internal/efi/options.go +++ b/internal/efi/options.go @@ -31,7 +31,7 @@ type PCRProfileOptionVisitor interface { AddPCRs(pcrs ...tpm2.Handle) // SetEnvironment overrides the host environment to the supplied environment. - SetEnvironment(env HostEnvironment) + SetEnvironment(env HostEnvironmentEFI) // AddInitialVariablesModifier adds a function that will be called to allow // the initial variable set for profile generation to be modified. diff --git a/internal/efi/testdata/sys.tar b/internal/efi/testdata/sys.tar new file mode 100644 index 0000000000000000000000000000000000000000..5eef5c806757b39817b08a97743aa1cdade1568b GIT binary patch literal 20480 zcmeHO+j8145cM-(!527MU7Nf$Y4X(RFE9k$i6?O{7}NIeyK94E5(Xq;rzxz58Dv|M zrE_$om9&~?t9+J~7s-H^z{@$4;2$_6y8rZwt^~AI)+(iyOay=dn~cqXWoomo%H?!1 z9w)PhMP9Z~Uk47of1TpLcrst}|6`WdL|h5cdt0E4z4$lUYN?Qa1%mm1llHTVf8q<< z!4=rFm&OpzzgDdOH=#c{$bXUL$94$e{2So}_y2Fv0=)RgHgHTw;LX34pe))4U>s=X z|9$a~EoFkX(lQG!7V)pO?%I7DJCI)C<<|C}MBO{&-&v5!SOkA5BEgy4f45&gUVpy* z6agG3IRym#`~R=MYWY|DCP}*yA%*YNzqPIYjlq5b^^d~<>wh<#y*|CvNRHHIBrfkj z7-2`m7CsVnfZkF-!<|s08+R*&y15?pS_IDQA0ptd|K;z=&nm03$x~kB%a9^dd$N1S zf3?v+2#xDRQs7b$>pukJ*tkZpm}9X!g>(V>U(6yaO#0tD{sVOMPwRhH<0BsbBk>MN zM%;k?aLK(u2x_Mj*I)l=|F5z|OwAAK-CAKzd95BCLi0F(J*6OM~JE;Wz`hU#7&Eu*bpnnjG?0;);8Ib4yac~Ev z!mWKC|AP=at;qiRZ(_#Op7ie=?&}})|6KcjNC4a}7|S%hXnvGKpB`zQTAca*zd!$F zbzlA|ms!yP&o4;qdGU|_p9bfGLMoxr|MQvurWMSdre%52gF{-k*vx` ztgBxa**4tpAL9S5BsK%~|85Z+F0j%6hX4F~QB*y*f^hz|!eao;|8TUoIq&9wUQ8Fq zczm|m*MSiJ4UqqzQ5yIE`25ET?&-|O z{|44=zWToN)<4>RO49rva0h_(-!~7t@*Bjae(@o1{w=y2*8czC$o}X4{{@f(+&=S9 zhdc)O0A}gm|F8MSV*n2Izn=eXhN$ilMar^W5TF?$l|IUB<^CV%)cp^u-InC9e?0$d z$p1^S{oe&}z;M^Bs;&M3zU8m~HUHb|e;fU8Pf`1tqMW(@2cVm?WCR!iMt~7u1Q-EE UfDvE>7y(9r5nu!ufp><$Urpr6W&i*H literal 0 HcmV?d00001 diff --git a/internal/efitest/hostenv.go b/internal/efitest/hostenv.go index 152ab767..09a2422d 100644 --- a/internal/efitest/hostenv.go +++ b/internal/efitest/hostenv.go @@ -20,35 +20,257 @@ package efitest import ( + "bytes" "context" "errors" + "io" efi "github.com/canonical/go-efilib" + "github.com/canonical/go-tpm2" "github.com/canonical/tcglog-parser" + internal_efi "github.com/snapcore/secboot/internal/efi" ) -// MockHostEnvironment provides a mock EFI host environment. +// MockHostEnvironment provides a mock host environment that can be used by both +// the efi and efi/preinstall packages. type MockHostEnvironment struct { Vars MockVars Log *tcglog.Log + + TPM2Device tpm2.TPMDevice + + VirtMode string + VirtModeType internal_efi.DetectVirtMode + VirtModeErr error + DelayedVirtModeOptions []MockHostEnvironmentOption + + Devices map[string][]internal_efi.SysfsDevice + + AMD64Env internal_efi.HostEnvironmentAMD64 } -// NewMockHostEnvironment returns a new MockHostEnvironment. func NewMockHostEnvironment(vars MockVars, log *tcglog.Log) *MockHostEnvironment { return &MockHostEnvironment{ - Vars: vars, - Log: log} + Vars: vars, + Log: log, + VirtMode: internal_efi.VirtModeNone, + } +} + +// MockHostEnvironmentOption is an option supplied to [NewMockHostEnvironmentWithOpts]. +type MockHostEnvironmentOption func(*MockHostEnvironment) + +// WithMockVars adds the supplied mock EFI variables to a [MockHostEnvironment]. +func WithMockVars(vars MockVars) MockHostEnvironmentOption { + return func(env *MockHostEnvironment) { + env.Vars = vars + } +} + +// WithLog adds the supplied TCG log to a [MockHostEnvironment]. +func WithLog(log *tcglog.Log) MockHostEnvironmentOption { + return func(env *MockHostEnvironment) { + env.Log = log + } +} + +// WithTPMDevice adds the specified TPM device to a [MockHostEnvironment]. +func WithTPMDevice(device tpm2.TPMDevice) MockHostEnvironmentOption { + return func(env *MockHostEnvironment) { + env.TPM2Device = device + } +} + +// WithVirtMode adds the supplied virtualization mode and type to a [MockHostEnvironment]. +func WithVirtMode(mode string, modeType internal_efi.DetectVirtMode) MockHostEnvironmentOption { + return func(env *MockHostEnvironment) { + env.VirtMode = mode + env.VirtModeType = modeType + } +} + +// WithVirtModeError makes [MockHostEnvironment.DetectVirtMode] return an error. +func WithVirtModeError(err error) MockHostEnvironmentOption { + return func(env *MockHostEnvironment) { + env.VirtModeErr = err + } +} + +// WithDelayedVirtMode queues to add the supplied virtualization mode and type to a [MockHostEnvironment]. +// These will be applied in turn on each call to [MockHostEnvironment.DetectVirtMode]. +func WithDelayedVirtMode(mode string, modeType internal_efi.DetectVirtMode) MockHostEnvironmentOption { + return func(env *MockHostEnvironment) { + env.DelayedVirtModeOptions = append(env.DelayedVirtModeOptions, WithVirtMode(mode, modeType)) + } +} + +// WithVirtModeError queues to make [MockHostEnvironment.DetectVirtMode] return an error. These will be +// applied in turn on each call to [MockHostEnvironment.DetectVirtMode]. +func WithDelayedVirtModeError(err error) MockHostEnvironmentOption { + return func(env *MockHostEnvironment) { + env.DelayedVirtModeOptions = append(env.DelayedVirtModeOptions, WithVirtModeError(err)) + } +} + +type mockHostEnvironmentAMD64 struct { + vendorIdentificator string + features map[uint64]struct{} + cpus uint32 + msrs map[uint32]uint64 +} + +func (e *mockHostEnvironmentAMD64) CPUVendorIdentificator() string { + return e.vendorIdentificator +} + +func (e *mockHostEnvironmentAMD64) HasCPUIDFeature(feature uint64) bool { + _, has := e.features[feature] + return has +} + +func (e *mockHostEnvironmentAMD64) ReadMSRs(msr uint32) (map[uint32]uint64, error) { + val, exists := e.msrs[msr] + if !exists { + return nil, errors.New("MSR does not exist") + } + out := make(map[uint32]uint64) + for i := uint32(0); i < e.cpus; i++ { + out[i] = val + } + return out, nil +} + +// MockSysfsDevice is a mock implementation of [internal_efi.SysfsDevice]. +type MockSysfsDevice struct { + DeviceName string + DevicePath string + DeviceSubsystem string + + DeviceAttributeVals map[string][]byte +} + +func (d *MockSysfsDevice) Name() string { return d.DeviceName } +func (d *MockSysfsDevice) Path() string { return d.DevicePath } +func (d *MockSysfsDevice) Subsystem() string { return d.DeviceSubsystem } + +func (d *MockSysfsDevice) AttributeReader(attr string) (rc io.ReadCloser, err error) { + if d.DeviceAttributeVals == nil { + return nil, internal_efi.ErrNoDeviceAttribute + } + data, exists := d.DeviceAttributeVals[attr] + if !exists { + return nil, internal_efi.ErrNoDeviceAttribute + } + return io.NopCloser(bytes.NewReader(data)), nil +} + +// NewMockSysfsDevice returns a new MockSysfsDevice. +func NewMockSysfsDevice(name, path, subsystem string, attributeVals map[string][]byte) *MockSysfsDevice { + return &MockSysfsDevice{ + DeviceName: name, + DevicePath: path, + DeviceSubsystem: subsystem, + DeviceAttributeVals: attributeVals, + } +} + +// WithSysfsDevices adds the supplied devices, keyed by class, to the [MockHostEnvironment]. +func WithSysfsDevices(devices map[string][]internal_efi.SysfsDevice) MockHostEnvironmentOption { + return func(env *MockHostEnvironment) { + env.Devices = devices + } +} + +// WithAMD64Environment adds a [github.com/snapcore/secboot/efi/internal.HostEnvironmentAMD64] to the [MockHostEnvironment]. +// Whilst this supports mocking MSRs, it only supports the same value for every CPU. +func WithAMD64Environment(cpuVendorIdentificator string, cpuidFeatures []uint64, cpus uint32, msrs map[uint32]uint64) MockHostEnvironmentOption { + return func(env *MockHostEnvironment) { + features := make(map[uint64]struct{}) + for _, feature := range cpuidFeatures { + features[feature] = struct{}{} + } + env.AMD64Env = &mockHostEnvironmentAMD64{ + vendorIdentificator: cpuVendorIdentificator, + features: features, + msrs: msrs, + } + } +} + +// NewMockHostEnvironmentWithOpts returns a new MockHostEnvironment. +func NewMockHostEnvironmentWithOpts(options ...MockHostEnvironmentOption) *MockHostEnvironment { + env := &MockHostEnvironment{ + VirtMode: internal_efi.VirtModeNone, + } + for _, opt := range options { + opt(env) + } + return env } -// VarContext implements [github.com/snapcore/secboot/efi.HostEnvironment.VarContext]. +// VarContext implements [github.com/snapcore/secboot/internal/efi.HostEnvironmentEFI.VarContext]. func (e *MockHostEnvironment) VarContext(parent context.Context) context.Context { + if e.Vars == nil { + return parent + } return context.WithValue(parent, efi.VarsBackendKey{}, e.Vars) } -// ReadEventLog implements [github.com/snapcore/secboot/efi.HostEnvironment.ReadEventLog]. +// ReadEventLog implements [github.com/snapcore/secboot/internal/efi.HostEnvironmentEFI.ReadEventLog]. func (e *MockHostEnvironment) ReadEventLog() (*tcglog.Log, error) { if e.Log == nil { return nil, errors.New("nil log") } return e.Log, nil } + +// TPMDevice implements [github.com/snapcore/secboot/internal/efi.HostEnvironment.TPMDevice]. +func (e *MockHostEnvironment) TPMDevice() (tpm2.TPMDevice, error) { + if e.TPM2Device == nil { + return nil, internal_efi.ErrNoTPM2Device + } + return e.TPM2Device, nil +} + +// DetectVirtMode implements [github.com/snapcore/secboot/internal/efi.HostEnvironment.DetectVirtMode]. +func (e *MockHostEnvironment) DetectVirtMode(mode internal_efi.DetectVirtMode) (string, error) { + if len(e.DelayedVirtModeOptions) > 0 { + opt := e.DelayedVirtModeOptions[0] + e.DelayedVirtModeOptions = e.DelayedVirtModeOptions[1:] + opt(e) + } + + if e.VirtModeErr != nil { + return "", e.VirtModeErr + } + + switch mode { + case internal_efi.DetectVirtModeAll: + return e.VirtMode, nil + case internal_efi.DetectVirtModeContainer, internal_efi.DetectVirtModeVM: + if e.VirtModeType == mode { + return e.VirtMode, nil + } + } + return internal_efi.VirtModeNone, nil +} + +// DevicesForClass implements [github.com/snapcore/secboot/internal/efi.HostEnvironment.DevicesForClass]. +func (e *MockHostEnvironment) DevicesForClass(class string) ([]internal_efi.SysfsDevice, error) { + if e.Devices == nil { + return nil, errors.New("nil devices") + } + devices, exists := e.Devices[class] + if !exists { + return nil, nil + } + return devices, nil +} + +// AMD64 implements [github.com/snapcore/secboot/internal/efi.HostEnvironment.AMD64]. +func (e *MockHostEnvironment) AMD64() (internal_efi.HostEnvironmentAMD64, error) { + if e.AMD64Env == nil { + return nil, internal_efi.ErrNotAMD64Host + } + return e.AMD64Env, nil +} diff --git a/tpm2/tpm_test.go b/tpm2/tpm_test.go index 65eed426..76389f12 100644 --- a/tpm2/tpm_test.go +++ b/tpm2/tpm_test.go @@ -20,6 +20,7 @@ package tpm2_test import ( + "bytes" "crypto/x509" "io" "os" @@ -165,27 +166,41 @@ func (s *tpmSuiteNoTPM) TestConnectToDefaultTPMNoTPM(c *C) { // We don't have a TPM1.2 simulator, so create a mock TCTI that just returns // a TPM_BAD_ORDINAL error -type mockTPM12Tcti struct{} +type mockTPM12Transport struct { + rsp io.Reader +} + +func (t *mockTPM12Transport) Read(data []byte) (int, error) { + for { + n, err := t.rsp.Read(data) + if err == io.EOF { + t.rsp = nil + err = nil + if n == 0 { + continue + } + } + return n, err + } +} -func (t *mockTPM12Tcti) Read(data []byte) (int, error) { +func (t *mockTPM12Transport) Write(data []byte) (int, error) { + buf := new(bytes.Buffer) // tag = TPM_TAG_RSP_COMMAND (0xc4) // paramSize = 10 // returnCode = TPM_BAD_ORDINAL (10) - b := mu.MustMarshalToBytes(tpm2.TagRspCommand, uint32(10), tpm2.ResponseBadTag) - return copy(data, b), io.EOF -} - -func (t *mockTPM12Tcti) Write(data []byte) (int, error) { + mu.MustMarshalToWriter(buf, tpm2.TagRspCommand, uint32(10), tpm2.ResponseBadTag) + t.rsp = buf return len(data), nil } -func (t *mockTPM12Tcti) Close() error { +func (t *mockTPM12Transport) Close() error { return nil } func (s *tpmSuiteNoTPM) TestConnectToDefaultTPM12(c *C) { restore := tpm2test.MockOpenDefaultTctiFn(func() (tpm2.TCTI, error) { - return &mockTPM12Tcti{}, nil + return &mockTPM12Transport{}, nil }) s.AddCleanup(restore) From 4dac53ee9f6a928f1ebddbc9ef146060738adc3c Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Tue, 23 Jul 2024 11:43:58 +0100 Subject: [PATCH 2/4] internal/efi: update doc comment --- internal/efi/env.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/efi/env.go b/internal/efi/env.go index 96cade63..e4dc9b31 100644 --- a/internal/efi/env.go +++ b/internal/efi/env.go @@ -73,7 +73,7 @@ type HostEnvironmentAMD64 interface { HasCPUIDFeature(feature uint64) bool // ReadMSRs reads the value of the specified MSR for all CPUs, - // returning a map of the result for all CPU numbers + // returning a map of the results keyed by the CPU numbers. ReadMSRs(msr uint32) (map[uint32]uint64, error) } From d0de70e6fa688a47efdd0b72fbd6b228e7f88504 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Tue, 23 Jul 2024 11:45:18 +0100 Subject: [PATCH 3/4] internal/efitest: revert the change to MockHostEnvironment.VarContext The previously committed change only added an implementation of efi.VarsBackend to the supplied context if the supplied efitest.MockVars was not nil, but this resulted in existing tests having to explicitly create an empty efitest.MockVars. Revert to the existing behaviour where we add an implementation of efi.VarsBackend to the supplied context in all cases. Although the implementation is a nil map, that is ok for reading (it's only adding keys to a nil map that causes go to panic). --- efi/env_test.go | 4 ++-- efi/fw_load_handler_test.go | 6 +++--- efi/pcr_images_measurer_test.go | 20 ++++++++++---------- efi/pcr_profile_test.go | 2 +- efi/shim_test.go | 4 ++-- internal/efitest/hostenv.go | 3 --- 6 files changed, 18 insertions(+), 21 deletions(-) diff --git a/efi/env_test.go b/efi/env_test.go index cbf75556..456efccb 100644 --- a/efi/env_test.go +++ b/efi/env_test.go @@ -251,7 +251,7 @@ func (s *envSuite) TestRootVarsCollectorWriteOne(c *C) { func (s *envSuite) TestRootVarsCollectorWriteOneNew(c *C) { // Test that one write in the initial state works and creates one new starting state s.testRootVarsCollector(c, &testRootVarsCollectorData{ - env: efitest.NewMockHostEnvironment(efitest.MockVars{}, nil), + env: efitest.NewMockHostEnvironment(nil, nil), expected: []efitest.MockVars{ { {Name: "foo", GUID: testGuid1}: nil, @@ -557,7 +557,7 @@ func (s *envSuite) TestRootVarsCollectorPeekAll(c *C) { } func (s *envSuite) TestVarBranchReadsUpdate(c *C) { - env := efitest.NewMockHostEnvironment(efitest.MockVars{}, nil) + env := efitest.NewMockHostEnvironment(nil, nil) collector := NewVariableSetCollector(env) root := collector.Next() diff --git a/efi/fw_load_handler_test.go b/efi/fw_load_handler_test.go index f5d1fd4c..96864f07 100644 --- a/efi/fw_load_handler_test.go +++ b/efi/fw_load_handler_test.go @@ -539,7 +539,7 @@ func (s *fwLoadHandlerSuite) TestMeasureImageStartErrBadLogPCR4_2(c *C) { func (s *fwLoadHandlerSuite) testMeasureImageStartErrBadLogSeparatorError(c *C, pcr tpm2.Handle) error { // Insert an invalid error separator event into the log for the specified pcr - collector := NewVariableSetCollector(efitest.NewMockHostEnvironment(efitest.MockVars{}, nil)) + collector := NewVariableSetCollector(efitest.NewMockHostEnvironment(nil, nil)) ctx := newMockPcrBranchContext(&mockPcrProfileContext{ alg: tpm2.HashAlgorithmSHA256, pcrs: MakePcrFlags(pcr)}, nil, collector.Next()) @@ -580,7 +580,7 @@ func (s *fwLoadHandlerSuite) TestMeasureImageStartErrBadLogSeparatorErrorPCR7(c func (s *fwLoadHandlerSuite) testMeasureImageStartErrBadLogInvalidSeparator(c *C, pcr tpm2.Handle) error { // Insert an invalid separator event into the log for the specified PCR - collector := NewVariableSetCollector(efitest.NewMockHostEnvironment(efitest.MockVars{}, nil)) + collector := NewVariableSetCollector(efitest.NewMockHostEnvironment(nil, nil)) ctx := newMockPcrBranchContext(&mockPcrProfileContext{ alg: tpm2.HashAlgorithmSHA256, pcrs: MakePcrFlags(pcr)}, nil, collector.Next()) @@ -621,7 +621,7 @@ func (s *fwLoadHandlerSuite) TestMeasureImageStartErrBadLogInvalidSeparatorPCR7( func (s *fwLoadHandlerSuite) testMeasureImageStartErrBadLogMissingSeparator(c *C, pcr tpm2.Handle) error { // Remove the separator from the specified PCR - collector := NewVariableSetCollector(efitest.NewMockHostEnvironment(efitest.MockVars{}, nil)) + collector := NewVariableSetCollector(efitest.NewMockHostEnvironment(nil, nil)) ctx := newMockPcrBranchContext(&mockPcrProfileContext{ alg: tpm2.HashAlgorithmSHA256, pcrs: MakePcrFlags(pcr)}, nil, collector.Next()) diff --git a/efi/pcr_images_measurer_test.go b/efi/pcr_images_measurer_test.go index 26de687a..f84fe6d7 100644 --- a/efi/pcr_images_measurer_test.go +++ b/efi/pcr_images_measurer_test.go @@ -45,7 +45,7 @@ func (s *pcrImagesMeasurerSuite) TestPcrImagesMeasurerMeasureOneLeaf(c *C) { profile := secboot_tpm2.NewPCRProtectionProfile() params := new(LoadParams) - vars := NewVariableSetCollector(efitest.NewMockHostEnvironment(efitest.MockVars{}, nil)).Next() + vars := NewVariableSetCollector(efitest.NewMockHostEnvironment(nil, nil)).Next() h := crypto.SHA256.New() io.WriteString(h, "foo") @@ -94,7 +94,7 @@ func (s *pcrImagesMeasurerSuite) TestPcrImagesMeasurerTwoLeaf(c *C) { profile := secboot_tpm2.NewPCRProtectionProfile() params := new(LoadParams) - vars := NewVariableSetCollector(efitest.NewMockHostEnvironment(efitest.MockVars{}, nil)).Next() + vars := NewVariableSetCollector(efitest.NewMockHostEnvironment(nil, nil)).Next() h := crypto.SHA256.New() io.WriteString(h, "foo") @@ -146,7 +146,7 @@ func (s *pcrImagesMeasurerSuite) TestPcrImagesMeasurerNonLeaf(c *C) { profile := secboot_tpm2.NewPCRProtectionProfile() params := new(LoadParams) - vars := NewVariableSetCollector(efitest.NewMockHostEnvironment(efitest.MockVars{}, nil)).Next() + vars := NewVariableSetCollector(efitest.NewMockHostEnvironment(nil, nil)).Next() h := crypto.SHA256.New() io.WriteString(h, "foo") @@ -203,7 +203,7 @@ func (s *pcrImagesMeasurerSuite) TestPcrImagesMeasurerTwoNonLeaf(c *C) { profile := secboot_tpm2.NewPCRProtectionProfile() params := new(LoadParams) - vars := NewVariableSetCollector(efitest.NewMockHostEnvironment(efitest.MockVars{}, nil)).Next() + vars := NewVariableSetCollector(efitest.NewMockHostEnvironment(nil, nil)).Next() h := crypto.SHA256.New() io.WriteString(h, "foo1") @@ -279,7 +279,7 @@ func (s *pcrImagesMeasurerSuite) TestPcrImagesMeasurerMeasureWithParams(c *C) { profile := secboot_tpm2.NewPCRProtectionProfile() params := new(LoadParams) - vars := NewVariableSetCollector(efitest.NewMockHostEnvironment(efitest.MockVars{}, nil)).Next() + vars := NewVariableSetCollector(efitest.NewMockHostEnvironment(nil, nil)).Next() h := crypto.SHA256.New() io.WriteString(h, "foo") @@ -341,7 +341,7 @@ func (s *pcrImagesMeasurerSuite) TestPcrImagesMeasurerMeasureWithInheritedParams profile := secboot_tpm2.NewPCRProtectionProfile() params := &LoadParams{SnapModel: model} - vars := NewVariableSetCollector(efitest.NewMockHostEnvironment(efitest.MockVars{}, nil)).Next() + vars := NewVariableSetCollector(efitest.NewMockHostEnvironment(nil, nil)).Next() h := crypto.SHA256.New() io.WriteString(h, "foo") @@ -504,7 +504,7 @@ func (s *pcrImagesMeasurerSuite) TestPcrImagesMeasurerMeasureWithFwContext(c *C) profile := secboot_tpm2.NewPCRProtectionProfile() params := new(LoadParams) - vars := NewVariableSetCollector(efitest.NewMockHostEnvironment(efitest.MockVars{}, nil)).Next() + vars := NewVariableSetCollector(efitest.NewMockHostEnvironment(nil, nil)).Next() h := crypto.SHA256.New() io.WriteString(h, "foo") @@ -555,7 +555,7 @@ func (s *pcrImagesMeasurerSuite) TestPcrImagesMeasurerMeasureEnsureFwContextIsCo profile := secboot_tpm2.NewPCRProtectionProfile() params := new(LoadParams) - vars := NewVariableSetCollector(efitest.NewMockHostEnvironment(efitest.MockVars{}, nil)).Next() + vars := NewVariableSetCollector(efitest.NewMockHostEnvironment(nil, nil)).Next() h := crypto.SHA256.New() io.WriteString(h, "foo") @@ -612,7 +612,7 @@ func (s *pcrImagesMeasurerSuite) TestPcrImagesMeasurerMeasureWithShimContext(c * profile := secboot_tpm2.NewPCRProtectionProfile() params := new(LoadParams) - vars := NewVariableSetCollector(efitest.NewMockHostEnvironment(efitest.MockVars{}, nil)).Next() + vars := NewVariableSetCollector(efitest.NewMockHostEnvironment(nil, nil)).Next() h := crypto.SHA256.New() io.WriteString(h, "foo") @@ -663,7 +663,7 @@ func (s *pcrImagesMeasurerSuite) TestPcrImagesMeasurerMeasureEnsureShimContextIs profile := secboot_tpm2.NewPCRProtectionProfile() params := new(LoadParams) - vars := NewVariableSetCollector(efitest.NewMockHostEnvironment(efitest.MockVars{}, nil)).Next() + vars := NewVariableSetCollector(efitest.NewMockHostEnvironment(nil, nil)).Next() h := crypto.SHA256.New() io.WriteString(h, "foo") diff --git a/efi/pcr_profile_test.go b/efi/pcr_profile_test.go index f1ad92d9..cbb76685 100644 --- a/efi/pcr_profile_test.go +++ b/efi/pcr_profile_test.go @@ -112,7 +112,7 @@ func (s *pcrProfileMockedSuite) TestAddPCRProfileLog(c *C) { defer restore() c.Check(AddPCRProfile(tpm2.HashAlgorithmSHA256, profile.RootBranch(), sequences, - WithHostEnvironment(efitest.NewMockHostEnvironment(efitest.MockVars{}, expectedLog)), + WithHostEnvironment(efitest.NewMockHostEnvironment(nil, expectedLog)), WithSecureBootPolicyProfile(), ), IsNil) } diff --git a/efi/shim_test.go b/efi/shim_test.go index 406f4b80..f232850c 100644 --- a/efi/shim_test.go +++ b/efi/shim_test.go @@ -58,7 +58,7 @@ func (s *shimSuite) TestReadShimSbatPolicyLatest(c *C) { } func (s *shimSuite) TestReadShimSbatPolicyNotExist(c *C) { - env := efitest.NewMockHostEnvironment(efitest.MockVars{}, nil) + env := efitest.NewMockHostEnvironment(nil, nil) policy, err := ReadShimSbatPolicy(newMockVarReader(env)) c.Check(err, IsNil) c.Check(policy, Equals, ShimSbatPolicyPrevious) @@ -400,7 +400,7 @@ func (s *shimSuite) TestShimSbatPolicyLatestUnset(c *C) { c.Assert(visitor.varModifiers, HasLen, 1) - collector := NewVariableSetCollector(efitest.NewMockHostEnvironment(efitest.MockVars{}, nil)) + collector := NewVariableSetCollector(efitest.NewMockHostEnvironment(nil, nil)) c.Check(visitor.varModifiers[0](collector.PeekAll()[0]), IsNil) c.Assert(collector.More(), testutil.IsTrue) diff --git a/internal/efitest/hostenv.go b/internal/efitest/hostenv.go index 09a2422d..0f63fecf 100644 --- a/internal/efitest/hostenv.go +++ b/internal/efitest/hostenv.go @@ -210,9 +210,6 @@ func NewMockHostEnvironmentWithOpts(options ...MockHostEnvironmentOption) *MockH // VarContext implements [github.com/snapcore/secboot/internal/efi.HostEnvironmentEFI.VarContext]. func (e *MockHostEnvironment) VarContext(parent context.Context) context.Context { - if e.Vars == nil { - return parent - } return context.WithValue(parent, efi.VarsBackendKey{}, e.Vars) } From 046e61175341228296f648443dea32364bcf0506 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Tue, 23 Jul 2024 12:08:21 +0100 Subject: [PATCH 4/4] internal/efi: add some extra tests --- internal/efi/default_env_test.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/internal/efi/default_env_test.go b/internal/efi/default_env_test.go index 63fb8a2d..a2879bad 100644 --- a/internal/efi/default_env_test.go +++ b/internal/efi/default_env_test.go @@ -279,3 +279,29 @@ func (s *defaultEnvSuite) TestDevicesForClassIOMMU(c *C) { c.Check(devices[0].Path(), Equals, filepath.Join(path, "devices/virtual/iommu/dmar0")) c.Check(devices[0].Subsystem(), Equals, "iommu") } + +func (s *defaultEnvSuite) TestDevicesForClassNotExist(c *C) { + _, restore := s.mockSysfsPath(c, "testdata/sys.tar") + defer restore() + + devices, err := DefaultEnv.DevicesForClass("foo") + c.Check(err, IsNil) + c.Assert(devices, HasLen, 0) +} + +func (s *defaultEnvSuite) TestSysfsDeviceAttributeReaderNoAttr(c *C) { + _, restore := s.mockSysfsPath(c, "testdata/sys.tar") + defer restore() + + devices, err := DefaultEnv.DevicesForClass("mei") + c.Check(err, IsNil) + c.Assert(devices, HasLen, 1) + + _, err = devices[0].AttributeReader("uevent") + c.Check(err, Equals, ErrNoDeviceAttribute) + _, err = devices[0].AttributeReader("foo") + c.Check(err, Equals, ErrNoDeviceAttribute) + _, err = devices[0].AttributeReader("subsystem") + c.Check(err, Equals, ErrNoDeviceAttribute) + +}