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