mirror of
https://github.com/systemd/systemd
synced 2026-04-10 17:15:03 +02:00
Compare commits
7 Commits
d2a58f0edb
...
b5d3138f91
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5d3138f91 | ||
|
|
6d7bc744ce | ||
|
|
db7f5ab68f | ||
|
|
986fd3ebc2 | ||
|
|
f7bc0fb35b | ||
|
|
7700e9ba15 | ||
|
|
245e9d55ae |
13
meson.build
13
meson.build
@ -1676,8 +1676,14 @@ conf.set10('ENABLE_TIMEDATECTL', get_option('timedated') or get_option('timesync
|
|||||||
|
|
||||||
conf.set10('SYSTEMD_SLOW_TESTS_DEFAULT', slow_tests)
|
conf.set10('SYSTEMD_SLOW_TESTS_DEFAULT', slow_tests)
|
||||||
|
|
||||||
#####################################################################
|
############################################################
|
||||||
|
|
||||||
|
tests = []
|
||||||
|
fuzzers = []
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
|
||||||
|
# Include these now as they provide gnu-efi detection.
|
||||||
subdir('src/fundamental')
|
subdir('src/fundamental')
|
||||||
subdir('src/boot/efi')
|
subdir('src/boot/efi')
|
||||||
|
|
||||||
@ -1695,7 +1701,7 @@ update_syscall_tables_sh = find_program('tools/update-syscall-tables.sh')
|
|||||||
xml_helper_py = find_program('tools/xml_helper.py')
|
xml_helper_py = find_program('tools/xml_helper.py')
|
||||||
export_dbus_interfaces_py = find_program('tools/dbus_exporter.py')
|
export_dbus_interfaces_py = find_program('tools/dbus_exporter.py')
|
||||||
|
|
||||||
#####################################################################
|
############################################################
|
||||||
|
|
||||||
config_h = configure_file(
|
config_h = configure_file(
|
||||||
output : 'config.h',
|
output : 'config.h',
|
||||||
@ -1716,9 +1722,6 @@ if dbus_interfaces_dir == ''
|
|||||||
dbus_interfaces_dir = get_option('datadir') + '/dbus-1'
|
dbus_interfaces_dir = get_option('datadir') + '/dbus-1'
|
||||||
endif
|
endif
|
||||||
|
|
||||||
tests = []
|
|
||||||
fuzzers = []
|
|
||||||
|
|
||||||
basic_includes = include_directories(
|
basic_includes = include_directories(
|
||||||
'src/basic',
|
'src/basic',
|
||||||
'src/fundamental',
|
'src/fundamental',
|
||||||
|
|||||||
@ -41,6 +41,7 @@ SUBSYSTEM=="drm", KERNEL=="renderD*", GROUP="render", MODE="{{GROUP_RENDER_MODE}
|
|||||||
SUBSYSTEM=="kfd", GROUP="render", MODE="{{GROUP_RENDER_MODE}}"
|
SUBSYSTEM=="kfd", GROUP="render", MODE="{{GROUP_RENDER_MODE}}"
|
||||||
|
|
||||||
SUBSYSTEM=="misc", KERNEL=="sgx_enclave", GROUP="sgx", MODE="0660"
|
SUBSYSTEM=="misc", KERNEL=="sgx_enclave", GROUP="sgx", MODE="0660"
|
||||||
|
SUBSYSTEM=="misc", KERNEL=="sgx_vepc", GROUP="sgx", MODE="0660"
|
||||||
|
|
||||||
# When using static_node= with non-default permissions, also update
|
# When using static_node= with non-default permissions, also update
|
||||||
# tmpfiles.d/static-nodes-permissions.conf.in to keep permissions synchronized.
|
# tmpfiles.d/static-nodes-permissions.conf.in to keep permissions synchronized.
|
||||||
|
|||||||
321
src/boot/efi/bcd.c
Normal file
321
src/boot/efi/bcd.c
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
|
||||||
|
#ifdef SD_BOOT
|
||||||
|
# include <efi.h>
|
||||||
|
# include "macro-fundamental.h"
|
||||||
|
# include "util.h"
|
||||||
|
# define TEST_STATIC
|
||||||
|
#else
|
||||||
|
/* Provide our own "EFI API" if we are running as a unit test. */
|
||||||
|
# include <stddef.h>
|
||||||
|
# include <stdint.h>
|
||||||
|
# include <uchar.h>
|
||||||
|
# include "string-util-fundamental.h"
|
||||||
|
|
||||||
|
# define CHAR8 char
|
||||||
|
# define CHAR16 char16_t
|
||||||
|
# define UINT8 uint8_t
|
||||||
|
# define UINT16 uint16_t
|
||||||
|
# define UINT32 uint32_t
|
||||||
|
# define UINT64 uint64_t
|
||||||
|
# define UINTN size_t
|
||||||
|
# define strncaseeqa(a, b, n) strncaseeq((a), (b), (n))
|
||||||
|
# define TEST_STATIC static
|
||||||
|
#endif
|
||||||
|
|
||||||
|
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. */
|
||||||
|
TEST_STATIC 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;
|
||||||
|
}
|
||||||
6
src/boot/efi/bcd.h
Normal file
6
src/boot/efi/bcd.h
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <efi.h>
|
||||||
|
|
||||||
|
CHAR16 *get_bcd_title(UINT8 *bcd, UINTN bcd_len);
|
||||||
@ -4,6 +4,7 @@
|
|||||||
#include <efigpt.h>
|
#include <efigpt.h>
|
||||||
#include <efilib.h>
|
#include <efilib.h>
|
||||||
|
|
||||||
|
#include "bcd.h"
|
||||||
#include "bootspec-fundamental.h"
|
#include "bootspec-fundamental.h"
|
||||||
#include "console.h"
|
#include "console.h"
|
||||||
#include "devicetree.h"
|
#include "devicetree.h"
|
||||||
@ -1941,7 +1942,7 @@ static void config_entry_add_osx(Config *config) {
|
|||||||
|
|
||||||
static void config_entry_add_windows(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir) {
|
static void config_entry_add_windows(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir) {
|
||||||
_cleanup_freepool_ CHAR8 *bcd = NULL;
|
_cleanup_freepool_ CHAR8 *bcd = NULL;
|
||||||
const CHAR16 *title = NULL;
|
CHAR16 *title = NULL;
|
||||||
EFI_STATUS err;
|
EFI_STATUS err;
|
||||||
UINTN len;
|
UINTN len;
|
||||||
|
|
||||||
@ -1954,34 +1955,8 @@ static void config_entry_add_windows(Config *config, EFI_HANDLE *device, EFI_FIL
|
|||||||
|
|
||||||
/* Try to find a better title. */
|
/* Try to find a better title. */
|
||||||
err = file_read(root_dir, L"\\EFI\\Microsoft\\Boot\\BCD", 0, 100*1024, &bcd, &len);
|
err = file_read(root_dir, L"\\EFI\\Microsoft\\Boot\\BCD", 0, 100*1024, &bcd, &len);
|
||||||
if (!EFI_ERROR(err)) {
|
if (!EFI_ERROR(err))
|
||||||
static const CHAR16 *versions[] = {
|
title = get_bcd_title((UINT8 *) bcd, len);
|
||||||
L"Windows 11",
|
|
||||||
L"Windows 10",
|
|
||||||
L"Windows 8.1",
|
|
||||||
L"Windows 8",
|
|
||||||
L"Windows 7",
|
|
||||||
L"Windows Vista",
|
|
||||||
};
|
|
||||||
|
|
||||||
CHAR8 *p = bcd;
|
|
||||||
while (!title) {
|
|
||||||
CHAR8 *q = mempmem_safe(p, len, versions[0], STRLEN(L"Windows "));
|
|
||||||
if (!q)
|
|
||||||
break;
|
|
||||||
|
|
||||||
len -= q - p;
|
|
||||||
p = q;
|
|
||||||
|
|
||||||
/* We found the prefix, now try all the version strings. */
|
|
||||||
for (UINTN i = 0; i < ELEMENTSOF(versions); i++) {
|
|
||||||
if (memory_startswith(p, len, versions[i] + STRLEN("Windows "))) {
|
|
||||||
title = versions[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
config_entry_add_loader_auto(config, device, root_dir, NULL,
|
config_entry_add_loader_auto(config, device, root_dir, NULL,
|
||||||
L"auto-windows", 'w', title ?: L"Windows Boot Manager",
|
L"auto-windows", 'w', title ?: L"Windows Boot Manager",
|
||||||
|
|||||||
@ -26,7 +26,7 @@ static EFI_STATUS tpm1_measure_to_pcr_and_event_log(
|
|||||||
assert(description);
|
assert(description);
|
||||||
|
|
||||||
desc_len = StrSize(description);
|
desc_len = StrSize(description);
|
||||||
tcg_event = xallocate_zero_pool(OFFSETOF(TCG_PCR_EVENT, Event) + desc_len);
|
tcg_event = xallocate_zero_pool(offsetof(TCG_PCR_EVENT, Event) + desc_len);
|
||||||
*tcg_event = (TCG_PCR_EVENT) {
|
*tcg_event = (TCG_PCR_EVENT) {
|
||||||
.EventSize = desc_len,
|
.EventSize = desc_len,
|
||||||
.PCRIndex = pcrindex,
|
.PCRIndex = pcrindex,
|
||||||
@ -57,9 +57,9 @@ static EFI_STATUS tpm2_measure_to_pcr_and_event_log(
|
|||||||
assert(description);
|
assert(description);
|
||||||
|
|
||||||
desc_len = StrSize(description);
|
desc_len = StrSize(description);
|
||||||
tcg_event = xallocate_zero_pool(OFFSETOF(EFI_TCG2_EVENT, Event) + desc_len);
|
tcg_event = xallocate_zero_pool(offsetof(EFI_TCG2_EVENT, Event) + desc_len);
|
||||||
*tcg_event = (EFI_TCG2_EVENT) {
|
*tcg_event = (EFI_TCG2_EVENT) {
|
||||||
.Size = OFFSETOF(EFI_TCG2_EVENT, Event) + desc_len,
|
.Size = offsetof(EFI_TCG2_EVENT, Event) + desc_len,
|
||||||
.Header.HeaderSize = sizeof(EFI_TCG2_EVENT_HEADER),
|
.Header.HeaderSize = sizeof(EFI_TCG2_EVENT_HEADER),
|
||||||
.Header.HeaderVersion = EFI_TCG2_EVENT_HEADER_VERSION,
|
.Header.HeaderVersion = EFI_TCG2_EVENT_HEADER_VERSION,
|
||||||
.Header.PCRIndex = pcrindex,
|
.Header.PCRIndex = pcrindex,
|
||||||
|
|||||||
@ -100,6 +100,7 @@ if efi_lds == ''
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
efi_headers = files('''
|
efi_headers = files('''
|
||||||
|
bcd.h
|
||||||
console.h
|
console.h
|
||||||
cpio.h
|
cpio.h
|
||||||
devicetree.h
|
devicetree.h
|
||||||
@ -129,6 +130,7 @@ common_sources = '''
|
|||||||
'''.split()
|
'''.split()
|
||||||
|
|
||||||
systemd_boot_sources = '''
|
systemd_boot_sources = '''
|
||||||
|
bcd.c
|
||||||
boot.c
|
boot.c
|
||||||
console.c
|
console.c
|
||||||
drivers.c
|
drivers.c
|
||||||
@ -371,6 +373,14 @@ endforeach
|
|||||||
|
|
||||||
############################################################
|
############################################################
|
||||||
|
|
||||||
|
tests += [
|
||||||
|
[['src/boot/efi/test-bcd.c'],
|
||||||
|
[],
|
||||||
|
[libzstd],
|
||||||
|
[],
|
||||||
|
'HAVE_ZSTD'],
|
||||||
|
]
|
||||||
|
|
||||||
test_efi_disk_img = custom_target(
|
test_efi_disk_img = custom_target(
|
||||||
'test-efi-disk.img',
|
'test-efi-disk.img',
|
||||||
input : [efi_stubs[0][0], efi_stubs[1][1]],
|
input : [efi_stubs[0][0], efi_stubs[1][1]],
|
||||||
|
|||||||
@ -129,7 +129,7 @@ static inline BOOLEAN verify_pe(const struct PeFileHeader *pe) {
|
|||||||
static inline UINTN section_table_offset(const struct DosFileHeader *dos, const struct PeFileHeader *pe) {
|
static inline UINTN section_table_offset(const struct DosFileHeader *dos, const struct PeFileHeader *pe) {
|
||||||
assert(dos);
|
assert(dos);
|
||||||
assert(pe);
|
assert(pe);
|
||||||
return dos->ExeHeader + OFFSETOF(struct PeFileHeader, OptionalHeader) + pe->FileHeader.SizeOfOptionalHeader;
|
return dos->ExeHeader + offsetof(struct PeFileHeader, OptionalHeader) + pe->FileHeader.SizeOfOptionalHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void locate_sections(
|
static void locate_sections(
|
||||||
|
|||||||
162
src/boot/efi/test-bcd.c
Normal file
162
src/boot/efi/test-bcd.c
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
|
||||||
|
#include "alloc-util.h"
|
||||||
|
#include "compress.h"
|
||||||
|
#include "fileio.h"
|
||||||
|
#include "tests.h"
|
||||||
|
#include "utf8.h"
|
||||||
|
|
||||||
|
/* Inlcude the implementation directly, so we can poke at some internals. */
|
||||||
|
#include "bcd.c"
|
||||||
|
|
||||||
|
static void load_bcd(const char *path, void **ret_bcd, size_t *ret_bcd_len) {
|
||||||
|
size_t len;
|
||||||
|
_cleanup_free_ char *fn = NULL, *compressed = NULL;
|
||||||
|
|
||||||
|
assert_se(get_testdata_dir(path, &fn) >= 0);
|
||||||
|
assert_se(read_full_file_full(AT_FDCWD, fn, UINT64_MAX, SIZE_MAX, 0, NULL, &compressed, &len) >= 0);
|
||||||
|
assert_se(decompress_blob_zstd(compressed, len, ret_bcd, ret_bcd_len, SIZE_MAX) >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_get_bcd_title_one(
|
||||||
|
const char *path,
|
||||||
|
const char16_t *title_expect,
|
||||||
|
size_t title_len_expect) {
|
||||||
|
|
||||||
|
size_t len;
|
||||||
|
_cleanup_free_ void *bcd = NULL;
|
||||||
|
|
||||||
|
log_info("/* %s(%s) */", __func__, path);
|
||||||
|
|
||||||
|
load_bcd(path, &bcd, &len);
|
||||||
|
|
||||||
|
char16_t *title = get_bcd_title(bcd, len);
|
||||||
|
if (title_expect) {
|
||||||
|
assert_se(title);
|
||||||
|
assert_se(memcmp(title, title_expect, title_len_expect) == 0);
|
||||||
|
} else
|
||||||
|
assert_se(!title);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(get_bcd_title) {
|
||||||
|
const char16_t win10[] = { 'W', 'i', 'n', 'd', 'o', 'w', 's', ' ', '1', '0', '\0' };
|
||||||
|
test_get_bcd_title_one("test-bcd/win10.bcd.zst", win10, sizeof(win10));
|
||||||
|
|
||||||
|
test_get_bcd_title_one("test-bcd/description-bad-type.bcd.zst", NULL, 0);
|
||||||
|
test_get_bcd_title_one("test-bcd/description-empty.bcd.zst", NULL, 0);
|
||||||
|
test_get_bcd_title_one("test-bcd/description-missing.bcd.zst", NULL, 0);
|
||||||
|
test_get_bcd_title_one("test-bcd/description-too-small.bcd.zst", NULL, 0);
|
||||||
|
test_get_bcd_title_one("test-bcd/displayorder-bad-name.bcd.zst", NULL, 0);
|
||||||
|
test_get_bcd_title_one("test-bcd/displayorder-bad-size.bcd.zst", NULL, 0);
|
||||||
|
test_get_bcd_title_one("test-bcd/displayorder-bad-type.bcd.zst", NULL, 0);
|
||||||
|
test_get_bcd_title_one("test-bcd/empty.bcd.zst", NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(base_block) {
|
||||||
|
size_t len;
|
||||||
|
BaseBlock backup;
|
||||||
|
uint8_t *bcd_base;
|
||||||
|
_cleanup_free_ BaseBlock *bcd = NULL;
|
||||||
|
|
||||||
|
load_bcd("test-bcd/win10.bcd.zst", (void **) &bcd, &len);
|
||||||
|
backup = *bcd;
|
||||||
|
bcd_base = (uint8_t *) bcd;
|
||||||
|
|
||||||
|
assert_se(get_bcd_title(bcd_base, len));
|
||||||
|
|
||||||
|
/* Try various "corruptions" of the base block. */
|
||||||
|
|
||||||
|
assert_se(!get_bcd_title(bcd_base, sizeof(BaseBlock) - 1));
|
||||||
|
|
||||||
|
bcd->sig = 0;
|
||||||
|
assert_se(!get_bcd_title(bcd_base, len));
|
||||||
|
*bcd = backup;
|
||||||
|
|
||||||
|
bcd->version_minor = 2;
|
||||||
|
assert_se(!get_bcd_title(bcd_base, len));
|
||||||
|
*bcd = backup;
|
||||||
|
|
||||||
|
bcd->version_major = 4;
|
||||||
|
assert_se(!get_bcd_title(bcd_base, len));
|
||||||
|
*bcd = backup;
|
||||||
|
|
||||||
|
bcd->type = 1;
|
||||||
|
assert_se(!get_bcd_title(bcd_base, len));
|
||||||
|
*bcd = backup;
|
||||||
|
|
||||||
|
bcd->primary_seqnum++;
|
||||||
|
assert_se(!get_bcd_title(bcd_base, len));
|
||||||
|
*bcd = backup;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(bad_bcd) {
|
||||||
|
size_t len;
|
||||||
|
uint8_t *hbins;
|
||||||
|
uint32_t offset;
|
||||||
|
_cleanup_free_ void *bcd = NULL;
|
||||||
|
|
||||||
|
/* This BCD hive has been manipulated to have bad offsets/sizes at various places. */
|
||||||
|
load_bcd("test-bcd/corrupt.bcd.zst", &bcd, &len);
|
||||||
|
|
||||||
|
assert_se(len >= HIVE_CELL_OFFSET);
|
||||||
|
hbins = (uint8_t *) bcd + HIVE_CELL_OFFSET;
|
||||||
|
len -= HIVE_CELL_OFFSET;
|
||||||
|
offset = ((BaseBlock *) bcd)->root_cell_offset;
|
||||||
|
|
||||||
|
const Key *root = get_key(hbins, len, offset, "\0");
|
||||||
|
assert_se(root);
|
||||||
|
assert_se(!get_key(hbins, sizeof(Key) - 1, offset, "\0"));
|
||||||
|
|
||||||
|
assert_se(!get_key(hbins, len, offset, "\0BadOffset\0"));
|
||||||
|
assert_se(!get_key(hbins, len, offset, "\0BadSig\0"));
|
||||||
|
assert_se(!get_key(hbins, len, offset, "\0BadKeyNameLen\0"));
|
||||||
|
assert_se(!get_key(hbins, len, offset, "\0SubkeyBadOffset\0Dummy\0"));
|
||||||
|
assert_se(!get_key(hbins, len, offset, "\0SubkeyBadSig\0Dummy\0"));
|
||||||
|
assert_se(!get_key(hbins, len, offset, "\0SubkeyBadNEntries\0Dummy\0"));
|
||||||
|
|
||||||
|
assert_se(!get_key_value(hbins, len, root, "Dummy"));
|
||||||
|
|
||||||
|
const Key *kv_bad_offset = get_key(hbins, len, offset, "\0KeyValuesBadOffset\0");
|
||||||
|
assert_se(kv_bad_offset);
|
||||||
|
assert_se(!get_key_value(hbins, len, kv_bad_offset, "Dummy"));
|
||||||
|
|
||||||
|
const Key *kv_bad_n_key_values = get_key(hbins, len, offset, "\0KeyValuesBadNKeyValues\0");
|
||||||
|
assert_se(kv_bad_n_key_values);
|
||||||
|
assert_se(!get_key_value(hbins, len, kv_bad_n_key_values, "Dummy"));
|
||||||
|
|
||||||
|
const Key *kv = get_key(hbins, len, offset, "\0KeyValues\0");
|
||||||
|
assert_se(kv);
|
||||||
|
|
||||||
|
assert_se(!get_key_value(hbins, len, kv, "BadOffset"));
|
||||||
|
assert_se(!get_key_value(hbins, len, kv, "BadSig"));
|
||||||
|
assert_se(!get_key_value(hbins, len, kv, "BadNameLen"));
|
||||||
|
assert_se(!get_key_value(hbins, len, kv, "InlineData"));
|
||||||
|
assert_se(!get_key_value(hbins, len, kv, "BadDataOffset"));
|
||||||
|
assert_se(!get_key_value(hbins, len, kv, "BadDataSize"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(argv_bcds) {
|
||||||
|
for (int i = 1; i < saved_argc; i++) {
|
||||||
|
size_t len;
|
||||||
|
_cleanup_free_ void *bcd = NULL;
|
||||||
|
|
||||||
|
assert_se(read_full_file_full(
|
||||||
|
AT_FDCWD,
|
||||||
|
saved_argv[i],
|
||||||
|
UINT64_MAX,
|
||||||
|
SIZE_MAX,
|
||||||
|
0,
|
||||||
|
NULL,
|
||||||
|
(char **) &bcd,
|
||||||
|
&len) >= 0);
|
||||||
|
|
||||||
|
char16_t *title = get_bcd_title(bcd, len);
|
||||||
|
if (title) {
|
||||||
|
_cleanup_free_ char *title_utf8 = utf16_to_utf8(title, char16_strlen(title) * 2);
|
||||||
|
log_info("%s: \"%s\"", saved_argv[i], title_utf8);
|
||||||
|
} else
|
||||||
|
log_info("%s: Bad BCD", saved_argv[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST_MAIN(LOG_INFO);
|
||||||
@ -505,20 +505,6 @@ EFI_STATUS log_oom(void) {
|
|||||||
return EFI_OUT_OF_RESOURCES;
|
return EFI_OUT_OF_RESOURCES;
|
||||||
}
|
}
|
||||||
|
|
||||||
void *memmem_safe(const void *haystack, UINTN haystack_len, const void *needle, UINTN needle_len) {
|
|
||||||
assert(haystack || haystack_len == 0);
|
|
||||||
assert(needle || needle_len == 0);
|
|
||||||
|
|
||||||
if (needle_len == 0)
|
|
||||||
return (void*)haystack;
|
|
||||||
|
|
||||||
for (const CHAR8 *h = haystack, *n = needle; haystack_len >= needle_len; h++, haystack_len--)
|
|
||||||
if (*h == *n && CompareMem(h + 1, n + 1, needle_len - 1) == 0)
|
|
||||||
return (void*)h;
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void print_at(UINTN x, UINTN y, UINTN attr, const CHAR16 *str) {
|
void print_at(UINTN x, UINTN y, UINTN attr, const CHAR16 *str) {
|
||||||
assert(str);
|
assert(str);
|
||||||
ST->ConOut->SetCursorPosition(ST->ConOut, x, y);
|
ST->ConOut->SetCursorPosition(ST->ConOut, x, y);
|
||||||
@ -566,7 +552,7 @@ EFI_STATUS get_file_info_harder(
|
|||||||
EFI_FILE_INFO **ret,
|
EFI_FILE_INFO **ret,
|
||||||
UINTN *ret_size) {
|
UINTN *ret_size) {
|
||||||
|
|
||||||
UINTN size = OFFSETOF(EFI_FILE_INFO, FileName) + 256;
|
UINTN size = offsetof(EFI_FILE_INFO, FileName) + 256;
|
||||||
_cleanup_freepool_ EFI_FILE_INFO *fi = NULL;
|
_cleanup_freepool_ EFI_FILE_INFO *fi = NULL;
|
||||||
EFI_STATUS err;
|
EFI_STATUS err;
|
||||||
|
|
||||||
@ -610,7 +596,7 @@ EFI_STATUS readdir_harder(
|
|||||||
* the specified buffer needs to be freed by caller, after final use. */
|
* the specified buffer needs to be freed by caller, after final use. */
|
||||||
|
|
||||||
if (!*buffer) {
|
if (!*buffer) {
|
||||||
sz = OFFSETOF(EFI_FILE_INFO, FileName) /* + 256 */;
|
sz = offsetof(EFI_FILE_INFO, FileName) /* + 256 */;
|
||||||
*buffer = xallocate_pool(sz);
|
*buffer = xallocate_pool(sz);
|
||||||
*buffer_size = sz;
|
*buffer_size = sz;
|
||||||
} else
|
} else
|
||||||
@ -649,6 +635,27 @@ UINTN strnlena(const CHAR8 *p, UINTN maxlen) {
|
|||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
INTN strncasecmpa(const CHAR8 *a, const CHAR8 *b, UINTN maxlen) {
|
||||||
|
if (!a || !b)
|
||||||
|
return CMP(a, b);
|
||||||
|
|
||||||
|
while (maxlen > 0) {
|
||||||
|
CHAR8 ca = *a, cb = *b;
|
||||||
|
if (ca >= 'A' && ca <= 'Z')
|
||||||
|
ca += 'a' - 'A';
|
||||||
|
if (cb >= 'A' && cb <= 'Z')
|
||||||
|
cb += 'a' - 'A';
|
||||||
|
if (!ca || ca != cb)
|
||||||
|
return ca - cb;
|
||||||
|
|
||||||
|
a++;
|
||||||
|
b++;
|
||||||
|
maxlen--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
CHAR8 *xstrndup8(const CHAR8 *p, UINTN sz) {
|
CHAR8 *xstrndup8(const CHAR8 *p, UINTN sz) {
|
||||||
CHAR8 *n;
|
CHAR8 *n;
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
/* This TPM PCR is where most Linux infrastructure extends the initrd binary images into, and so do we. */
|
/* This TPM PCR is where most Linux infrastructure extends the initrd binary images into, and so do we. */
|
||||||
#define TPM_PCR_INDEX_INITRD 4
|
#define TPM_PCR_INDEX_INITRD 4
|
||||||
|
|
||||||
#define OFFSETOF(x,y) __builtin_offsetof(x,y)
|
#define offsetof(type, member) __builtin_offsetof(type, member)
|
||||||
|
|
||||||
#define UINTN_MAX (~(UINTN)0)
|
#define UINTN_MAX (~(UINTN)0)
|
||||||
#define INTN_MAX ((INTN)(UINTN_MAX>>1))
|
#define INTN_MAX ((INTN)(UINTN_MAX>>1))
|
||||||
@ -111,13 +111,6 @@ EFI_STATUS log_oom(void);
|
|||||||
err; \
|
err; \
|
||||||
})
|
})
|
||||||
|
|
||||||
void *memmem_safe(const void *haystack, UINTN haystack_len, const void *needle, UINTN needle_len);
|
|
||||||
|
|
||||||
static inline void *mempmem_safe(const void *haystack, UINTN haystack_len, const void *needle, UINTN needle_len) {
|
|
||||||
CHAR8 *p = memmem_safe(haystack, haystack_len, needle, needle_len);
|
|
||||||
return p ? p + needle_len : NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void print_at(UINTN x, UINTN y, UINTN attr, const CHAR16 *str);
|
void print_at(UINTN x, UINTN y, UINTN attr, const CHAR16 *str);
|
||||||
void clear_screen(UINTN attr);
|
void clear_screen(UINTN attr);
|
||||||
|
|
||||||
@ -130,6 +123,10 @@ EFI_STATUS readdir_harder(EFI_FILE_HANDLE handle, EFI_FILE_INFO **buffer, UINTN
|
|||||||
|
|
||||||
UINTN strnlena(const CHAR8 *p, UINTN maxlen);
|
UINTN strnlena(const CHAR8 *p, UINTN maxlen);
|
||||||
CHAR8 *xstrndup8(const CHAR8 *p, UINTN sz);
|
CHAR8 *xstrndup8(const CHAR8 *p, UINTN sz);
|
||||||
|
INTN strncasecmpa(const CHAR8 *a, const CHAR8 *b, UINTN maxlen);
|
||||||
|
static inline BOOLEAN strncaseeqa(const CHAR8 *a, const CHAR8 *b, UINTN maxlen) {
|
||||||
|
return strncasecmpa(a, b, maxlen) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
BOOLEAN is_ascii(const CHAR16 *f);
|
BOOLEAN is_ascii(const CHAR16 *f);
|
||||||
|
|
||||||
|
|||||||
@ -67,6 +67,11 @@ if install_tests
|
|||||||
'../-.mount',
|
'../-.mount',
|
||||||
testsuite08_dir + '/local-fs.target.wants/-.mount')
|
testsuite08_dir + '/local-fs.target.wants/-.mount')
|
||||||
|
|
||||||
|
if conf.get('HAVE_GNU_EFI') == 1 and conf.get('HAVE_ZSTD') == 1
|
||||||
|
install_subdir('test-bcd',
|
||||||
|
exclude_files : '.gitattributes',
|
||||||
|
install_dir : testdata_dir)
|
||||||
|
endif
|
||||||
if conf.get('ENABLE_RESOLVE') == 1
|
if conf.get('ENABLE_RESOLVE') == 1
|
||||||
install_subdir('test-resolve',
|
install_subdir('test-resolve',
|
||||||
exclude_files : '.gitattributes',
|
exclude_files : '.gitattributes',
|
||||||
|
|||||||
2
test/test-bcd/.gitattributes
vendored
Normal file
2
test/test-bcd/.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*.bcd binary
|
||||||
|
*.bcd.zst binary
|
||||||
BIN
test/test-bcd/corrupt.bcd.zst
Normal file
BIN
test/test-bcd/corrupt.bcd.zst
Normal file
Binary file not shown.
BIN
test/test-bcd/description-bad-type.bcd.zst
Normal file
BIN
test/test-bcd/description-bad-type.bcd.zst
Normal file
Binary file not shown.
BIN
test/test-bcd/description-empty.bcd.zst
Normal file
BIN
test/test-bcd/description-empty.bcd.zst
Normal file
Binary file not shown.
BIN
test/test-bcd/description-missing.bcd.zst
Normal file
BIN
test/test-bcd/description-missing.bcd.zst
Normal file
Binary file not shown.
BIN
test/test-bcd/description-too-small.bcd.zst
Normal file
BIN
test/test-bcd/description-too-small.bcd.zst
Normal file
Binary file not shown.
BIN
test/test-bcd/displayorder-bad-name.bcd.zst
Normal file
BIN
test/test-bcd/displayorder-bad-name.bcd.zst
Normal file
Binary file not shown.
BIN
test/test-bcd/displayorder-bad-size.bcd.zst
Normal file
BIN
test/test-bcd/displayorder-bad-size.bcd.zst
Normal file
Binary file not shown.
BIN
test/test-bcd/displayorder-bad-type.bcd.zst
Normal file
BIN
test/test-bcd/displayorder-bad-type.bcd.zst
Normal file
Binary file not shown.
BIN
test/test-bcd/empty.bcd.zst
Normal file
BIN
test/test-bcd/empty.bcd.zst
Normal file
Binary file not shown.
BIN
test/test-bcd/win10.bcd.zst
Normal file
BIN
test/test-bcd/win10.bcd.zst
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user