blob: 92b8ac9ee31e044b8ca69040eb6050776be3583a [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
67 // OptionalData gets passed as an argument to the executed PE executable.
68 // If zero-length a NULL value is passed to the executable.
69 OptionalData []byte
Mateusz Zalega6cefe512021-11-08 18:19:42 +010070}
71
Lorenz Brunca1cff02023-06-26 17:52:44 +020072// Marshal encodes a LoadOption into a binary EFI_LOAD_OPTION.
73func (e *LoadOption) Marshal() ([]byte, error) {
74 var data []byte
75 var attrs uint32
76 attrs |= (uint32(e.Category) & 0x1f) << 8
77 if e.Hidden {
78 attrs |= 0x08
Mateusz Zalega6cefe512021-11-08 18:19:42 +010079 }
Lorenz Brunca1cff02023-06-26 17:52:44 +020080 if !e.Inactive {
81 attrs |= 0x01
Mateusz Zalega6cefe512021-11-08 18:19:42 +010082 }
Lorenz Brunca1cff02023-06-26 17:52:44 +020083 data = append32(data, attrs)
84 filePathRaw, err := e.FilePath.Marshal()
85 if err != nil {
86 return nil, fmt.Errorf("failed marshalling FilePath: %w", err)
Mateusz Zalega6cefe512021-11-08 18:19:42 +010087 }
Lorenz Brunca1cff02023-06-26 17:52:44 +020088 if len(filePathRaw) > math.MaxUint16 {
89 return nil, fmt.Errorf("failed marshalling FilePath: value too big (%d)", len(filePathRaw))
Mateusz Zalega6cefe512021-11-08 18:19:42 +010090 }
Lorenz Brunca1cff02023-06-26 17:52:44 +020091 data = append16(data, uint16(len(filePathRaw)))
92 if strings.IndexByte(e.Description, 0x00) != -1 {
93 return nil, fmt.Errorf("failed to encode Description: contains invalid null bytes")
Mateusz Zalega6cefe512021-11-08 18:19:42 +010094 }
Lorenz Brunca1cff02023-06-26 17:52:44 +020095 encodedDesc, err := Encoding.NewEncoder().Bytes([]byte(e.Description))
96 if err != nil {
97 return nil, fmt.Errorf("failed to encode Description: %w", err)
98 }
99 data = append(data, encodedDesc...)
100 data = append(data, 0x00, 0x00) // Final UTF-16/UCS-2 null code
101 data = append(data, filePathRaw...)
102 data = append(data, e.OptionalData...)
103 return data, nil
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100104}
105
Lorenz Brunca1cff02023-06-26 17:52:44 +0200106// UnmarshalLoadOption decodes a binary EFI_LOAD_OPTION into a LoadOption.
107func UnmarshalLoadOption(data []byte) (*LoadOption, error) {
108 if len(data) < 6 {
109 return nil, fmt.Errorf("invalid load option: minimum 6 bytes are required, got %d", len(data))
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100110 }
Lorenz Brunca1cff02023-06-26 17:52:44 +0200111 var opt LoadOption
112 attrs := binary.LittleEndian.Uint32(data[:4])
113 opt.Category = LoadOptionCategory((attrs >> 8) & 0x1f)
114 opt.Hidden = attrs&0x08 != 0
115 opt.Inactive = attrs&0x01 == 0
116 lenPath := binary.LittleEndian.Uint16(data[4:6])
117 // Search for UTF-16 null code
118 nullIdx := bytes.Index(data[6:], []byte{0x00, 0x00})
119 if nullIdx == -1 {
120 return nil, errors.New("no null code point marking end of Description found")
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100121 }
Lorenz Brunca1cff02023-06-26 17:52:44 +0200122 descriptionEnd := 6 + nullIdx + 1
123 descriptionRaw := data[6:descriptionEnd]
124 description, err := Encoding.NewDecoder().Bytes(descriptionRaw)
125 if err != nil {
126 return nil, fmt.Errorf("error decoding UTF-16 in Description: %w", err)
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100127 }
Lorenz Brunca1cff02023-06-26 17:52:44 +0200128 descriptionEnd += 2 // 2 null bytes terminating UTF-16 string
129 opt.Description = string(description)
130 if descriptionEnd+int(lenPath) > len(data) {
131 return nil, fmt.Errorf("declared length of FilePath (%d) overruns available data (%d)", lenPath, len(data)-descriptionEnd)
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100132 }
Lorenz Brunca1cff02023-06-26 17:52:44 +0200133 filePathData := data[descriptionEnd : descriptionEnd+int(lenPath)]
134 opt.FilePath, err = UnmarshalDevicePath(filePathData)
135 if err != nil {
136 return nil, fmt.Errorf("failed unmarshaling FilePath: %w", err)
137 }
138 if descriptionEnd+int(lenPath) < len(data) {
139 opt.OptionalData = data[descriptionEnd+int(lenPath):]
140 }
141 return &opt, nil
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100142}
143
144// BootOrder represents the contents of the BootOrder EFI variable.
145type BootOrder []uint16
146
147// Marshal generates the binary representation of a BootOrder.
148func (t *BootOrder) Marshal() []byte {
Lorenz Brunca1cff02023-06-26 17:52:44 +0200149 var out []byte
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100150 for _, v := range *t {
151 out = append16(out, v)
152 }
153 return out
154}
155
156// UnmarshalBootOrder loads a BootOrder from its binary representation.
157func UnmarshalBootOrder(d []byte) (*BootOrder, error) {
Lorenz Brunca1cff02023-06-26 17:52:44 +0200158 if len(d)%2 != 0 {
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100159 return nil, fmt.Errorf("invalid length: %v bytes", len(d))
160 }
Lorenz Brunca1cff02023-06-26 17:52:44 +0200161 l := len(d) / 2
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100162 out := make(BootOrder, l)
163 for i := 0; i < l; i++ {
164 out[i] = uint16(d[4+2*i]) | uint16(d[4+2*i+1])<<8
165 }
166 return &out, nil
167}
168
169func append16(d []byte, v uint16) []byte {
170 return append(d,
171 byte(v&0xFF),
172 byte(v>>8&0xFF),
173 )
174}
175
176func append32(d []byte, v uint32) []byte {
177 return append(d,
178 byte(v&0xFF),
179 byte(v>>8&0xFF),
180 byte(v>>16&0xFF),
181 byte(v>>24&0xFF),
182 )
183}