blob: ada393c5a8ae6540d374d1de692b823a010028ad [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
2// SPDX-License-Identifier: Apache-2.0
3
Lorenz Brunca1cff02023-06-26 17:52:44 +02004package efivarfs
5
6import (
7 "bytes"
8 "encoding/binary"
9 "errors"
10 "fmt"
11 "math"
12 "strings"
13
14 "github.com/google/uuid"
15
Tim Windelschmidt9f21f532024-05-07 15:14:20 +020016 "source.monogon.dev/osbase/msguid"
Lorenz Brunca1cff02023-06-26 17:52:44 +020017)
18
19// DevicePath represents a path consisting of one or more elements to an
20// entity implementing an EFI protocol. It's very broadly used inside EFI
21// for representing all sorts of abstract paths. In the context of this
22// package it is used to represent paths to EFI loaders.
23// See https://uefi.org/specs/UEFI/2.10/10_Protocols_Device_Path_Protocol.html
24// for more information.
25type DevicePath []DevicePathElem
26
27// DevicePathElem is a common interface for all UEFI device path elements.
28type DevicePathElem interface {
29 typ() uint8
30 subType() uint8
31 data() ([]byte, error)
32}
33
34type pathElemUnmarshalFunc func([]byte) (DevicePathElem, error)
35
36// PartitionMBR matches a drive or partition formatted with legacy MBR
37// (Master Boot Record).
38type PartitionMBR struct {
39 // DiskSignature contains a 4-byte signature identifying the drive, located
40 // just after the 440 bytes of boot sector loading code.
41 // Note that since MBR does not have per-partition signatures, this is
42 // combined with PartitionNumber to select a partition.
43 DiskSignature [4]byte
44}
45
46func (p PartitionMBR) partitionSignature() (sig [16]byte) {
47 copy(sig[:4], p.DiskSignature[:])
48 return
49}
50
51func (p PartitionMBR) partitionFormat() uint8 {
52 return 0x01
53}
54
55func (p PartitionMBR) signatureType() uint8 {
56 return 0x01
57}
58
59// PartitionGPT matches a partition on a drive formatted with GPT.
60type PartitionGPT struct {
61 // UUID of the partition to be matched. Conversion into mixed-endian format
62 // is taken care of, a standard big-endian UUID can be put in here.
63 PartitionUUID uuid.UUID
64}
65
66func (p PartitionGPT) partitionSignature() [16]byte {
67 return msguid.From(p.PartitionUUID)
68}
69
70func (p PartitionGPT) partitionFormat() uint8 {
71 return 0x02
72}
73
74func (p PartitionGPT) signatureType() uint8 {
75 return 0x02
76}
77
78// PartitionUnknown is being used to represent unknown partitioning schemas or
79// combinations of PartitionFormat/SignatureType. It contains raw uninterpreted
80// data.
81type PartitionUnknown struct {
82 PartitionSignature [16]byte
83 PartitionFormat uint8
84 SignatureType uint8
85}
86
87func (p PartitionUnknown) partitionSignature() [16]byte {
88 return p.PartitionSignature
89}
90
91func (p PartitionUnknown) partitionFormat() uint8 {
92 return p.PartitionFormat
93}
94
95func (p PartitionUnknown) signatureType() uint8 {
96 return p.SignatureType
97}
98
99type PartitionMatch interface {
100 partitionSignature() [16]byte
101 partitionFormat() uint8
102 signatureType() uint8
103}
104
105// HardDrivePath matches whole drives or partitions on GPT/MBR formatted
106// drives.
107type HardDrivePath struct {
108 // Partition number, starting at 1. If zero or unset, the whole drive is
109 // selected.
110 PartitionNumber uint32
111 // Block address at which the partition starts. Not used for matching
112 // partitions in EDK2.
113 PartitionStartBlock uint64
114 // Number of blocks occupied by the partition starting from the
115 // PartitionStartBlock. Not used for matching partitions in EDK2.
116 PartitionSizeBlocks uint64
117 // PartitionMatch is used to match drive or partition signatures.
118 // Use PartitionMBR and PartitionGPT types here.
119 PartitionMatch PartitionMatch
120}
121
122func (h *HardDrivePath) typ() uint8 {
123 return 4
124}
125
126func (h *HardDrivePath) subType() uint8 {
127 return 1
128}
129
130func (h *HardDrivePath) data() ([]byte, error) {
131 out := make([]byte, 38)
132 le := binary.LittleEndian
133 le.PutUint32(out[0:4], h.PartitionNumber)
134 le.PutUint64(out[4:12], h.PartitionStartBlock)
135 le.PutUint64(out[12:20], h.PartitionSizeBlocks)
136 if h.PartitionMatch == nil {
137 return nil, errors.New("PartitionMatch needs to be set")
138 }
139 sig := h.PartitionMatch.partitionSignature()
140 copy(out[20:36], sig[:])
141 out[36] = h.PartitionMatch.partitionFormat()
142 out[37] = h.PartitionMatch.signatureType()
143 return out, nil
144}
145
146func unmarshalHardDrivePath(data []byte) (DevicePathElem, error) {
147 var h HardDrivePath
148 if len(data) != 38 {
149 return nil, fmt.Errorf("invalid HardDrivePath element, expected 38 bytes, got %d", len(data))
150 }
151 le := binary.LittleEndian
152 h.PartitionNumber = le.Uint32(data[0:4])
153 h.PartitionStartBlock = le.Uint64(data[4:12])
154 h.PartitionSizeBlocks = le.Uint64(data[12:20])
155 partitionFormat := data[36]
156 signatureType := data[37]
157 var rawSig [16]byte
158 copy(rawSig[:], data[20:36])
159 switch {
160 case partitionFormat == 1 && signatureType == 1:
161 // MBR
162 var mbr PartitionMBR
163 copy(mbr.DiskSignature[:], rawSig[:4])
164 h.PartitionMatch = mbr
165 case partitionFormat == 2 && signatureType == 2:
166 // GPT
167 h.PartitionMatch = PartitionGPT{
168 PartitionUUID: msguid.To(rawSig),
169 }
170 default:
171 // Unknown
172 h.PartitionMatch = PartitionUnknown{
173 PartitionSignature: rawSig,
174 PartitionFormat: partitionFormat,
175 SignatureType: signatureType,
176 }
177 }
178 return &h, nil
179}
180
181// FilePath contains a backslash-separated path or part of a path to a file on
182// a filesystem.
183type FilePath string
184
185func (f FilePath) typ() uint8 {
186 return 4
187}
188
189func (f FilePath) subType() uint8 {
190 return 4
191}
192
193func (f FilePath) data() ([]byte, error) {
194 if strings.IndexByte(string(f), 0x00) != -1 {
195 return nil, fmt.Errorf("contains invalid null bytes")
196 }
197 withBackslashes := bytes.ReplaceAll([]byte(f), []byte(`/`), []byte(`\`))
198 out, err := Encoding.NewEncoder().Bytes(withBackslashes)
199 if err != nil {
200 return nil, fmt.Errorf("failed to encode FilePath to UTF-16: %w", err)
201 }
202 return append(out, 0x00, 0x00), nil
203}
204
205func unmarshalFilePath(data []byte) (DevicePathElem, error) {
206 if len(data) < 2 {
207 return nil, fmt.Errorf("FilePath must be at least 2 bytes because of UTF-16 null terminator")
208 }
209 out, err := Encoding.NewDecoder().Bytes(data)
210 if err != nil {
211 return nil, fmt.Errorf("error decoding FilePath UTF-16 string: %w", err)
212 }
213 nullIdx := bytes.IndexByte(out, 0x00)
214 if nullIdx != len(out)-1 {
215 return nil, fmt.Errorf("FilePath not properly null-terminated")
216 }
Tim Windelschmidt13b49472024-04-18 22:39:50 +0200217 withoutBackslashes := strings.ReplaceAll(string(out[:len(out)-1]), `\`, `/`)
Lorenz Brunca1cff02023-06-26 17:52:44 +0200218 return FilePath(withoutBackslashes), nil
219}
220
221// Map key contains type and subtype
222var pathElementUnmarshalMap = map[[2]byte]pathElemUnmarshalFunc{
223 {4, 1}: unmarshalHardDrivePath,
224 {4, 4}: unmarshalFilePath,
225}
226
227// UnknownPath is a generic structure for all types of path elements not
228// understood by this library. The UEFI-specified set of path element
229// types is vast and mostly unused, this generic type allows for parsing as
230// well as pass-through of not-understood path elements.
231type UnknownPath struct {
232 TypeVal uint8
233 SubTypeVal uint8
234 DataVal []byte
235}
236
237func (u UnknownPath) typ() uint8 {
238 return u.TypeVal
239}
240
241func (u UnknownPath) subType() uint8 {
242 return u.SubTypeVal
243}
244
245func (u UnknownPath) data() ([]byte, error) {
246 return u.DataVal, nil
247}
248
249// Marshal encodes the device path in binary form.
250func (d DevicePath) Marshal() ([]byte, error) {
251 var buf []byte
252 for _, p := range d {
253 buf = append(buf, p.typ(), p.subType())
254 elemBuf, err := p.data()
255 if err != nil {
256 return nil, fmt.Errorf("failed marshaling path element: %w", err)
257 }
258 // 4 is size of header which is included in length field
259 if len(elemBuf)+4 > math.MaxUint16 {
260 return nil, fmt.Errorf("path element payload over maximum size")
261 }
262 buf = append16(buf, uint16(len(elemBuf)+4))
263 buf = append(buf, elemBuf...)
264 }
265 // End of device path (Type 0x7f, SubType 0xFF)
266 buf = append(buf, 0x7f, 0xff, 0x04, 0x00)
267 return buf, nil
268}
269
Lorenz Brunf025d1b2023-08-07 14:52:49 +0200270// UnmarshalDevicePath parses a binary device path until it encounters an end
271// device path structure. It returns that device path (excluding the final end
272// device path marker) as well as all all data following the end marker.
273func UnmarshalDevicePath(data []byte) (DevicePath, []byte, error) {
Lorenz Brunca1cff02023-06-26 17:52:44 +0200274 rest := data
275 var p DevicePath
276 for {
277 if len(rest) < 4 {
278 if len(rest) != 0 {
Lorenz Brunf025d1b2023-08-07 14:52:49 +0200279 return nil, nil, fmt.Errorf("dangling bytes at the end of device path: %x", rest)
Lorenz Brunca1cff02023-06-26 17:52:44 +0200280 }
281 break
282 }
283 t := rest[0]
284 subT := rest[1]
285 dataLen := binary.LittleEndian.Uint16(rest[2:4])
286 if int(dataLen) > len(rest) {
Lorenz Brunf025d1b2023-08-07 14:52:49 +0200287 return nil, nil, fmt.Errorf("path element larger than rest of buffer: %d > %d", dataLen, len(rest))
Lorenz Brunca1cff02023-06-26 17:52:44 +0200288 }
289 if dataLen < 4 {
Lorenz Brunf025d1b2023-08-07 14:52:49 +0200290 return nil, nil, fmt.Errorf("path element must be at least 4 bytes (header), length indicates %d", dataLen)
Lorenz Brunca1cff02023-06-26 17:52:44 +0200291 }
292 elemData := rest[4:dataLen]
293 rest = rest[dataLen:]
294
Lorenz Brunf025d1b2023-08-07 14:52:49 +0200295 // End of Device Path
296 if t == 0x7f && subT == 0xff {
297 return p, rest, nil
298 }
299
Lorenz Brunca1cff02023-06-26 17:52:44 +0200300 unmarshal, ok := pathElementUnmarshalMap[[2]byte{t, subT}]
301 if !ok {
302 p = append(p, &UnknownPath{
303 TypeVal: t,
304 SubTypeVal: subT,
305 DataVal: elemData,
306 })
307 continue
308 }
309 elem, err := unmarshal(elemData)
310 if err != nil {
Lorenz Brunf025d1b2023-08-07 14:52:49 +0200311 return nil, nil, fmt.Errorf("failed decoding path element %d: %w", len(p), err)
Lorenz Brunca1cff02023-06-26 17:52:44 +0200312 }
313 p = append(p, elem)
314 }
Lorenz Brunf025d1b2023-08-07 14:52:49 +0200315 if len(p) == 0 {
316 return nil, nil, errors.New("empty DevicePath without End Of Path element")
Lorenz Brunca1cff02023-06-26 17:52:44 +0200317 }
Lorenz Brunf025d1b2023-08-07 14:52:49 +0200318 return nil, nil, fmt.Errorf("got DevicePath with %d elements, but without End Of Path element", len(p))
Lorenz Brunca1cff02023-06-26 17:52:44 +0200319}