blob: 8fe1a55083c97c97e1307de34e2b41f37240a800 [file] [log] [blame]
Mateusz Zalega6cefe512021-11-08 18:19:42 +01001// MIT License
2//
3// Copyright (c) 2021 Philippe Voinov (philippevoinov@gmail.com)
4// Copyright 2021 The Monogon Project Authors.
5//
6// Permission is hereby granted, free of charge, to any person obtaining a copy
7// of this software and associated documentation files (the "Software"), to deal
8// in the Software without restriction, including without limitation the rights
9// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10// copies of the Software, and to permit persons to whom the Software is
11// furnished to do so, subject to the following conditions:
12//
13// The above copyright notice and this permission notice shall be included in all
14// copies or substantial portions of the Software.
15//
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22// SOFTWARE.
23
24package efivarfs
25
26import (
Lorenz Brunca1cff02023-06-26 17:52:44 +020027 "bytes"
28 "encoding/binary"
29 "errors"
Mateusz Zalega6cefe512021-11-08 18:19:42 +010030 "fmt"
31 "math"
Mateusz Zalega612a0332021-11-17 20:04:52 +010032 "strings"
Mateusz Zalega6cefe512021-11-08 18:19:42 +010033)
34
Lorenz Brunca1cff02023-06-26 17:52:44 +020035type LoadOptionCategory uint8
Mateusz Zalega6cefe512021-11-08 18:19:42 +010036
Lorenz Brunca1cff02023-06-26 17:52:44 +020037const (
38 // Boot entries belonging to the Boot category are normal boot entries.
39 LoadOptionCategoryBoot LoadOptionCategory = 0x0
40 // Boot entries belonging to the App category are not booted as part of
41 // the normal boot order, but are only launched via menu or hotkey.
42 // This category is optional for bootloaders to support, before creating
43 // new boot entries of this category firmware support needs to be
44 // confirmed.
45 LoadOptionCategoryApp LoadOptionCategory = 0x1
46)
Mateusz Zalega6cefe512021-11-08 18:19:42 +010047
Lorenz Brunca1cff02023-06-26 17:52:44 +020048// LoadOption contains information on a payload to be loaded by EFI.
49type LoadOption struct {
50 // Human-readable description of what this load option loads.
51 // This is what's being shown by the firmware when selecting a boot option.
52 Description string
53 // If set, firmware will skip this load option when it is in BootOrder.
54 // It is unspecificed whether this prevents the user from booting the entry
55 // manually.
56 Inactive bool
57 // If set, this load option will not be shown in any menu for load option
58 // selection. This does not affect other functionality.
59 Hidden bool
60 // Category contains the category of the load entry. The selected category
61 // affects various firmware behaviors, see the individual value
62 // descriptions for more information.
63 Category LoadOptionCategory
64 // Path to the UEFI PE executable to execute when this load option is being
65 // loaded.
66 FilePath DevicePath
Lorenz Brunf025d1b2023-08-07 14:52:49 +020067 // ExtraPaths contains additional device paths with vendor-specific
68 // behavior. Can generally be left empty.
69 ExtraPaths []DevicePath
Lorenz Brunca1cff02023-06-26 17:52:44 +020070 // OptionalData gets passed as an argument to the executed PE executable.
71 // If zero-length a NULL value is passed to the executable.
72 OptionalData []byte
Mateusz Zalega6cefe512021-11-08 18:19:42 +010073}
74
Lorenz Brunca1cff02023-06-26 17:52:44 +020075// Marshal encodes a LoadOption into a binary EFI_LOAD_OPTION.
76func (e *LoadOption) Marshal() ([]byte, error) {
77 var data []byte
78 var attrs uint32
79 attrs |= (uint32(e.Category) & 0x1f) << 8
80 if e.Hidden {
81 attrs |= 0x08
Mateusz Zalega6cefe512021-11-08 18:19:42 +010082 }
Lorenz Brunca1cff02023-06-26 17:52:44 +020083 if !e.Inactive {
84 attrs |= 0x01
Mateusz Zalega6cefe512021-11-08 18:19:42 +010085 }
Lorenz Brunca1cff02023-06-26 17:52:44 +020086 data = append32(data, attrs)
87 filePathRaw, err := e.FilePath.Marshal()
88 if err != nil {
89 return nil, fmt.Errorf("failed marshalling FilePath: %w", err)
Mateusz Zalega6cefe512021-11-08 18:19:42 +010090 }
Lorenz Brunf025d1b2023-08-07 14:52:49 +020091 for _, ep := range e.ExtraPaths {
92 epRaw, err := ep.Marshal()
93 if err != nil {
94 return nil, fmt.Errorf("failed marshalling ExtraPath: %w", err)
95 }
96 filePathRaw = append(filePathRaw, epRaw...)
97 }
Lorenz Brunca1cff02023-06-26 17:52:44 +020098 if len(filePathRaw) > math.MaxUint16 {
Lorenz Brunf025d1b2023-08-07 14:52:49 +020099 return nil, fmt.Errorf("failed marshalling FilePath/ExtraPath: value too big (%d)", len(filePathRaw))
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100100 }
Lorenz Brunca1cff02023-06-26 17:52:44 +0200101 data = append16(data, uint16(len(filePathRaw)))
102 if strings.IndexByte(e.Description, 0x00) != -1 {
103 return nil, fmt.Errorf("failed to encode Description: contains invalid null bytes")
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100104 }
Lorenz Brunca1cff02023-06-26 17:52:44 +0200105 encodedDesc, err := Encoding.NewEncoder().Bytes([]byte(e.Description))
106 if err != nil {
107 return nil, fmt.Errorf("failed to encode Description: %w", err)
108 }
109 data = append(data, encodedDesc...)
110 data = append(data, 0x00, 0x00) // Final UTF-16/UCS-2 null code
111 data = append(data, filePathRaw...)
112 data = append(data, e.OptionalData...)
113 return data, nil
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100114}
115
Lorenz Brunca1cff02023-06-26 17:52:44 +0200116// UnmarshalLoadOption decodes a binary EFI_LOAD_OPTION into a LoadOption.
117func UnmarshalLoadOption(data []byte) (*LoadOption, error) {
118 if len(data) < 6 {
119 return nil, fmt.Errorf("invalid load option: minimum 6 bytes are required, got %d", len(data))
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100120 }
Lorenz Brunca1cff02023-06-26 17:52:44 +0200121 var opt LoadOption
122 attrs := binary.LittleEndian.Uint32(data[:4])
123 opt.Category = LoadOptionCategory((attrs >> 8) & 0x1f)
124 opt.Hidden = attrs&0x08 != 0
125 opt.Inactive = attrs&0x01 == 0
126 lenPath := binary.LittleEndian.Uint16(data[4:6])
127 // Search for UTF-16 null code
128 nullIdx := bytes.Index(data[6:], []byte{0x00, 0x00})
129 if nullIdx == -1 {
130 return nil, errors.New("no null code point marking end of Description found")
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100131 }
Lorenz Brunca1cff02023-06-26 17:52:44 +0200132 descriptionEnd := 6 + nullIdx + 1
133 descriptionRaw := data[6:descriptionEnd]
134 description, err := Encoding.NewDecoder().Bytes(descriptionRaw)
135 if err != nil {
136 return nil, fmt.Errorf("error decoding UTF-16 in Description: %w", err)
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100137 }
Lorenz Brunca1cff02023-06-26 17:52:44 +0200138 descriptionEnd += 2 // 2 null bytes terminating UTF-16 string
139 opt.Description = string(description)
140 if descriptionEnd+int(lenPath) > len(data) {
141 return nil, fmt.Errorf("declared length of FilePath (%d) overruns available data (%d)", lenPath, len(data)-descriptionEnd)
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100142 }
Lorenz Brunca1cff02023-06-26 17:52:44 +0200143 filePathData := data[descriptionEnd : descriptionEnd+int(lenPath)]
Lorenz Brunf025d1b2023-08-07 14:52:49 +0200144 opt.FilePath, filePathData, err = UnmarshalDevicePath(filePathData)
Lorenz Brunca1cff02023-06-26 17:52:44 +0200145 if err != nil {
146 return nil, fmt.Errorf("failed unmarshaling FilePath: %w", err)
147 }
Lorenz Brunf025d1b2023-08-07 14:52:49 +0200148 for len(filePathData) > 0 {
149 var extraPath DevicePath
150 extraPath, filePathData, err = UnmarshalDevicePath(filePathData)
151 if err != nil {
152 return nil, fmt.Errorf("failed unmarshaling ExtraPath: %w", err)
153 }
154 opt.ExtraPaths = append(opt.ExtraPaths, extraPath)
155 }
156
Lorenz Brunca1cff02023-06-26 17:52:44 +0200157 if descriptionEnd+int(lenPath) < len(data) {
158 opt.OptionalData = data[descriptionEnd+int(lenPath):]
159 }
160 return &opt, nil
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100161}
162
163// BootOrder represents the contents of the BootOrder EFI variable.
164type BootOrder []uint16
165
166// Marshal generates the binary representation of a BootOrder.
167func (t *BootOrder) Marshal() []byte {
Lorenz Brunca1cff02023-06-26 17:52:44 +0200168 var out []byte
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100169 for _, v := range *t {
170 out = append16(out, v)
171 }
172 return out
173}
174
175// UnmarshalBootOrder loads a BootOrder from its binary representation.
Lorenz Brun9933ef02023-07-06 18:28:29 +0200176func UnmarshalBootOrder(d []byte) (BootOrder, error) {
Lorenz Brunca1cff02023-06-26 17:52:44 +0200177 if len(d)%2 != 0 {
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100178 return nil, fmt.Errorf("invalid length: %v bytes", len(d))
179 }
Lorenz Brunca1cff02023-06-26 17:52:44 +0200180 l := len(d) / 2
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100181 out := make(BootOrder, l)
182 for i := 0; i < l; i++ {
Lorenz Brun9933ef02023-07-06 18:28:29 +0200183 out[i] = uint16(d[2*i]) | uint16(d[2*i+1])<<8
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100184 }
Lorenz Brun9933ef02023-07-06 18:28:29 +0200185 return out, nil
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100186}
187
188func append16(d []byte, v uint16) []byte {
189 return append(d,
190 byte(v&0xFF),
191 byte(v>>8&0xFF),
192 )
193}
194
195func append32(d []byte, v uint32) []byte {
196 return append(d,
197 byte(v&0xFF),
198 byte(v>>8&0xFF),
199 byte(v>>16&0xFF),
200 byte(v>>24&0xFF),
201 )
202}