blob: 41fa7ce0d04bcd5d058b29a63083c7bf4800f4eb [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
2// SPDX-License-Identifier: Apache-2.0
Mateusz Zalega6cefe512021-11-08 18:19:42 +01003
4package efivarfs
5
6import (
Lorenz Brunca1cff02023-06-26 17:52:44 +02007 "bytes"
8 "encoding/binary"
9 "errors"
Mateusz Zalega6cefe512021-11-08 18:19:42 +010010 "fmt"
11 "math"
Mateusz Zalega612a0332021-11-17 20:04:52 +010012 "strings"
Mateusz Zalega6cefe512021-11-08 18:19:42 +010013)
14
Lorenz Brunca1cff02023-06-26 17:52:44 +020015type LoadOptionCategory uint8
Mateusz Zalega6cefe512021-11-08 18:19:42 +010016
Lorenz Brunca1cff02023-06-26 17:52:44 +020017const (
18 // Boot entries belonging to the Boot category are normal boot entries.
19 LoadOptionCategoryBoot LoadOptionCategory = 0x0
20 // Boot entries belonging to the App category are not booted as part of
21 // the normal boot order, but are only launched via menu or hotkey.
22 // This category is optional for bootloaders to support, before creating
23 // new boot entries of this category firmware support needs to be
24 // confirmed.
25 LoadOptionCategoryApp LoadOptionCategory = 0x1
26)
Mateusz Zalega6cefe512021-11-08 18:19:42 +010027
Lorenz Brunca1cff02023-06-26 17:52:44 +020028// LoadOption contains information on a payload to be loaded by EFI.
29type LoadOption struct {
30 // Human-readable description of what this load option loads.
31 // This is what's being shown by the firmware when selecting a boot option.
32 Description string
33 // If set, firmware will skip this load option when it is in BootOrder.
34 // It is unspecificed whether this prevents the user from booting the entry
35 // manually.
36 Inactive bool
37 // If set, this load option will not be shown in any menu for load option
38 // selection. This does not affect other functionality.
39 Hidden bool
40 // Category contains the category of the load entry. The selected category
41 // affects various firmware behaviors, see the individual value
42 // descriptions for more information.
43 Category LoadOptionCategory
44 // Path to the UEFI PE executable to execute when this load option is being
45 // loaded.
46 FilePath DevicePath
Lorenz Brunf025d1b2023-08-07 14:52:49 +020047 // ExtraPaths contains additional device paths with vendor-specific
48 // behavior. Can generally be left empty.
49 ExtraPaths []DevicePath
Lorenz Brunca1cff02023-06-26 17:52:44 +020050 // OptionalData gets passed as an argument to the executed PE executable.
51 // If zero-length a NULL value is passed to the executable.
52 OptionalData []byte
Mateusz Zalega6cefe512021-11-08 18:19:42 +010053}
54
Lorenz Brunca1cff02023-06-26 17:52:44 +020055// Marshal encodes a LoadOption into a binary EFI_LOAD_OPTION.
56func (e *LoadOption) Marshal() ([]byte, error) {
57 var data []byte
58 var attrs uint32
59 attrs |= (uint32(e.Category) & 0x1f) << 8
60 if e.Hidden {
61 attrs |= 0x08
Mateusz Zalega6cefe512021-11-08 18:19:42 +010062 }
Lorenz Brunca1cff02023-06-26 17:52:44 +020063 if !e.Inactive {
64 attrs |= 0x01
Mateusz Zalega6cefe512021-11-08 18:19:42 +010065 }
Lorenz Brunca1cff02023-06-26 17:52:44 +020066 data = append32(data, attrs)
67 filePathRaw, err := e.FilePath.Marshal()
68 if err != nil {
69 return nil, fmt.Errorf("failed marshalling FilePath: %w", err)
Mateusz Zalega6cefe512021-11-08 18:19:42 +010070 }
Lorenz Brunf025d1b2023-08-07 14:52:49 +020071 for _, ep := range e.ExtraPaths {
72 epRaw, err := ep.Marshal()
73 if err != nil {
74 return nil, fmt.Errorf("failed marshalling ExtraPath: %w", err)
75 }
76 filePathRaw = append(filePathRaw, epRaw...)
77 }
Lorenz Brunca1cff02023-06-26 17:52:44 +020078 if len(filePathRaw) > math.MaxUint16 {
Lorenz Brunf025d1b2023-08-07 14:52:49 +020079 return nil, fmt.Errorf("failed marshalling FilePath/ExtraPath: value too big (%d)", len(filePathRaw))
Mateusz Zalega6cefe512021-11-08 18:19:42 +010080 }
Lorenz Brunca1cff02023-06-26 17:52:44 +020081 data = append16(data, uint16(len(filePathRaw)))
82 if strings.IndexByte(e.Description, 0x00) != -1 {
83 return nil, fmt.Errorf("failed to encode Description: contains invalid null bytes")
Mateusz Zalega6cefe512021-11-08 18:19:42 +010084 }
Lorenz Brunca1cff02023-06-26 17:52:44 +020085 encodedDesc, err := Encoding.NewEncoder().Bytes([]byte(e.Description))
86 if err != nil {
87 return nil, fmt.Errorf("failed to encode Description: %w", err)
88 }
89 data = append(data, encodedDesc...)
90 data = append(data, 0x00, 0x00) // Final UTF-16/UCS-2 null code
91 data = append(data, filePathRaw...)
92 data = append(data, e.OptionalData...)
93 return data, nil
Mateusz Zalega6cefe512021-11-08 18:19:42 +010094}
95
Lorenz Brunca1cff02023-06-26 17:52:44 +020096// UnmarshalLoadOption decodes a binary EFI_LOAD_OPTION into a LoadOption.
97func UnmarshalLoadOption(data []byte) (*LoadOption, error) {
98 if len(data) < 6 {
99 return nil, fmt.Errorf("invalid load option: minimum 6 bytes are required, got %d", len(data))
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100100 }
Lorenz Brunca1cff02023-06-26 17:52:44 +0200101 var opt LoadOption
102 attrs := binary.LittleEndian.Uint32(data[:4])
103 opt.Category = LoadOptionCategory((attrs >> 8) & 0x1f)
104 opt.Hidden = attrs&0x08 != 0
105 opt.Inactive = attrs&0x01 == 0
106 lenPath := binary.LittleEndian.Uint16(data[4:6])
107 // Search for UTF-16 null code
108 nullIdx := bytes.Index(data[6:], []byte{0x00, 0x00})
109 if nullIdx == -1 {
110 return nil, errors.New("no null code point marking end of Description found")
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100111 }
Lorenz Brunca1cff02023-06-26 17:52:44 +0200112 descriptionEnd := 6 + nullIdx + 1
113 descriptionRaw := data[6:descriptionEnd]
114 description, err := Encoding.NewDecoder().Bytes(descriptionRaw)
115 if err != nil {
116 return nil, fmt.Errorf("error decoding UTF-16 in Description: %w", err)
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100117 }
Lorenz Brunca1cff02023-06-26 17:52:44 +0200118 descriptionEnd += 2 // 2 null bytes terminating UTF-16 string
119 opt.Description = string(description)
120 if descriptionEnd+int(lenPath) > len(data) {
121 return nil, fmt.Errorf("declared length of FilePath (%d) overruns available data (%d)", lenPath, len(data)-descriptionEnd)
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100122 }
Lorenz Brunca1cff02023-06-26 17:52:44 +0200123 filePathData := data[descriptionEnd : descriptionEnd+int(lenPath)]
Lorenz Brunf025d1b2023-08-07 14:52:49 +0200124 opt.FilePath, filePathData, err = UnmarshalDevicePath(filePathData)
Lorenz Brunca1cff02023-06-26 17:52:44 +0200125 if err != nil {
126 return nil, fmt.Errorf("failed unmarshaling FilePath: %w", err)
127 }
Lorenz Brunf025d1b2023-08-07 14:52:49 +0200128 for len(filePathData) > 0 {
129 var extraPath DevicePath
130 extraPath, filePathData, err = UnmarshalDevicePath(filePathData)
131 if err != nil {
132 return nil, fmt.Errorf("failed unmarshaling ExtraPath: %w", err)
133 }
134 opt.ExtraPaths = append(opt.ExtraPaths, extraPath)
135 }
136
Lorenz Brunca1cff02023-06-26 17:52:44 +0200137 if descriptionEnd+int(lenPath) < len(data) {
138 opt.OptionalData = data[descriptionEnd+int(lenPath):]
139 }
140 return &opt, nil
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100141}
142
143// BootOrder represents the contents of the BootOrder EFI variable.
144type BootOrder []uint16
145
146// Marshal generates the binary representation of a BootOrder.
147func (t *BootOrder) Marshal() []byte {
Lorenz Brunca1cff02023-06-26 17:52:44 +0200148 var out []byte
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100149 for _, v := range *t {
150 out = append16(out, v)
151 }
152 return out
153}
154
155// UnmarshalBootOrder loads a BootOrder from its binary representation.
Lorenz Brun9933ef02023-07-06 18:28:29 +0200156func UnmarshalBootOrder(d []byte) (BootOrder, error) {
Lorenz Brunca1cff02023-06-26 17:52:44 +0200157 if len(d)%2 != 0 {
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100158 return nil, fmt.Errorf("invalid length: %v bytes", len(d))
159 }
Lorenz Brunca1cff02023-06-26 17:52:44 +0200160 l := len(d) / 2
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100161 out := make(BootOrder, l)
162 for i := 0; i < l; i++ {
Lorenz Brun9933ef02023-07-06 18:28:29 +0200163 out[i] = uint16(d[2*i]) | uint16(d[2*i+1])<<8
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100164 }
Lorenz Brun9933ef02023-07-06 18:28:29 +0200165 return out, nil
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100166}
167
168func append16(d []byte, v uint16) []byte {
169 return append(d,
170 byte(v&0xFF),
171 byte(v>>8&0xFF),
172 )
173}
174
175func append32(d []byte, v uint32) []byte {
176 return append(d,
177 byte(v&0xFF),
178 byte(v>>8&0xFF),
179 byte(v>>16&0xFF),
180 byte(v>>24&0xFF),
181 )
182}