1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #ifdef SD_BOOT
4 #  include <efi.h>
5 #  include "macro-fundamental.h"
6 #  include "util.h"
7 #  define TEST_STATIC
8 #else
9 /* Provide our own "EFI API" if we are running as a unit test. */
10 #  include <stddef.h>
11 #  include <stdint.h>
12 #  include <uchar.h>
13 #  include "string-util-fundamental.h"
14 
15 #  define CHAR8 char
16 #  define CHAR16 char16_t
17 #  define UINT8 uint8_t
18 #  define UINT16 uint16_t
19 #  define UINT32 uint32_t
20 #  define UINT64 uint64_t
21 #  define UINTN size_t
22 #  define strlena(s) strlen(s)
23 #  define strncaseeqa(a, b, n) strncaseeq((a), (b), (n))
24 #  define TEST_STATIC static
25 #endif
26 
27 enum {
28         SIG_BASE_BLOCK  = 1718052210, /* regf */
29         SIG_KEY         = 27502,      /* nk */
30         SIG_SUBKEY_FAST = 26220,      /* lf */
31         SIG_KEY_VALUE   = 27510,      /* vk */
32 };
33 
34 enum {
35         REG_SZ       = 1,
36         REG_MULTI_SZ = 7,
37 };
38 
39 /* These structs contain a lot more members than we care for. They have all
40  * been squashed into _padN for our convenience. */
41 
42 typedef struct {
43         UINT32 sig;
44         UINT32 primary_seqnum;
45         UINT32 secondary_seqnum;
46         UINT64 _pad1;
47         UINT32 version_major;
48         UINT32 version_minor;
49         UINT32 type;
50         UINT32 _pad2;
51         UINT32 root_cell_offset;
52         UINT64 _pad3[507];
53 } _packed_ BaseBlock;
54 assert_cc(sizeof(BaseBlock) == 4096);
55 assert_cc(offsetof(BaseBlock, sig) == 0);
56 assert_cc(offsetof(BaseBlock, primary_seqnum) == 4);
57 assert_cc(offsetof(BaseBlock, secondary_seqnum) == 8);
58 assert_cc(offsetof(BaseBlock, version_major) == 20);
59 assert_cc(offsetof(BaseBlock, version_minor) == 24);
60 assert_cc(offsetof(BaseBlock, type) == 28);
61 assert_cc(offsetof(BaseBlock, root_cell_offset) == 36);
62 
63 /* All offsets are relative to the base block and technically point to a hive
64  * cell struct. But for our usecase we don't need to bother about that one,
65  * so skip over the cell_size UINT32. */
66 #define HIVE_CELL_OFFSET (sizeof(BaseBlock) + 4)
67 
68 typedef struct {
69         UINT16 sig;
70         UINT16 _pad1[13];
71         UINT32 subkeys_offset;
72         UINT32 _pad2;
73         UINT32 n_key_values;
74         UINT32 key_values_offset;
75         UINT32 _pad3[7];
76         UINT16 key_name_len;
77         UINT16 _pad4;
78         CHAR8 key_name[];
79 } _packed_ Key;
80 assert_cc(offsetof(Key, sig) == 0);
81 assert_cc(offsetof(Key, subkeys_offset) == 28);
82 assert_cc(offsetof(Key, n_key_values) == 36);
83 assert_cc(offsetof(Key, key_values_offset) == 40);
84 assert_cc(offsetof(Key, key_name_len) == 72);
85 assert_cc(offsetof(Key, key_name) == 76);
86 
87 typedef struct {
88         UINT16 sig;
89         UINT16 n_entries;
90         struct SubkeyFastEntry {
91                 UINT32 key_offset;
92                 CHAR8 name_hint[4];
93         } _packed_ entries[];
94 } _packed_ SubkeyFast;
95 assert_cc(offsetof(SubkeyFast, sig) == 0);
96 assert_cc(offsetof(SubkeyFast, n_entries) == 2);
97 assert_cc(offsetof(SubkeyFast, entries) == 4);
98 
99 typedef struct {
100         UINT16 sig;
101         UINT16 name_len;
102         UINT32 data_size;
103         UINT32 data_offset;
104         UINT32 data_type;
105         UINT32 _pad;
106         CHAR8 name[];
107 } _packed_ KeyValue;
108 assert_cc(offsetof(KeyValue, sig) == 0);
109 assert_cc(offsetof(KeyValue, name_len) == 2);
110 assert_cc(offsetof(KeyValue, data_size) == 4);
111 assert_cc(offsetof(KeyValue, data_offset) == 8);
112 assert_cc(offsetof(KeyValue, data_type) == 12);
113 assert_cc(offsetof(KeyValue, name) == 20);
114 
115 #define BAD_OFFSET(offset, len, max) \
116         ((UINT64) (offset) + (len) >= (max))
117 
118 #define BAD_STRUCT(type, offset, max) \
119         ((UINT64) (offset) + sizeof(type) >= (max))
120 
121 #define BAD_ARRAY(type, array, offset, array_len, max) \
122         ((UINT64) (offset) + offsetof(type, array) + \
123          sizeof((type){}.array[0]) * (UINT64) (array_len) >= (max))
124 
125 static const Key *get_key(const UINT8 *bcd, UINT32 bcd_len, UINT32 offset, const CHAR8 *name);
126 
get_subkey(const UINT8 * bcd,UINT32 bcd_len,UINT32 offset,const CHAR8 * name)127 static const Key *get_subkey(const UINT8 *bcd, UINT32 bcd_len, UINT32 offset, const CHAR8 *name) {
128         assert(bcd);
129         assert(name);
130 
131         if (BAD_STRUCT(SubkeyFast, offset, bcd_len))
132                 return NULL;
133 
134         const SubkeyFast *subkey = (const SubkeyFast *) (bcd + offset);
135         if (subkey->sig != SIG_SUBKEY_FAST)
136                 return NULL;
137 
138         if (BAD_ARRAY(SubkeyFast, entries, offset, subkey->n_entries, bcd_len))
139                 return NULL;
140 
141         for (UINT16 i = 0; i < subkey->n_entries; i++) {
142                 if (!strncaseeqa(name, subkey->entries[i].name_hint, sizeof(subkey->entries[i].name_hint)))
143                         continue;
144 
145                 const Key *key = get_key(bcd, bcd_len, subkey->entries[i].key_offset, name);
146                 if (key)
147                         return key;
148         }
149 
150         return NULL;
151 }
152 
153 /* We use NUL as registry path separators for convenience. To start from the root, begin
154  * name with a NUL. Name must end with two NUL. The lookup depth is not restricted, so
155  * name must be properly validated before calling get_key(). */
get_key(const UINT8 * bcd,UINT32 bcd_len,UINT32 offset,const CHAR8 * name)156 static const Key *get_key(const UINT8 *bcd, UINT32 bcd_len, UINT32 offset, const CHAR8 *name) {
157         assert(bcd);
158         assert(name);
159 
160         if (BAD_STRUCT(Key, offset, bcd_len))
161                 return NULL;
162 
163         const Key *key = (const Key *) (bcd + offset);
164         if (key->sig != SIG_KEY)
165                 return NULL;
166 
167         if (BAD_ARRAY(Key, key_name, offset, key->key_name_len, bcd_len))
168                 return NULL;
169 
170         if (*name) {
171                 if (strncaseeqa(name, key->key_name, key->key_name_len) && strlena(name) == key->key_name_len)
172                         name += key->key_name_len;
173                 else
174                         return NULL;
175         }
176 
177         name++;
178         return *name ? get_subkey(bcd, bcd_len, key->subkeys_offset, name) : key;
179 }
180 
get_key_value(const UINT8 * bcd,UINT32 bcd_len,const Key * key,const CHAR8 * name)181 static const KeyValue *get_key_value(const UINT8 *bcd, UINT32 bcd_len, const Key *key, const CHAR8 *name) {
182         assert(bcd);
183         assert(key);
184         assert(name);
185 
186         if (key->n_key_values == 0)
187                 return NULL;
188 
189         if (BAD_OFFSET(key->key_values_offset, sizeof(UINT32) * (UINT64) key->n_key_values, bcd_len) ||
190             (UINTN)(bcd + key->key_values_offset) % sizeof(UINT32) != 0)
191                 return NULL;
192 
193         const UINT32 *key_value_list = (const UINT32 *) (bcd + key->key_values_offset);
194         for (UINT32 i = 0; i < key->n_key_values; i++) {
195                 UINT32 offset = *(key_value_list + i);
196 
197                 if (BAD_STRUCT(KeyValue, offset, bcd_len))
198                         continue;
199 
200                 const KeyValue *kv = (const KeyValue *) (bcd + offset);
201                 if (kv->sig != SIG_KEY_VALUE)
202                         continue;
203 
204                 if (BAD_ARRAY(KeyValue, name, offset, kv->name_len, bcd_len))
205                         continue;
206 
207                 /* If most significant bit is set, data is stored in data_offset itself, but
208                  * we are only interested in UTF16 strings. The only strings that could fit
209                  * would have just one char in it, so let's not bother with this. */
210                 if (FLAGS_SET(kv->data_size, UINT32_C(1) << 31))
211                         continue;
212 
213                 if (BAD_OFFSET(kv->data_offset, kv->data_size, bcd_len))
214                         continue;
215 
216                 if (strncaseeqa(name, kv->name, kv->name_len) && strlena(name) == kv->name_len)
217                         return kv;
218         }
219 
220         return NULL;
221 }
222 
223 /* The BCD store is really just a regular windows registry hive with a rather cryptic internal
224  * key structure. On a running system it gets mounted to HKEY_LOCAL_MACHINE\BCD00000000.
225  *
226  * Of interest to us are the these two keys:
227  * - \Objects\{bootmgr}\Elements\24000001
228  *   This key is the "displayorder" property and contains a value of type REG_MULTI_SZ
229  *   with the name "Element" that holds a {GUID} list (UTF16, NUL-separated).
230  * - \Objects\{GUID}\Elements\12000004
231  *   This key is the "description" property and contains a value of type REG_SZ with the
232  *   name "Element" that holds a NUL-terminated UTF16 string.
233  *
234  * The GUIDs and properties are as reported by "bcdedit.exe /v".
235  *
236  * To get a title for the BCD store we first look at the displayorder property of {bootmgr}
237  * (it always has the GUID 9dea862c-5cdd-4e70-acc1-f32b344d4795). If it contains more than
238  * one GUID, the BCD is multi-boot and we stop looking. Otherwise we take that GUID, look it
239  * up, and return its description property. */
get_bcd_title(UINT8 * bcd,UINTN bcd_len)240 TEST_STATIC CHAR16 *get_bcd_title(UINT8 *bcd, UINTN bcd_len) {
241         assert(bcd);
242 
243         if (HIVE_CELL_OFFSET >= bcd_len)
244                 return NULL;
245 
246         BaseBlock *base_block = (BaseBlock *) bcd;
247         if (base_block->sig != SIG_BASE_BLOCK ||
248             base_block->version_major != 1 ||
249             base_block->version_minor != 3 ||
250             base_block->type != 0 ||
251             base_block->primary_seqnum != base_block->secondary_seqnum)
252                 return NULL;
253 
254         bcd += HIVE_CELL_OFFSET;
255         bcd_len -= HIVE_CELL_OFFSET;
256 
257         const Key *objects_key = get_key(
258                 bcd, bcd_len,
259                 base_block->root_cell_offset,
260                 (const CHAR8 *) "\0Objects\0");
261         if (!objects_key)
262                 return NULL;
263 
264         const Key *displayorder_key = get_subkey(
265                 bcd, bcd_len,
266                 objects_key->subkeys_offset,
267                 (const CHAR8 *) "{9dea862c-5cdd-4e70-acc1-f32b344d4795}\0Elements\00024000001\0");
268         if (!displayorder_key)
269                 return NULL;
270 
271         const KeyValue *displayorder_value = get_key_value(
272                 bcd, bcd_len,
273                 displayorder_key,
274                 (const CHAR8 *) "Element");
275         if (!displayorder_value)
276                 return NULL;
277 
278         CHAR8 order_guid[sizeof("{00000000-0000-0000-0000-000000000000}\0")];
279         if (displayorder_value->data_type != REG_MULTI_SZ ||
280             displayorder_value->data_size != sizeof(CHAR16[sizeof(order_guid)]) ||
281             (UINTN)(bcd + displayorder_value->data_offset) % sizeof(CHAR16) != 0)
282                 /* BCD is multi-boot. */
283                 return NULL;
284 
285         /* Keys are stored as ASCII in registry hives if the data fits (and GUIDS always should). */
286         CHAR16 *order_guid_utf16 = (CHAR16 *) (bcd + displayorder_value->data_offset);
287         for (UINTN i = 0; i < sizeof(order_guid) - 2; i++) {
288                 CHAR16 c = order_guid_utf16[i];
289                 switch (c) {
290                 case '-':
291                 case '{':
292                 case '}':
293                 case '0' ... '9':
294                 case 'a' ... 'f':
295                 case 'A' ... 'F':
296                         order_guid[i] = c;
297                         break;
298                 default:
299                         /* Not a valid GUID. */
300                         return NULL;
301                 }
302         }
303         /* Our functions expect the lookup key to be double-derminated. */
304         order_guid[sizeof(order_guid) - 2] = '\0';
305         order_guid[sizeof(order_guid) - 1] = '\0';
306 
307         const Key *default_key = get_subkey(bcd, bcd_len, objects_key->subkeys_offset, order_guid);
308         if (!default_key)
309                 return NULL;
310 
311         const Key *description_key = get_subkey(
312                 bcd, bcd_len,
313                 default_key->subkeys_offset,
314                 (const CHAR8 *) "Elements\00012000004\0");
315         if (!description_key)
316                 return NULL;
317 
318         const KeyValue *description_value = get_key_value(
319                 bcd, bcd_len,
320                 description_key,
321                 (const CHAR8 *) "Element");
322         if (!description_value)
323                 return NULL;
324 
325         if (description_value->data_type != REG_SZ ||
326             description_value->data_size < sizeof(CHAR16) ||
327             description_value->data_size % sizeof(CHAR16) != 0 ||
328             (UINTN)(bcd + description_value->data_offset) % sizeof(CHAR16))
329                 return NULL;
330 
331         /* The data should already be NUL-terminated. */
332         CHAR16 *title = (CHAR16 *) (bcd + description_value->data_offset);
333         title[description_value->data_size / sizeof(CHAR16) - 1] = '\0';
334         return title;
335 }
336