--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <efi.h>
+#include "macro-fundamental.h"
+#include "util.h"
+
+enum {
+ SIG_BASE_BLOCK = 1718052210, /* regf */
+ SIG_KEY = 27502, /* nk */
+ SIG_SUBKEY_FAST = 26220, /* lf */
+ SIG_KEY_VALUE = 27510, /* vk */
+};
+
+enum {
+ REG_SZ = 1,
+ REG_MULTI_SZ = 7,
+};
+
+/* These structs contain a lot more members than we care for. They have all
+ * been squashed into _padN for our convenience. */
+
+typedef struct {
+ UINT32 sig;
+ UINT32 primary_seqnum;
+ UINT32 secondary_seqnum;
+ UINT64 _pad1;
+ UINT32 version_major;
+ UINT32 version_minor;
+ UINT32 type;
+ UINT32 _pad2;
+ UINT32 root_cell_offset;
+ UINT64 _pad3[507];
+} _packed_ BaseBlock;
+assert_cc(sizeof(BaseBlock) == 4096);
+assert_cc(offsetof(BaseBlock, sig) == 0);
+assert_cc(offsetof(BaseBlock, primary_seqnum) == 4);
+assert_cc(offsetof(BaseBlock, secondary_seqnum) == 8);
+assert_cc(offsetof(BaseBlock, version_major) == 20);
+assert_cc(offsetof(BaseBlock, version_minor) == 24);
+assert_cc(offsetof(BaseBlock, type) == 28);
+assert_cc(offsetof(BaseBlock, root_cell_offset) == 36);
+
+/* All offsets are relative to the base block and technically point to a hive
+ * cell struct. But for our usecase we don't need to bother about that one,
+ * so skip over the cell_size UINT32. */
+#define HIVE_CELL_OFFSET (sizeof(BaseBlock) + 4)
+
+typedef struct {
+ UINT16 sig;
+ UINT16 _pad1[13];
+ UINT32 subkeys_offset;
+ UINT32 _pad2;
+ UINT32 n_key_values;
+ UINT32 key_values_offset;
+ UINT32 _pad3[7];
+ UINT16 key_name_len;
+ UINT16 _pad4;
+ CHAR8 key_name[];
+} _packed_ Key;
+assert_cc(offsetof(Key, sig) == 0);
+assert_cc(offsetof(Key, subkeys_offset) == 28);
+assert_cc(offsetof(Key, n_key_values) == 36);
+assert_cc(offsetof(Key, key_values_offset) == 40);
+assert_cc(offsetof(Key, key_name_len) == 72);
+assert_cc(offsetof(Key, key_name) == 76);
+
+typedef struct {
+ UINT16 sig;
+ UINT16 n_entries;
+ struct SubkeyFastEntry {
+ UINT32 key_offset;
+ CHAR8 name_hint[4];
+ } _packed_ entries[];
+} _packed_ SubkeyFast;
+assert_cc(offsetof(SubkeyFast, sig) == 0);
+assert_cc(offsetof(SubkeyFast, n_entries) == 2);
+assert_cc(offsetof(SubkeyFast, entries) == 4);
+
+typedef struct {
+ UINT16 sig;
+ UINT16 name_len;
+ UINT32 data_size;
+ UINT32 data_offset;
+ UINT32 data_type;
+ UINT32 _pad;
+ CHAR8 name[];
+} _packed_ KeyValue;
+assert_cc(offsetof(KeyValue, sig) == 0);
+assert_cc(offsetof(KeyValue, name_len) == 2);
+assert_cc(offsetof(KeyValue, data_size) == 4);
+assert_cc(offsetof(KeyValue, data_offset) == 8);
+assert_cc(offsetof(KeyValue, data_type) == 12);
+assert_cc(offsetof(KeyValue, name) == 20);
+
+static const Key *get_key(const UINT8 *bcd, UINT32 bcd_len, UINT32 offset, const CHAR8 *name);
+
+static const Key *get_subkey(const UINT8 *bcd, UINT32 bcd_len, UINT32 offset, const CHAR8 *name) {
+ assert(bcd);
+ assert(name);
+
+ if ((UINT64) offset + sizeof(SubkeyFast) > bcd_len)
+ return NULL;
+
+ const SubkeyFast *subkey = (const SubkeyFast *) (bcd + offset);
+ if (subkey->sig != SIG_SUBKEY_FAST)
+ return NULL;
+
+ if ((UINT64) offset + offsetof(SubkeyFast, entries) + sizeof(struct SubkeyFastEntry[subkey->n_entries]) > bcd_len)
+ return NULL;
+
+ for (UINT16 i = 0; i < subkey->n_entries; i++) {
+ if (!strncaseeqa(name, subkey->entries[i].name_hint, sizeof(subkey->entries[i].name_hint)))
+ continue;
+
+ const Key *key = get_key(bcd, bcd_len, subkey->entries[i].key_offset, name);
+ if (key)
+ return key;
+ }
+
+ return NULL;
+}
+
+/* We use NUL as registry path separators for convenience. To start from the root, begin
+ * name with a NUL. Name must end with two NUL. The lookup depth is not restricted, so
+ * name must be properly validated before calling get_key(). */
+static const Key *get_key(const UINT8 *bcd, UINT32 bcd_len, UINT32 offset, const CHAR8 *name) {
+ assert(bcd);
+ assert(name);
+
+ if ((UINT64) offset + sizeof(Key) > bcd_len)
+ return NULL;
+
+ const Key *key = (const Key *) (bcd + offset);
+ if (key->sig != SIG_KEY)
+ return NULL;
+
+ if ((UINT64) offset + offsetof(Key, key_name) + sizeof(CHAR8[key->key_name_len]) > bcd_len)
+ return NULL;
+
+ if (*name) {
+ if (strncaseeqa(name, key->key_name, key->key_name_len) && !name[key->key_name_len])
+ name += key->key_name_len;
+ else
+ return NULL;
+ }
+
+ name++;
+ return *name ? get_subkey(bcd, bcd_len, key->subkeys_offset, name) : key;
+}
+
+static const KeyValue *get_key_value(const UINT8 *bcd, UINT32 bcd_len, const Key *key, const CHAR8 *name) {
+ assert(bcd);
+ assert(key);
+ assert(name);
+
+ if (key->n_key_values == 0)
+ return NULL;
+
+ if ((UINT64) key->key_values_offset + sizeof(UINT32[key->n_key_values]) > bcd_len)
+ return NULL;
+
+ const UINT32 *key_value_list = (const UINT32 *) (bcd + key->key_values_offset);
+ for (UINT32 i = 0; i < key->n_key_values; i++) {
+ UINT32 offset = *(key_value_list + i);
+
+ if ((UINT64) offset + sizeof(KeyValue) > bcd_len)
+ continue;
+
+ const KeyValue *kv = (const KeyValue *) (bcd + offset);
+ if (kv->sig != SIG_KEY_VALUE)
+ continue;
+
+ if ((UINT64) offset + offsetof(KeyValue, name) + kv->name_len > bcd_len)
+ continue;
+
+ /* If most significant bit is set, data is stored in data_offset itself, but
+ * we are only interested in UTF16 strings. The only strings that could fit
+ * would have just one char in it, so let's not bother with this. */
+ if (FLAGS_SET(kv->data_size, UINT32_C(1) << 31))
+ continue;
+
+ if ((UINT64) kv->data_offset + kv->data_size > bcd_len)
+ continue;
+
+ if (strncaseeqa(name, kv->name, kv->name_len) && !name[kv->name_len])
+ return kv;
+ }
+
+ return NULL;
+}
+
+/* The BCD store is really just a regular windows registry hive with a rather cryptic internal
+ * key structure. On a running system it gets mounted to HKEY_LOCAL_MACHINE\BCD00000000.
+ *
+ * Of interest to us are the these two keys:
+ * - \Objects\{bootmgr}\Elements\24000001
+ * This key is the "displayorder" property and contains a value of type REG_MULTI_SZ
+ * with the name "Element" that holds a {GUID} list (UTF16, NUL-separated).
+ * - \Objects\{GUID}\Elements\12000004
+ * This key is the "description" property and contains a value of type REG_SZ with the
+ * name "Element" that holds a NUL-terminated UTF16 string.
+ *
+ * The GUIDs and properties are as reported by "bcdedit.exe /v".
+ *
+ * To get a title for the BCD store we first look at the displayorder property of {bootmgr}
+ * (it always has the GUID 9dea862c-5cdd-4e70-acc1-f32b344d4795). If it contains more than
+ * one GUID, the BCD is multi-boot and we stop looking. Otherwise we take that GUID, look it
+ * up, and return its description property. */
+CHAR16 *get_bcd_title(UINT8 *bcd, UINTN bcd_len) {
+ assert(bcd);
+
+ if (HIVE_CELL_OFFSET > bcd_len)
+ return NULL;
+
+ BaseBlock *base_block = (BaseBlock *) bcd;
+ if (base_block->sig != SIG_BASE_BLOCK ||
+ base_block->version_major != 1 ||
+ base_block->version_minor != 3 ||
+ base_block->type != 0 ||
+ base_block->primary_seqnum != base_block->secondary_seqnum)
+ return NULL;
+
+ bcd += HIVE_CELL_OFFSET;
+ bcd_len -= HIVE_CELL_OFFSET;
+
+ const Key *objects_key = get_key(
+ bcd, bcd_len,
+ base_block->root_cell_offset,
+ (const CHAR8 *) "\0Objects\0");
+ if (!objects_key)
+ return NULL;
+
+ const Key *displayorder_key = get_subkey(
+ bcd, bcd_len,
+ objects_key->subkeys_offset,
+ (const CHAR8 *) "{9dea862c-5cdd-4e70-acc1-f32b344d4795}\0Elements\00024000001\0");
+ if (!displayorder_key)
+ return NULL;
+
+ const KeyValue *displayorder_value = get_key_value(
+ bcd, bcd_len,
+ displayorder_key,
+ (const CHAR8 *) "Element");
+ if (!displayorder_value)
+ return NULL;
+
+ CHAR8 order_guid[sizeof("{00000000-0000-0000-0000-000000000000}\0")];
+ if (displayorder_value->data_type != REG_MULTI_SZ ||
+ displayorder_value->data_size != sizeof(CHAR16) * sizeof(order_guid))
+ /* BCD is multi-boot. */
+ return NULL;
+
+ /* Keys are stored as ASCII in registry hives if the data fits (and GUIDS always should). */
+ CHAR16 *order_guid_utf16 = (CHAR16 *) (bcd + displayorder_value->data_offset);
+ for (UINTN i = 0; i < sizeof(order_guid) - 2; i++) {
+ CHAR16 c = order_guid_utf16[i];
+ switch (c) {
+ case '-':
+ case '{':
+ case '}':
+ case '0' ... '9':
+ case 'a' ... 'f':
+ case 'A' ... 'F':
+ order_guid[i] = c;
+ break;
+ default:
+ /* Not a valid GUID. */
+ return NULL;
+ }
+ }
+ /* Our functions expect the lookup key to be double-derminated. */
+ order_guid[sizeof(order_guid) - 2] = '\0';
+ order_guid[sizeof(order_guid) - 1] = '\0';
+
+ const Key *default_key = get_subkey(bcd, bcd_len, objects_key->subkeys_offset, order_guid);
+ if (!default_key)
+ return NULL;
+
+ const Key *description_key = get_subkey(
+ bcd, bcd_len,
+ default_key->subkeys_offset,
+ (const CHAR8 *) "Elements\00012000004\0");
+ if (!description_key)
+ return NULL;
+
+ const KeyValue *description_value = get_key_value(
+ bcd, bcd_len,
+ description_key,
+ (const CHAR8 *) "Element");
+ if (!description_value)
+ return NULL;
+
+ if (description_value->data_type != REG_SZ ||
+ description_value->data_size < sizeof(CHAR16) ||
+ description_value->data_size % sizeof(CHAR16) != 0)
+ return NULL;
+
+ /* The data should already be NUL-terminated. */
+ CHAR16 *title = (CHAR16 *) (bcd + description_value->data_offset);
+ title[description_value->data_size / sizeof(CHAR16)] = '\0';
+ return title;
+}