-
Notifications
You must be signed in to change notification settings - Fork 17
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
Changes from all commits
1bc68cf
4dac53e
d0de70e
046e611
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same for this There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this case is not tested There was a problem hiding this comment. Choose a reason for hiding this commentThe 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{} |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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