From patchwork Tue Jul 2 06:06:30 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alex Hung X-Patchwork-Id: 256286 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from huckleberry.canonical.com (huckleberry.canonical.com [91.189.94.19]) by ozlabs.org (Postfix) with ESMTP id 2AF662C00A3 for ; Tue, 2 Jul 2013 16:06:56 +1000 (EST) Received: from localhost ([127.0.0.1] helo=huckleberry.canonical.com) by huckleberry.canonical.com with esmtp (Exim 4.76) (envelope-from ) id 1Uttjr-0006kb-2t; Tue, 02 Jul 2013 06:06:55 +0000 Received: from youngberry.canonical.com ([91.189.89.112]) by huckleberry.canonical.com with esmtp (Exim 4.76) (envelope-from ) id 1Uttjj-0006kL-LI for fwts-devel@lists.ubuntu.com; Tue, 02 Jul 2013 06:06:47 +0000 Received: from [175.41.48.77] (helo=canonical.com) by youngberry.canonical.com with esmtpsa (TLS1.0:DHE_RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1Uttjh-00081n-7o; Tue, 02 Jul 2013 06:06:47 +0000 From: Alex Hung To: fwts-devel@lists.ubuntu.com Subject: [PATCH 2/2] dmi: rename dmi_decode test to dmicheck Date: Tue, 2 Jul 2013 14:06:30 +0800 Message-Id: <1372745190-14359-3-git-send-email-alex.hung@canonical.com> X-Mailer: git-send-email 1.7.9.5 In-Reply-To: <1372745190-14359-1-git-send-email-alex.hung@canonical.com> References: <1372745190-14359-1-git-send-email-alex.hung@canonical.com> X-BeenThere: fwts-devel@lists.ubuntu.com X-Mailman-Version: 2.1.14 Precedence: list List-Id: Firmware Test Suite Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: fwts-devel-bounces@lists.ubuntu.com Sender: fwts-devel-bounces@lists.ubuntu.com Signed-off-by: Alex Hung --- src/Makefile.am | 2 +- src/dmi/dmi_decode/dmi_decode.c | 1492 --------------------------------------- src/dmi/dmicheck/dmicheck.c | 1492 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 1493 insertions(+), 1493 deletions(-) delete mode 100644 src/dmi/dmi_decode/dmi_decode.c create mode 100644 src/dmi/dmicheck/dmicheck.c diff --git a/src/Makefile.am b/src/Makefile.am index 1516639..8591606 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -67,7 +67,7 @@ fwts_SOURCES = main.c \ cpu/nx/nx.c \ cpu/msr/msr.c \ cpu/microcode/microcode.c \ - dmi/dmi_decode/dmi_decode.c \ + dmi/dmicheck/dmicheck.c \ hotkey/hotkey/hotkey.c \ hpet/hpet_check/hpet_check.c \ kernel/klog/klog.c \ diff --git a/src/dmi/dmi_decode/dmi_decode.c b/src/dmi/dmi_decode/dmi_decode.c deleted file mode 100644 index a657f73..0000000 --- a/src/dmi/dmi_decode/dmi_decode.c +++ /dev/null @@ -1,1492 +0,0 @@ -/* - * Copyright (C) 2010-2013 Canonical - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#include -#include -#include -#include - -#include "fwts.h" - -#ifdef FWTS_ARCH_INTEL - -#include -#include -#include -#include -#include -#include -#include - -#define DMI_VERSION (0x0207) -#define VERSION_MAJOR(v) ((v) >> 8) -#define VERSION_MINOR(v) ((v) & 0xff) - -#define SMBIOS_END_OF_TABLE (127) - -#define DMI_NO_TABLE "DMINoTable" -#define DMI_NO_TABLE_HEADER "DMINoTableHeader" -#define DMI_BAD_TABLE_LENGTH "DMIBadTableLength" -#define DMI_BAD_UUID "DMIBadUUID" -#define DMI_STRUCT_COUNT "DMIStructCount" -#define DMI_VALUE_OUT_OF_RANGE "DMIValueOutOfRange" -#define DMI_STRING_INDEX_OUT_OF_RANGE "DMIStringIndexOutOfRange" -#define DMI_ILLEGAL_MAPPED_ADDR_RANGE "DMIIllegalMappedAddrRange" -#define DMI_MGMT_CTRL_HOST_TYPE "DMIMgmtCtrlHostType" -#define DMI_INVALID_ENTRY_LENGTH "DMIInvalidEntryLength" -#define DMI_INVALID_HARDWARE_ENTRY "DMIInvalidHardwareEntry" - -#define GET_UINT16(x) (uint16_t)(*(const uint16_t *)(x)) -#define GET_UINT32(x) (uint32_t)(*(const uint32_t *)(x)) -#define GET_UINT64(x) (uint64_t)(*(const uint64_t *)(x)) - -#define CHASSIS_OTHER 0x00 -#define CHASSIS_DESKTOP 0x01 -#define CHASSIS_WORKSTATION 0x02 -#define CHASSIS_MOBILE 0x04 -#define CHASSIS_SERVER 0x08 - -typedef struct { - const char *label; - const char *field; - const char *value; -} fwts_dmi_pattern; - -typedef struct { - uint16_t old; - uint16_t new; -} fwts_dmi_version; - -typedef struct { - const char *name; - uint8_t original; - uint8_t mapped; -} fwts_chassis_type_map; - -typedef struct { - uint8_t type; - uint8_t offset; -} fwts_dmi_used_by_kernel; - -static const fwts_dmi_pattern dmi_patterns[] = { - { "DMISerialNumber", "Serial Number", "0123456789" }, - { "DMISerialNumber", "Serial Number", "System Serial Number" }, - { "DMISerialNumber", "Serial Number", "MB-1234567890" }, - { "DMISerialNumber", NULL, "Chassis Serial Number" }, - { "DMIAssetTag", "Asset Tag", "1234567890" }, - { "DMIAssetTag", "Asset Tag", "Asset-1234567890" }, - { "DMIChassisVendor", NULL, "Chassis Manufacture" }, - { "DMIChassisVersion", NULL, "Chassis Version" }, - { "DMIProductVersion", NULL, "System Version" }, - { "DMIBadDefault", NULL, "To Be Filled By O.E.M." }, - { NULL, NULL, NULL } -}; - -static const char *uuid_patterns[] = { - "0A0A0A0A-0A0A-0A0A-0A0A-0A0A0A0A0A0A", - NULL, -}; - -static const fwts_chassis_type_map fwts_dmi_chassis_type[] = { - { "Invalid", FWTS_SMBIOS_CHASSIS_INVALID, CHASSIS_OTHER }, - { "Other", FWTS_SMBIOS_CHASSIS_OTHER, CHASSIS_OTHER }, - { "Unknown", FWTS_SMBIOS_CHASSIS_UNKNOWN, CHASSIS_OTHER }, - { "Desktop", FWTS_SMBIOS_CHASSIS_DESKTOP, CHASSIS_DESKTOP }, - { "Low Profile Desktop",FWTS_SMBIOS_CHASSIS_LOW_PROFILE_DESKTOP, CHASSIS_DESKTOP }, - { "Pizza Box", FWTS_SMBIOS_CHASSIS_PIZZA_BOX, CHASSIS_DESKTOP }, - { "Mini Tower", FWTS_SMBIOS_CHASSIS_MINI_TOWER, CHASSIS_DESKTOP }, - { "Chassis Tower", FWTS_SMBIOS_CHASSIS_TOWER, CHASSIS_DESKTOP }, - { "Portable", FWTS_SMBIOS_CHASSIS_PORTABLE, CHASSIS_MOBILE }, - { "Laptop", FWTS_SMBIOS_CHASSIS_LAPTOP, CHASSIS_MOBILE }, - { "Notebook", FWTS_SMBIOS_CHASSIS_NOTEBOOK, CHASSIS_MOBILE }, - { "Handheld", FWTS_SMBIOS_CHASSIS_HANDHELD, CHASSIS_MOBILE }, - { "Docking Station", FWTS_SMBIOS_CHASSIS_DOCKING_STATION, CHASSIS_DESKTOP }, - { "All In One", FWTS_SMBIOS_CHASSIS_ALL_IN_ONE, CHASSIS_DESKTOP }, - { "Sub Notebook", FWTS_SMBIOS_CHASSIS_SUB_NOTEBOOK, CHASSIS_MOBILE }, - { "Space Saving", FWTS_SMBIOS_CHASSIS_SPACE_SAVING, CHASSIS_DESKTOP }, - { "Lunch Box", FWTS_SMBIOS_CHASSIS_LUNCH_BOX, CHASSIS_DESKTOP | CHASSIS_MOBILE}, - { "Server Chassis", FWTS_SMBIOS_CHASSIS_MAIN_SERVER_CHASSIS, CHASSIS_SERVER }, - { "Expansion Chassis", FWTS_SMBIOS_CHASSIS_EXPANISON_CHASSIS, CHASSIS_OTHER }, - { "Sub Chassis", FWTS_SMBIOS_CHASSIS_SUB_CHASSIS, CHASSIS_OTHER }, - { "Bus Expansion Chassis", FWTS_SMBIOS_CHASSIS_BUS_EXPANSION_CHASSIS, CHASSIS_OTHER }, - { "Peripheral Chassis", FWTS_SMBIOS_CHASSIS_PERIPHERAL_CHASSIS, CHASSIS_OTHER }, - { "Raid Chassis", FWTS_SMBIOS_CHASSIS_RAID_CHASSIS, CHASSIS_OTHER }, - { "Rack Mount Chassis", FWTS_SMBIOS_CHASSIS_RACK_MOUNT_CHASSIS, CHASSIS_OTHER }, - { "Sealed Case PC", FWTS_SMBIOS_CHASSIS_SEALED_CASE_PC, CHASSIS_DESKTOP }, - { "Multi System Chassis",FWTS_SMBIOS_CHASSIS_MULTI_SYSTEM_CHASSIS, CHASSIS_OTHER }, - { "Compact PCI", FWTS_SMBIOS_CHASSIS_COMPACT_PCI, CHASSIS_OTHER }, - { "Advanced TCA", FWTS_SMBIOS_CHASSIS_ADVANCED_TCA, CHASSIS_OTHER }, - { "Blade", FWTS_SMBIOS_CHASSIS_BLADE, CHASSIS_SERVER }, - { "Enclosure", FWTS_SMBIOS_CHASSIS_BLASE_ENCLOSURE, CHASSIS_SERVER } -}; - -static const fwts_chassis_type_map fwts_acpi_pm_profile_type[] = { - { "Unspecified", FWTS_FACP_UNSPECIFIED, CHASSIS_OTHER }, - { "Desktop", FWTS_FACP_DESKTOP, CHASSIS_DESKTOP }, - { "Mobile", FWTS_FACP_MOBILE, CHASSIS_MOBILE }, - { "Workstation", FWTS_FACP_WORKSTATION, CHASSIS_WORKSTATION }, - { "Enterprise Server", FWTS_FACP_ENTERPRISE_SERVER, CHASSIS_SERVER }, - { "SOHO Server", FWTS_FACP_SOHO_SERVER, CHASSIS_SERVER | CHASSIS_DESKTOP }, - { "Appliance PC", FWTS_FACP_APPLIANCE_PC, CHASSIS_DESKTOP }, - { "Performance Server", FWTS_FACP_PERFORMANCE_SERVER, CHASSIS_SERVER }, - { "Tablet", FWTS_FACP_TABLET, CHASSIS_MOBILE } -}; - -/* Remapping table from buggy version numbers to correct values */ -static const fwts_dmi_version dmi_versions[] = { - { 0x021f, 0x0203 }, - { 0x0221, 0x0203 }, - { 0x0233, 0x0206 }, - { 0, 0 } -}; - -#define FIELD_ANY 0xff -#define TYPE_EOD 0xff - -/* - * DMI decoded fields used by the kernel, i.e. fields - * we care that work, - * see drivers/firmware/dmi_scan.c, dmi_decode() - */ -static fwts_dmi_used_by_kernel dmi_used_by_kernel_table[] = { - /* Type 0 BIOS Information fields */ - { 0, 4 }, - { 0, 5 }, - { 0, 8 }, - /* Type 1, System Information */ - { 1, 4 }, - { 1, 5 }, - { 1, 6 }, - { 1, 7 }, - { 1, 8 }, - /* Type 2, Base Board Information */ - { 2, 4 }, - { 2, 5 }, - { 2, 6 }, - { 2, 7 }, - { 2, 8 }, - /* Type 3, Chassis Information */ - { 3, 4 }, - { 3, 5 }, - { 3, 6 }, - { 3, 7 }, - { 3, 8 }, - /* Type 10, Onboard Devices Information */ - { 10, FIELD_ANY }, - /* Type 11, OEM Strings */ - { 11, FIELD_ANY }, - /* Type 38, IPMI Device Information */ - { 38, FIELD_ANY }, - /* Type 41, Onboard Devices Extended Information */ - { 41, FIELD_ANY }, - /* End */ - { TYPE_EOD, 0xff }, -}; - -static bool dmi_used_by_kernel(uint8_t type, uint8_t offset) -{ - int i; - - for (i = 0; dmi_used_by_kernel_table[i].type != TYPE_EOD; i++) { - if (dmi_used_by_kernel_table[i].type == type) - if ((dmi_used_by_kernel_table[i].offset == FIELD_ANY) || - (dmi_used_by_kernel_table[i].offset == offset)) - return true; - } - return false; -} - -static uint16_t dmi_remap_version(fwts_framework *fw, uint16_t old) -{ - int i; - - for (i=0; dmi_versions[i].old != 0; i++) { - if (old == dmi_versions[i].old) { - uint16_t new = dmi_versions[i].new; - fwts_warning(fw, - "Detected a buggy DMI version number " - "%" PRIu16 ".%" PRIu16 "remapping to " - "%" PRIu16 ".%" PRIu16, - VERSION_MAJOR(old), VERSION_MINOR(old), - VERSION_MAJOR(new), VERSION_MINOR(new)); - return new; - } - } - - /* All OK, return original */ - return old; -} - -static void dmi_out_of_range_advice(fwts_framework *fw, uint8_t type, uint8_t offset) -{ - if (dmi_used_by_kernel(type, offset)) - fwts_advice(fw, - "A value that is out of range is incorrect and not conforming to " - "the SMBIOS specification. The Linux kernel extracts and uses " - "this particular data item, so it is recommended that this SMBIOS " - "configuration is corrected even if the impact on the system " - "is most probably not critical."); - else - fwts_advice(fw, - "A value that is out of range is incorrect and not conforming to " - "the SMBIOS specification. This field is not currently used by " - "the Linux kernel, so this firmware bug shouldn't cause any " - "problems."); -} - -static void dmi_min_max_uint8_check(fwts_framework *fw, - const char *table, - uint32_t addr, - const char *field, - const fwts_dmi_header *hdr, - uint8_t offset, - uint8_t min, - uint8_t max) -{ - uint8_t val = hdr->data[offset]; - if ((val < min) || (val > max)) { - fwts_failed(fw, LOG_LEVEL_HIGH, - DMI_VALUE_OUT_OF_RANGE, - "Out of range value 0x%2.2" PRIx8 - " (range allowed 0x%2.2" PRIx8 "..0x%2.2" PRIx8 ") " - "while accessing entry '%s' @ 0x%8.8" PRIx32 - ", field '%s', offset 0x%2.2" PRIx8, - val, min, max, table, addr, field, offset); - dmi_out_of_range_advice(fw, hdr->type, offset); - } -} - -static void dmi_min_max_mask_uint8_check(fwts_framework *fw, - const char *table, - uint32_t addr, - const char *field, - const fwts_dmi_header *hdr, - uint8_t offset, - uint8_t min, - uint8_t max, - uint8_t shift, - uint8_t mask) -{ - uint8_t val = (hdr->data[offset] >> shift) & mask; - - if ((val < min) || (val > max)) { - fwts_failed(fw, LOG_LEVEL_HIGH, DMI_VALUE_OUT_OF_RANGE, - "Out of range value 0x%2.2" PRIx8 - " (range allowed 0x%2.2" PRIx8 "..0x%2.2" PRIx8 ") " - "while accessing entry '%s' @ 0x%8.8" PRIx32 - ", field '%s', offset 0x%2.2" PRIx8, - val, min, max, table, addr, field, offset); - dmi_out_of_range_advice(fw, hdr->type, offset); - } -} - -static void dmi_str_check_index(fwts_framework *fw, - const char *table, - uint32_t addr, - const char *field, - const fwts_dmi_header *hdr, - uint8_t offset, - uint8_t index) -{ - char *data = (char *)hdr->data; - uint8_t i = index; - bool used_by_kernel = dmi_used_by_kernel(hdr->type, offset); - - if (i > 0) { - int j; - int failed = -1; - - data += hdr->length; - while (i > 1 && *data) { - data += strlen(data) + 1; - i--; - } - - /* Sanity checks */ - if (*data == '\0') { - int level = used_by_kernel ? LOG_LEVEL_HIGH : LOG_LEVEL_LOW; - - /* This entry is clearly broken so flag it as a corrupt entry */ - fwts_failed(fw, level, DMI_STRING_INDEX_OUT_OF_RANGE, - "Out of range string index 0x%2.2" PRIx8 - " while accessing entry '%s' " - "@ 0x%8.8" PRIx32 ", field '%s', offset 0x%2.2" PRIx8, - index, table, addr, field, offset); - if (used_by_kernel) - fwts_advice(fw, - "DMI strings are stored in a manner that uses a special " - "index to fetch the Nth string from the data. For this " - "particular DMI string the index given is not in range " - "which means this particular entry is broken. The Linux " - "kernel uses this string - hence this string should be " - "fixed to ensure sane data is passed to the kernel. " - "Note that this probably won't cause any critical system " - "errors."); - else - fwts_advice(fw, - "DMI strings are stored in a manner that uses a special " - "index to fetch the Nth string from the data. For this " - "particular DMI string the index given is not in range " - "which means this particular entry is broken. The Linux " - "kernel does not use this string, so this error will not " - "cause any system errors."); - return; - } - - /* Scan for known BIOS defaults that vendors forget to set */ - for (j=0; dmi_patterns[j].label != NULL; j++) { - if (dmi_patterns[j].field && - (strcmp(dmi_patterns[j].field, field) == 0) && - (strcmp(dmi_patterns[j].value, data) == 0)) { - failed = j; - break; - } else if (strcmp(dmi_patterns[j].value, data) == 0) { - failed = j; - break; - } - } - if (failed != -1) { - int level = used_by_kernel ? LOG_LEVEL_MEDIUM : LOG_LEVEL_LOW; - - fwts_failed(fw, level, dmi_patterns[j].label, - "String index 0x%2.2" PRIx8 - " in table entry '%s' @ 0x%8.8" PRIx32 - ", field '%s', offset 0x%2.2" PRIx8 - " has a default value '%s' and probably has " - "not been updated by the BIOS vendor.", - index, table, addr, field, offset, data); - - if (used_by_kernel) { - fwts_advice(fw, - "The DMI table contains data which is clearly been " - "left in a default setting and not been configured " - "for this machine. " - "Somebody has probably forgotten to define this " - "field and it basically means this field is effectively " - "useless. Note that the kernel uses this field so " - "it probably should be corrected to ensure the kernel " - "is using sane values."); - } else { - /* This string is broken, but we don't care about it too much */ - fwts_advice(fw, - "The DMI table contains data which is clearly been " - "left in a default setting and not been configured " - "for this machine. " - "Somebody has probably forgotten to define this " - "field and it basically means this field is effectively " - "useless, however the kernel does not use this data " - "so the issue is fairly low."); - } - } - } -} - -static inline void dmi_str_check(fwts_framework *fw, - const char *table, - uint32_t addr, - const char *field, - const fwts_dmi_header *hdr, - uint8_t offset) -{ - dmi_str_check_index(fw, table, addr, field, hdr, offset, hdr->data[offset]); -} - -static void dmi_uuid_check(fwts_framework *fw, - const char *table, - uint32_t addr, - const char *field, - const fwts_dmi_header *hdr, - uint8_t offset) -{ - char guid_str[37]; - int i; - - fwts_guid_buf_to_str(hdr->data + offset, guid_str, sizeof(guid_str)); - - for (i=0; uuid_patterns[i] != NULL; i++) { - if (strcmp(guid_str, uuid_patterns[i]) == 0) { - fwts_failed(fw, LOG_LEVEL_LOW, DMI_BAD_UUID, - "UUID in table entry '%s' @ 0x%8.8" PRIx32 - " field '%s', offset 0x%2.2" PRIx8 - " has a default value '%s' and probably has " - "not been updated by the BIOS vendor.", - table, addr, field, offset, guid_str); - fwts_advice(fw, - "The DMI table contains a UUID which is clearly been " - "left in a default setting and not been configured " - "for this machine. While this is not critical it does " - "mean that somebody has probably forgotten to define this " - "field and it basically means this field is effectively " - "useless."); - } - } -} - -static void dmi_decode_entry(fwts_framework *fw, - uint32_t addr, - const fwts_dmi_header *hdr) -{ - uint8_t *ptr; - uint8_t count; - uint8_t val; - uint8_t *data = hdr->data; - char tmp[64]; - char *table; - int i; - int len; - uint32_t failed_count = fw->minor_tests.failed; - int battery_count; - int ret; - int both_ok; - fwts_acpi_table_info *acpi_table; - fwts_acpi_table_fadt *fadt; - bool advice_given = false; - - switch (hdr->type) { - case 0: /* 7.1 */ - table = "BIOS Information (Type 0)"; - if (hdr->length < 0x12) - break; - dmi_str_check(fw, table, addr, "Vendor", hdr, 0x4); - dmi_str_check(fw, table, addr, "BIOS Version", hdr, 0x5); - dmi_str_check(fw, table, addr, "Release Date", hdr, 0x8); - break; - - case 1: /* 7.2 */ - table = "System Information (Type 1)"; - if (hdr->length < 0x08) - break; - dmi_str_check(fw, table, addr, "Manufacturer", hdr, 0x4); - dmi_str_check(fw, table, addr, "Product Name", hdr, 0x5); - dmi_str_check(fw, table, addr, "Version", hdr, 0x6); - dmi_str_check(fw, table, addr, "Serial Number", hdr, 0x7); - if (hdr->length < 0x19) - break; - dmi_uuid_check(fw, table, addr, "UUID", hdr, 0x8); - dmi_min_max_uint8_check(fw, table, addr, "Wakeup Type", hdr, 0x18, 0x0, 0x08); - if (hdr->length < 0x1b) - break; - dmi_str_check(fw, table, addr, "SKU Number", hdr, 0x19); - dmi_str_check(fw, table, addr, "Family", hdr, 0x1a); - break; - - case 2: /* 7.3 */ - table = "Base Board Information (Type 2)"; - if (hdr->length < 0x08) - break; - dmi_str_check(fw, table, addr, "Manufacturer", hdr, 0x4); - dmi_str_check(fw, table, addr, "Product", hdr, 0x5); - dmi_str_check(fw, table, addr, "Version", hdr, 0x6); - dmi_str_check(fw, table, addr, "Serial Number", hdr, 0x7); - if (hdr->length < 0x09) - break; - dmi_str_check(fw, table, addr, "Asset Tag", hdr, 0x8); - if (hdr->length < 0x0f) - break; - dmi_str_check(fw, table, addr, "Location In Chassis", hdr, 0xa); - dmi_min_max_uint8_check(fw, table, addr, "Type", hdr, 0xd, 0x1, 0xd); - break; - - case 3: /* 7.4 */ - table = "Chassis Information (Type 3)"; - if (hdr->length < 0x09) - break; - dmi_str_check(fw, table, addr, "Manufacturer", hdr, 0x4); - dmi_min_max_mask_uint8_check(fw, table, addr, "Chassis Type", hdr, 0x5, 0x1, 0x1d, 0x0, 0x7f); - if (fwts_acpi_find_table(fw, "FACP", 0, &acpi_table) != FWTS_OK) - break; - if (acpi_table == NULL) - break; - fadt = (fwts_acpi_table_fadt *)acpi_table->data; - - if (fadt->preferred_pm_profile >= - (sizeof(fwts_acpi_pm_profile_type) / sizeof(fwts_chassis_type_map))) { - fwts_failed(fw, LOG_LEVEL_HIGH, DMI_INVALID_HARDWARE_ENTRY, - "Incorrect Chassis Type " - "ACPI FACP reports 0x%" PRIx8, - fadt->preferred_pm_profile); - break; - } - if (data[5] >= - (sizeof(fwts_dmi_chassis_type) / sizeof(fwts_chassis_type_map))) { - fwts_failed(fw, LOG_LEVEL_HIGH, DMI_INVALID_HARDWARE_ENTRY, - "Incorrect Chassis Type " - "SMBIOS Type 3 reports 0x%" PRIx8, - data[5]); - fwts_advice(fw, - "The Chassis Type in the ACPI FADT is out of range " - "and hence we cannot identify the preferred power " - "management profile for this machine."); - break; - } - - /* - * LP: #1021674 - * We should only check profile and chassis types when we know for sure - * that we can compare valid types togther. So it is only valid to do - * a check if both aren't CHASSIS_OTHER types - */ - both_ok = (fwts_acpi_pm_profile_type[fadt->preferred_pm_profile].mapped != CHASSIS_OTHER) & - (fwts_dmi_chassis_type[data[5]].mapped != CHASSIS_OTHER); - - if ( both_ok && - !(fwts_acpi_pm_profile_type[fadt->preferred_pm_profile].mapped & - fwts_dmi_chassis_type[data[5]].mapped)) { - fwts_failed(fw, LOG_LEVEL_HIGH, DMI_INVALID_HARDWARE_ENTRY, - "Unmatched Chassis Type: " - "SMBIOS Type 3 reports 0x%" PRIx8 " '%s' " - "ACPI FACP reports 0x%" PRIx8 " '%s'", - data[5], - fwts_dmi_chassis_type[data[5]].name, - fadt->preferred_pm_profile, - fwts_acpi_pm_profile_type[fadt->preferred_pm_profile].name); - /* - * Make it a bit more wordy to explain the ramifications - */ - fwts_advice(fw, - "The SMBIOS System Enclosure/Chassis type is defined as " - "0x%" PRIx8 " '%s' where as the ACPI FACP reports the preferred " - "power management profile is 0x%" PRIx8 " '%s' so we possibly " - "have conflicting definitions of the machine's PM profile " - "for the type of machine. " - "See Table 16 of section 4.5.1 of the SMBIOS specification " - "and Table 5-34 of section 5.2.9 of the ACPI specification " - "for more details. " - "This kind of mismatch may lead to incorrect power " - "management handling for the type of workload expected " - "for this hardware.", - data[5], - fwts_dmi_chassis_type[data[5]].name, - fadt->preferred_pm_profile, - fwts_acpi_pm_profile_type[fadt->preferred_pm_profile].name); - advice_given = true; - } - dmi_min_max_mask_uint8_check(fw, table, addr, "Chassis Lock", hdr, 0x5, 0x0, 0x1, 0x7, 0x1); - dmi_str_check(fw, table, addr, "Version", hdr, 0x6); - dmi_str_check(fw, table, addr, "Serial Number", hdr, 0x7); - dmi_str_check(fw, table, addr, "Asset Tag", hdr, 0x8); - dmi_min_max_uint8_check(fw, table, addr, "Boot-up State", hdr, 0x9, 0x1, 0x6); - dmi_min_max_uint8_check(fw, table, addr, "Power Supply State", hdr, 0xa, 0x1, 0x6); - dmi_min_max_uint8_check(fw, table, addr, "Thermal State", hdr, 0xb, 0x1, 0x6); - dmi_min_max_uint8_check(fw, table, addr, "Security Status", hdr, 0xc, 0x1, 0x5); - if (hdr->length < 0x15 + data[0x13] * data[0x14]) - break; - ptr = data + 0x15; - len = data[0x14]; - if (len >= 0x3) { - for (i = 0; i < data[0x13]; i++) { - val = ptr[i * len] & 0x7f; - if (ptr[i * len] & 0x80) { - if (val > 0x42) - fwts_failed(fw, LOG_LEVEL_HIGH, DMI_VALUE_OUT_OF_RANGE, - "Out of range value 0x%2.2" PRIx8 - " (range allowed 0x00..0x42) " - "while accessing entry '%s' @ " - "0x%8.8" PRIx32 ", field " - "'SMBIOS Structure Type %d', " - "offset 0x%2.2x", - val, table, addr, i, 0x15 + (i*len)); - } else { - if ((val < 0x1) || (val > 0xd)) { - fwts_failed(fw, LOG_LEVEL_HIGH, DMI_VALUE_OUT_OF_RANGE, - "Out of range value 0x%2.2" PRIx8 - " (range allowed 0x00..0x42) " - "while accessing entry '%s' @ " - "0x%8.8" PRIx32 ", field " - "'Base Board Type %d', offset 0x%2.2x", - val, table, addr, i, 0x15 + (i*len)); - } - } - } - } - if (hdr->length < 0x16 + data[0x13] * data[0x14]) - break; - dmi_str_check(fw, table, addr, "SKU Number", hdr, 0x15 + data[0x13] * data[0x14]); - break; - - case 4: /* 7.5 */ - table = "Processor Information (Type 4)"; - if (hdr->length < 0x1a) - break; - dmi_str_check(fw, table, addr, "Socket Designation", hdr, 0x4); - dmi_min_max_uint8_check(fw, table, addr, "Processor Type", hdr, 0x5, 0x1, 0x6); - dmi_str_check(fw, table, addr, "Processor Manufacturer", hdr, 0x7); - dmi_str_check(fw, table, addr, "Processor Version", hdr, 0x10); - dmi_min_max_uint8_check(fw, table, addr, "Upgrade", hdr, 0x19, 0x1, 0x2a); - if (hdr->length < 0x23) - break; - dmi_str_check(fw, table, addr, "Serial Number", hdr, 0x20); - dmi_str_check(fw, table, addr, "Asset Tag", hdr, 0x21); - dmi_str_check(fw, table, addr, "Part Number", hdr, 0x22); - break; - - case 5: /* 7.6 */ - table = "Memory Controller Information (Type 5)"; - if (hdr->length < 0x0f) - break; - dmi_min_max_uint8_check(fw, table, addr, "Error Detecting Method", hdr, 0x4, 0x1, 0x8); - dmi_min_max_uint8_check(fw, table, addr, "Supported Interleave", hdr, 0x6, 0x1, 0x7); - dmi_min_max_uint8_check(fw, table, addr, "Current Interleave", hdr, 0x7, 0x1, 0x7); - break; - - case 6: /* 7.7 */ - table = "Memory Module Information (Type 6)"; - if (hdr->length < 0x0c) - break; - dmi_str_check(fw, table, addr, "Socket Designation", hdr, 0x4); - break; - - case 7: /* 7.8 */ - table = "Cache Information (Type 7)"; - if (hdr->length < 0x0f) - break; - dmi_str_check(fw, table, addr, "Socket Designation", hdr, 0x4); - if (((GET_UINT16(data + 0x05) >> 5) & 0x0003) == 0x2) - fwts_failed(fw, LOG_LEVEL_HIGH, DMI_VALUE_OUT_OF_RANGE, - "Out of range value %x4.4" PRIx16 " " - "bits 5..6 set to illegal value 0x2, only allowed" - "0x0, 0x1, 0x3 while accessing entry '%s' @ " - "0x%8.8" PRIx32 ", field '%s', offset 0x%2.2x", - GET_UINT16(data + 0x05), - table, addr, "Cache Location", 0x5); - if (hdr->length < 0x13) - break; - dmi_min_max_uint8_check(fw, table, addr, "Error Correction Type", hdr, 0x10, 0x1, 0x6); - dmi_min_max_uint8_check(fw, table, addr, "System Cache Type", hdr, 0x11, 0x1, 0x5); - dmi_min_max_uint8_check(fw, table, addr, "Associativity", hdr, 0x12, 0x1, 0xe); - break; - - case 8: /* 7.9 */ - table = "Port Connector Information (Type 8)"; - if (hdr->length < 0x09) - break; - dmi_str_check(fw, table, addr, "Internal Reference Designator", hdr, 0x4); - if (!((data[0x5] <= 0x22) || - (data[0x5] == 0xff) || - ((data[0x5] >= 0xa0) && (data[0x5] <= 0xa4)))) - fwts_failed(fw, LOG_LEVEL_HIGH, DMI_VALUE_OUT_OF_RANGE, - "Out of range value 0x%2.2" PRIx8 " " - "(range allowed 0x00..0x22, 0xa0..0xa4, 0xff) " - "while accessing entry '%s' @ " - "0x%8.8" PRIx32 ", field '%s', offset 0x%2.2x", - data[0x5], table, addr, "Internal Connector Type", 0x5); - dmi_str_check(fw, table, addr, "External Reference Designator", hdr, 0x6); - if (!((data[0x7] <= 0x22) || - (data[0x7] == 0xff) || - ((data[0x7] >= 0xa0) && (data[0x7] <= 0xa4)))) - fwts_failed(fw, LOG_LEVEL_HIGH, DMI_VALUE_OUT_OF_RANGE, - "Out of range value 0x%2.2" PRIx8 " " - "(range allowed 0x00..0x22, 0xa0..0xa4, 0xff) " - "while accessing entry '%s' @ " - "0x%8.8" PRIx32 ", field '%s', offset 0x%2.2x", - data[0x7], table, addr, "Internal Connector Type", 0x7); - - if (!((data[0x8] <= 0x21) || - (data[0x8] == 0xff) || - ((data[0x8] >= 0xa0) && (data[0x8] <= 0xa1)))) - fwts_failed(fw, LOG_LEVEL_HIGH, DMI_VALUE_OUT_OF_RANGE, - "Out of range value 0x%2.2" PRIx8 " " - "(range allowed 0x00..0x21, 0xa0..0xa1, 0xff) " - "while accessing entry '%s' @ 0x%8.8" PRIx32 ", " - "field '%s', offset 0x%2.2x", - data[0x8], table, addr, "Port Type", 0x8); - break; - - case 9: /* 7.10 */ - table = "System Slot Information (Type 9)"; - if (hdr->length < 0x0c) - break; - dmi_str_check(fw, table, addr, "Slot Designation", hdr, 0x4); - if (!(((data[0x5] >= 0x01) && (data[0x5] <= 0x13)) || - ((data[0x5] >= 0xa0) && (data[0x5] <= 0xb6)))) - fwts_failed(fw, LOG_LEVEL_HIGH, DMI_VALUE_OUT_OF_RANGE, - "Out of range value 0x%2.2x" PRIx8 " " - "(range allowed 0x01..0x08, 0xa0..0xa2) " - "while accessing entry '%s' @ 0x%8.8" PRIx32 ", " - "field '%s', offset 0x%2.2x", - data[0x5], table, addr, "Slot Type", 0x5); - dmi_min_max_uint8_check(fw, table, addr, "Slot Data Bus Width", hdr, 0x6, 0x1, 0xe); - dmi_min_max_uint8_check(fw, table, addr, "Current Usage", hdr, 0x7, 0x1, 0x4); - dmi_min_max_uint8_check(fw, table, addr, "Slot Length", hdr, 0x8, 0x1, 0x4); - break; - - case 10: /* 7.11 */ - table = "On Board Devices (Type 10)"; - count = (hdr->length - 4) / 2; - for (i = 0; i < count; i++) { - snprintf(tmp, sizeof(tmp), "Type (Device #%d)", i); - dmi_min_max_mask_uint8_check(fw, table, addr, tmp, hdr, 4 + (2 * i), 0x1, 0xa, 0x0, 0x7f); - snprintf(tmp, sizeof(tmp), "Description (Device #%d)", i); - dmi_str_check(fw, table, addr, tmp, hdr, 5 + (2 * i)); - } - break; - - case 11: /* 7.12 */ - table = "OEM Strings (Type 11)"; - if (hdr->length < 0x5) - break; - for (i = 1; i <= hdr->data[4]; i++) { - snprintf(tmp, sizeof(tmp), "String %d", i); - dmi_str_check_index(fw, table, addr, tmp, hdr, 0x4, i); - } - break; - - case 12: /* 7.13 */ - table = "System Configuration Options (Type 12)"; - if (hdr->length < 0x5) - break; - for (i = 1; i <= hdr->data[4]; i++) { - snprintf(tmp, sizeof(tmp), "Option %d", i); - dmi_str_check_index(fw, table, addr, tmp, hdr, 0x4, i); - } - break; - - case 13: /* 7.14 */ - table = "BIOS Language Information (Type 13)"; - if (hdr->length < 0x16) - break; - for (i = 1; i <= hdr->data[4]; i++) { - snprintf(tmp, sizeof(tmp), "BIOS Language String %d", i); - dmi_str_check_index(fw, table, addr, tmp, hdr, 0x4, i); - } - dmi_str_check(fw, table, addr, "Currently Installed Language", hdr, 0x15); - break; - - case 14: /* 7.15 */ - table = "Group Associations (Type 14)"; - if (hdr->length < 0x05) - break; - dmi_str_check(fw, table, addr, "Name", hdr, 0x4); - break; - - case 15: /* 7.16 */ - table = "System Event Log (Type 15)"; - if (hdr->length < 0x14) - break; - val = hdr->data[0x0a]; - if (!((val <= 0x04) || (val >= 0x80))) { - fwts_failed(fw, LOG_LEVEL_HIGH, DMI_VALUE_OUT_OF_RANGE, - "Out of range value 0x%2.2" PRIx8 " " - "(range allowed 0x00..0x01, " - "0x80..0xff) while accessing entry '%s' @ " - "0x%8.8" PRIx32 ", field '%s', offset 0x%2.2x", - val, table, addr, "Access Method", 0x0a); - } - if (hdr->length < 0x17) - break; - val = hdr->data[0x14]; - if (!((val <= 0x01) || (val >= 0x80))) { - fwts_failed(fw, LOG_LEVEL_HIGH, DMI_VALUE_OUT_OF_RANGE, - "Out of range value 0x%2.2" PRIx8 " " - "(range allowed 0x00..0x01, " - "0x80..0xff) while accessing entry '%s' @ " - "0x%8.8" PRIx32 ", field '%s', offset 0x%2.2x", - val, table, addr, "Log Header Format", 0x14); - } - if (hdr->length < 0x17 + data[0x15] * data[0x16]) - break; - if (data[0x16] >= 0x02) { - uint8_t *ptr = data + 0x17; - int i; - for (i = 0; i < data[0x15]; i++) { - int j = data[0x16] * i; - val = ptr[j]; - if (!(((val >= 0x01) && (val <= 0x0e)) || - ((val >= 0x10) && (val <= 0x17)) || - (val >= 0x80))) { - fwts_failed(fw, LOG_LEVEL_HIGH, DMI_VALUE_OUT_OF_RANGE, - "Out of range value 0x%2.2" PRIx8 " " - "(range allowed 0x01..0x0e, 0x10..0x17, " - "0x80..0xff) while accessing entry '%s' @ " - "0x%8.8" PRIx32 ", field '%s', item %d", - val, table, addr, "Log Descriptor Type", i); - } - val = ptr[j + 1]; - if ((val > 0x06) && (val < 0x80)) { - fwts_failed(fw, LOG_LEVEL_HIGH, DMI_VALUE_OUT_OF_RANGE, - "Out of range value 0x%2.2" PRIx8 " " - "(range allowed 0x00..0x06, 0x80..0xff) " - "while accessing entry '%s' @ " - "0x%8.8" PRIx32 ", field '%s', item %d", - val, table, addr, "Log Descriptor Format", i); - } - } - } - break; - - case 16: /* 7.17 */ - table = "Physical Memory Array (Type 16)"; - if (hdr->length < 0x0f) - break; - if (!(((data[0x4] >= 0x01) && (data[0x4] <= 0x0a)) || - ((data[0x4] >= 0xa0) && (data[0x4] <= 0xa3)))) - fwts_failed(fw, LOG_LEVEL_HIGH, DMI_VALUE_OUT_OF_RANGE, - "Out of range value 0x%2.2" PRIx8 " " - "(range allowed 0x01..0x0a, 0xa0..0xa3) " - "while accessing entry '%s' @ " - "0x%8.8" PRIx32 ", field '%s', offset 0x%2.2x", - data[0x4], table, addr, "Location", 0x4); - dmi_min_max_uint8_check(fw, table, addr, "Use", hdr, 0x5, 0x1, 0x7); - dmi_min_max_uint8_check(fw, table, addr, "Error Corrrection Type", hdr, 0x6, 0x1, 0x7); - break; - - case 17: /* 7.18 */ - table = "Memory Device (Type 17)"; - if (hdr->length < 0x15) - break; - dmi_min_max_uint8_check(fw, table, addr, "Form Factor", hdr, 0xe, 0x1, 0xf); - dmi_str_check(fw, table, addr, "Locator", hdr, 0x10); - dmi_str_check(fw, table, addr, "Bank Locator", hdr, 0x11); - dmi_min_max_uint8_check(fw, table, addr, "Memory Type", hdr, 0x12, 0x1, 0x19); - if (hdr->length < 0x1b) - break; - dmi_str_check(fw, table, addr, "Manufacturer", hdr, 0x17); - dmi_str_check(fw, table, addr, "Serial Number", hdr, 0x18); - dmi_str_check(fw, table, addr, "Asset Tag", hdr, 0x19); - dmi_str_check(fw, table, addr, "Part Number", hdr, 0x1a); - break; - - case 18: /* 7.19 */ - table = "32-bit Memory Error Information (Type 18)"; - if (hdr->length < 0x17) - break; - dmi_min_max_uint8_check(fw, table, addr, "Error Type", hdr, 0x4, 0x1, 0xe); - dmi_min_max_uint8_check(fw, table, addr, "Error Granularity", hdr, 0x5, 0x1, 0x4); - dmi_min_max_uint8_check(fw, table, addr, "Error Operation", hdr, 0x6, 0x1, 0x5); - break; - - case 19: /* 7.20 */ - table = "Memory Array Mapped Address (Type 19)"; - if (hdr->length < 0x0F) - break; - if (hdr->length >= 0x1F && GET_UINT32(data + 0x04) == 0xFFFFFFFF) { - uint64_t start, end; - start = GET_UINT64(data + 0x0F); - end = GET_UINT64(data + 0x17); - if (start == end) - fwts_failed(fw, LOG_LEVEL_HIGH, DMI_ILLEGAL_MAPPED_ADDR_RANGE, - "Extended Start and End addresses are identical " - "while accessing entry '%s' @ 0x%8.8" PRIx32 ", " - "fields 'Extended Starting Address' and 'Extended Ending Address'", - table, addr); - } else { - if (GET_UINT32(data + 0x08) - GET_UINT32(data + 0x04) + 1 == 0) - fwts_failed(fw, LOG_LEVEL_HIGH, DMI_ILLEGAL_MAPPED_ADDR_RANGE, - "Illegal zero mapped address range " - "for entry '%s' @ 0x%8.8" PRIx32, table, addr); - } - break; - - case 20: /* 7.21 */ - table = "Memory Device Mapped Address (Type 20)"; - if (hdr->length < 0x13) - break; - if (hdr->length >= 0x23 && GET_UINT32(data + 0x04) == 0xFFFFFFFF) { - uint64_t start, end; - start = GET_UINT64(data + 0x13); - end = GET_UINT64(data + 0x1B); - if (start == end) - fwts_failed(fw, LOG_LEVEL_HIGH, DMI_ILLEGAL_MAPPED_ADDR_RANGE, - "Extended Start and End addresses are identical " - "while accessing entry '%s' @ 0x%8.8" PRIx32 ", " - "fields 'Extended Starting Address' and 'Extended Ending Address'", - table, addr); - } else { - if (GET_UINT32(data + 0x08) - GET_UINT32(data + 0x04) + 1 == 0) - fwts_failed(fw, LOG_LEVEL_HIGH, DMI_ILLEGAL_MAPPED_ADDR_RANGE, - "Illegal zero mapped address range " - "for entry '%s' @ 0x%8.8" PRIx32, table, addr); - } - if (data[0x10] == 0) - fwts_failed(fw, LOG_LEVEL_HIGH, DMI_ILLEGAL_MAPPED_ADDR_RANGE, - "Illegal row position %2.2" PRIx8 ", " - "while accessing entry '%s' @ 0x%8.8" PRIx32 - ", field '%s', offset 0x%2.2x", - data[0x10], table, addr, "Partial Row Position", 0x10); - break; - - case 21: /* 7.22 */ - table = "Built-in Pointing Device (Type 21)"; - if (hdr->length < 0x07) - break; - dmi_min_max_uint8_check(fw, table, addr, "Type", hdr, 0x4, 0x1, 0x9); - if (!(((data[0x5] >= 0x01) && (data[0x5] <= 0x08)) || - ((data[0x5] >= 0xa0) && (data[0x5] <= 0xa2)))) { - fwts_failed(fw, LOG_LEVEL_HIGH, DMI_VALUE_OUT_OF_RANGE, - "Out of range value 0x%2.2" PRIx8 " " - "(range allowed 0x01..0x08, 0xa0..0xa2) " - "while accessing '%s', field '%s', offset 0x%2.2x", - data[0x5], table, "Interface", 0x5); - } - break; - - case 22: /* 7.23 */ - table = "Portable Battery (Type 22)"; - if (hdr->length < 0x10) - break; - dmi_str_check(fw, table, addr, "Location", hdr, 0x4); - dmi_str_check(fw, table, addr, "Manufacturer", hdr, 0x5); - if (data[0x06] || hdr->length < 0x1A) - dmi_str_check(fw, table, addr, "Manufacturer Date", hdr, 0x6); - if (data[0x07] || hdr->length < 0x1A) - dmi_str_check(fw, table, addr, "Serial Number", hdr, 0x7); - dmi_str_check(fw, table, addr, "Device Name", hdr, 0x8); - if (data[0x09] != 0x02 || hdr->length < 0x1A) - dmi_str_check(fw, table, addr, "Device Chemistry", hdr, 0x9); - - dmi_str_check(fw, table, addr, "SBDS Version Number", hdr, 0xe); - if (hdr->length < 0x1A) - break; - if (data[0x09] == 0x02) - dmi_str_check(fw, table, addr, "SBDS Device Chemistry", hdr, 0x14); - - ret = fwts_battery_get_count(fw, &battery_count); - if (ret != FWTS_OK || battery_count < 1) { - fwts_failed(fw, LOG_LEVEL_MEDIUM, DMI_INVALID_HARDWARE_ENTRY, - "Invalid Hardware Configuration " - "(no battery found) "); - } - break; - case 23: /* 7.24 */ - table = "System Reset (Type 23)"; - if (hdr->length < 0x0D) - break; - if (!(data[0x04] & (1 << 5))) - break; - dmi_min_max_mask_uint8_check(fw, table, addr, "Capabilities (bits 1..2)", hdr, 0x4, 0x1, 0x3, 1, 0x3); - dmi_min_max_mask_uint8_check(fw, table, addr, "Capabilities (bits 3..4)", hdr, 0x4, 0x1, 0x3, 3, 0x3); - break; - - case 24: /* 7.25 */ - table = "Hardware Security (Type 24)"; - /* if (hdr->length < 0x05) - break; */ - break; - - case 25: /* 7.26 */ - table = "System Power Controls (Type 25)"; - /* if (hdr->length < 0x9) - break; */ - break; - - case 26: /* 7.27 */ - table = "Voltage Probe (Type 26)"; - if (hdr->length < 0x14) - break; - dmi_str_check(fw, table, addr, "Description", hdr, 0x4); - dmi_min_max_mask_uint8_check(fw, table, addr, "Location (bits 0..4)", hdr, 0x5, 0x1, 0xb, 0, 0x1f); - dmi_min_max_mask_uint8_check(fw, table, addr, "Status (bits 5..7)", hdr, 0x5, 0x1, 0x6, 5, 0x7); - break; - - case 27: /* 7.28 */ - table = "Cooling Device (Type 27)"; - if (hdr->length < 0xc) - break; - val = data[0x06] & 0x1f; - if (!(((val >= 0x01) && (val <= 0x09)) || - ((val >= 0x10) && (val <= 0x11)))) - fwts_failed(fw, LOG_LEVEL_HIGH, DMI_VALUE_OUT_OF_RANGE, - "Out of range value 0x%2.2" PRIx8 " " - "(range allowed 0x01..0x09, 0x10..0x11) " - "while accessing entry '%s' @ " - "0x%8.8" PRIx32 ", field '%s', " - "offset 0x%2.2x, mask 0x%2.2x", - data[0x6], table, addr, "Device Type", 0x6, 0x1f); - dmi_min_max_mask_uint8_check(fw, table, addr, "Status (bits 5..7)", hdr, 0x6, 0x1, 0x6, 5, 0x7); - if (hdr->length < 0x0f) - break; - dmi_str_check(fw, table, addr, "Description", hdr, 0xe); - break; - - case 28: /* 7.29 */ - table = "Temperature Probe (Type 28)"; - if (hdr->length < 0x14) - break; - dmi_str_check(fw, table, addr, "Description", hdr, 0x4); - dmi_min_max_mask_uint8_check(fw, table, addr, "Location (bits 0..4)", hdr, 0x5, 0x1, 0xf, 0, 0x1f); - dmi_min_max_mask_uint8_check(fw, table, addr, "Status (bits 5..7)", hdr, 0x5, 0x1, 0x6, 5, 0x7); - break; - - case 29: /* 7.30 */ - table = "Electrical Current Probe (Type 29)"; - if (hdr->length < 0x14) - break; - dmi_str_check(fw, table, addr, "Description", hdr, 0x4); - dmi_min_max_mask_uint8_check(fw, table, addr, "Location (bits 0..4)", hdr, 0x5, 0x1, 0xb, 0, 0x1f); - dmi_min_max_mask_uint8_check(fw, table, addr, "Status (bits 5..7)", hdr, 0x5, 0x1, 0x6, 5, 0x7); - break; - - case 30: /* 7.31 */ - table = "Out-of-band Remote Access (Type 30)"; - if (hdr->length < 0x06) - break; - dmi_str_check(fw, table, addr, "Manufacturer Name", hdr, 0x4); - break; - - case 31: /* 7.32 */ - table = "Boot Integrity Services Entry Point (Type 31)"; - /* - if (hdr->length < 0x1c) - break; - */ - break; - - case 32: /* 7.33 */ - table = "System Boot Information (Type 32)"; - if (hdr->length < 0xb) - break; - if ((data[0xa] > 0x8) && (data[0xa] < 128)) - fwts_failed(fw, LOG_LEVEL_HIGH, DMI_VALUE_OUT_OF_RANGE, - "Out of range value 0x%2.2" PRIx8 " " - "(range allowed 0x00..0x08, 0x80..0xff) " - "while accessing entry '%s' @ " - "0x%8.8" PRIx32 ", field '%s', offset 0x%2.2x", - data[0xa], table, addr, "Boot Status", 0xa); - break; - - case 33: /* 7.34 */ - table = "64-bit Memory Error Information (Type 33)"; - if (hdr->length < 0x1f) - break; - dmi_min_max_uint8_check(fw, table, addr, "Error Type", hdr, 0x4, 0x1, 0xe); - dmi_min_max_uint8_check(fw, table, addr, "Error Granularity", hdr, 0x5, 0x1, 0x4); - dmi_min_max_uint8_check(fw, table, addr, "Error Operation", hdr, 0x6, 0x1, 0x5); - break; - - case 34: /* 7.35 */ - table = "Management Device (Type 34)"; - if (hdr->length < 0x0b) - break; - dmi_str_check(fw, table, addr, "Description", hdr, 0x4); - dmi_min_max_uint8_check(fw, table, addr, "Type", hdr, 0x5, 0x1, 0xd); - dmi_min_max_uint8_check(fw, table, addr, "Address Type", hdr, 0xa, 0x1, 0x5); - break; - - case 35: /* 7.36 */ - table = "Management Device Component (Type 35)"; - if (hdr->length < 0x0b) - break; - dmi_str_check(fw, table, addr, "Description", hdr, 0x4); - break; - - case 36: /* 7.37 */ - table = "Management Device Threshold Data (Type 36)"; - /* - if (hdr->length < 0x10) - break; - */ - break; - - case 37: /* 7.38 */ - table = "Memory Channel (Type 37)"; - if (hdr->length < 0x07) - break; - dmi_min_max_uint8_check(fw, table, addr, "Type", hdr, 0x4, 0x1, 0x4); - break; - - case 38: /* 7.39 */ - table = "IPMI Device Information (Type 38)"; - dmi_min_max_uint8_check(fw, table, addr, "Interface Type", hdr, 0x4, 0x0, 0x4); - break; - - case 39: /* 7.40 */ - table = "System Power Supply (Type 39)"; - if (hdr->length < 0x10) - break; - dmi_str_check(fw, table, addr, "Location", hdr, 0x5); - dmi_str_check(fw, table, addr, "Device Name", hdr, 0x6); - dmi_str_check(fw, table, addr, "Manufacturer", hdr, 0x7); - dmi_str_check(fw, table, addr, "Serial Number", hdr, 0x8); - dmi_str_check(fw, table, addr, "Asset Tag", hdr, 0x9); - dmi_str_check(fw, table, addr, "Model Part Number", hdr, 0xa); - dmi_str_check(fw, table, addr, "Revision Level", hdr, 0xb); - break; - - case 40: /* 7.41 */ - table = "Additional Information (Type 40)"; - break; - - case 41: /* 7.42 */ - table = "Onboard Device (Type 41)"; - if (hdr->length < 0xb) - break; - dmi_str_check(fw, table, addr, "Reference Designation", hdr, 0x4); - dmi_min_max_mask_uint8_check(fw, table, addr, "Device Type", hdr, 0x5, 0x1, 0xa, 0, 0x7f); - break; - - case 42: /* 7.43 */ - table = "Management Controller Host Interface (Type 42)"; - if (hdr->length < 0x05) - break; - if (!((data[0x04] >= 0x02 && data[0x04] <= 0x08) || - (data[0x04] == 0xF0))) - fwts_failed(fw, LOG_LEVEL_MEDIUM, DMI_MGMT_CTRL_HOST_TYPE, - "Out of range value 0x%2.2" PRIx8 " " - "(range allowed 0x02..0x08, 0xf0) " - "while accessing entry '%s' @ " - "0x%8.8" PRIx32 ", field '%s', offset 0x%2.2x", - data[0x4], table, addr, "Reference Designation", 0x4); - break; - - case 126: /* 7.44 */ - table = "Inactive (Type 126)"; - break; - case SMBIOS_END_OF_TABLE: /* 7.45 */ - table = "End of Table (Type 127)"; - break; - default: - snprintf(tmp, sizeof(tmp), "Unknown (Type %" PRId8 ")", hdr->type); - table = tmp; - break; - } - if (fw->minor_tests.failed == failed_count) - fwts_passed(fw, "Entry @ 0x%8.8" PRIx32 " '%s'", addr, table); - else if (!advice_given && hdr->type <= 42) - fwts_advice(fw, - "It may be worth checking against section 7.%" PRId8 " of the " - "System Management BIOS (SMBIOS) Reference Specification " - "(see http://www.dmtf.org/standards/smbios).", hdr->type+1); -} - -static int dmi_version_check(fwts_framework *fw, uint16_t version) -{ - if (version > DMI_VERSION) { - fwts_warning(fw, - "SMBIOS version %" PRIu16 ".%" PRIu16 - " is not supported by the dmi_decode " - "test. This test only supports SMBIOS version " - "%" PRIu16 ".%" PRIu16 " and lower.", - VERSION_MAJOR(version), VERSION_MINOR(version), - VERSION_MAJOR(DMI_VERSION), VERSION_MINOR(DMI_VERSION)); - return FWTS_ERROR; - } - return FWTS_OK; -} - -static void dmi_scan_tables(fwts_framework *fw, - fwts_smbios_entry *entry, - uint8_t *table) -{ - uint8_t *entry_data = table; - uint16_t table_length; - uint16_t struct_count; - int i = 0; - - table_length = entry->struct_table_length; - struct_count = entry->number_smbios_structures; - - while ((i < struct_count) && - (entry_data <= (table + table_length - 4))) { - uint32_t addr = entry->struct_table_address + (entry_data - table); - fwts_dmi_header hdr; - uint8_t *next_entry; - - hdr.type = entry_data[0]; - hdr.length = entry_data[1]; - hdr.handle = GET_UINT16(entry_data + 2); - hdr.data = entry_data; - - /* Sanity check */ - if (hdr.length < 4) { - fwts_failed(fw, LOG_LEVEL_HIGH, DMI_INVALID_ENTRY_LENGTH, - "Invald header length of entry #%d, " - "length was 0x%2.2" PRIx8 ".", - i, hdr.length); - fwts_advice(fw, - "DMI entry header lengths must be 4 or more bytes long " - "so this error indicates that the DMI table is unreliable " - "and DMI table checking has been aborted at entry #%d.", i); - break; - } - - /* Real Physical Address */ - next_entry = entry_data + hdr.length; - - /* Look for structure terminator, ends in two zero bytes */ - while (((next_entry - table + 1) < table_length) && - ((next_entry[0] != 0) || (next_entry[1] != 0))) { - next_entry++; - } - - /* Skip over terminating two zero bytes, see section 6.1 of spec */ - next_entry += 2; - - if ((next_entry - table) <= table_length) - dmi_decode_entry(fw, addr, &hdr); - - i++; - entry_data = next_entry; - } - - if (table_length != (entry_data - table)) - fwts_failed(fw, LOG_LEVEL_MEDIUM, DMI_BAD_TABLE_LENGTH, - "DMI table length was %" PRId16 " bytes (as specified by " - "the SMBIOS header) but the DMI entries used %td bytes.", - table_length, entry_data - table); - - if (struct_count != i) - fwts_failed(fw, LOG_LEVEL_MEDIUM, DMI_STRUCT_COUNT, - "DMI table was DMI %d entries in size (as specified by " - "the SMBIOS header) but only %d DMI entries were found.", - struct_count, i); -} - -static int dmi_decode_test2(fwts_framework *fw) -{ - void *addr; - fwts_smbios_entry entry; - fwts_smbios_type type; - uint16_t version = 0; - uint8_t *table; - - addr = fwts_smbios_find_entry(fw, &entry, &type, &version); - if (addr == NULL) { - fwts_failed(fw, LOG_LEVEL_HIGH, DMI_NO_TABLE_HEADER, - "Cannot find SMBIOS or DMI table entry."); - return FWTS_ERROR; - } - if (type == FWTS_SMBIOS_UNKNOWN) { - fwts_failed(fw, LOG_LEVEL_HIGH, DMI_NO_TABLE, - "Cannot find a valid SMBIOS or DMI table."); - return FWTS_ERROR; - } - - /* Fix broken version numbers found in the wild */ - version = dmi_remap_version(fw, version); - if (dmi_version_check(fw, version) != FWTS_OK) - return FWTS_SKIP; - - table = fwts_mmap((off_t)entry.struct_table_address, - (size_t)entry.struct_table_length); - if (table == FWTS_MAP_FAILED) { - fwts_log_error(fw, "Cannot mmap SMBIOS tables from " - "%8.8" PRIx32 "..%8.8" PRIx32 ".", - entry.struct_table_address, - entry.struct_table_address + entry.struct_table_length); - return FWTS_ERROR; - } - - dmi_scan_tables(fw, &entry, table); - - (void)fwts_munmap(table, (size_t)entry.struct_table_length); - - return FWTS_OK; -} - -static void smbios_dump_entry(fwts_framework *fw, fwts_smbios_entry *entry, fwts_smbios_type type) -{ - if (type == FWTS_SMBIOS) { - fwts_log_info_verbatum(fw, "SMBIOS Entry Point Structure:"); - fwts_log_info_verbatum(fw, " Anchor String : %4.4s", entry->signature); - fwts_log_info_verbatum(fw, " Checksum : 0x%2.2x", entry->checksum); - fwts_log_info_verbatum(fw, " Entry Point Length : 0x%2.2x", entry->length); - fwts_log_info_verbatum(fw, " Major Version : 0x%2.2x", entry->major_version); - fwts_log_info_verbatum(fw, " Minor Version : 0x%2.2x", entry->minor_version); - fwts_log_info_verbatum(fw, " Maximum Struct Size : 0x%2.2x", entry->max_struct_size); - fwts_log_info_verbatum(fw, " Entry Point Revision : 0x%2.2x", entry->revision); - fwts_log_info_verbatum(fw, " Formatted Area : 0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x", - entry->formatted_area[0], entry->formatted_area[1], - entry->formatted_area[2], entry->formatted_area[3], - entry->formatted_area[4]); - } - if (type == FWTS_SMBIOS_DMI_LEGACY) - fwts_log_info_verbatum(fw, "Legacy DMI Entry Point Structure:"); - - /* Common to SMBIOS and SMBIOS_DMI_LEGACY */ - fwts_log_info_verbatum(fw, " Intermediate Anchor : %5.5s", (char *)entry->anchor_string); - fwts_log_info_verbatum(fw, " Intermediate Checksum : 0x%2.2x", entry->intermediate_checksum); - fwts_log_info_verbatum(fw, " Structure Table Length : 0x%4.4x", entry->struct_table_length); - fwts_log_info_verbatum(fw, " Structure Table Address: 0x%8.8x", entry->struct_table_address); - fwts_log_info_verbatum(fw, " # of SMBIOS Structures : 0x%4.4x", entry->number_smbios_structures); - fwts_log_info_verbatum(fw, " SBMIOS BCD Revision : %2.2x", entry->smbios_bcd_revision); - if (entry->smbios_bcd_revision == 0) - fwts_log_info_verbatum(fw, " BCD Revision 00 indicates compliance with specification stated in Major/Minor Version."); -} - -static int smbios_dmi_sane(fwts_framework *fw, fwts_smbios_entry *entry) -{ - uint8_t *table, *ptr; - uint8_t dmi_entry_length; - uint8_t dmi_entry_type = 0; - uint16_t i = 0; - uint16_t table_length = entry->struct_table_length; - int ret = FWTS_OK; - - ptr = table = fwts_mmap((off_t)entry->struct_table_address, - (size_t)table_length); - if (table == FWTS_MAP_FAILED) { - fwts_failed(fw, LOG_LEVEL_MEDIUM, - "SMBIOSTableAddressNotMapped", - "Cannot mmap SMBIOS tables from " - "%8.8" PRIx32 "..%8.8" PRIx32 ".", - entry->struct_table_address, - entry->struct_table_address + table_length); - return FWTS_ERROR; - } - - for (i = 0; i < entry->number_smbios_structures; i++) { - if (ptr > table + table_length) { - fwts_failed(fw, LOG_LEVEL_MEDIUM, - "SMBIOSTableLengthTooSmall", - "The size indicated by the SMBIOS table length is " - "smaller than the DMI data."); - ret = FWTS_ERROR; - break; - } - - dmi_entry_type = ptr[0]; - dmi_entry_length = ptr[1]; - - if (dmi_entry_length < 4) { - fwts_failed(fw, LOG_LEVEL_MEDIUM, - "SMBIOSIllegalTableEntry", - "The size of a DMI entry %" PRIu16 " is illegal, " - "DMI data is either wrong or the SMBIOS Table " - "Pointer is pointing to the wrong memory region.", i); - ret = FWTS_ERROR; - break; - } - ptr += dmi_entry_length; - - /* Scan for end of DMI entry, must be 2 zero bytes */ - while (((ptr - table + 1) < table_length) && - ((ptr[0] != 0) || (ptr[1] != 0))) - ptr++; - /* Skip over the two zero bytes */ - ptr += 2; - } - - /* We found DMI end of table, are number of entries correct? */ - if ((dmi_entry_type == 127) && (i != entry->number_smbios_structures)) { - fwts_failed(fw, LOG_LEVEL_MEDIUM, - "SMBIOSNumberOfStructures", - "The end of DMI table marker structure was found " - "but only %d DMI structures were found. The SMBIOS " - "entry table reported that there were %" PRIu16 - " DMI structures in the DMI table.", - i, entry->number_smbios_structures); - /* And table length can't be correct either */ - ret = FWTS_ERROR; - } - - (void)fwts_munmap(table, (size_t)entry->struct_table_length); - - return ret; -} - -static int dmi_decode_test1(fwts_framework *fw) -{ - void *addr = 0; - fwts_smbios_entry entry; - fwts_smbios_type type; - uint16_t version; - uint8_t checksum; - static char warning[] = - "This field is not checked by the kernel, and so will not affect the system, " - "however it should be fixed to conform to the latest version of the " - "System Management BIOS (SMBIOS) Reference Specification. "; - - fwts_log_info(fw, - "This test tries to find and sanity check the SMBIOS " - "data structures."); - - if ((addr = fwts_smbios_find_entry(fw, &entry, &type, &version)) == NULL) { - fwts_failed(fw, LOG_LEVEL_MEDIUM, - "SMBIOSNoEntryPoint", - "Could not find SMBIOS Table Entry Point."); - return FWTS_OK; - } - - fwts_passed(fw, "Found SMBIOS Table Entry Point at %p", addr); - smbios_dump_entry(fw, &entry, type); - fwts_log_nl(fw); - - if (type == FWTS_SMBIOS) { - checksum = fwts_checksum((uint8_t*)&entry, sizeof(fwts_smbios_entry)); - if (checksum != 0) - fwts_failed(fw, LOG_LEVEL_HIGH, - "SMBIOSBadChecksum", - "SMBIOS Table Entry Point Checksum is 0x%2.2x, should be 0x%2.2x", - entry.checksum, (uint8_t)(entry.checksum - checksum)); - else - fwts_passed(fw, "SMBIOS Table Entry Point Checksum is valid."); - - if (entry.length != 0x1f) { - fwts_failed(fw, LOG_LEVEL_LOW, - "SMBIOSBadEntryLength", - "SMBIOS Table Entry Point Length is 0x%2.2x, should be 0x1f", entry.length); - fwts_advice(fw, "%s Note that version 2.1 of the specification incorrectly stated that the " - "Entry Point Length should be 0x1e when it should be 0x1f.", warning); - } else - fwts_passed(fw, "SMBIOS Table Entry Point Length is valid."); - } - - if (memcmp(entry.anchor_string, "_DMI_", 5) != 0) { - fwts_failed(fw, LOG_LEVEL_HIGH, - "SMBIOSBadIntermediateAnchor", - "SMBIOS Table Entry Intermediate Anchor String was '%5.5s' and should be '_DMI_'.", - entry.anchor_string); - fwts_advice(fw, "%s", warning); - } else - fwts_passed(fw, "SMBIOS Table Entry Intermediate Anchor String _DMI_ is valid."); - - /* Intermediate checksum for legacy DMI */ - checksum = fwts_checksum(((uint8_t*)&entry)+16, 15); - if (checksum != 0) - fwts_failed(fw, LOG_LEVEL_HIGH, - "SMBIOSBadChecksum", - "SMBIOS Table Entry Point Intermediate Checksum is 0x%2.2x, should be 0x%2.2x", - entry.intermediate_checksum, - (uint8_t)(entry.intermediate_checksum - checksum)); - else - fwts_passed(fw, "SMBIOS Table Entry Point Intermediate Checksum is valid."); - - if ((entry.struct_table_length > 0) && (entry.struct_table_address == 0)) { - fwts_failed(fw, LOG_LEVEL_HIGH, - "SMBIOSBadTableAddress", - "SMBIOS Table Entry Structure Table Address is NULL and should be defined."); - fwts_advice(fw, - "The address of the SMBIOS Structure Tables must be defined if the " - "length of these tables is defined."); - } else { - /* - * Now does the DMI table look sane? If not, - * the SMBIOS Structure Table could be bad - */ - if (smbios_dmi_sane(fw, &entry) == FWTS_OK) - fwts_passed(fw, "SMBIOS Table Entry Structure Table Address and Length looks valid."); - } - - return FWTS_OK; -} - -static fwts_framework_minor_test dmi_decode_tests[] = { - { dmi_decode_test1, "Find and Check SMBIOS Table Entry Point." }, - { dmi_decode_test2, "Test DMI/SMBIOS tables for errors." }, - { NULL, NULL } -}; - -static fwts_framework_ops dmi_decode_ops = { - .description = "Test DMI/SMBIOS tables for errors.", - .minor_tests = dmi_decode_tests -}; - -FWTS_REGISTER("dmi_decode", &dmi_decode_ops, FWTS_TEST_ANYTIME, FWTS_FLAG_BATCH | FWTS_FLAG_ROOT_PRIV); - -#endif diff --git a/src/dmi/dmicheck/dmicheck.c b/src/dmi/dmicheck/dmicheck.c new file mode 100644 index 0000000..e5456ad --- /dev/null +++ b/src/dmi/dmicheck/dmicheck.c @@ -0,0 +1,1492 @@ +/* + * Copyright (C) 2010-2013 Canonical + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include +#include +#include +#include + +#include "fwts.h" + +#ifdef FWTS_ARCH_INTEL + +#include +#include +#include +#include +#include +#include +#include + +#define DMI_VERSION (0x0207) +#define VERSION_MAJOR(v) ((v) >> 8) +#define VERSION_MINOR(v) ((v) & 0xff) + +#define SMBIOS_END_OF_TABLE (127) + +#define DMI_NO_TABLE "DMINoTable" +#define DMI_NO_TABLE_HEADER "DMINoTableHeader" +#define DMI_BAD_TABLE_LENGTH "DMIBadTableLength" +#define DMI_BAD_UUID "DMIBadUUID" +#define DMI_STRUCT_COUNT "DMIStructCount" +#define DMI_VALUE_OUT_OF_RANGE "DMIValueOutOfRange" +#define DMI_STRING_INDEX_OUT_OF_RANGE "DMIStringIndexOutOfRange" +#define DMI_ILLEGAL_MAPPED_ADDR_RANGE "DMIIllegalMappedAddrRange" +#define DMI_MGMT_CTRL_HOST_TYPE "DMIMgmtCtrlHostType" +#define DMI_INVALID_ENTRY_LENGTH "DMIInvalidEntryLength" +#define DMI_INVALID_HARDWARE_ENTRY "DMIInvalidHardwareEntry" + +#define GET_UINT16(x) (uint16_t)(*(const uint16_t *)(x)) +#define GET_UINT32(x) (uint32_t)(*(const uint32_t *)(x)) +#define GET_UINT64(x) (uint64_t)(*(const uint64_t *)(x)) + +#define CHASSIS_OTHER 0x00 +#define CHASSIS_DESKTOP 0x01 +#define CHASSIS_WORKSTATION 0x02 +#define CHASSIS_MOBILE 0x04 +#define CHASSIS_SERVER 0x08 + +typedef struct { + const char *label; + const char *field; + const char *value; +} fwts_dmi_pattern; + +typedef struct { + uint16_t old; + uint16_t new; +} fwts_dmi_version; + +typedef struct { + const char *name; + uint8_t original; + uint8_t mapped; +} fwts_chassis_type_map; + +typedef struct { + uint8_t type; + uint8_t offset; +} fwts_dmi_used_by_kernel; + +static const fwts_dmi_pattern dmi_patterns[] = { + { "DMISerialNumber", "Serial Number", "0123456789" }, + { "DMISerialNumber", "Serial Number", "System Serial Number" }, + { "DMISerialNumber", "Serial Number", "MB-1234567890" }, + { "DMISerialNumber", NULL, "Chassis Serial Number" }, + { "DMIAssetTag", "Asset Tag", "1234567890" }, + { "DMIAssetTag", "Asset Tag", "Asset-1234567890" }, + { "DMIChassisVendor", NULL, "Chassis Manufacture" }, + { "DMIChassisVersion", NULL, "Chassis Version" }, + { "DMIProductVersion", NULL, "System Version" }, + { "DMIBadDefault", NULL, "To Be Filled By O.E.M." }, + { NULL, NULL, NULL } +}; + +static const char *uuid_patterns[] = { + "0A0A0A0A-0A0A-0A0A-0A0A-0A0A0A0A0A0A", + NULL, +}; + +static const fwts_chassis_type_map fwts_dmi_chassis_type[] = { + { "Invalid", FWTS_SMBIOS_CHASSIS_INVALID, CHASSIS_OTHER }, + { "Other", FWTS_SMBIOS_CHASSIS_OTHER, CHASSIS_OTHER }, + { "Unknown", FWTS_SMBIOS_CHASSIS_UNKNOWN, CHASSIS_OTHER }, + { "Desktop", FWTS_SMBIOS_CHASSIS_DESKTOP, CHASSIS_DESKTOP }, + { "Low Profile Desktop",FWTS_SMBIOS_CHASSIS_LOW_PROFILE_DESKTOP, CHASSIS_DESKTOP }, + { "Pizza Box", FWTS_SMBIOS_CHASSIS_PIZZA_BOX, CHASSIS_DESKTOP }, + { "Mini Tower", FWTS_SMBIOS_CHASSIS_MINI_TOWER, CHASSIS_DESKTOP }, + { "Chassis Tower", FWTS_SMBIOS_CHASSIS_TOWER, CHASSIS_DESKTOP }, + { "Portable", FWTS_SMBIOS_CHASSIS_PORTABLE, CHASSIS_MOBILE }, + { "Laptop", FWTS_SMBIOS_CHASSIS_LAPTOP, CHASSIS_MOBILE }, + { "Notebook", FWTS_SMBIOS_CHASSIS_NOTEBOOK, CHASSIS_MOBILE }, + { "Handheld", FWTS_SMBIOS_CHASSIS_HANDHELD, CHASSIS_MOBILE }, + { "Docking Station", FWTS_SMBIOS_CHASSIS_DOCKING_STATION, CHASSIS_DESKTOP }, + { "All In One", FWTS_SMBIOS_CHASSIS_ALL_IN_ONE, CHASSIS_DESKTOP }, + { "Sub Notebook", FWTS_SMBIOS_CHASSIS_SUB_NOTEBOOK, CHASSIS_MOBILE }, + { "Space Saving", FWTS_SMBIOS_CHASSIS_SPACE_SAVING, CHASSIS_DESKTOP }, + { "Lunch Box", FWTS_SMBIOS_CHASSIS_LUNCH_BOX, CHASSIS_DESKTOP | CHASSIS_MOBILE}, + { "Server Chassis", FWTS_SMBIOS_CHASSIS_MAIN_SERVER_CHASSIS, CHASSIS_SERVER }, + { "Expansion Chassis", FWTS_SMBIOS_CHASSIS_EXPANISON_CHASSIS, CHASSIS_OTHER }, + { "Sub Chassis", FWTS_SMBIOS_CHASSIS_SUB_CHASSIS, CHASSIS_OTHER }, + { "Bus Expansion Chassis", FWTS_SMBIOS_CHASSIS_BUS_EXPANSION_CHASSIS, CHASSIS_OTHER }, + { "Peripheral Chassis", FWTS_SMBIOS_CHASSIS_PERIPHERAL_CHASSIS, CHASSIS_OTHER }, + { "Raid Chassis", FWTS_SMBIOS_CHASSIS_RAID_CHASSIS, CHASSIS_OTHER }, + { "Rack Mount Chassis", FWTS_SMBIOS_CHASSIS_RACK_MOUNT_CHASSIS, CHASSIS_OTHER }, + { "Sealed Case PC", FWTS_SMBIOS_CHASSIS_SEALED_CASE_PC, CHASSIS_DESKTOP }, + { "Multi System Chassis",FWTS_SMBIOS_CHASSIS_MULTI_SYSTEM_CHASSIS, CHASSIS_OTHER }, + { "Compact PCI", FWTS_SMBIOS_CHASSIS_COMPACT_PCI, CHASSIS_OTHER }, + { "Advanced TCA", FWTS_SMBIOS_CHASSIS_ADVANCED_TCA, CHASSIS_OTHER }, + { "Blade", FWTS_SMBIOS_CHASSIS_BLADE, CHASSIS_SERVER }, + { "Enclosure", FWTS_SMBIOS_CHASSIS_BLASE_ENCLOSURE, CHASSIS_SERVER } +}; + +static const fwts_chassis_type_map fwts_acpi_pm_profile_type[] = { + { "Unspecified", FWTS_FACP_UNSPECIFIED, CHASSIS_OTHER }, + { "Desktop", FWTS_FACP_DESKTOP, CHASSIS_DESKTOP }, + { "Mobile", FWTS_FACP_MOBILE, CHASSIS_MOBILE }, + { "Workstation", FWTS_FACP_WORKSTATION, CHASSIS_WORKSTATION }, + { "Enterprise Server", FWTS_FACP_ENTERPRISE_SERVER, CHASSIS_SERVER }, + { "SOHO Server", FWTS_FACP_SOHO_SERVER, CHASSIS_SERVER | CHASSIS_DESKTOP }, + { "Appliance PC", FWTS_FACP_APPLIANCE_PC, CHASSIS_DESKTOP }, + { "Performance Server", FWTS_FACP_PERFORMANCE_SERVER, CHASSIS_SERVER }, + { "Tablet", FWTS_FACP_TABLET, CHASSIS_MOBILE } +}; + +/* Remapping table from buggy version numbers to correct values */ +static const fwts_dmi_version dmi_versions[] = { + { 0x021f, 0x0203 }, + { 0x0221, 0x0203 }, + { 0x0233, 0x0206 }, + { 0, 0 } +}; + +#define FIELD_ANY 0xff +#define TYPE_EOD 0xff + +/* + * DMI decoded fields used by the kernel, i.e. fields + * we care that work, + * see drivers/firmware/dmi_scan.c, dmi_decode() + */ +static fwts_dmi_used_by_kernel dmi_used_by_kernel_table[] = { + /* Type 0 BIOS Information fields */ + { 0, 4 }, + { 0, 5 }, + { 0, 8 }, + /* Type 1, System Information */ + { 1, 4 }, + { 1, 5 }, + { 1, 6 }, + { 1, 7 }, + { 1, 8 }, + /* Type 2, Base Board Information */ + { 2, 4 }, + { 2, 5 }, + { 2, 6 }, + { 2, 7 }, + { 2, 8 }, + /* Type 3, Chassis Information */ + { 3, 4 }, + { 3, 5 }, + { 3, 6 }, + { 3, 7 }, + { 3, 8 }, + /* Type 10, Onboard Devices Information */ + { 10, FIELD_ANY }, + /* Type 11, OEM Strings */ + { 11, FIELD_ANY }, + /* Type 38, IPMI Device Information */ + { 38, FIELD_ANY }, + /* Type 41, Onboard Devices Extended Information */ + { 41, FIELD_ANY }, + /* End */ + { TYPE_EOD, 0xff }, +}; + +static bool dmi_used_by_kernel(uint8_t type, uint8_t offset) +{ + int i; + + for (i = 0; dmi_used_by_kernel_table[i].type != TYPE_EOD; i++) { + if (dmi_used_by_kernel_table[i].type == type) + if ((dmi_used_by_kernel_table[i].offset == FIELD_ANY) || + (dmi_used_by_kernel_table[i].offset == offset)) + return true; + } + return false; +} + +static uint16_t dmi_remap_version(fwts_framework *fw, uint16_t old) +{ + int i; + + for (i=0; dmi_versions[i].old != 0; i++) { + if (old == dmi_versions[i].old) { + uint16_t new = dmi_versions[i].new; + fwts_warning(fw, + "Detected a buggy DMI version number " + "%" PRIu16 ".%" PRIu16 "remapping to " + "%" PRIu16 ".%" PRIu16, + VERSION_MAJOR(old), VERSION_MINOR(old), + VERSION_MAJOR(new), VERSION_MINOR(new)); + return new; + } + } + + /* All OK, return original */ + return old; +} + +static void dmi_out_of_range_advice(fwts_framework *fw, uint8_t type, uint8_t offset) +{ + if (dmi_used_by_kernel(type, offset)) + fwts_advice(fw, + "A value that is out of range is incorrect and not conforming to " + "the SMBIOS specification. The Linux kernel extracts and uses " + "this particular data item, so it is recommended that this SMBIOS " + "configuration is corrected even if the impact on the system " + "is most probably not critical."); + else + fwts_advice(fw, + "A value that is out of range is incorrect and not conforming to " + "the SMBIOS specification. This field is not currently used by " + "the Linux kernel, so this firmware bug shouldn't cause any " + "problems."); +} + +static void dmi_min_max_uint8_check(fwts_framework *fw, + const char *table, + uint32_t addr, + const char *field, + const fwts_dmi_header *hdr, + uint8_t offset, + uint8_t min, + uint8_t max) +{ + uint8_t val = hdr->data[offset]; + if ((val < min) || (val > max)) { + fwts_failed(fw, LOG_LEVEL_HIGH, + DMI_VALUE_OUT_OF_RANGE, + "Out of range value 0x%2.2" PRIx8 + " (range allowed 0x%2.2" PRIx8 "..0x%2.2" PRIx8 ") " + "while accessing entry '%s' @ 0x%8.8" PRIx32 + ", field '%s', offset 0x%2.2" PRIx8, + val, min, max, table, addr, field, offset); + dmi_out_of_range_advice(fw, hdr->type, offset); + } +} + +static void dmi_min_max_mask_uint8_check(fwts_framework *fw, + const char *table, + uint32_t addr, + const char *field, + const fwts_dmi_header *hdr, + uint8_t offset, + uint8_t min, + uint8_t max, + uint8_t shift, + uint8_t mask) +{ + uint8_t val = (hdr->data[offset] >> shift) & mask; + + if ((val < min) || (val > max)) { + fwts_failed(fw, LOG_LEVEL_HIGH, DMI_VALUE_OUT_OF_RANGE, + "Out of range value 0x%2.2" PRIx8 + " (range allowed 0x%2.2" PRIx8 "..0x%2.2" PRIx8 ") " + "while accessing entry '%s' @ 0x%8.8" PRIx32 + ", field '%s', offset 0x%2.2" PRIx8, + val, min, max, table, addr, field, offset); + dmi_out_of_range_advice(fw, hdr->type, offset); + } +} + +static void dmi_str_check_index(fwts_framework *fw, + const char *table, + uint32_t addr, + const char *field, + const fwts_dmi_header *hdr, + uint8_t offset, + uint8_t index) +{ + char *data = (char *)hdr->data; + uint8_t i = index; + bool used_by_kernel = dmi_used_by_kernel(hdr->type, offset); + + if (i > 0) { + int j; + int failed = -1; + + data += hdr->length; + while (i > 1 && *data) { + data += strlen(data) + 1; + i--; + } + + /* Sanity checks */ + if (*data == '\0') { + int level = used_by_kernel ? LOG_LEVEL_HIGH : LOG_LEVEL_LOW; + + /* This entry is clearly broken so flag it as a corrupt entry */ + fwts_failed(fw, level, DMI_STRING_INDEX_OUT_OF_RANGE, + "Out of range string index 0x%2.2" PRIx8 + " while accessing entry '%s' " + "@ 0x%8.8" PRIx32 ", field '%s', offset 0x%2.2" PRIx8, + index, table, addr, field, offset); + if (used_by_kernel) + fwts_advice(fw, + "DMI strings are stored in a manner that uses a special " + "index to fetch the Nth string from the data. For this " + "particular DMI string the index given is not in range " + "which means this particular entry is broken. The Linux " + "kernel uses this string - hence this string should be " + "fixed to ensure sane data is passed to the kernel. " + "Note that this probably won't cause any critical system " + "errors."); + else + fwts_advice(fw, + "DMI strings are stored in a manner that uses a special " + "index to fetch the Nth string from the data. For this " + "particular DMI string the index given is not in range " + "which means this particular entry is broken. The Linux " + "kernel does not use this string, so this error will not " + "cause any system errors."); + return; + } + + /* Scan for known BIOS defaults that vendors forget to set */ + for (j=0; dmi_patterns[j].label != NULL; j++) { + if (dmi_patterns[j].field && + (strcmp(dmi_patterns[j].field, field) == 0) && + (strcmp(dmi_patterns[j].value, data) == 0)) { + failed = j; + break; + } else if (strcmp(dmi_patterns[j].value, data) == 0) { + failed = j; + break; + } + } + if (failed != -1) { + int level = used_by_kernel ? LOG_LEVEL_MEDIUM : LOG_LEVEL_LOW; + + fwts_failed(fw, level, dmi_patterns[j].label, + "String index 0x%2.2" PRIx8 + " in table entry '%s' @ 0x%8.8" PRIx32 + ", field '%s', offset 0x%2.2" PRIx8 + " has a default value '%s' and probably has " + "not been updated by the BIOS vendor.", + index, table, addr, field, offset, data); + + if (used_by_kernel) { + fwts_advice(fw, + "The DMI table contains data which is clearly been " + "left in a default setting and not been configured " + "for this machine. " + "Somebody has probably forgotten to define this " + "field and it basically means this field is effectively " + "useless. Note that the kernel uses this field so " + "it probably should be corrected to ensure the kernel " + "is using sane values."); + } else { + /* This string is broken, but we don't care about it too much */ + fwts_advice(fw, + "The DMI table contains data which is clearly been " + "left in a default setting and not been configured " + "for this machine. " + "Somebody has probably forgotten to define this " + "field and it basically means this field is effectively " + "useless, however the kernel does not use this data " + "so the issue is fairly low."); + } + } + } +} + +static inline void dmi_str_check(fwts_framework *fw, + const char *table, + uint32_t addr, + const char *field, + const fwts_dmi_header *hdr, + uint8_t offset) +{ + dmi_str_check_index(fw, table, addr, field, hdr, offset, hdr->data[offset]); +} + +static void dmi_uuid_check(fwts_framework *fw, + const char *table, + uint32_t addr, + const char *field, + const fwts_dmi_header *hdr, + uint8_t offset) +{ + char guid_str[37]; + int i; + + fwts_guid_buf_to_str(hdr->data + offset, guid_str, sizeof(guid_str)); + + for (i=0; uuid_patterns[i] != NULL; i++) { + if (strcmp(guid_str, uuid_patterns[i]) == 0) { + fwts_failed(fw, LOG_LEVEL_LOW, DMI_BAD_UUID, + "UUID in table entry '%s' @ 0x%8.8" PRIx32 + " field '%s', offset 0x%2.2" PRIx8 + " has a default value '%s' and probably has " + "not been updated by the BIOS vendor.", + table, addr, field, offset, guid_str); + fwts_advice(fw, + "The DMI table contains a UUID which is clearly been " + "left in a default setting and not been configured " + "for this machine. While this is not critical it does " + "mean that somebody has probably forgotten to define this " + "field and it basically means this field is effectively " + "useless."); + } + } +} + +static void dmicheck_entry(fwts_framework *fw, + uint32_t addr, + const fwts_dmi_header *hdr) +{ + uint8_t *ptr; + uint8_t count; + uint8_t val; + uint8_t *data = hdr->data; + char tmp[64]; + char *table; + int i; + int len; + uint32_t failed_count = fw->minor_tests.failed; + int battery_count; + int ret; + int both_ok; + fwts_acpi_table_info *acpi_table; + fwts_acpi_table_fadt *fadt; + bool advice_given = false; + + switch (hdr->type) { + case 0: /* 7.1 */ + table = "BIOS Information (Type 0)"; + if (hdr->length < 0x12) + break; + dmi_str_check(fw, table, addr, "Vendor", hdr, 0x4); + dmi_str_check(fw, table, addr, "BIOS Version", hdr, 0x5); + dmi_str_check(fw, table, addr, "Release Date", hdr, 0x8); + break; + + case 1: /* 7.2 */ + table = "System Information (Type 1)"; + if (hdr->length < 0x08) + break; + dmi_str_check(fw, table, addr, "Manufacturer", hdr, 0x4); + dmi_str_check(fw, table, addr, "Product Name", hdr, 0x5); + dmi_str_check(fw, table, addr, "Version", hdr, 0x6); + dmi_str_check(fw, table, addr, "Serial Number", hdr, 0x7); + if (hdr->length < 0x19) + break; + dmi_uuid_check(fw, table, addr, "UUID", hdr, 0x8); + dmi_min_max_uint8_check(fw, table, addr, "Wakeup Type", hdr, 0x18, 0x0, 0x08); + if (hdr->length < 0x1b) + break; + dmi_str_check(fw, table, addr, "SKU Number", hdr, 0x19); + dmi_str_check(fw, table, addr, "Family", hdr, 0x1a); + break; + + case 2: /* 7.3 */ + table = "Base Board Information (Type 2)"; + if (hdr->length < 0x08) + break; + dmi_str_check(fw, table, addr, "Manufacturer", hdr, 0x4); + dmi_str_check(fw, table, addr, "Product", hdr, 0x5); + dmi_str_check(fw, table, addr, "Version", hdr, 0x6); + dmi_str_check(fw, table, addr, "Serial Number", hdr, 0x7); + if (hdr->length < 0x09) + break; + dmi_str_check(fw, table, addr, "Asset Tag", hdr, 0x8); + if (hdr->length < 0x0f) + break; + dmi_str_check(fw, table, addr, "Location In Chassis", hdr, 0xa); + dmi_min_max_uint8_check(fw, table, addr, "Type", hdr, 0xd, 0x1, 0xd); + break; + + case 3: /* 7.4 */ + table = "Chassis Information (Type 3)"; + if (hdr->length < 0x09) + break; + dmi_str_check(fw, table, addr, "Manufacturer", hdr, 0x4); + dmi_min_max_mask_uint8_check(fw, table, addr, "Chassis Type", hdr, 0x5, 0x1, 0x1d, 0x0, 0x7f); + if (fwts_acpi_find_table(fw, "FACP", 0, &acpi_table) != FWTS_OK) + break; + if (acpi_table == NULL) + break; + fadt = (fwts_acpi_table_fadt *)acpi_table->data; + + if (fadt->preferred_pm_profile >= + (sizeof(fwts_acpi_pm_profile_type) / sizeof(fwts_chassis_type_map))) { + fwts_failed(fw, LOG_LEVEL_HIGH, DMI_INVALID_HARDWARE_ENTRY, + "Incorrect Chassis Type " + "ACPI FACP reports 0x%" PRIx8, + fadt->preferred_pm_profile); + break; + } + if (data[5] >= + (sizeof(fwts_dmi_chassis_type) / sizeof(fwts_chassis_type_map))) { + fwts_failed(fw, LOG_LEVEL_HIGH, DMI_INVALID_HARDWARE_ENTRY, + "Incorrect Chassis Type " + "SMBIOS Type 3 reports 0x%" PRIx8, + data[5]); + fwts_advice(fw, + "The Chassis Type in the ACPI FADT is out of range " + "and hence we cannot identify the preferred power " + "management profile for this machine."); + break; + } + + /* + * LP: #1021674 + * We should only check profile and chassis types when we know for sure + * that we can compare valid types togther. So it is only valid to do + * a check if both aren't CHASSIS_OTHER types + */ + both_ok = (fwts_acpi_pm_profile_type[fadt->preferred_pm_profile].mapped != CHASSIS_OTHER) & + (fwts_dmi_chassis_type[data[5]].mapped != CHASSIS_OTHER); + + if ( both_ok && + !(fwts_acpi_pm_profile_type[fadt->preferred_pm_profile].mapped & + fwts_dmi_chassis_type[data[5]].mapped)) { + fwts_failed(fw, LOG_LEVEL_HIGH, DMI_INVALID_HARDWARE_ENTRY, + "Unmatched Chassis Type: " + "SMBIOS Type 3 reports 0x%" PRIx8 " '%s' " + "ACPI FACP reports 0x%" PRIx8 " '%s'", + data[5], + fwts_dmi_chassis_type[data[5]].name, + fadt->preferred_pm_profile, + fwts_acpi_pm_profile_type[fadt->preferred_pm_profile].name); + /* + * Make it a bit more wordy to explain the ramifications + */ + fwts_advice(fw, + "The SMBIOS System Enclosure/Chassis type is defined as " + "0x%" PRIx8 " '%s' where as the ACPI FACP reports the preferred " + "power management profile is 0x%" PRIx8 " '%s' so we possibly " + "have conflicting definitions of the machine's PM profile " + "for the type of machine. " + "See Table 16 of section 4.5.1 of the SMBIOS specification " + "and Table 5-34 of section 5.2.9 of the ACPI specification " + "for more details. " + "This kind of mismatch may lead to incorrect power " + "management handling for the type of workload expected " + "for this hardware.", + data[5], + fwts_dmi_chassis_type[data[5]].name, + fadt->preferred_pm_profile, + fwts_acpi_pm_profile_type[fadt->preferred_pm_profile].name); + advice_given = true; + } + dmi_min_max_mask_uint8_check(fw, table, addr, "Chassis Lock", hdr, 0x5, 0x0, 0x1, 0x7, 0x1); + dmi_str_check(fw, table, addr, "Version", hdr, 0x6); + dmi_str_check(fw, table, addr, "Serial Number", hdr, 0x7); + dmi_str_check(fw, table, addr, "Asset Tag", hdr, 0x8); + dmi_min_max_uint8_check(fw, table, addr, "Boot-up State", hdr, 0x9, 0x1, 0x6); + dmi_min_max_uint8_check(fw, table, addr, "Power Supply State", hdr, 0xa, 0x1, 0x6); + dmi_min_max_uint8_check(fw, table, addr, "Thermal State", hdr, 0xb, 0x1, 0x6); + dmi_min_max_uint8_check(fw, table, addr, "Security Status", hdr, 0xc, 0x1, 0x5); + if (hdr->length < 0x15 + data[0x13] * data[0x14]) + break; + ptr = data + 0x15; + len = data[0x14]; + if (len >= 0x3) { + for (i = 0; i < data[0x13]; i++) { + val = ptr[i * len] & 0x7f; + if (ptr[i * len] & 0x80) { + if (val > 0x42) + fwts_failed(fw, LOG_LEVEL_HIGH, DMI_VALUE_OUT_OF_RANGE, + "Out of range value 0x%2.2" PRIx8 + " (range allowed 0x00..0x42) " + "while accessing entry '%s' @ " + "0x%8.8" PRIx32 ", field " + "'SMBIOS Structure Type %d', " + "offset 0x%2.2x", + val, table, addr, i, 0x15 + (i*len)); + } else { + if ((val < 0x1) || (val > 0xd)) { + fwts_failed(fw, LOG_LEVEL_HIGH, DMI_VALUE_OUT_OF_RANGE, + "Out of range value 0x%2.2" PRIx8 + " (range allowed 0x00..0x42) " + "while accessing entry '%s' @ " + "0x%8.8" PRIx32 ", field " + "'Base Board Type %d', offset 0x%2.2x", + val, table, addr, i, 0x15 + (i*len)); + } + } + } + } + if (hdr->length < 0x16 + data[0x13] * data[0x14]) + break; + dmi_str_check(fw, table, addr, "SKU Number", hdr, 0x15 + data[0x13] * data[0x14]); + break; + + case 4: /* 7.5 */ + table = "Processor Information (Type 4)"; + if (hdr->length < 0x1a) + break; + dmi_str_check(fw, table, addr, "Socket Designation", hdr, 0x4); + dmi_min_max_uint8_check(fw, table, addr, "Processor Type", hdr, 0x5, 0x1, 0x6); + dmi_str_check(fw, table, addr, "Processor Manufacturer", hdr, 0x7); + dmi_str_check(fw, table, addr, "Processor Version", hdr, 0x10); + dmi_min_max_uint8_check(fw, table, addr, "Upgrade", hdr, 0x19, 0x1, 0x2a); + if (hdr->length < 0x23) + break; + dmi_str_check(fw, table, addr, "Serial Number", hdr, 0x20); + dmi_str_check(fw, table, addr, "Asset Tag", hdr, 0x21); + dmi_str_check(fw, table, addr, "Part Number", hdr, 0x22); + break; + + case 5: /* 7.6 */ + table = "Memory Controller Information (Type 5)"; + if (hdr->length < 0x0f) + break; + dmi_min_max_uint8_check(fw, table, addr, "Error Detecting Method", hdr, 0x4, 0x1, 0x8); + dmi_min_max_uint8_check(fw, table, addr, "Supported Interleave", hdr, 0x6, 0x1, 0x7); + dmi_min_max_uint8_check(fw, table, addr, "Current Interleave", hdr, 0x7, 0x1, 0x7); + break; + + case 6: /* 7.7 */ + table = "Memory Module Information (Type 6)"; + if (hdr->length < 0x0c) + break; + dmi_str_check(fw, table, addr, "Socket Designation", hdr, 0x4); + break; + + case 7: /* 7.8 */ + table = "Cache Information (Type 7)"; + if (hdr->length < 0x0f) + break; + dmi_str_check(fw, table, addr, "Socket Designation", hdr, 0x4); + if (((GET_UINT16(data + 0x05) >> 5) & 0x0003) == 0x2) + fwts_failed(fw, LOG_LEVEL_HIGH, DMI_VALUE_OUT_OF_RANGE, + "Out of range value %x4.4" PRIx16 " " + "bits 5..6 set to illegal value 0x2, only allowed" + "0x0, 0x1, 0x3 while accessing entry '%s' @ " + "0x%8.8" PRIx32 ", field '%s', offset 0x%2.2x", + GET_UINT16(data + 0x05), + table, addr, "Cache Location", 0x5); + if (hdr->length < 0x13) + break; + dmi_min_max_uint8_check(fw, table, addr, "Error Correction Type", hdr, 0x10, 0x1, 0x6); + dmi_min_max_uint8_check(fw, table, addr, "System Cache Type", hdr, 0x11, 0x1, 0x5); + dmi_min_max_uint8_check(fw, table, addr, "Associativity", hdr, 0x12, 0x1, 0xe); + break; + + case 8: /* 7.9 */ + table = "Port Connector Information (Type 8)"; + if (hdr->length < 0x09) + break; + dmi_str_check(fw, table, addr, "Internal Reference Designator", hdr, 0x4); + if (!((data[0x5] <= 0x22) || + (data[0x5] == 0xff) || + ((data[0x5] >= 0xa0) && (data[0x5] <= 0xa4)))) + fwts_failed(fw, LOG_LEVEL_HIGH, DMI_VALUE_OUT_OF_RANGE, + "Out of range value 0x%2.2" PRIx8 " " + "(range allowed 0x00..0x22, 0xa0..0xa4, 0xff) " + "while accessing entry '%s' @ " + "0x%8.8" PRIx32 ", field '%s', offset 0x%2.2x", + data[0x5], table, addr, "Internal Connector Type", 0x5); + dmi_str_check(fw, table, addr, "External Reference Designator", hdr, 0x6); + if (!((data[0x7] <= 0x22) || + (data[0x7] == 0xff) || + ((data[0x7] >= 0xa0) && (data[0x7] <= 0xa4)))) + fwts_failed(fw, LOG_LEVEL_HIGH, DMI_VALUE_OUT_OF_RANGE, + "Out of range value 0x%2.2" PRIx8 " " + "(range allowed 0x00..0x22, 0xa0..0xa4, 0xff) " + "while accessing entry '%s' @ " + "0x%8.8" PRIx32 ", field '%s', offset 0x%2.2x", + data[0x7], table, addr, "Internal Connector Type", 0x7); + + if (!((data[0x8] <= 0x21) || + (data[0x8] == 0xff) || + ((data[0x8] >= 0xa0) && (data[0x8] <= 0xa1)))) + fwts_failed(fw, LOG_LEVEL_HIGH, DMI_VALUE_OUT_OF_RANGE, + "Out of range value 0x%2.2" PRIx8 " " + "(range allowed 0x00..0x21, 0xa0..0xa1, 0xff) " + "while accessing entry '%s' @ 0x%8.8" PRIx32 ", " + "field '%s', offset 0x%2.2x", + data[0x8], table, addr, "Port Type", 0x8); + break; + + case 9: /* 7.10 */ + table = "System Slot Information (Type 9)"; + if (hdr->length < 0x0c) + break; + dmi_str_check(fw, table, addr, "Slot Designation", hdr, 0x4); + if (!(((data[0x5] >= 0x01) && (data[0x5] <= 0x13)) || + ((data[0x5] >= 0xa0) && (data[0x5] <= 0xb6)))) + fwts_failed(fw, LOG_LEVEL_HIGH, DMI_VALUE_OUT_OF_RANGE, + "Out of range value 0x%2.2x" PRIx8 " " + "(range allowed 0x01..0x08, 0xa0..0xa2) " + "while accessing entry '%s' @ 0x%8.8" PRIx32 ", " + "field '%s', offset 0x%2.2x", + data[0x5], table, addr, "Slot Type", 0x5); + dmi_min_max_uint8_check(fw, table, addr, "Slot Data Bus Width", hdr, 0x6, 0x1, 0xe); + dmi_min_max_uint8_check(fw, table, addr, "Current Usage", hdr, 0x7, 0x1, 0x4); + dmi_min_max_uint8_check(fw, table, addr, "Slot Length", hdr, 0x8, 0x1, 0x4); + break; + + case 10: /* 7.11 */ + table = "On Board Devices (Type 10)"; + count = (hdr->length - 4) / 2; + for (i = 0; i < count; i++) { + snprintf(tmp, sizeof(tmp), "Type (Device #%d)", i); + dmi_min_max_mask_uint8_check(fw, table, addr, tmp, hdr, 4 + (2 * i), 0x1, 0xa, 0x0, 0x7f); + snprintf(tmp, sizeof(tmp), "Description (Device #%d)", i); + dmi_str_check(fw, table, addr, tmp, hdr, 5 + (2 * i)); + } + break; + + case 11: /* 7.12 */ + table = "OEM Strings (Type 11)"; + if (hdr->length < 0x5) + break; + for (i = 1; i <= hdr->data[4]; i++) { + snprintf(tmp, sizeof(tmp), "String %d", i); + dmi_str_check_index(fw, table, addr, tmp, hdr, 0x4, i); + } + break; + + case 12: /* 7.13 */ + table = "System Configuration Options (Type 12)"; + if (hdr->length < 0x5) + break; + for (i = 1; i <= hdr->data[4]; i++) { + snprintf(tmp, sizeof(tmp), "Option %d", i); + dmi_str_check_index(fw, table, addr, tmp, hdr, 0x4, i); + } + break; + + case 13: /* 7.14 */ + table = "BIOS Language Information (Type 13)"; + if (hdr->length < 0x16) + break; + for (i = 1; i <= hdr->data[4]; i++) { + snprintf(tmp, sizeof(tmp), "BIOS Language String %d", i); + dmi_str_check_index(fw, table, addr, tmp, hdr, 0x4, i); + } + dmi_str_check(fw, table, addr, "Currently Installed Language", hdr, 0x15); + break; + + case 14: /* 7.15 */ + table = "Group Associations (Type 14)"; + if (hdr->length < 0x05) + break; + dmi_str_check(fw, table, addr, "Name", hdr, 0x4); + break; + + case 15: /* 7.16 */ + table = "System Event Log (Type 15)"; + if (hdr->length < 0x14) + break; + val = hdr->data[0x0a]; + if (!((val <= 0x04) || (val >= 0x80))) { + fwts_failed(fw, LOG_LEVEL_HIGH, DMI_VALUE_OUT_OF_RANGE, + "Out of range value 0x%2.2" PRIx8 " " + "(range allowed 0x00..0x01, " + "0x80..0xff) while accessing entry '%s' @ " + "0x%8.8" PRIx32 ", field '%s', offset 0x%2.2x", + val, table, addr, "Access Method", 0x0a); + } + if (hdr->length < 0x17) + break; + val = hdr->data[0x14]; + if (!((val <= 0x01) || (val >= 0x80))) { + fwts_failed(fw, LOG_LEVEL_HIGH, DMI_VALUE_OUT_OF_RANGE, + "Out of range value 0x%2.2" PRIx8 " " + "(range allowed 0x00..0x01, " + "0x80..0xff) while accessing entry '%s' @ " + "0x%8.8" PRIx32 ", field '%s', offset 0x%2.2x", + val, table, addr, "Log Header Format", 0x14); + } + if (hdr->length < 0x17 + data[0x15] * data[0x16]) + break; + if (data[0x16] >= 0x02) { + uint8_t *ptr = data + 0x17; + int i; + for (i = 0; i < data[0x15]; i++) { + int j = data[0x16] * i; + val = ptr[j]; + if (!(((val >= 0x01) && (val <= 0x0e)) || + ((val >= 0x10) && (val <= 0x17)) || + (val >= 0x80))) { + fwts_failed(fw, LOG_LEVEL_HIGH, DMI_VALUE_OUT_OF_RANGE, + "Out of range value 0x%2.2" PRIx8 " " + "(range allowed 0x01..0x0e, 0x10..0x17, " + "0x80..0xff) while accessing entry '%s' @ " + "0x%8.8" PRIx32 ", field '%s', item %d", + val, table, addr, "Log Descriptor Type", i); + } + val = ptr[j + 1]; + if ((val > 0x06) && (val < 0x80)) { + fwts_failed(fw, LOG_LEVEL_HIGH, DMI_VALUE_OUT_OF_RANGE, + "Out of range value 0x%2.2" PRIx8 " " + "(range allowed 0x00..0x06, 0x80..0xff) " + "while accessing entry '%s' @ " + "0x%8.8" PRIx32 ", field '%s', item %d", + val, table, addr, "Log Descriptor Format", i); + } + } + } + break; + + case 16: /* 7.17 */ + table = "Physical Memory Array (Type 16)"; + if (hdr->length < 0x0f) + break; + if (!(((data[0x4] >= 0x01) && (data[0x4] <= 0x0a)) || + ((data[0x4] >= 0xa0) && (data[0x4] <= 0xa3)))) + fwts_failed(fw, LOG_LEVEL_HIGH, DMI_VALUE_OUT_OF_RANGE, + "Out of range value 0x%2.2" PRIx8 " " + "(range allowed 0x01..0x0a, 0xa0..0xa3) " + "while accessing entry '%s' @ " + "0x%8.8" PRIx32 ", field '%s', offset 0x%2.2x", + data[0x4], table, addr, "Location", 0x4); + dmi_min_max_uint8_check(fw, table, addr, "Use", hdr, 0x5, 0x1, 0x7); + dmi_min_max_uint8_check(fw, table, addr, "Error Corrrection Type", hdr, 0x6, 0x1, 0x7); + break; + + case 17: /* 7.18 */ + table = "Memory Device (Type 17)"; + if (hdr->length < 0x15) + break; + dmi_min_max_uint8_check(fw, table, addr, "Form Factor", hdr, 0xe, 0x1, 0xf); + dmi_str_check(fw, table, addr, "Locator", hdr, 0x10); + dmi_str_check(fw, table, addr, "Bank Locator", hdr, 0x11); + dmi_min_max_uint8_check(fw, table, addr, "Memory Type", hdr, 0x12, 0x1, 0x19); + if (hdr->length < 0x1b) + break; + dmi_str_check(fw, table, addr, "Manufacturer", hdr, 0x17); + dmi_str_check(fw, table, addr, "Serial Number", hdr, 0x18); + dmi_str_check(fw, table, addr, "Asset Tag", hdr, 0x19); + dmi_str_check(fw, table, addr, "Part Number", hdr, 0x1a); + break; + + case 18: /* 7.19 */ + table = "32-bit Memory Error Information (Type 18)"; + if (hdr->length < 0x17) + break; + dmi_min_max_uint8_check(fw, table, addr, "Error Type", hdr, 0x4, 0x1, 0xe); + dmi_min_max_uint8_check(fw, table, addr, "Error Granularity", hdr, 0x5, 0x1, 0x4); + dmi_min_max_uint8_check(fw, table, addr, "Error Operation", hdr, 0x6, 0x1, 0x5); + break; + + case 19: /* 7.20 */ + table = "Memory Array Mapped Address (Type 19)"; + if (hdr->length < 0x0F) + break; + if (hdr->length >= 0x1F && GET_UINT32(data + 0x04) == 0xFFFFFFFF) { + uint64_t start, end; + start = GET_UINT64(data + 0x0F); + end = GET_UINT64(data + 0x17); + if (start == end) + fwts_failed(fw, LOG_LEVEL_HIGH, DMI_ILLEGAL_MAPPED_ADDR_RANGE, + "Extended Start and End addresses are identical " + "while accessing entry '%s' @ 0x%8.8" PRIx32 ", " + "fields 'Extended Starting Address' and 'Extended Ending Address'", + table, addr); + } else { + if (GET_UINT32(data + 0x08) - GET_UINT32(data + 0x04) + 1 == 0) + fwts_failed(fw, LOG_LEVEL_HIGH, DMI_ILLEGAL_MAPPED_ADDR_RANGE, + "Illegal zero mapped address range " + "for entry '%s' @ 0x%8.8" PRIx32, table, addr); + } + break; + + case 20: /* 7.21 */ + table = "Memory Device Mapped Address (Type 20)"; + if (hdr->length < 0x13) + break; + if (hdr->length >= 0x23 && GET_UINT32(data + 0x04) == 0xFFFFFFFF) { + uint64_t start, end; + start = GET_UINT64(data + 0x13); + end = GET_UINT64(data + 0x1B); + if (start == end) + fwts_failed(fw, LOG_LEVEL_HIGH, DMI_ILLEGAL_MAPPED_ADDR_RANGE, + "Extended Start and End addresses are identical " + "while accessing entry '%s' @ 0x%8.8" PRIx32 ", " + "fields 'Extended Starting Address' and 'Extended Ending Address'", + table, addr); + } else { + if (GET_UINT32(data + 0x08) - GET_UINT32(data + 0x04) + 1 == 0) + fwts_failed(fw, LOG_LEVEL_HIGH, DMI_ILLEGAL_MAPPED_ADDR_RANGE, + "Illegal zero mapped address range " + "for entry '%s' @ 0x%8.8" PRIx32, table, addr); + } + if (data[0x10] == 0) + fwts_failed(fw, LOG_LEVEL_HIGH, DMI_ILLEGAL_MAPPED_ADDR_RANGE, + "Illegal row position %2.2" PRIx8 ", " + "while accessing entry '%s' @ 0x%8.8" PRIx32 + ", field '%s', offset 0x%2.2x", + data[0x10], table, addr, "Partial Row Position", 0x10); + break; + + case 21: /* 7.22 */ + table = "Built-in Pointing Device (Type 21)"; + if (hdr->length < 0x07) + break; + dmi_min_max_uint8_check(fw, table, addr, "Type", hdr, 0x4, 0x1, 0x9); + if (!(((data[0x5] >= 0x01) && (data[0x5] <= 0x08)) || + ((data[0x5] >= 0xa0) && (data[0x5] <= 0xa2)))) { + fwts_failed(fw, LOG_LEVEL_HIGH, DMI_VALUE_OUT_OF_RANGE, + "Out of range value 0x%2.2" PRIx8 " " + "(range allowed 0x01..0x08, 0xa0..0xa2) " + "while accessing '%s', field '%s', offset 0x%2.2x", + data[0x5], table, "Interface", 0x5); + } + break; + + case 22: /* 7.23 */ + table = "Portable Battery (Type 22)"; + if (hdr->length < 0x10) + break; + dmi_str_check(fw, table, addr, "Location", hdr, 0x4); + dmi_str_check(fw, table, addr, "Manufacturer", hdr, 0x5); + if (data[0x06] || hdr->length < 0x1A) + dmi_str_check(fw, table, addr, "Manufacturer Date", hdr, 0x6); + if (data[0x07] || hdr->length < 0x1A) + dmi_str_check(fw, table, addr, "Serial Number", hdr, 0x7); + dmi_str_check(fw, table, addr, "Device Name", hdr, 0x8); + if (data[0x09] != 0x02 || hdr->length < 0x1A) + dmi_str_check(fw, table, addr, "Device Chemistry", hdr, 0x9); + + dmi_str_check(fw, table, addr, "SBDS Version Number", hdr, 0xe); + if (hdr->length < 0x1A) + break; + if (data[0x09] == 0x02) + dmi_str_check(fw, table, addr, "SBDS Device Chemistry", hdr, 0x14); + + ret = fwts_battery_get_count(fw, &battery_count); + if (ret != FWTS_OK || battery_count < 1) { + fwts_failed(fw, LOG_LEVEL_MEDIUM, DMI_INVALID_HARDWARE_ENTRY, + "Invalid Hardware Configuration " + "(no battery found) "); + } + break; + case 23: /* 7.24 */ + table = "System Reset (Type 23)"; + if (hdr->length < 0x0D) + break; + if (!(data[0x04] & (1 << 5))) + break; + dmi_min_max_mask_uint8_check(fw, table, addr, "Capabilities (bits 1..2)", hdr, 0x4, 0x1, 0x3, 1, 0x3); + dmi_min_max_mask_uint8_check(fw, table, addr, "Capabilities (bits 3..4)", hdr, 0x4, 0x1, 0x3, 3, 0x3); + break; + + case 24: /* 7.25 */ + table = "Hardware Security (Type 24)"; + /* if (hdr->length < 0x05) + break; */ + break; + + case 25: /* 7.26 */ + table = "System Power Controls (Type 25)"; + /* if (hdr->length < 0x9) + break; */ + break; + + case 26: /* 7.27 */ + table = "Voltage Probe (Type 26)"; + if (hdr->length < 0x14) + break; + dmi_str_check(fw, table, addr, "Description", hdr, 0x4); + dmi_min_max_mask_uint8_check(fw, table, addr, "Location (bits 0..4)", hdr, 0x5, 0x1, 0xb, 0, 0x1f); + dmi_min_max_mask_uint8_check(fw, table, addr, "Status (bits 5..7)", hdr, 0x5, 0x1, 0x6, 5, 0x7); + break; + + case 27: /* 7.28 */ + table = "Cooling Device (Type 27)"; + if (hdr->length < 0xc) + break; + val = data[0x06] & 0x1f; + if (!(((val >= 0x01) && (val <= 0x09)) || + ((val >= 0x10) && (val <= 0x11)))) + fwts_failed(fw, LOG_LEVEL_HIGH, DMI_VALUE_OUT_OF_RANGE, + "Out of range value 0x%2.2" PRIx8 " " + "(range allowed 0x01..0x09, 0x10..0x11) " + "while accessing entry '%s' @ " + "0x%8.8" PRIx32 ", field '%s', " + "offset 0x%2.2x, mask 0x%2.2x", + data[0x6], table, addr, "Device Type", 0x6, 0x1f); + dmi_min_max_mask_uint8_check(fw, table, addr, "Status (bits 5..7)", hdr, 0x6, 0x1, 0x6, 5, 0x7); + if (hdr->length < 0x0f) + break; + dmi_str_check(fw, table, addr, "Description", hdr, 0xe); + break; + + case 28: /* 7.29 */ + table = "Temperature Probe (Type 28)"; + if (hdr->length < 0x14) + break; + dmi_str_check(fw, table, addr, "Description", hdr, 0x4); + dmi_min_max_mask_uint8_check(fw, table, addr, "Location (bits 0..4)", hdr, 0x5, 0x1, 0xf, 0, 0x1f); + dmi_min_max_mask_uint8_check(fw, table, addr, "Status (bits 5..7)", hdr, 0x5, 0x1, 0x6, 5, 0x7); + break; + + case 29: /* 7.30 */ + table = "Electrical Current Probe (Type 29)"; + if (hdr->length < 0x14) + break; + dmi_str_check(fw, table, addr, "Description", hdr, 0x4); + dmi_min_max_mask_uint8_check(fw, table, addr, "Location (bits 0..4)", hdr, 0x5, 0x1, 0xb, 0, 0x1f); + dmi_min_max_mask_uint8_check(fw, table, addr, "Status (bits 5..7)", hdr, 0x5, 0x1, 0x6, 5, 0x7); + break; + + case 30: /* 7.31 */ + table = "Out-of-band Remote Access (Type 30)"; + if (hdr->length < 0x06) + break; + dmi_str_check(fw, table, addr, "Manufacturer Name", hdr, 0x4); + break; + + case 31: /* 7.32 */ + table = "Boot Integrity Services Entry Point (Type 31)"; + /* + if (hdr->length < 0x1c) + break; + */ + break; + + case 32: /* 7.33 */ + table = "System Boot Information (Type 32)"; + if (hdr->length < 0xb) + break; + if ((data[0xa] > 0x8) && (data[0xa] < 128)) + fwts_failed(fw, LOG_LEVEL_HIGH, DMI_VALUE_OUT_OF_RANGE, + "Out of range value 0x%2.2" PRIx8 " " + "(range allowed 0x00..0x08, 0x80..0xff) " + "while accessing entry '%s' @ " + "0x%8.8" PRIx32 ", field '%s', offset 0x%2.2x", + data[0xa], table, addr, "Boot Status", 0xa); + break; + + case 33: /* 7.34 */ + table = "64-bit Memory Error Information (Type 33)"; + if (hdr->length < 0x1f) + break; + dmi_min_max_uint8_check(fw, table, addr, "Error Type", hdr, 0x4, 0x1, 0xe); + dmi_min_max_uint8_check(fw, table, addr, "Error Granularity", hdr, 0x5, 0x1, 0x4); + dmi_min_max_uint8_check(fw, table, addr, "Error Operation", hdr, 0x6, 0x1, 0x5); + break; + + case 34: /* 7.35 */ + table = "Management Device (Type 34)"; + if (hdr->length < 0x0b) + break; + dmi_str_check(fw, table, addr, "Description", hdr, 0x4); + dmi_min_max_uint8_check(fw, table, addr, "Type", hdr, 0x5, 0x1, 0xd); + dmi_min_max_uint8_check(fw, table, addr, "Address Type", hdr, 0xa, 0x1, 0x5); + break; + + case 35: /* 7.36 */ + table = "Management Device Component (Type 35)"; + if (hdr->length < 0x0b) + break; + dmi_str_check(fw, table, addr, "Description", hdr, 0x4); + break; + + case 36: /* 7.37 */ + table = "Management Device Threshold Data (Type 36)"; + /* + if (hdr->length < 0x10) + break; + */ + break; + + case 37: /* 7.38 */ + table = "Memory Channel (Type 37)"; + if (hdr->length < 0x07) + break; + dmi_min_max_uint8_check(fw, table, addr, "Type", hdr, 0x4, 0x1, 0x4); + break; + + case 38: /* 7.39 */ + table = "IPMI Device Information (Type 38)"; + dmi_min_max_uint8_check(fw, table, addr, "Interface Type", hdr, 0x4, 0x0, 0x4); + break; + + case 39: /* 7.40 */ + table = "System Power Supply (Type 39)"; + if (hdr->length < 0x10) + break; + dmi_str_check(fw, table, addr, "Location", hdr, 0x5); + dmi_str_check(fw, table, addr, "Device Name", hdr, 0x6); + dmi_str_check(fw, table, addr, "Manufacturer", hdr, 0x7); + dmi_str_check(fw, table, addr, "Serial Number", hdr, 0x8); + dmi_str_check(fw, table, addr, "Asset Tag", hdr, 0x9); + dmi_str_check(fw, table, addr, "Model Part Number", hdr, 0xa); + dmi_str_check(fw, table, addr, "Revision Level", hdr, 0xb); + break; + + case 40: /* 7.41 */ + table = "Additional Information (Type 40)"; + break; + + case 41: /* 7.42 */ + table = "Onboard Device (Type 41)"; + if (hdr->length < 0xb) + break; + dmi_str_check(fw, table, addr, "Reference Designation", hdr, 0x4); + dmi_min_max_mask_uint8_check(fw, table, addr, "Device Type", hdr, 0x5, 0x1, 0xa, 0, 0x7f); + break; + + case 42: /* 7.43 */ + table = "Management Controller Host Interface (Type 42)"; + if (hdr->length < 0x05) + break; + if (!((data[0x04] >= 0x02 && data[0x04] <= 0x08) || + (data[0x04] == 0xF0))) + fwts_failed(fw, LOG_LEVEL_MEDIUM, DMI_MGMT_CTRL_HOST_TYPE, + "Out of range value 0x%2.2" PRIx8 " " + "(range allowed 0x02..0x08, 0xf0) " + "while accessing entry '%s' @ " + "0x%8.8" PRIx32 ", field '%s', offset 0x%2.2x", + data[0x4], table, addr, "Reference Designation", 0x4); + break; + + case 126: /* 7.44 */ + table = "Inactive (Type 126)"; + break; + case SMBIOS_END_OF_TABLE: /* 7.45 */ + table = "End of Table (Type 127)"; + break; + default: + snprintf(tmp, sizeof(tmp), "Unknown (Type %" PRId8 ")", hdr->type); + table = tmp; + break; + } + if (fw->minor_tests.failed == failed_count) + fwts_passed(fw, "Entry @ 0x%8.8" PRIx32 " '%s'", addr, table); + else if (!advice_given && hdr->type <= 42) + fwts_advice(fw, + "It may be worth checking against section 7.%" PRId8 " of the " + "System Management BIOS (SMBIOS) Reference Specification " + "(see http://www.dmtf.org/standards/smbios).", hdr->type+1); +} + +static int dmi_version_check(fwts_framework *fw, uint16_t version) +{ + if (version > DMI_VERSION) { + fwts_warning(fw, + "SMBIOS version %" PRIu16 ".%" PRIu16 + " is not supported by the dmicheck " + "test. This test only supports SMBIOS version " + "%" PRIu16 ".%" PRIu16 " and lower.", + VERSION_MAJOR(version), VERSION_MINOR(version), + VERSION_MAJOR(DMI_VERSION), VERSION_MINOR(DMI_VERSION)); + return FWTS_ERROR; + } + return FWTS_OK; +} + +static void dmi_scan_tables(fwts_framework *fw, + fwts_smbios_entry *entry, + uint8_t *table) +{ + uint8_t *entry_data = table; + uint16_t table_length; + uint16_t struct_count; + int i = 0; + + table_length = entry->struct_table_length; + struct_count = entry->number_smbios_structures; + + while ((i < struct_count) && + (entry_data <= (table + table_length - 4))) { + uint32_t addr = entry->struct_table_address + (entry_data - table); + fwts_dmi_header hdr; + uint8_t *next_entry; + + hdr.type = entry_data[0]; + hdr.length = entry_data[1]; + hdr.handle = GET_UINT16(entry_data + 2); + hdr.data = entry_data; + + /* Sanity check */ + if (hdr.length < 4) { + fwts_failed(fw, LOG_LEVEL_HIGH, DMI_INVALID_ENTRY_LENGTH, + "Invald header length of entry #%d, " + "length was 0x%2.2" PRIx8 ".", + i, hdr.length); + fwts_advice(fw, + "DMI entry header lengths must be 4 or more bytes long " + "so this error indicates that the DMI table is unreliable " + "and DMI table checking has been aborted at entry #%d.", i); + break; + } + + /* Real Physical Address */ + next_entry = entry_data + hdr.length; + + /* Look for structure terminator, ends in two zero bytes */ + while (((next_entry - table + 1) < table_length) && + ((next_entry[0] != 0) || (next_entry[1] != 0))) { + next_entry++; + } + + /* Skip over terminating two zero bytes, see section 6.1 of spec */ + next_entry += 2; + + if ((next_entry - table) <= table_length) + dmicheck_entry(fw, addr, &hdr); + + i++; + entry_data = next_entry; + } + + if (table_length != (entry_data - table)) + fwts_failed(fw, LOG_LEVEL_MEDIUM, DMI_BAD_TABLE_LENGTH, + "DMI table length was %" PRId16 " bytes (as specified by " + "the SMBIOS header) but the DMI entries used %td bytes.", + table_length, entry_data - table); + + if (struct_count != i) + fwts_failed(fw, LOG_LEVEL_MEDIUM, DMI_STRUCT_COUNT, + "DMI table was DMI %d entries in size (as specified by " + "the SMBIOS header) but only %d DMI entries were found.", + struct_count, i); +} + +static int dmicheck_test2(fwts_framework *fw) +{ + void *addr; + fwts_smbios_entry entry; + fwts_smbios_type type; + uint16_t version = 0; + uint8_t *table; + + addr = fwts_smbios_find_entry(fw, &entry, &type, &version); + if (addr == NULL) { + fwts_failed(fw, LOG_LEVEL_HIGH, DMI_NO_TABLE_HEADER, + "Cannot find SMBIOS or DMI table entry."); + return FWTS_ERROR; + } + if (type == FWTS_SMBIOS_UNKNOWN) { + fwts_failed(fw, LOG_LEVEL_HIGH, DMI_NO_TABLE, + "Cannot find a valid SMBIOS or DMI table."); + return FWTS_ERROR; + } + + /* Fix broken version numbers found in the wild */ + version = dmi_remap_version(fw, version); + if (dmi_version_check(fw, version) != FWTS_OK) + return FWTS_SKIP; + + table = fwts_mmap((off_t)entry.struct_table_address, + (size_t)entry.struct_table_length); + if (table == FWTS_MAP_FAILED) { + fwts_log_error(fw, "Cannot mmap SMBIOS tables from " + "%8.8" PRIx32 "..%8.8" PRIx32 ".", + entry.struct_table_address, + entry.struct_table_address + entry.struct_table_length); + return FWTS_ERROR; + } + + dmi_scan_tables(fw, &entry, table); + + (void)fwts_munmap(table, (size_t)entry.struct_table_length); + + return FWTS_OK; +} + +static void smbios_dump_entry(fwts_framework *fw, fwts_smbios_entry *entry, fwts_smbios_type type) +{ + if (type == FWTS_SMBIOS) { + fwts_log_info_verbatum(fw, "SMBIOS Entry Point Structure:"); + fwts_log_info_verbatum(fw, " Anchor String : %4.4s", entry->signature); + fwts_log_info_verbatum(fw, " Checksum : 0x%2.2x", entry->checksum); + fwts_log_info_verbatum(fw, " Entry Point Length : 0x%2.2x", entry->length); + fwts_log_info_verbatum(fw, " Major Version : 0x%2.2x", entry->major_version); + fwts_log_info_verbatum(fw, " Minor Version : 0x%2.2x", entry->minor_version); + fwts_log_info_verbatum(fw, " Maximum Struct Size : 0x%2.2x", entry->max_struct_size); + fwts_log_info_verbatum(fw, " Entry Point Revision : 0x%2.2x", entry->revision); + fwts_log_info_verbatum(fw, " Formatted Area : 0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x", + entry->formatted_area[0], entry->formatted_area[1], + entry->formatted_area[2], entry->formatted_area[3], + entry->formatted_area[4]); + } + if (type == FWTS_SMBIOS_DMI_LEGACY) + fwts_log_info_verbatum(fw, "Legacy DMI Entry Point Structure:"); + + /* Common to SMBIOS and SMBIOS_DMI_LEGACY */ + fwts_log_info_verbatum(fw, " Intermediate Anchor : %5.5s", (char *)entry->anchor_string); + fwts_log_info_verbatum(fw, " Intermediate Checksum : 0x%2.2x", entry->intermediate_checksum); + fwts_log_info_verbatum(fw, " Structure Table Length : 0x%4.4x", entry->struct_table_length); + fwts_log_info_verbatum(fw, " Structure Table Address: 0x%8.8x", entry->struct_table_address); + fwts_log_info_verbatum(fw, " # of SMBIOS Structures : 0x%4.4x", entry->number_smbios_structures); + fwts_log_info_verbatum(fw, " SBMIOS BCD Revision : %2.2x", entry->smbios_bcd_revision); + if (entry->smbios_bcd_revision == 0) + fwts_log_info_verbatum(fw, " BCD Revision 00 indicates compliance with specification stated in Major/Minor Version."); +} + +static int smbios_dmi_sane(fwts_framework *fw, fwts_smbios_entry *entry) +{ + uint8_t *table, *ptr; + uint8_t dmi_entry_length; + uint8_t dmi_entry_type = 0; + uint16_t i = 0; + uint16_t table_length = entry->struct_table_length; + int ret = FWTS_OK; + + ptr = table = fwts_mmap((off_t)entry->struct_table_address, + (size_t)table_length); + if (table == FWTS_MAP_FAILED) { + fwts_failed(fw, LOG_LEVEL_MEDIUM, + "SMBIOSTableAddressNotMapped", + "Cannot mmap SMBIOS tables from " + "%8.8" PRIx32 "..%8.8" PRIx32 ".", + entry->struct_table_address, + entry->struct_table_address + table_length); + return FWTS_ERROR; + } + + for (i = 0; i < entry->number_smbios_structures; i++) { + if (ptr > table + table_length) { + fwts_failed(fw, LOG_LEVEL_MEDIUM, + "SMBIOSTableLengthTooSmall", + "The size indicated by the SMBIOS table length is " + "smaller than the DMI data."); + ret = FWTS_ERROR; + break; + } + + dmi_entry_type = ptr[0]; + dmi_entry_length = ptr[1]; + + if (dmi_entry_length < 4) { + fwts_failed(fw, LOG_LEVEL_MEDIUM, + "SMBIOSIllegalTableEntry", + "The size of a DMI entry %" PRIu16 " is illegal, " + "DMI data is either wrong or the SMBIOS Table " + "Pointer is pointing to the wrong memory region.", i); + ret = FWTS_ERROR; + break; + } + ptr += dmi_entry_length; + + /* Scan for end of DMI entry, must be 2 zero bytes */ + while (((ptr - table + 1) < table_length) && + ((ptr[0] != 0) || (ptr[1] != 0))) + ptr++; + /* Skip over the two zero bytes */ + ptr += 2; + } + + /* We found DMI end of table, are number of entries correct? */ + if ((dmi_entry_type == 127) && (i != entry->number_smbios_structures)) { + fwts_failed(fw, LOG_LEVEL_MEDIUM, + "SMBIOSNumberOfStructures", + "The end of DMI table marker structure was found " + "but only %d DMI structures were found. The SMBIOS " + "entry table reported that there were %" PRIu16 + " DMI structures in the DMI table.", + i, entry->number_smbios_structures); + /* And table length can't be correct either */ + ret = FWTS_ERROR; + } + + (void)fwts_munmap(table, (size_t)entry->struct_table_length); + + return ret; +} + +static int dmicheck_test1(fwts_framework *fw) +{ + void *addr = 0; + fwts_smbios_entry entry; + fwts_smbios_type type; + uint16_t version; + uint8_t checksum; + static char warning[] = + "This field is not checked by the kernel, and so will not affect the system, " + "however it should be fixed to conform to the latest version of the " + "System Management BIOS (SMBIOS) Reference Specification. "; + + fwts_log_info(fw, + "This test tries to find and sanity check the SMBIOS " + "data structures."); + + if ((addr = fwts_smbios_find_entry(fw, &entry, &type, &version)) == NULL) { + fwts_failed(fw, LOG_LEVEL_MEDIUM, + "SMBIOSNoEntryPoint", + "Could not find SMBIOS Table Entry Point."); + return FWTS_OK; + } + + fwts_passed(fw, "Found SMBIOS Table Entry Point at %p", addr); + smbios_dump_entry(fw, &entry, type); + fwts_log_nl(fw); + + if (type == FWTS_SMBIOS) { + checksum = fwts_checksum((uint8_t*)&entry, sizeof(fwts_smbios_entry)); + if (checksum != 0) + fwts_failed(fw, LOG_LEVEL_HIGH, + "SMBIOSBadChecksum", + "SMBIOS Table Entry Point Checksum is 0x%2.2x, should be 0x%2.2x", + entry.checksum, (uint8_t)(entry.checksum - checksum)); + else + fwts_passed(fw, "SMBIOS Table Entry Point Checksum is valid."); + + if (entry.length != 0x1f) { + fwts_failed(fw, LOG_LEVEL_LOW, + "SMBIOSBadEntryLength", + "SMBIOS Table Entry Point Length is 0x%2.2x, should be 0x1f", entry.length); + fwts_advice(fw, "%s Note that version 2.1 of the specification incorrectly stated that the " + "Entry Point Length should be 0x1e when it should be 0x1f.", warning); + } else + fwts_passed(fw, "SMBIOS Table Entry Point Length is valid."); + } + + if (memcmp(entry.anchor_string, "_DMI_", 5) != 0) { + fwts_failed(fw, LOG_LEVEL_HIGH, + "SMBIOSBadIntermediateAnchor", + "SMBIOS Table Entry Intermediate Anchor String was '%5.5s' and should be '_DMI_'.", + entry.anchor_string); + fwts_advice(fw, "%s", warning); + } else + fwts_passed(fw, "SMBIOS Table Entry Intermediate Anchor String _DMI_ is valid."); + + /* Intermediate checksum for legacy DMI */ + checksum = fwts_checksum(((uint8_t*)&entry)+16, 15); + if (checksum != 0) + fwts_failed(fw, LOG_LEVEL_HIGH, + "SMBIOSBadChecksum", + "SMBIOS Table Entry Point Intermediate Checksum is 0x%2.2x, should be 0x%2.2x", + entry.intermediate_checksum, + (uint8_t)(entry.intermediate_checksum - checksum)); + else + fwts_passed(fw, "SMBIOS Table Entry Point Intermediate Checksum is valid."); + + if ((entry.struct_table_length > 0) && (entry.struct_table_address == 0)) { + fwts_failed(fw, LOG_LEVEL_HIGH, + "SMBIOSBadTableAddress", + "SMBIOS Table Entry Structure Table Address is NULL and should be defined."); + fwts_advice(fw, + "The address of the SMBIOS Structure Tables must be defined if the " + "length of these tables is defined."); + } else { + /* + * Now does the DMI table look sane? If not, + * the SMBIOS Structure Table could be bad + */ + if (smbios_dmi_sane(fw, &entry) == FWTS_OK) + fwts_passed(fw, "SMBIOS Table Entry Structure Table Address and Length looks valid."); + } + + return FWTS_OK; +} + +static fwts_framework_minor_test dmicheck_tests[] = { + { dmicheck_test1, "Find and Check SMBIOS Table Entry Point." }, + { dmicheck_test2, "Test DMI/SMBIOS tables for errors." }, + { NULL, NULL } +}; + +static fwts_framework_ops dmicheck_ops = { + .description = "Test DMI/SMBIOS tables for errors.", + .minor_tests = dmicheck_tests +}; + +FWTS_REGISTER("dmicheck", &dmicheck_ops, FWTS_TEST_ANYTIME, FWTS_FLAG_BATCH | FWTS_FLAG_ROOT_PRIV); + +#endif