blob: 4178c8da2b95b7e80ea86d1507c5185a48cc7bdd [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 (
27 "fmt"
28 "math"
Mateusz Zalega612a0332021-11-17 20:04:52 +010029 "strings"
Mateusz Zalega6cefe512021-11-08 18:19:42 +010030
Mateusz Zalega612a0332021-11-17 20:04:52 +010031 "github.com/google/uuid"
Mateusz Zalega6cefe512021-11-08 18:19:42 +010032 "golang.org/x/text/transform"
33)
34
35// Note on binary format of EFI variables:
36// This code follows Section 3 "Boot Manager" of version 2.6 of the UEFI Spec:
37// http://www.uefi.org/sites/default/files/resources/UEFI%20Spec%202_6.pdf
38// It uses the binary representation from the Linux "efivars" filesystem.
39// Specifically, all binary data that is marshaled and unmarshaled is preceded by
40// 4 bytes of "Variable Attributes".
41// All binary data must have exactly the correct length and may not be padded
42// with extra bytes while reading or writing.
43
44// Note on EFI variable attributes:
45// This code ignores all EFI variable attributes when reading.
46// This code always writes variables with the following attributes:
47// - EFI_VARIABLE_NON_VOLATILE (0x00000001)
48// - EFI_VARIABLE_BOOTSERVICE_ACCESS (0x00000002)
49// - EFI_VARIABLE_RUNTIME_ACCESS (0x00000004)
50const defaultAttrsByte0 uint8 = 7
51
52// BootEntry represents a subset of the contents of a Boot#### EFI variable.
53type BootEntry struct {
54 Description string // eg. "Linux Boot Manager"
55 Path string // eg. `\EFI\systemd\systemd-bootx64.efi`
Mateusz Zalega612a0332021-11-17 20:04:52 +010056 PartitionGUID uuid.UUID
Mateusz Zalega6cefe512021-11-08 18:19:42 +010057 PartitionNumber uint32 // Starts with 1
58 PartitionStart uint64 // LBA
59 PartitionSize uint64 // LBA
60}
61
62// Marshal generates the binary representation of a BootEntry (EFI_LOAD_OPTION).
63// Description, DiskGUID and Path must be set.
64// Attributes of the boot entry (EFI_LOAD_OPTION.Attributes, not the same
65// as attributes of an EFI variable) are always set to LOAD_OPTION_ACTIVE.
66func (t *BootEntry) Marshal() ([]byte, error) {
67 if t.Description == "" ||
Mateusz Zalega612a0332021-11-17 20:04:52 +010068 t.PartitionGUID.String() == "00000000-0000-0000-0000-000000000000" ||
Mateusz Zalega6cefe512021-11-08 18:19:42 +010069 t.Path == "" ||
70 t.PartitionNumber == 0 ||
71 t.PartitionStart == 0 ||
72 t.PartitionSize == 0 {
73 return nil, fmt.Errorf("missing field, all are required: %+v", *t)
74 }
75
76 // EFI_LOAD_OPTION.FilePathList
77 var dp []byte
78
79 // EFI_LOAD_OPTION.FilePathList[0]
80 dp = append(dp,
81 0x04, // Type ("Media Device Path")
82 0x01, // Sub-Type ("Hard Drive")
83 0x2a, 0x00, // Length (always 42 bytes for this type)
84 )
85 dp = append32(dp, t.PartitionNumber)
86 dp = append64(dp, t.PartitionStart)
87 dp = append64(dp, t.PartitionSize)
Mateusz Zalega612a0332021-11-17 20:04:52 +010088 // Append the partition GUID in the EFI format.
89 dp = append(dp, MarshalEFIGUID(t.PartitionGUID)...)
90
Mateusz Zalega6cefe512021-11-08 18:19:42 +010091 dp = append(dp,
92 0x02, // Partition Format ("GUID Partition Table")
93 0x02, // Signature Type ("GUID signature")
94 )
95
96 // EFI_LOAD_OPTION.FilePathList[1]
97 enc := Encoding.NewEncoder()
Mateusz Zalega612a0332021-11-17 20:04:52 +010098 bsp := strings.ReplaceAll(t.Path, "/", "\\")
99 path, _, e := transform.Bytes(enc, []byte(bsp))
Mateusz Zalega6cefe512021-11-08 18:19:42 +0100100 if e != nil {
101 return nil, fmt.Errorf("while encoding Path: %v", e)
102 }
103 path = append16(path, 0) // null terminate string
104 filePathLen := len(path) + 4
105 dp = append(dp,
106 0x04, // Type ("Media Device Path")
107 0x04, // Sub-Type ("File Path")
108 )
109 dp = append16(dp, uint16(filePathLen))
110 dp = append(dp, path...)
111
112 // EFI_LOAD_OPTION.FilePathList[2] ("Device Path End Structure")
113 dp = append(dp,
114 0x7F, // Type ("End of Hardware Device Path")
115 0xFF, // Sub-Type ("End Entire Device Path")
116 0x04, 0x00, // Length (always 4 bytes for this type)
117 )
118
119 out := []byte{
120 // EFI variable attributes
121 defaultAttrsByte0, 0x00, 0x00, 0x00,
122
123 // EFI_LOAD_OPTION.Attributes (only LOAD_OPTION_ACTIVE)
124 0x01, 0x00, 0x00, 0x00,
125 }
126
127 // EFI_LOAD_OPTION.FilePathListLength
128 if len(dp) > math.MaxUint16 {
129 // No need to also check for overflows for Path length field explicitly,
130 // since if that overflows, this field will definitely overflow as well.
131 // There is no explicit length field for Description, so no special
132 // handling is required.
133 return nil, fmt.Errorf("variable too large, use shorter strings")
134 }
135 out = append16(out, uint16(len(dp)))
136
137 // EFI_LOAD_OPTION.Description
138 desc, _, e := transform.Bytes(enc, []byte(t.Description))
139 if e != nil {
140 return nil, fmt.Errorf("while encoding Description: %v", e)
141 }
142 desc = append16(desc, 0) // null terminate string
143 out = append(out, desc...)
144
145 // EFI_LOAD_OPTION.FilePathList
146 out = append(out, dp...)
147
148 // EFI_LOAD_OPTION.OptionalData is always empty
149
150 return out, nil
151}
152
153// UnmarshalBootEntry loads a BootEntry from its binary representation.
154// WARNING: UnmarshalBootEntry only loads the Description field.
155// Everything else is ignored (and not validated if possible)
156func UnmarshalBootEntry(d []byte) (*BootEntry, error) {
157 descOffset := 4 /* EFI Var Attrs */ + 4 /* EFI_LOAD_OPTION.Attributes */ + 2 /*FilePathListLength*/
158 if len(d) < descOffset {
159 return nil, fmt.Errorf("too short: %v bytes", len(d))
160 }
161 descBytes := []byte{}
162 var foundNull bool
163 for i := descOffset; i+1 < len(d); i += 2 {
164 a := d[i]
165 b := d[i+1]
166 if a == 0 && b == 0 {
167 foundNull = true
168 break
169 }
170 descBytes = append(descBytes, a, b)
171 }
172 if !foundNull {
173 return nil, fmt.Errorf("didn't find null terminator for Description")
174 }
175 descDecoded, _, e := transform.Bytes(Encoding.NewDecoder(), descBytes)
176 if e != nil {
177 return nil, fmt.Errorf("while decoding Description: %v", e)
178 }
179 return &BootEntry{Description: string(descDecoded)}, nil
180}
181
182// BootOrder represents the contents of the BootOrder EFI variable.
183type BootOrder []uint16
184
185// Marshal generates the binary representation of a BootOrder.
186func (t *BootOrder) Marshal() []byte {
187 out := []byte{defaultAttrsByte0, 0x00, 0x00, 0x00}
188 for _, v := range *t {
189 out = append16(out, v)
190 }
191 return out
192}
193
194// UnmarshalBootOrder loads a BootOrder from its binary representation.
195func UnmarshalBootOrder(d []byte) (*BootOrder, error) {
196 if len(d) < 4 || len(d)%2 != 0 {
197 return nil, fmt.Errorf("invalid length: %v bytes", len(d))
198 }
199 l := (len(d) - 4) / 2
200 out := make(BootOrder, l)
201 for i := 0; i < l; i++ {
202 out[i] = uint16(d[4+2*i]) | uint16(d[4+2*i+1])<<8
203 }
204 return &out, nil
205}
206
207func append16(d []byte, v uint16) []byte {
208 return append(d,
209 byte(v&0xFF),
210 byte(v>>8&0xFF),
211 )
212}
213
214func append32(d []byte, v uint32) []byte {
215 return append(d,
216 byte(v&0xFF),
217 byte(v>>8&0xFF),
218 byte(v>>16&0xFF),
219 byte(v>>24&0xFF),
220 )
221}
222
223func append64(d []byte, v uint64) []byte {
224 return append(d,
225 byte(v&0xFF),
226 byte(v>>8&0xFF),
227 byte(v>>16&0xFF),
228 byte(v>>24&0xFF),
229 byte(v>>32&0xFF),
230 byte(v>>40&0xFF),
231 byte(v>>48&0xFF),
232 byte(v>>56&0xFF),
233 )
234}