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