Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

internal/efi: Add HostEnvironment bits for efi/preinstall #314

Merged
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
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
4 changes: 2 additions & 2 deletions efi/env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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()
Expand Down
20 changes: 10 additions & 10 deletions efi/pcr_images_measurer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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")
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
2 changes: 1 addition & 1 deletion efi/pcr_profile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
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
Comment on lines +139 to +145
Copy link
Collaborator

Choose a reason for hiding this comment

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

these error path are no tested

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I've added a test for this

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
Copy link
Collaborator

Choose a reason for hiding this comment

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

same for this

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I've added a test for this

}

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
Copy link
Collaborator

Choose a reason for hiding this comment

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

this case is not tested

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I've added a test for this

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
Loading