diff --git a/go.mod b/go.mod index 36805cb9d2f..626081ab4f5 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.22.4 require ( github.com/bramvdbogaerde/go-scp v1.5.0 - github.com/canonical/go-efilib v1.2.0 + github.com/canonical/go-efilib v1.3.1 github.com/cavaliergopher/grab/v3 v3.0.1 github.com/cenkalti/backoff/v4 v4.3.0 github.com/containerd/containerd v1.7.22 diff --git a/go.sum b/go.sum index 66d794b01be..44a539030ce 100644 --- a/go.sum +++ b/go.sum @@ -15,8 +15,8 @@ github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/bramvdbogaerde/go-scp v1.5.0 h1:a9BinAjTfQh273eh7vd3qUgmBC+bx+3TRDtkZWmIpzM= github.com/bramvdbogaerde/go-scp v1.5.0/go.mod h1:on2aH5AxaFb2G0N5Vsdy6B0Ml7k9HuHSwfo1y0QzAbQ= -github.com/canonical/go-efilib v1.2.0 h1:+fvJdkj3oVyURFtfk8gSft6pdKyVzzdzNn9GC1kMJw8= -github.com/canonical/go-efilib v1.2.0/go.mod h1:n0Ttsy1JuHAvqaFbZBs6PAzoiiJdfkHsAmDOEbexYEQ= +github.com/canonical/go-efilib v1.3.1 h1:KnVlqrKn0ZDGAbgQt9tke5cvtqNRCmpEp0v7RGUVpqs= +github.com/canonical/go-efilib v1.3.1/go.mod h1:n0Ttsy1JuHAvqaFbZBs6PAzoiiJdfkHsAmDOEbexYEQ= github.com/cavaliergopher/grab/v3 v3.0.1 h1:4z7TkBfmPjmLAAmkkAZNX/6QJ1nNFdv3SdIHXju0Fr4= github.com/cavaliergopher/grab/v3 v3.0.1/go.mod h1:1U/KNnD+Ft6JJiYoYBAimKH2XrYptb8Kl3DFGmsjpq4= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= diff --git a/vendor/github.com/canonical/go-efilib/devicepath.go b/vendor/github.com/canonical/go-efilib/devicepath.go index de63c3a7403..305ab236aab 100644 --- a/vendor/github.com/canonical/go-efilib/devicepath.go +++ b/vendor/github.com/canonical/go-efilib/devicepath.go @@ -22,6 +22,35 @@ import ( "github.com/canonical/go-efilib/mbr" ) +// DevicePathShortFormType describes whether a path is a recognized short-form +// path, and what type it is. +type DevicePathShortFormType int + +const ( + // DevicePathNotShortForm indicates that a path is not a recognized short-form path + DevicePathNotShortForm DevicePathShortFormType = iota + + // DevicePathShortFormHD indicates that a path is a HD() short-form path + DevicePathShortFormHD + + // DevicePathShortFormUSBWWID indicates that a path is a UsbWwid() short-form path + DevicePathShortFormUSBWWID + + // DevicePathShortFormUSBClass indicates that a path is a UsbClass() short-form path + DevicePathShortFormUSBClass + + // DevicePathShortFormURI indicates that a path is a Uri() short-form path. Note that + // this package does not currently directly support device paths containing URIs. + DevicePathShortFormURI + + // DevicePathShortFormFilePath indicates that a path is a file path short-form path + DevicePathShortFormFilePath +) + +func (t DevicePathShortFormType) IsShortForm() bool { + return t > DevicePathNotShortForm +} + // DevicePathMatch indicates how a device path matched type DevicePathMatch int @@ -36,6 +65,14 @@ const ( // *[HardDriveDevicePathNode] and matches the end of the longer device path. DevicePathShortFormHDMatch + // DevicePathShortFormUSBWWIDMatch indicates that one device path begins with + // a *[USBWWIDDevicePathNode] and matches the end of the longer device path. + DevicePathShortFormUSBWWIDMatch + + // DevicePathShortFormUSBClassMatch indicates that one device path begins with + // a *[USBClassDevicePathNode] and matches the end of the longer device path. + DevicePathShortFormUSBClassMatch + // DevicePathShortFormFileMatch indicates that one device path begins with a // [FilePathDevicePathNode] and matches the end of the longer device path. DevicePathShortFormFileMatch @@ -136,7 +173,10 @@ func (p DevicePath) Write(w io.Writer) error { return binary.Write(w, binary.LittleEndian, &end) } -func devicePathFindFirst[T DevicePathNode](p DevicePath) DevicePath { +// DevicePathFindFirstOccurrence finds the first occurrence of the device path +// node with the specified type and returns it and the remaining components of +// the device path. +func DevicePathFindFirstOccurrence[T DevicePathNode](p DevicePath) DevicePath { for i, n := range p { if _, ok := n.(T); ok { return p[i:] @@ -155,46 +195,84 @@ func (p DevicePath) matchesInternal(other DevicePath, onlyFull bool) DevicePathM return DevicePathNoMatch } if bytes.Equal(pBytes.Bytes(), otherBytes.Bytes()) { + // We have a full, exact match return DevicePathFullMatch } if onlyFull { - return DevicePathNoMatch - } - if len(other) == 0 { + // If we're only permitted to find a full match, return no match now. return DevicePathNoMatch } - switch n := other[0].(type) { - case *HardDriveDevicePathNode: - _ = n - p = devicePathFindFirst[*HardDriveDevicePathNode](p) - if res := p.matchesInternal(other, true); res == DevicePathFullMatch && len(p) == 2 { + // Check if other is a short-form path. If so, convert p to the same type of + // short-form path and test if there is a short-form match. + switch other.ShortFormType() { + case DevicePathShortFormHD: + p = DevicePathFindFirstOccurrence[*HardDriveDevicePathNode](p) + if res := p.matchesInternal(other, true); res == DevicePathFullMatch { return DevicePathShortFormHDMatch } - case FilePathDevicePathNode: - _ = n - p = devicePathFindFirst[FilePathDevicePathNode](p) - if res := p.matchesInternal(other, true); res == DevicePathFullMatch && len(p) == 1 { + case DevicePathShortFormUSBWWID: + p = DevicePathFindFirstOccurrence[*USBWWIDDevicePathNode](p) + if res := p.matchesInternal(other, true); res == DevicePathFullMatch { + return DevicePathShortFormUSBWWIDMatch + } + case DevicePathShortFormUSBClass: + p = DevicePathFindFirstOccurrence[*USBClassDevicePathNode](p) + if res := p.matchesInternal(other, true); res == DevicePathFullMatch { + return DevicePathShortFormUSBClassMatch + } + case DevicePathShortFormFilePath: + p = DevicePathFindFirstOccurrence[FilePathDevicePathNode](p) + if res := p.matchesInternal(other, true); res == DevicePathFullMatch { return DevicePathShortFormFileMatch } - default: - // TODO: handle short form USB WWID, USB Class and URI device paths } return DevicePathNoMatch } // Matches indicates whether other matches this path in some way, and returns -// the type of match. If other begins with *[HardDriveDevicePathNode] and is 2 -// nodes long, this may return DevicePathShortFormHDMatch. If other begins with -// [FilePathDevicePathNode] and is a single node long, this may return -// DevicePathShortFormFileMatch. This returns DevicePathFullMatch if the supplied -// path fully matches, and DevicePathNoMatch if there is no match. +// the type of match. If other is a HD() short-form path, this may return +// DevicePathShortFormHDMatch. If other is a UsbWwid() short-form path, this may +// return DevicePathShortFormUSBWWIDMatch. If other is a UsbClass() short-form path, +// this may return DevicePathShortFormUSBClassMatch. If other is a file path short-form +// path, this may return DevicePathShortFormFileMatch. This returns DevicePathFullMatch +// if the supplied path fully matches, and DevicePathNoMatch if there is no match. func (p DevicePath) Matches(other DevicePath) DevicePathMatch { return p.matchesInternal(other, false) } +// ShortFormType returns whether this is a short-form type of path, and if so, +// what type of short-form path. The UEFI boot manager is required to handle a +// certain set of well defined short-form paths that begin with a specific +// component. +func (p DevicePath) ShortFormType() DevicePathShortFormType { + if len(p) == 0 { + return DevicePathNotShortForm + } + + switch n := p[0].(type) { + case *HardDriveDevicePathNode: + _ = n + return DevicePathShortFormHD + case *USBWWIDDevicePathNode: + _ = n + return DevicePathShortFormUSBWWID + case *USBClassDevicePathNode: + _ = n + return DevicePathShortFormUSBClass + case *GenericDevicePathNode: + if n.Type == MessagingDevicePath && n.SubType == uefi.MSG_URI_DP { + return DevicePathShortFormURI + } + case FilePathDevicePathNode: + return DevicePathShortFormFilePath + } + + return DevicePathNotShortForm +} + // GenericDevicePathNode corresponds to a device path nodes with a type that is // not handled by this package type GenericDevicePathNode struct { diff --git a/vendor/github.com/canonical/go-efilib/internal/uefi/devicepath.go b/vendor/github.com/canonical/go-efilib/internal/uefi/devicepath.go index 508837d9960..e9172f94931 100644 --- a/vendor/github.com/canonical/go-efilib/internal/uefi/devicepath.go +++ b/vendor/github.com/canonical/go-efilib/internal/uefi/devicepath.go @@ -38,6 +38,7 @@ const ( MSG_DEVICE_LOGICAL_UNIT_DP = 0x11 MSG_SATA_DP = 0x12 MSG_NVME_NAMESPACE_DP = 0x17 + MSG_URI_DP = 0x18 MEDIA_HARDDRIVE_DP = 0x01 MEDIA_CDROM_DP = 0x02 diff --git a/vendor/github.com/canonical/go-efilib/vars.go b/vendor/github.com/canonical/go-efilib/vars.go index 52e7ee5346f..499e7bd091c 100644 --- a/vendor/github.com/canonical/go-efilib/vars.go +++ b/vendor/github.com/canonical/go-efilib/vars.go @@ -107,32 +107,59 @@ func (v nullVarsBackend) List() ([]VariableDescriptor, error) { return nil, ErrVarsUnavailable } +func isContextDone(ctx context.Context) error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + return nil + } +} + // ReadVariable returns the value and attributes of the EFI variable with the specified -// name and GUID. In general, [DefaultVarContext] should be supplied to this. +// name and GUID. In general, [DefaultVarContext] or the result of [WithDefaultVarsBackend] +// should be supplied to this. This will return an error if the context is done. func ReadVariable(ctx context.Context, name string, guid GUID) ([]byte, VariableAttributes, error) { - attrs, data, err := getVarsBackend(ctx).Get(ctx, name, guid) + backend := getVarsBackend(ctx) + if err := isContextDone(ctx); err != nil { + return nil, 0, err + } + attrs, data, err := backend.Get(ctx, name, guid) return data, attrs, err } // WriteVariable writes the supplied data value with the specified attributes to the -// EFI variable with the specified name and GUID. In general, [DefaultVarContext] should -// be supplied to this. +// EFI variable with the specified name and GUID. In general, [DefaultVarContext] or the +// result of [WithDefaultVarsBackend] should be supplied to this. This will return an error +// if the context is done. // // If the variable already exists, the specified attributes must match the existing // attributes with the exception of AttributeAppendWrite. // // If the variable does not exist, it will be created. func WriteVariable(ctx context.Context, name string, guid GUID, attrs VariableAttributes, data []byte) error { - return getVarsBackend(ctx).Set(ctx, name, guid, attrs, data) + backend := getVarsBackend(ctx) + if err := isContextDone(ctx); err != nil { + return err + } + return backend.Set(ctx, name, guid, attrs, data) } // ListVariables returns a sorted list of variables that can be accessed. In -// general, [DefaultVarContext] should be supplied to this. +// general, [DefaultVarContext] or the result of [WithDefaultVarsBackend] should +// be supplied to this. This will return an error if the context is done. func ListVariables(ctx context.Context) ([]VariableDescriptor, error) { - names, err := getVarsBackend(ctx).List(ctx) + backend := getVarsBackend(ctx) + if err := isContextDone(ctx); err != nil { + return nil, err + } + names, err := backend.List(ctx) if err != nil { return nil, err } + if err := isContextDone(ctx); err != nil { + return nil, err + } sort.Stable(variableDescriptorSlice(names)) return names, nil } @@ -182,11 +209,28 @@ func newDefaultVarContext() context.Context { // DefaultVarContext should generally be passed to functions that interact with // EFI variables in order to use the default system backend for accessing EFI -// variables. It is based on a background context. +// variables. It is based on a new background context. +// +// On Linux, this uses efivarfs. The kernel rate limits unprivileged users' read +// accesses to the EFI variable runtime service to 100 accesses per second, after +// which any thread that attempts to perform a read access will sleep in an +// uninterruptible state. This makes adding a deadline to the context for sections +// of code that perform multiple variable reads worthwhile in some cases. +// Unfortunately, there is no way to determine whether an access will be ratelimited +// before performing it. var DefaultVarContext = newDefaultVarContext() // WithDefaultVarsBackend adds the default system backend for accesssing EFI -// variables to an existing context. +// variables to an existing context. It allows for usage of any context other +// than the internally created background one. +// +// On Linux, this uses efivarfs. The kernel rate limits unprivileged users' read +// accesses to the EFI variable runtime service to 100 accesses per second, after +// which any thread that attempts to perform a read access will sleep in an +// uninterruptible state. This makes adding a deadline to the context for sections +// of code that perform multiple variable reads worthwhile in some cases. +// Unfortunately, there is no way to determine whether an access will be ratelimited +// before performing it. func WithDefaultVarsBackend(ctx context.Context) context.Context { return addDefaultVarsBackend(ctx) } diff --git a/vendor/modules.txt b/vendor/modules.txt index 144632e9ae5..3ddcd34ee12 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -29,7 +29,7 @@ github.com/StackExchange/wmi # github.com/bramvdbogaerde/go-scp v1.5.0 ## explicit; go 1.21 github.com/bramvdbogaerde/go-scp -# github.com/canonical/go-efilib v1.2.0 +# github.com/canonical/go-efilib v1.3.1 ## explicit; go 1.18 github.com/canonical/go-efilib github.com/canonical/go-efilib/internal/ioerr