diff --git a/LICENSES/README.md b/LICENSES/README.md index 29ae23c727..f8668be820 100644 --- a/LICENSES/README.md +++ b/LICENSES/README.md @@ -69,6 +69,9 @@ The following exceptions apply: * the following sources are under **Public Domain** (LicenseRef-alg-sha1-public-domain): - src/fundamental/sha1-fundamental.c - src/fundamental/sha1-fundamental.h + * the following files are licensed under **BSD-3-Clause** license: + - src/boot/efi/chid.c + - src/boot/efi/chid.h * Heebo fonts under docs/fonts/ are licensed under the **SIL Open Font License 1.1**, * any files under test/ without an explicit license we assume non-copyrightable (eg: computer-generated fuzzer data) diff --git a/man/systemd-measure.xml b/man/systemd-measure.xml index 71983ffe00..69b9db59bb 100644 --- a/man/systemd-measure.xml +++ b/man/systemd-measure.xml @@ -77,7 +77,7 @@ , , , , , , , , , - , see below. Only is mandatory. (Alternatively, + , , , see below. Only is mandatory. (Alternatively, specify to use the current values of PCR register 11 instead.) @@ -125,6 +125,8 @@ + + When used with the calculate or sign verb, configures the files to read the unified kernel image components from. Each option corresponds with @@ -134,7 +136,7 @@ - With the exception of , which has been added in version + With the exception of , and , which have been added in version 257. diff --git a/man/systemd-stub.xml b/man/systemd-stub.xml index 439d999c64..6625fca91e 100644 --- a/man/systemd-stub.xml +++ b/man/systemd-stub.xml @@ -79,6 +79,20 @@ A .dtb section with a compiled binary DeviceTree. + Zero or more .dtbauto sections. Stub will always try to find first matching one. + Matching process extracts first compatible string from .dtbauto + section and compares it with the first Devicetree's compatible string supplied by + the firmware in configuration tables. If firmware does not provide Devicetree, matching with + .hwids section will be used instead. Stub will use SMBIOS data to calculate hardware + IDs of the machine (as per specification), + then it will proceed to trying to find any of them in .hwids section and will use first + matching entry's compatible as a search key among the .dtbauto + entries, in a similar fashion as the use of compatible string read from the firmware + provided Devicetree was described before. First matching .dtbauto section will be + loaded and will override .dtb if present. + + A .hwids section with hardware IDs of the machines to match Devicetrees (refer to .dtbauto section description). + A .uname section with the kernel version information, i.e. the output of uname -r for the kernel included in the .linux section. diff --git a/src/boot/efi/chid.c b/src/boot/efi/chid.c new file mode 100644 index 0000000000..50d840aea0 --- /dev/null +++ b/src/boot/efi/chid.c @@ -0,0 +1,129 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ + +/* + * Based on Nikita Travkin's dtbloader implementation. + * Copyright (c) 2024 Nikita Travkin + * + * https://github.com/TravMurav/dtbloader/blob/main/src/chid.c + */ + +/* + * Based on Linaro dtbloader implementation. + * Copyright (c) 2019, Linaro. All rights reserved. + * + * https://github.com/aarch64-laptops/edk2/blob/dtbloader-app/EmbeddedPkg/Application/ConfigTableLoader/CHID.c + */ + +#include "chid.h" +#include "chid-fundamental.h" +#include "efi.h" +#include "sha1-fundamental.h" +#include "smbios.h" +#include "util.h" + +/** + * smbios_to_hashable_string() - Convert ascii smbios string to stripped char16_t. + */ +static char16_t *smbios_to_hashable_string(const char *str) { + if (!str) + /* User of this function is expected to free the result. */ + return xnew0(char16_t, 1); + + /* + * We need to strip leading and trailing spaces, leading zeroes. + * See fwupd/libfwupdplugin/fu-hwids-smbios.c + */ + while (*str == ' ') + str++; + + while (*str == '0') + str++; + + size_t len = strlen8(str); + + while (len > 0 && str[len - 1] == ' ') + len--; + + return xstrn8_to_16(str, len); +} + +/* This has to be in a struct due to _cleanup_ in populate_board_chids */ +typedef struct SmbiosInfo { + const char16_t *smbios_fields[_CHID_SMBIOS_FIELDS_MAX]; +} SmbiosInfo; + +static void smbios_info_populate(SmbiosInfo *ret_info) { + static RawSmbiosInfo raw = {}; + static bool raw_info_populated = false; + + if (!raw_info_populated) { + smbios_raw_info_populate(&raw); + raw_info_populated = true; + } + + ret_info->smbios_fields[CHID_SMBIOS_MANUFACTURER] = smbios_to_hashable_string(raw.manufacturer); + ret_info->smbios_fields[CHID_SMBIOS_PRODUCT_NAME] = smbios_to_hashable_string(raw.product_name); + ret_info->smbios_fields[CHID_SMBIOS_PRODUCT_SKU] = smbios_to_hashable_string(raw.product_sku); + ret_info->smbios_fields[CHID_SMBIOS_FAMILY] = smbios_to_hashable_string(raw.family); + ret_info->smbios_fields[CHID_SMBIOS_BASEBOARD_PRODUCT] = smbios_to_hashable_string(raw.baseboard_product); + ret_info->smbios_fields[CHID_SMBIOS_BASEBOARD_MANUFACTURER] = smbios_to_hashable_string(raw.baseboard_manufacturer); +} + +static void smbios_info_done(SmbiosInfo *info) { + FOREACH_ELEMENT(i, info->smbios_fields) + free(i); +} + +static EFI_STATUS populate_board_chids(EFI_GUID ret_chids[static CHID_TYPES_MAX]) { + _cleanup_(smbios_info_done) SmbiosInfo info = {}; + + if (!ret_chids) + return EFI_INVALID_PARAMETER; + + smbios_info_populate(&info); + chid_calculate(info.smbios_fields, ret_chids); + + return EFI_SUCCESS; +} + +EFI_STATUS chid_match(const void *hwid_buffer, size_t hwid_length, const Device **ret_device) { + EFI_STATUS status; + + if ((uintptr_t) hwid_buffer % alignof(Device) != 0) + return EFI_INVALID_PARAMETER; + + const Device *devices = ASSERT_PTR(hwid_buffer); + + EFI_GUID chids[CHID_TYPES_MAX] = {}; + static const size_t priority[] = { 3, 6, 8, 10, 4, 5, 7, 9, 11 }; /* From most to least specific. */ + + status = populate_board_chids(chids); + if (EFI_STATUS_IS_ERROR(status)) + return log_error_status(status, "Failed to populate board CHIDs: %m"); + + size_t n_devices = 0; + + /* Count devices and check validity */ + for (; (n_devices + 1) * sizeof(*devices) < hwid_length;) { + if (devices[n_devices].struct_size == 0) + break; + if (devices[n_devices].struct_size != sizeof(*devices)) + return EFI_UNSUPPORTED; + n_devices++; + } + + if (n_devices == 0) + return EFI_NOT_FOUND; + + FOREACH_ELEMENT(i, priority) + FOREACH_ARRAY(dev, devices, n_devices) { + /* Can't take a pointer to a packed struct member, so copy to a local variable */ + EFI_GUID chid = dev->chid; + if (efi_guid_equal(&chids[*i], &chid)) { + *ret_device = dev; + return EFI_SUCCESS; + } + } + + return EFI_NOT_FOUND; +} diff --git a/src/boot/efi/chid.h b/src/boot/efi/chid.h new file mode 100644 index 0000000000..ea6e2d348f --- /dev/null +++ b/src/boot/efi/chid.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +#pragma once + +#include "efi.h" + +#include "chid-fundamental.h" + +typedef struct Device { + uint32_t struct_size; /* = sizeof(struct Device), or 0 for EOL */ + uint32_t name_offset; /* nul-terminated string or 0 if not present */ + uint32_t compatible_offset; /* nul-terminated string or 0 if not present */ + EFI_GUID chid; +} _packed_ Device; + +static inline const char* device_get_name(const void *base, const Device *device) { + return device->name_offset == 0 ? NULL : (const char *) ((const uint8_t *) base + device->name_offset); +} + +static inline const char* device_get_compatible(const void *base, const Device *device) { + return device->compatible_offset == 0 ? NULL : (const char *) ((const uint8_t *) base + device->compatible_offset); +} + +EFI_STATUS chid_match(const void *chids_buffer, size_t chids_length, const Device **ret_device); diff --git a/src/boot/efi/devicetree.c b/src/boot/efi/devicetree.c index 61a43cd77d..f3563f296f 100644 --- a/src/boot/efi/devicetree.c +++ b/src/boot/efi/devicetree.c @@ -106,6 +106,129 @@ EFI_STATUS devicetree_install(struct devicetree_state *state, EFI_FILE *root_dir MAKE_GUID_PTR(EFI_DTB_TABLE), PHYSICAL_ADDRESS_TO_POINTER(state->addr)); } +static const char* devicetree_get_compatible(const void *dtb) { + if ((uintptr_t) dtb % alignof(FdtHeader) != 0) + return NULL; + + const FdtHeader *dt_header = ASSERT_PTR(dtb); + + if (be32toh(dt_header->magic) != UINT32_C(0xd00dfeed)) + return NULL; + + uint32_t dt_size = be32toh(dt_header->total_size); + uint32_t struct_off = be32toh(dt_header->off_dt_struct); + uint32_t struct_size = be32toh(dt_header->size_dt_struct); + uint32_t strings_off = be32toh(dt_header->off_dt_strings); + uint32_t strings_size = be32toh(dt_header->size_dt_strings); + uint32_t end; + + if (PTR_TO_SIZE(dtb) > SIZE_MAX - dt_size) + return NULL; + + if (!ADD_SAFE(&end, strings_off, strings_size) || end > dt_size) + return NULL; + const char *strings_block = (const char *) ((const uint8_t *) dt_header + strings_off); + + if (struct_off % sizeof(uint32_t) != 0) + return NULL; + if (struct_size % sizeof(uint32_t) != 0 || + !ADD_SAFE(&end, struct_off, struct_size) || + end > strings_off) + return NULL; + const uint32_t *cursor = (const uint32_t *) ((const uint8_t *) dt_header + struct_off); + + size_t size_words = struct_size / sizeof(uint32_t); + size_t len, name_off, len_words, s; + + for (size_t i = 0; i < end; i++) { + switch (be32toh(cursor[i])) { + case FDT_BEGIN_NODE: + if (i >= size_words || cursor[++i] != 0) + return NULL; + break; + case FDT_NOP: + break; + case FDT_PROP: + /* At least 3 words should present: len, name_off, c (nul-terminated string always has non-zero length) */ + if (i + 3 >= size_words || cursor[++i] != 0) + return NULL; + len = be32toh(cursor[++i]); + name_off = be32toh(cursor[++i]); + len_words = DIV_ROUND_UP(len, sizeof(uint32_t)); + + if (ADD_SAFE(&s, name_off, STRLEN("compatible")) && + s < strings_size && streq8(strings_block + name_off, "compatible")) { + const char *c = (const char *) &cursor[++i]; + if (len == 0 || i + len_words > size_words || c[len - 1] != '\0') + c = NULL; + + return c; + } + i += len_words; + break; + default: + return NULL; + } + } + + return NULL; +} + +bool firmware_devicetree_exists(void) { + return !!find_configuration_table(MAKE_GUID_PTR(EFI_DTB_TABLE)); +} + +/* This function checks if the firmware provided Devicetree + * and a UKI provided Devicetree contain the same first entry + * on their respective "compatible" fields (which usually defines + * the actual device model). More specifically, given the FW/UKI + * "compatible" property pair: + * + * compatible = "string1", "string2"; + * compatible = "string1", "string3"; + * + * the function reports a match, while for + * + * compatible = "string1", "string3"; + * compatible = "string2", "string1"; + * + * it reports a mismatch. + * + * Other entries might refer to SoC and therefore can't be used for matching + */ +EFI_STATUS devicetree_match(const void *uki_dtb, size_t uki_dtb_length) { + const void *fw_dtb = find_configuration_table(MAKE_GUID_PTR(EFI_DTB_TABLE)); + if (!fw_dtb) + return EFI_UNSUPPORTED; + + const char *fw_compat = devicetree_get_compatible(fw_dtb); + if (!fw_compat) + return EFI_UNSUPPORTED; + + return devicetree_match_by_compatible(uki_dtb, uki_dtb_length, fw_compat); +} + +EFI_STATUS devicetree_match_by_compatible(const void *uki_dtb, size_t uki_dtb_length, const char *compat) { + if ((uintptr_t) uki_dtb % alignof(FdtHeader) != 0) + return EFI_INVALID_PARAMETER; + + const FdtHeader *dt_header = ASSERT_PTR(uki_dtb); + + if (uki_dtb_length < sizeof(FdtHeader) || + uki_dtb_length < be32toh(dt_header->total_size)) + return EFI_INVALID_PARAMETER; + + if (!compat) + return EFI_INVALID_PARAMETER; + + const char *dt_compat = devicetree_get_compatible(uki_dtb); + if (!dt_compat) + return EFI_INVALID_PARAMETER; + + /* Only matches the first compatible string from each DT */ + return streq8(dt_compat, compat) ? EFI_SUCCESS : EFI_NOT_FOUND; +} + EFI_STATUS devicetree_install_from_memory( struct devicetree_state *state, const void *dtb_buffer, size_t dtb_length) { diff --git a/src/boot/efi/devicetree.h b/src/boot/efi/devicetree.h index 33eaa2256c..5f6720f655 100644 --- a/src/boot/efi/devicetree.h +++ b/src/boot/efi/devicetree.h @@ -9,6 +9,30 @@ struct devicetree_state { void *orig; }; +enum { + FDT_BEGIN_NODE = 1, + FDT_END_NODE = 2, + FDT_PROP = 3, + FDT_NOP = 4, + FDT_END = 9, +}; + +typedef struct FdtHeader { + uint32_t magic; + uint32_t total_size; + uint32_t off_dt_struct; + uint32_t off_dt_strings; + uint32_t off_mem_rsv_map; + uint32_t version; + uint32_t last_comp_version; + uint32_t boot_cpuid_phys; + uint32_t size_dt_strings; + uint32_t size_dt_struct; +} FdtHeader; + +bool firmware_devicetree_exists(void); +EFI_STATUS devicetree_match(const void *uki_dtb, size_t uki_dtb_length); +EFI_STATUS devicetree_match_by_compatible(const void *uki_dtb, size_t uki_dtb_length, const char *compat); EFI_STATUS devicetree_install(struct devicetree_state *state, EFI_FILE *root_dir, char16_t *name); EFI_STATUS devicetree_install_from_memory( struct devicetree_state *state, const void *dtb_buffer, size_t dtb_length); diff --git a/src/boot/efi/meson.build b/src/boot/efi/meson.build index 0109793b7a..29c5455dbd 100644 --- a/src/boot/efi/meson.build +++ b/src/boot/efi/meson.build @@ -254,6 +254,7 @@ endif ############################################################ libefi_sources = files( + 'chid.c', 'console.c', 'device-path-util.c', 'devicetree.c', diff --git a/src/boot/efi/pe.c b/src/boot/efi/pe.c index 26dfcd4291..00739a7c74 100644 --- a/src/boot/efi/pe.c +++ b/src/boot/efi/pe.c @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "chid.h" +#include "devicetree.h" #include "pe.h" #include "util.h" @@ -162,11 +164,46 @@ static bool pe_section_name_equal(const char *a, const char *b) { return true; } -static void pe_locate_sections( +static bool pe_use_this_dtb( + const void *dtb, + size_t dtb_size, + const void *base, + const Device *device, + size_t section_nb) { + + assert(dtb); + + EFI_STATUS err; + + err = devicetree_match(dtb, dtb_size); + if (err == EFI_SUCCESS) + return true; + if (err != EFI_UNSUPPORTED) + return false; + + /* There's nothing to match against if firmware does not provide DTB and there is no .hwids section */ + if (!device || !base) + return false; + + const char *compatible = device_get_compatible(base, device); + if (!compatible) + return false; + + err = devicetree_match_by_compatible(dtb, dtb_size, compatible); + if (err == EFI_SUCCESS) + return true; + if (err == EFI_INVALID_PARAMETER) + log_error_status(err, "Found bad DT blob in PE section %zu", section_nb); + return false; +} + +static void pe_locate_sections_internal( const PeSectionHeader section_table[], size_t n_section_table, const char *const section_names[], size_t validate_base, + const void *device_table, + const Device *device, PeSectionVector sections[]) { assert(section_table || n_section_table == 0); @@ -206,6 +243,20 @@ static void pe_locate_sections( continue; } + /* Special handling for .dtbauto sections compared to plain .dtb */ + if (pe_section_name_equal(section_names[i], ".dtbauto")) { + /* .dtbauto sections require validate_base for matching */ + if (!validate_base) + break; + if (!pe_use_this_dtb( + (const uint8_t *) SIZE_TO_PTR(validate_base) + j->VirtualAddress, + j->VirtualSize, + device_table, + device, + i)) + continue; + } + /* At this time, the sizes and offsets have been validated. Store them away */ sections[i] = (PeSectionVector) { .memory_size = j->VirtualSize, @@ -224,6 +275,73 @@ static void pe_locate_sections( } } +static bool looking_for_dbauto(const char *const section_names[]) { + assert(section_names); + + for (size_t i = 0; section_names[i]; i++) + if (pe_section_name_equal(section_names[i], ".dtbauto")) + return true; + return false; +} + +static void pe_locate_sections( + const PeSectionHeader section_table[], + size_t n_section_table, + const char *const section_names[], + size_t validate_base, + PeSectionVector sections[]) { + + if (!looking_for_dbauto(section_names)) + return pe_locate_sections_internal( + section_table, + n_section_table, + section_names, + validate_base, + /* device_base */ NULL, + /* device */ NULL, + sections); + + /* It doesn't make sense not to provide validate_base here */ + assert(validate_base != 0); + + const void *hwids = NULL; + const Device *device = NULL; + + if (!firmware_devicetree_exists()) { + /* Find HWIDs table and search for the current device */ + PeSectionVector hwids_section = {}; + + pe_locate_sections_internal( + section_table, + n_section_table, + (const char *const[]) { ".hwids", NULL }, + validate_base, + /* device_table */ NULL, + /* device */ NULL, + &hwids_section); + + if (hwids_section.memory_offset != 0) { + hwids = (const uint8_t *) SIZE_TO_PTR(validate_base) + hwids_section.memory_offset; + + EFI_STATUS err = chid_match(hwids, hwids_section.memory_size, &device); + if (err != EFI_SUCCESS) { + log_error_status(err, "HWID matching failed, no DT blob will be selected: %m"); + hwids = NULL; + } + } else + log_info("HWIDs section is missing, no DT blob will be selected"); + } + + return pe_locate_sections_internal( + section_table, + n_section_table, + section_names, + validate_base, + hwids, + device, + sections); +} + static uint32_t get_compatibility_entry_address(const DosFileHeader *dos, const PeFileHeader *pe) { /* The kernel may provide alternative PE entry points for different PE architectures. This allows * booting a 64-bit kernel on 32-bit EFI that is otherwise running on a 64-bit CPU. The locations of any diff --git a/src/boot/efi/stub.c b/src/boot/efi/stub.c index 9664c95d57..7261e942d3 100644 --- a/src/boot/efi/stub.c +++ b/src/boot/efi/stub.c @@ -614,12 +614,13 @@ static EFI_STATUS load_addons( if (err != EFI_SUCCESS || (!PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_CMDLINE) && !PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_DTB) && + !PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_DTBAUTO) && !PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_INITRD) && !PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_UCODE))) { if (err == EFI_SUCCESS) err = EFI_NOT_FOUND; log_error_status(err, - "Unable to locate embedded .cmdline/.dtb/.initrd/.ucode sections in %ls, ignoring: %m", + "Unable to locate embedded .cmdline/.dtb/.dtbauto/.initrd/.ucode sections in %ls, ignoring: %m", items[i]); continue; } @@ -647,7 +648,21 @@ static EFI_STATUS load_addons( *cmdline = xasprintf("%ls%ls%ls", strempty(tmp), isempty(tmp) ? u"" : u" ", extra16); } - if (devicetree_addons && PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_DTB)) { + // FIXME: do we want to do something else here? + // This should behave exactly as .dtb/.dtbauto in the main UKI + if (devicetree_addons && PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_DTBAUTO)) { + *devicetree_addons = xrealloc(*devicetree_addons, + *n_devicetree_addons * sizeof(NamedAddon), + (*n_devicetree_addons + 1) * sizeof(NamedAddon)); + + (*devicetree_addons)[(*n_devicetree_addons)++] = (NamedAddon) { + .blob = { + .iov_base = xmemdup((const uint8_t*) loaded_addon->ImageBase + sections[UNIFIED_SECTION_DTBAUTO].memory_offset, sections[UNIFIED_SECTION_DTBAUTO].memory_size), + .iov_len = sections[UNIFIED_SECTION_DTBAUTO].memory_size, + }, + .filename = xstrdup16(items[i]), + }; + } else if (devicetree_addons && PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_DTB)) { *devicetree_addons = xrealloc(*devicetree_addons, *n_devicetree_addons * sizeof(NamedAddon), (*n_devicetree_addons + 1) * sizeof(NamedAddon)); @@ -968,13 +983,20 @@ static void install_embedded_devicetree( assert(sections); assert(dt_state); - if (!PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_DTB)) + UnifiedSection section = _UNIFIED_SECTION_MAX; + + /* Use automatically selected DT if available, otherwise go for "normal" one */ + if (PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_DTBAUTO)) + section = UNIFIED_SECTION_DTBAUTO; + else if (PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_DTB)) + section = UNIFIED_SECTION_DTB; + else return; err = devicetree_install_from_memory( dt_state, - (const uint8_t*) loaded_image->ImageBase + sections[UNIFIED_SECTION_DTB].memory_offset, - sections[UNIFIED_SECTION_DTB].memory_size); + (const uint8_t*) loaded_image->ImageBase + sections[section].memory_offset, + sections[section].memory_size); if (err != EFI_SUCCESS) log_error_status(err, "Error loading embedded devicetree, ignoring: %m"); } diff --git a/src/boot/efi/util.h b/src/boot/efi/util.h index 054d49ef02..c7634576cf 100644 --- a/src/boot/efi/util.h +++ b/src/boot/efi/util.h @@ -69,6 +69,7 @@ static inline void* xmemdup(const void *p, size_t l) { } #define xnew(type, n) ((type *) xmalloc_multiply((n), sizeof(type))) +#define xnew0(type, n) ((type *) xcalloc_multiply((n), sizeof(type))) bool free_and_xstrdup16(char16_t **p, const char16_t *s); diff --git a/src/boot/measure.c b/src/boot/measure.c index 3c409f8bd9..9e6295b9da 100644 --- a/src/boot/measure.c +++ b/src/boot/measure.c @@ -103,6 +103,7 @@ static int help(int argc, char *argv[], void *userdata) { " --sbat=PATH Path to SBAT file %7$s .sbat\n" " --pcrpkey=PATH Path to public key for PCR signatures %7$s .pcrpkey\n" " --profile=PATH Path to profile file %7$s .profile\n" + " --hwids=PATH Path to HWIDs file %7$s .hwids\n" "\nSee the %2$s for details.\n", program_invocation_short_name, link, @@ -146,8 +147,10 @@ static int parse_argv(int argc, char *argv[]) { ARG_SBAT, _ARG_PCRSIG, /* the .pcrsig section is not input for signing, hence not actually an argument here */ ARG_PCRPKEY, + ARG_PROFILE, + ARG_HWIDS, _ARG_SECTION_LAST, - ARG_PROFILE = _ARG_SECTION_LAST, + ARG_DTBAUTO = _ARG_SECTION_LAST, ARG_BANK, ARG_PRIVATE_KEY, ARG_PRIVATE_KEY_SOURCE, @@ -170,10 +173,12 @@ static int parse_argv(int argc, char *argv[]) { { "ucode", required_argument, NULL, ARG_UCODE }, { "splash", required_argument, NULL, ARG_SPLASH }, { "dtb", required_argument, NULL, ARG_DTB }, + { "dtbauto", required_argument, NULL, ARG_DTBAUTO }, { "uname", required_argument, NULL, ARG_UNAME }, { "sbat", required_argument, NULL, ARG_SBAT }, { "pcrpkey", required_argument, NULL, ARG_PCRPKEY }, { "profile", required_argument, NULL, ARG_PROFILE }, + { "hwids", required_argument, NULL, ARG_HWIDS }, { "current", no_argument, NULL, 'c' }, { "bank", required_argument, NULL, ARG_BANK }, { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, diff --git a/src/fundamental/chid-fundamental.c b/src/fundamental/chid-fundamental.c new file mode 100644 index 0000000000..55b04fa2ab --- /dev/null +++ b/src/fundamental/chid-fundamental.c @@ -0,0 +1,120 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ + +/* + * Based on Nikita Travkin's dtbloader implementation. + * Copyright (c) 2024 Nikita Travkin + * + * https://github.com/TravMurav/dtbloader/blob/main/src/chid.c + */ + +/* + * Based on Linaro dtbloader implementation. + * Copyright (c) 2019, Linaro. All rights reserved. + * + * https://github.com/aarch64-laptops/edk2/blob/dtbloader-app/EmbeddedPkg/Application/ConfigTableLoader/CHID.c + */ + +#if SD_BOOT +# include "efi-string.h" +# include "util.h" +#else +# include +# include +# include +# include +#define strsize16(str) ((char16_strlen(str) + 1) * sizeof(char16_t)) +#endif + +#include "chid-fundamental.h" +#include "macro-fundamental.h" +#include "memory-util-fundamental.h" +#include "sha1-fundamental.h" + +static void get_chid(const char16_t *const smbios_fields[static _CHID_SMBIOS_FIELDS_MAX], uint32_t mask, EFI_GUID *ret_chid) { + assert(mask != 0); + assert(ret_chid); + const EFI_GUID namespace = { UINT32_C(0x12d8ff70), UINT16_C(0x7f4c), UINT16_C(0x7d4c), {} }; /* Swapped to BE */ + + struct sha1_ctx ctx = {}; + sha1_init_ctx(&ctx); + + sha1_process_bytes(&namespace, sizeof(namespace), &ctx); + + for (unsigned i = 0; i < _CHID_SMBIOS_FIELDS_MAX; i++) + if ((mask >> i) & 1) { + if (i > 0) + sha1_process_bytes(L"&", 2, &ctx); + sha1_process_bytes(smbios_fields[i], strsize16(smbios_fields[i]), &ctx); + } + + uint8_t hash[SHA1_DIGEST_SIZE]; + sha1_finish_ctx(&ctx, hash); + + assert_cc(sizeof(hash) >= sizeof(*ret_chid)); + memcpy(ret_chid, hash, sizeof(*ret_chid)); + + /* Convert the resulting CHID back to little-endian: */ + ret_chid->Data1 = bswap_32(ret_chid->Data1); + ret_chid->Data2 = bswap_16(ret_chid->Data2); + ret_chid->Data3 = bswap_16(ret_chid->Data3); + + /* set specific bits according to RFC4122 Section 4.1.3 */ + ret_chid->Data3 = (ret_chid->Data3 & 0x0fff) | (5 << 12); + ret_chid->Data4[0] = (ret_chid->Data4[0] & UINT8_C(0x3f)) | UINT8_C(0x80); +} + +static const uint32_t chid_smbios_table[CHID_TYPES_MAX] = { + [3] = (UINT32_C(1) << CHID_SMBIOS_MANUFACTURER) | + (UINT32_C(1) << CHID_SMBIOS_FAMILY) | + (UINT32_C(1) << CHID_SMBIOS_PRODUCT_NAME) | + (UINT32_C(1) << CHID_SMBIOS_PRODUCT_SKU) | + (UINT32_C(1) << CHID_SMBIOS_BASEBOARD_MANUFACTURER) | + (UINT32_C(1) << CHID_SMBIOS_BASEBOARD_PRODUCT), + + [4] = (UINT32_C(1) << CHID_SMBIOS_MANUFACTURER) | + (UINT32_C(1) << CHID_SMBIOS_FAMILY) | + (UINT32_C(1) << CHID_SMBIOS_PRODUCT_NAME) | + (UINT32_C(1) << CHID_SMBIOS_PRODUCT_SKU), + + [5] = (UINT32_C(1) << CHID_SMBIOS_MANUFACTURER) | + (UINT32_C(1) << CHID_SMBIOS_FAMILY) | + (UINT32_C(1) << CHID_SMBIOS_PRODUCT_NAME), + + [6] = (UINT32_C(1) << CHID_SMBIOS_MANUFACTURER) | + (UINT32_C(1) << CHID_SMBIOS_PRODUCT_SKU) | + (UINT32_C(1) << CHID_SMBIOS_BASEBOARD_MANUFACTURER) | + (UINT32_C(1) << CHID_SMBIOS_BASEBOARD_PRODUCT), + + [7] = (UINT32_C(1) << CHID_SMBIOS_MANUFACTURER) | + (UINT32_C(1) << CHID_SMBIOS_PRODUCT_SKU), + + [8] = (UINT32_C(1) << CHID_SMBIOS_MANUFACTURER) | + (UINT32_C(1) << CHID_SMBIOS_PRODUCT_NAME) | + (UINT32_C(1) << CHID_SMBIOS_BASEBOARD_MANUFACTURER) | + (UINT32_C(1) << CHID_SMBIOS_BASEBOARD_PRODUCT), + + [9] = (UINT32_C(1) << CHID_SMBIOS_MANUFACTURER) | + (UINT32_C(1) << CHID_SMBIOS_PRODUCT_NAME), + + [10] = (UINT32_C(1) << CHID_SMBIOS_MANUFACTURER) | + (UINT32_C(1) << CHID_SMBIOS_FAMILY) | + (UINT32_C(1) << CHID_SMBIOS_BASEBOARD_MANUFACTURER) | + (UINT32_C(1) << CHID_SMBIOS_BASEBOARD_PRODUCT), + + [11] = (UINT32_C(1) << CHID_SMBIOS_MANUFACTURER) | + (UINT32_C(1) << CHID_SMBIOS_FAMILY), + + [13] = (UINT32_C(1) << CHID_SMBIOS_MANUFACTURER) | + (UINT32_C(1) << CHID_SMBIOS_BASEBOARD_MANUFACTURER) | + (UINT32_C(1) << CHID_SMBIOS_BASEBOARD_PRODUCT), +}; + +void chid_calculate(const char16_t *const smbios_fields[static _CHID_SMBIOS_FIELDS_MAX], EFI_GUID ret_chids[static CHID_TYPES_MAX]) { + assert(smbios_fields); + assert(ret_chids); + for (size_t i = 0; i < _CHID_SMBIOS_FIELDS_MAX; i++) + if (chid_smbios_table[i] != 0) + get_chid(smbios_fields, chid_smbios_table[i], &ret_chids[i]); + else + memzero(&ret_chids[i], sizeof(EFI_GUID)); +} diff --git a/src/fundamental/chid-fundamental.h b/src/fundamental/chid-fundamental.h new file mode 100644 index 0000000000..e8c5c1add2 --- /dev/null +++ b/src/fundamental/chid-fundamental.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ + +#pragma once + +#include "efi-fundamental.h" +#include "string-util-fundamental.h" + +#define CHID_TYPES_MAX 15 + +typedef enum ChidSmbiosFields { + CHID_SMBIOS_MANUFACTURER, + CHID_SMBIOS_FAMILY, + CHID_SMBIOS_PRODUCT_NAME, + CHID_SMBIOS_PRODUCT_SKU, + CHID_SMBIOS_BASEBOARD_MANUFACTURER, + CHID_SMBIOS_BASEBOARD_PRODUCT, + _CHID_SMBIOS_FIELDS_MAX, +} ChidSmbiosFields; + +/* CHID (also called HWID by fwupd) is described at https://github.com/fwupd/fwupd/blob/main/docs/hwids.md */ +void chid_calculate(const char16_t *const smbios_fields[static _CHID_SMBIOS_FIELDS_MAX], EFI_GUID ret_chids[static CHID_TYPES_MAX]); diff --git a/src/fundamental/meson.build b/src/fundamental/meson.build index b1522a88f8..7b72372e83 100644 --- a/src/fundamental/meson.build +++ b/src/fundamental/meson.build @@ -4,6 +4,7 @@ fundamental_include = include_directories('.') fundamental_sources = files( 'bootspec-fundamental.c', + 'chid-fundamental.c', 'efivars-fundamental.c', 'iovec-util-fundamental.h', 'sha1-fundamental.c', diff --git a/src/fundamental/uki.c b/src/fundamental/uki.c index da5da1cf10..441d466a97 100644 --- a/src/fundamental/uki.c +++ b/src/fundamental/uki.c @@ -21,5 +21,7 @@ const char* const unified_sections[_UNIFIED_SECTION_MAX + 1] = { [UNIFIED_SECTION_PCRSIG] = ".pcrsig", [UNIFIED_SECTION_PCRPKEY] = ".pcrpkey", [UNIFIED_SECTION_PROFILE] = ".profile", + [UNIFIED_SECTION_DTBAUTO] = ".dtbauto", + [UNIFIED_SECTION_HWIDS] = ".hwids", NULL, }; diff --git a/src/fundamental/uki.h b/src/fundamental/uki.h index e7c59100e1..4b6195f9b7 100644 --- a/src/fundamental/uki.h +++ b/src/fundamental/uki.h @@ -18,6 +18,8 @@ typedef enum UnifiedSection { UNIFIED_SECTION_PCRSIG, UNIFIED_SECTION_PCRPKEY, UNIFIED_SECTION_PROFILE, + UNIFIED_SECTION_DTBAUTO, + UNIFIED_SECTION_HWIDS, _UNIFIED_SECTION_MAX, } UnifiedSection;