Skip to content

Commit

Permalink
internal/efi: Add HostEnvironment bits for efi/preinstall
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
chrisccoulson committed Jul 10, 2024
1 parent 526df75 commit dbf3805
Show file tree
Hide file tree
Showing 20 changed files with 1,075 additions and 48 deletions.
2 changes: 1 addition & 1 deletion efi/efi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
16 changes: 1 addition & 15 deletions efi/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,28 +29,14 @@ 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"
)

// 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
Expand Down
6 changes: 3 additions & 3 deletions efi/fw_load_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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())
Expand Down
3 changes: 2 additions & 1 deletion efi/pcr_profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
4 changes: 2 additions & 2 deletions efi/shim_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand All @@ -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=
Expand Down
160 changes: 156 additions & 4 deletions internal/efi/default_env.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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{}
Loading

0 comments on commit dbf3805

Please sign in to comment.