diff --git a/src/analyze/analyze-chid.c b/src/analyze/analyze-chid.c index 5bb50f54d86..0916e60c1fd 100644 --- a/src/analyze/analyze-chid.c +++ b/src/analyze/analyze-chid.c @@ -4,6 +4,7 @@ #include "analyze-chid.h" #include "chid-fundamental.h" #include "efi-api.h" +#include "escape.h" #include "fd-util.h" #include "fileio.h" #include "format-table.h" @@ -30,6 +31,20 @@ static int parse_chid_type(const char *s, size_t *ret) { return 0; } +static const char *const chid_smbios_friendly[_CHID_SMBIOS_FIELDS_MAX] = { + [CHID_SMBIOS_MANUFACTURER] = "manufacturer", + [CHID_SMBIOS_FAMILY] = "family", + [CHID_SMBIOS_PRODUCT_NAME] = "product-name", + [CHID_SMBIOS_PRODUCT_SKU] = "product-sku", + [CHID_SMBIOS_BASEBOARD_MANUFACTURER] = "baseboard-manufacturer", + [CHID_SMBIOS_BASEBOARD_PRODUCT] = "baseboard-product", + [CHID_SMBIOS_BIOS_VENDOR] = "bios-vendor", + [CHID_SMBIOS_BIOS_VERSION] = "bios-version", + [CHID_SMBIOS_BIOS_MAJOR] = "bios-major", + [CHID_SMBIOS_BIOS_MINOR] = "bios-minor", + [CHID_SMBIOS_ENCLOSURE_TYPE] = "enclosure-type", +}; + static const char chid_smbios_fields_char[_CHID_SMBIOS_FIELDS_MAX] = { [CHID_SMBIOS_MANUFACTURER] = 'M', [CHID_SMBIOS_FAMILY] = 'F', @@ -37,6 +52,11 @@ static const char chid_smbios_fields_char[_CHID_SMBIOS_FIELDS_MAX] = { [CHID_SMBIOS_PRODUCT_SKU] = 'S', [CHID_SMBIOS_BASEBOARD_MANUFACTURER] = 'm', [CHID_SMBIOS_BASEBOARD_PRODUCT] = 'p', + [CHID_SMBIOS_BIOS_VENDOR] = 'B', + [CHID_SMBIOS_BIOS_VERSION] = 'v', + [CHID_SMBIOS_BIOS_MAJOR] = 'R', + [CHID_SMBIOS_BIOS_MINOR] = 'r', + [CHID_SMBIOS_ENCLOSURE_TYPE] = 'e', }; static char *chid_smbios_fields_string(uint32_t combination) { @@ -87,7 +107,7 @@ static void smbios_fields_free(char16_t *(*fields)[_CHID_SMBIOS_FIELDS_MAX]) { free(*i); } -int verb_chid(int argc, char *argv[], void *userdata) { +static int smbios_fields_acquire(char16_t *fields[static _CHID_SMBIOS_FIELDS_MAX]) { static const char *const smbios_files[_CHID_SMBIOS_FIELDS_MAX] = { [CHID_SMBIOS_MANUFACTURER] = "sys_vendor", @@ -96,8 +116,115 @@ int verb_chid(int argc, char *argv[], void *userdata) { [CHID_SMBIOS_PRODUCT_SKU] = "product_sku", [CHID_SMBIOS_BASEBOARD_MANUFACTURER] = "board_vendor", [CHID_SMBIOS_BASEBOARD_PRODUCT] = "board_name", + [CHID_SMBIOS_BIOS_VENDOR] = "bios_vendor", + [CHID_SMBIOS_BIOS_VERSION] = "bios_version", + [CHID_SMBIOS_BIOS_MAJOR] = "bios_release", + [CHID_SMBIOS_BIOS_MINOR] = "bios_release", + [CHID_SMBIOS_ENCLOSURE_TYPE] = "chassis_type", }; + int r; + + _cleanup_close_ int smbios_fd = open("/sys/class/dmi/id", O_RDONLY|O_DIRECTORY|O_CLOEXEC); + if (smbios_fd < 0) + return log_error_errno(errno, "Failed to open SMBIOS sysfs object: %m"); + + for (ChidSmbiosFields f = 0; f < _CHID_SMBIOS_FIELDS_MAX; f++) { + _cleanup_free_ char *buf = NULL; + size_t size; + + /* According to the CHID spec we should not generate CHIDs for SMBIOS fields that aren't set + * or are set to an empty string. Hence leave them NULL here. */ + + if (!smbios_files[f]) + continue; + + r = read_virtual_file_at(smbios_fd, smbios_files[f], SIZE_MAX, &buf, &size); + if (r == -ENOENT) { + log_debug_errno(r, "SMBIOS field '%s' not set, skipping.", smbios_files[f]); + continue; + } + if (r < 0) + return log_error_errno(r, "Failed to read SMBIOS field '%s': %m", smbios_files[f]); + + if (size == 0 || (size == 1 && buf[0] == '\n')) { + log_debug("SMBIOS field '%s' is empty, skipping.", smbios_files[f]); + continue; + } + + if (buf[size-1] != '\n') + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected SMBIOS field '%s' to end in newline, but it doesn't, refusing.", smbios_files[f]); + + buf[size-1] = 0; + size--; + + switch (f) { + + case CHID_SMBIOS_BIOS_MAJOR: + case CHID_SMBIOS_BIOS_MINOR: { + /* The kernel exposes this a string ., split them apart again. */ + char *dot = memchr(buf, '.', size); + if (!dot) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "BIOS release field '%s' contains no dot?", smbios_files[f]); + + const char *p; + if (f == CHID_SMBIOS_BIOS_MAJOR) { + *dot = 0; + p = buf; + } else { + assert(f == CHID_SMBIOS_BIOS_MINOR); + p = dot + 1; + } + + /* The kernel exports the enclosure in decimal, we need it in hex (zero left-padded) */ + + uint8_t u; + r = safe_atou8(p, &u); + if (r < 0) + return log_error_errno(r, "Failed to parse BIOS release: %s", p); + + buf = mfree(buf); + if (asprintf(&buf, "%02x", u) < 0) + return log_oom(); + + size = strlen(buf); + break; + } + + case CHID_SMBIOS_ENCLOSURE_TYPE: { + /* The kernel exports the enclosure in decimal, we need it in hex (no padding!) */ + + uint8_t u; + r = safe_atou8(buf, &u); + if (r < 0) + return log_error_errno(r, "Failed to parse enclosure type: %s", buf); + + buf = mfree(buf); + if (u == 0) + buf = strdup(""); /* zero is mapped to empty string */ + else + (void) asprintf(&buf, "%x", u); + if (!buf) + return log_oom(); + + size = strlen(buf); + break; + } + + default: + break; + } + + fields[f] = utf8_to_utf16(buf, size); + if (!fields[f]) + return log_oom(); + } + + return 0; +} + +int verb_chid(int argc, char *argv[], void *userdata) { + _cleanup_(table_unrefp) Table *table = NULL; int r; @@ -111,28 +238,10 @@ int verb_chid(int argc, char *argv[], void *userdata) { (void) table_set_align_percent(table, table_get_cell(table, 0, 0), 100); (void) table_set_align_percent(table, table_get_cell(table, 0, 1), 50); - _cleanup_close_ int smbios_fd = open("/sys/class/dmi/id", O_RDONLY|O_DIRECTORY|O_CLOEXEC); - if (smbios_fd < 0) - return log_error_errno(errno, "Failed to open SMBIOS sysfs object: %m"); - _cleanup_(smbios_fields_free) char16_t* smbios_fields[_CHID_SMBIOS_FIELDS_MAX] = {}; - for (ChidSmbiosFields f = 0; f < _CHID_SMBIOS_FIELDS_MAX; f++) { - _cleanup_free_ char *buf = NULL; - size_t size; - - r = read_virtual_file_at(smbios_fd, smbios_files[f], SIZE_MAX, &buf, &size); - if (r < 0) - return log_error_errno(r, "Failed to read SMBIOS field '%s': %m", smbios_files[f]); - - if (size < 1 || buf[size-1] != '\n') - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected SMBIOS field '%s' to end in newline, but it doesn't, refusing.", smbios_files[f]); - - size--; - - smbios_fields[f] = utf8_to_utf16(buf, size); - if (!smbios_fields[f]) - return log_oom(); - } + r = smbios_fields_acquire(smbios_fields); + if (r < 0) + return r; EFI_GUID chids[CHID_TYPES_MAX] = {}; chid_calculate((const char16_t* const*) smbios_fields, chids); @@ -172,9 +281,19 @@ int verb_chid(int argc, char *argv[], void *userdata) { return log_oom(); for (ChidSmbiosFields f = 0; f < _CHID_SMBIOS_FIELDS_MAX; f++) { - _cleanup_free_ char *c = utf16_to_utf8(smbios_fields[f], SIZE_MAX); - if (!c) - return log_oom(); + _cleanup_free_ char *c = NULL; + + if (smbios_fields[f]) { + _cleanup_free_ char *u = NULL; + + u = utf16_to_utf8(smbios_fields[f], SIZE_MAX); + if (!u) + return log_oom(); + + c = cescape(u); + if (!c) + return log_oom(); + } if (!strextend(&legend, ansi_grey(), @@ -188,11 +307,11 @@ int verb_chid(int argc, char *argv[], void *userdata) { special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), " ", ansi_normal(), - smbios_files[f], + chid_smbios_friendly[f], ansi_grey(), " (", - ansi_highlight(), - c, + c ? ansi_highlight() : ansi_grey(), + strna(c), ansi_grey(), ")", ansi_normal())) @@ -200,9 +319,9 @@ int verb_chid(int argc, char *argv[], void *userdata) { w += separator * 3 + 4 + - utf8_console_width(smbios_files[f]) + + utf8_console_width(chid_smbios_friendly[f]) + 2 + - utf8_console_width(c) + + utf8_console_width(strna(c)) + 1; if (w > 79) { diff --git a/src/fundamental/chid-fundamental.c b/src/fundamental/chid-fundamental.c index 9c719a334dd..47eabdb3499 100644 --- a/src/fundamental/chid-fundamental.c +++ b/src/fundamental/chid-fundamental.c @@ -28,23 +28,36 @@ #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) { +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); + static const EFI_GUID namespace = { UINT32_C(0x12d8ff70), UINT16_C(0x7f4c), UINT16_C(0x7d4c), {} }; /* Swapped to BE */ 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], strlen16(smbios_fields[i]) * sizeof(char16_t), &ctx); + for (ChidSmbiosFields i = 0; i < _CHID_SMBIOS_FIELDS_MAX; i++) { + if (!FLAGS_SET(mask, UINT32_C(1) << i)) + continue; + + if (!smbios_fields[i]) { + /* If some SMBIOS field is missing, don't generate the CHID, as per spec */ + memzero(ret_chid, sizeof(EFI_GUID)); + return; } + if (i > 0) + sha1_process_bytes(L"&", 2, &ctx); + + sha1_process_bytes(smbios_fields[i], strlen16(smbios_fields[i]) * sizeof(char16_t), &ctx); + } + uint8_t hash[SHA1_DIGEST_SIZE]; sha1_finish_ctx(&ctx, hash); @@ -62,6 +75,30 @@ static void get_chid(const char16_t *const smbios_fields[static _CHID_SMBIOS_FIE } const uint32_t chid_smbios_table[CHID_TYPES_MAX] = { + [0] = (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_BIOS_VENDOR) | + (UINT32_C(1) << CHID_SMBIOS_BIOS_VERSION) | + (UINT32_C(1) << CHID_SMBIOS_BIOS_MAJOR) | + (UINT32_C(1) << CHID_SMBIOS_BIOS_MINOR), + + [1] = (UINT32_C(1) << CHID_SMBIOS_MANUFACTURER) | + (UINT32_C(1) << CHID_SMBIOS_FAMILY) | + (UINT32_C(1) << CHID_SMBIOS_PRODUCT_NAME) | + (UINT32_C(1) << CHID_SMBIOS_BIOS_VENDOR) | + (UINT32_C(1) << CHID_SMBIOS_BIOS_VERSION) | + (UINT32_C(1) << CHID_SMBIOS_BIOS_MAJOR) | + (UINT32_C(1) << CHID_SMBIOS_BIOS_MINOR), + + [2] = (UINT32_C(1) << CHID_SMBIOS_MANUFACTURER) | + (UINT32_C(1) << CHID_SMBIOS_PRODUCT_NAME) | + (UINT32_C(1) << CHID_SMBIOS_BIOS_VENDOR) | + (UINT32_C(1) << CHID_SMBIOS_BIOS_VERSION) | + (UINT32_C(1) << CHID_SMBIOS_BIOS_MAJOR) | + (UINT32_C(1) << CHID_SMBIOS_BIOS_MINOR), + [3] = (UINT32_C(1) << CHID_SMBIOS_MANUFACTURER) | (UINT32_C(1) << CHID_SMBIOS_FAMILY) | (UINT32_C(1) << CHID_SMBIOS_PRODUCT_NAME) | @@ -102,18 +139,26 @@ const uint32_t chid_smbios_table[CHID_TYPES_MAX] = { [11] = (UINT32_C(1) << CHID_SMBIOS_MANUFACTURER) | (UINT32_C(1) << CHID_SMBIOS_FAMILY), + [12] = (UINT32_C(1) << CHID_SMBIOS_MANUFACTURER) | + (UINT32_C(1) << CHID_SMBIOS_ENCLOSURE_TYPE), + [13] = (UINT32_C(1) << CHID_SMBIOS_MANUFACTURER) | (UINT32_C(1) << CHID_SMBIOS_BASEBOARD_MANUFACTURER) | (UINT32_C(1) << CHID_SMBIOS_BASEBOARD_PRODUCT), + + [14] = (UINT32_C(1) << CHID_SMBIOS_MANUFACTURER), }; 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_TYPES_MAX; i++) - if (chid_smbios_table[i] != 0) - get_chid(smbios_fields, chid_smbios_table[i], &ret_chids[i]); - else + for (size_t i = 0; i < CHID_TYPES_MAX; i++) { + if (chid_smbios_table[i] == 0) { memzero(&ret_chids[i], sizeof(EFI_GUID)); + continue; + } + + get_chid(smbios_fields, chid_smbios_table[i], &ret_chids[i]); + } } diff --git a/src/fundamental/chid-fundamental.h b/src/fundamental/chid-fundamental.h index 41f7be337dd..48b1343f5c3 100644 --- a/src/fundamental/chid-fundamental.h +++ b/src/fundamental/chid-fundamental.h @@ -20,6 +20,11 @@ typedef enum ChidSmbiosFields { CHID_SMBIOS_PRODUCT_SKU, CHID_SMBIOS_BASEBOARD_MANUFACTURER, CHID_SMBIOS_BASEBOARD_PRODUCT, + CHID_SMBIOS_BIOS_VENDOR, + CHID_SMBIOS_BIOS_VERSION, + CHID_SMBIOS_BIOS_MAJOR, + CHID_SMBIOS_BIOS_MINOR, + CHID_SMBIOS_ENCLOSURE_TYPE, _CHID_SMBIOS_FIELDS_MAX, } ChidSmbiosFields; diff --git a/src/test/test-chid.c b/src/test/test-chid.c index ae1726679d0..2faf886c977 100644 --- a/src/test/test-chid.c +++ b/src/test/test-chid.c @@ -11,6 +11,7 @@ const char16_t *const test_fields[_CHID_SMBIOS_FIELDS_MAX] = { [CHID_SMBIOS_FAMILY] = u"To be filled by O.E.M.", [CHID_SMBIOS_BASEBOARD_PRODUCT] = u"MPG X670E CARBON WIFI (MS-7D70)", [CHID_SMBIOS_BASEBOARD_MANUFACTURER] = u"Micro-Star International Co., Ltd.", + [CHID_SMBIOS_ENCLOSURE_TYPE] = u"3", }; /* Actual output of `fwupdtool hwids`: @@ -51,7 +52,7 @@ Extra Hardware IDs {7b3d90ce-ed79-5951-a48a-764ea9f11146} <- Manufacturer + BiosVendor */ -static const EFI_GUID actual_chids[] = { +static const EFI_GUID actual_chids[CHID_TYPES_MAX] = { {0x00000000, 0x0000, 0x0000, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, {0x00000000, 0x0000, 0x0000, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, {0x00000000, 0x0000, 0x0000, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, @@ -64,9 +65,9 @@ static const EFI_GUID actual_chids[] = { {0xc12c1f4a, 0x332d, 0x5d72, {0xaa, 0x36, 0x7a, 0x3d, 0x41, 0x3b, 0x47, 0x9a}}, {0x28ac9cf2, 0x5bde, 0x59f7, {0xae, 0xbe, 0x4b, 0x3d, 0x00, 0x80, 0x90, 0xfe}}, {0xe821e0e2, 0xe11a, 0x5e94, {0xbf, 0x5d, 0xff, 0xe5, 0x3c, 0x5e, 0x50, 0x48}}, - {0x00000000, 0x0000, 0x0000, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {0xbdd76d3e, 0x147f, 0x58a9, {0xa0, 0xb2, 0x42, 0x13, 0x64, 0x54, 0xed, 0x07}}, {0xb2e58e8b, 0xfb10, 0x5cd0, {0x8f, 0xb0, 0x5b, 0xd9, 0x31, 0xf1, 0x87, 0x1a}}, - {0x00000000, 0x0000, 0x0000, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {0x50af5797, 0xa2f2, 0x58b1, {0x9a, 0x1a, 0x45, 0x3b, 0xcb, 0xb2, 0xe0, 0x25}}, }; TEST(chid) {