m/p/efivarfs: allow multiple paths in LoadOption
The FilePathList in an EFI LOAD_OPTION technically allows multiple
device paths separated by end of path markers. These additional paths
beyond the first one do not have any standardized function, but some
EFI vendors use them to store data. Currently we fail to parse them
because they contain more than one end of path marker. Fix this by
adding an ExtraPaths field to LoadOption and implementing marshalling/
unmarshalling for it. I chose to use a separate field in the Go
struct because these additional paths do not have the same functionality
as the first one (they are not bootable).
Change-Id: I96df17915966a973a7553fe9864a0b3e6f6b61e8
Reviewed-on: https://review.monogon.dev/c/monogon/+/2037
Reviewed-by: Serge Bazanski <serge@monogon.tech>
Reviewed-by: Tim Windelschmidt <tim@monogon.tech>
Tested-by: Jenkins CI
diff --git a/metropolis/pkg/efivarfs/devicepath.go b/metropolis/pkg/efivarfs/devicepath.go
index 1606fd6..8e9dddb 100644
--- a/metropolis/pkg/efivarfs/devicepath.go
+++ b/metropolis/pkg/efivarfs/devicepath.go
@@ -264,14 +264,16 @@
return buf, nil
}
-// UnmarshalDevicePath parses a binary device path.
-func UnmarshalDevicePath(data []byte) (DevicePath, error) {
+// UnmarshalDevicePath parses a binary device path until it encounters an end
+// device path structure. It returns that device path (excluding the final end
+// device path marker) as well as all all data following the end marker.
+func UnmarshalDevicePath(data []byte) (DevicePath, []byte, error) {
rest := data
var p DevicePath
for {
if len(rest) < 4 {
if len(rest) != 0 {
- return nil, fmt.Errorf("dangling bytes at the end of device path: %x", rest)
+ return nil, nil, fmt.Errorf("dangling bytes at the end of device path: %x", rest)
}
break
}
@@ -279,14 +281,19 @@
subT := rest[1]
dataLen := binary.LittleEndian.Uint16(rest[2:4])
if int(dataLen) > len(rest) {
- return nil, fmt.Errorf("path element larger than rest of buffer: %d > %d", dataLen, len(rest))
+ return nil, nil, fmt.Errorf("path element larger than rest of buffer: %d > %d", dataLen, len(rest))
}
if dataLen < 4 {
- return nil, fmt.Errorf("path element must be at least 4 bytes (header), length indicates %d", dataLen)
+ return nil, nil, fmt.Errorf("path element must be at least 4 bytes (header), length indicates %d", dataLen)
}
elemData := rest[4:dataLen]
rest = rest[dataLen:]
+ // End of Device Path
+ if t == 0x7f && subT == 0xff {
+ return p, rest, nil
+ }
+
unmarshal, ok := pathElementUnmarshalMap[[2]byte{t, subT}]
if !ok {
p = append(p, &UnknownPath{
@@ -298,26 +305,12 @@
}
elem, err := unmarshal(elemData)
if err != nil {
- return nil, fmt.Errorf("failed decoding path element %d: %w", len(p), err)
+ return nil, nil, fmt.Errorf("failed decoding path element %d: %w", len(p), err)
}
p = append(p, elem)
}
- var endOfPathIdx int
- for i, e := range p {
- if e.typ() == 0x7f && e.subType() == 0xff {
- endOfPathIdx = i
- break
- }
+ if len(p) == 0 {
+ return nil, nil, errors.New("empty DevicePath without End Of Path element")
}
- switch {
- case len(p) == 0:
- return nil, errors.New("empty DevicePath without End Of Path element")
- case endOfPathIdx == -1:
- return nil, fmt.Errorf("got DevicePath with %d elements, but without End Of Path element", len(p))
- case endOfPathIdx != len(p)-1:
- return nil, fmt.Errorf("got DevicePath with %d elements with End Of Path element at %d (wanted as last element)", len(p), endOfPathIdx)
- }
- p = p[:len(p)-1]
-
- return p, nil
+ return nil, nil, fmt.Errorf("got DevicePath with %d elements, but without End Of Path element", len(p))
}