diff --git a/docs/hsi.md b/docs/hsi.md index e097f5dd1..31ea55d63 100644 --- a/docs/hsi.md +++ b/docs/hsi.md @@ -350,6 +350,33 @@ To meet HSI-1 on systems that run this test, the result must be `locked`. *[v1.5 - [Chromium documentation for Intel ME](https://chromium.googlesource.com/chromiumos/third_party/flashrom/+/master/Documentation/mysteries_intel.txt) + + +### [ME BootGuard Platform Key](#org.fwupd.hsi.Mei.BootGuardPlatformKey) + +The BootGuard Platform Key is fused into the CPU PCH during manufacturing by the OEM. + +At bootup, an authenticated code module computes a hash of the Platform Key and and compares it +with the one stored in field-programmable fuses. +If the key matches the ACM will pass control to the firmware, otherwise the boot process will stop. + +In 2022 a number of Platform **secret** Keys were leaked by Lenovo and confirmed by Intel. + +**Impact:** A custom system firmware can be signed using the leaked private key to completely +disable UEFI Secure Boot and allow complete persistent compromise of the affected machine. + +**Possible results:** + +- `valid`: device uses a BootGuard Platform Key that is not known to be compromised +- `not-valid`: device uses a key that is compromised + +To meet HSI-1 on systems that run this test, the result must be `valid`. *[v1.8.7]* + +**References:** + +- [Intel leak checker](https://github.com/phretor/intel-leak-checker/) +- [Tom's Hardware Article](https://www.tomshardware.com/news/intel-confirms-6gb-alder-lake-bios-source-code-leak-new-details-emerge) + ### [CSME Version](#org.fwupd.hsi.Mei.Version) diff --git a/libfwupd/fwupd-security-attr-private.h b/libfwupd/fwupd-security-attr-private.h index f6025beac..5057f2546 100644 --- a/libfwupd/fwupd-security-attr-private.h +++ b/libfwupd/fwupd-security-attr-private.h @@ -166,6 +166,14 @@ G_BEGIN_DECLS * Since: 1.5.0 **/ #define FWUPD_SECURITY_ATTR_ID_MEI_OVERRIDE_STRAP "org.fwupd.hsi.Mei.OverrideStrap" +/** + * FWUPD_SECURITY_ATTR_ID_MEI_KEY_MANIFEST: + * + * Host Security ID attribute for Intel ME Key Manifest + * + * Since: 1.8.7 + **/ +#define FWUPD_SECURITY_ATTR_ID_MEI_KEY_MANIFEST "org.fwupd.hsi.Mei.KeyManifest" /** * FWUPD_SECURITY_ATTR_ID_MEI_VERSION: * diff --git a/libfwupdplugin/fu-mei-device.c b/libfwupdplugin/fu-mei-device.c index efd245ab9..2ec97c9cf 100644 --- a/libfwupdplugin/fu-mei-device.c +++ b/libfwupdplugin/fu-mei-device.c @@ -157,6 +157,8 @@ fu_mei_device_connect(FuMeiDevice *self, guchar req_protocol_version, GError **e if (!fwupd_guid_from_string(priv->uuid, &guid_le, FWUPD_GUID_FLAG_MIXED_ENDIAN, error)) return FALSE; + if (g_getenv("FU_MEI_DEVICE_DEBUG") != NULL) + fu_dump_raw(G_LOG_DOMAIN, "guid_le", (guint8 *)&guid_le, sizeof(guid_le)); memcpy(&data.in_client_uuid, &guid_le, sizeof(guid_le)); if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), IOCTL_MEI_CONNECT_CLIENT, diff --git a/libfwupdplugin/fu-security-attrs.c b/libfwupdplugin/fu-security-attrs.c index 739aef07c..ed77368e6 100644 --- a/libfwupdplugin/fu-security-attrs.c +++ b/libfwupdplugin/fu-security-attrs.c @@ -330,6 +330,7 @@ static struct { {FWUPD_SECURITY_ATTR_ID_IOMMU, FWUPD_SECURITY_ATTR_LEVEL_IMPORTANT}, {FWUPD_SECURITY_ATTR_ID_MEI_MANUFACTURING_MODE, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL}, {FWUPD_SECURITY_ATTR_ID_MEI_OVERRIDE_STRAP, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL}, + {FWUPD_SECURITY_ATTR_ID_MEI_KEY_MANIFEST, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL}, {FWUPD_SECURITY_ATTR_ID_MEI_VERSION, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL}, {FWUPD_SECURITY_ATTR_ID_PLATFORM_DEBUG_ENABLED, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL}, {FWUPD_SECURITY_ATTR_ID_PLATFORM_DEBUG_LOCKED, FWUPD_SECURITY_ATTR_LEVEL_IMPORTANT}, diff --git a/plugins/intel-me/README.md b/plugins/intel-me/README.md new file mode 100644 index 000000000..5657a98b0 --- /dev/null +++ b/plugins/intel-me/README.md @@ -0,0 +1,19 @@ +# Intel ME + +## Introduction + +This plugin is used to talk to the Intel ME device, typically CSME. + +It allows us to get the Platform Key as used for BootGuard. + +## GUID Generation + +These devices use the existing GUIDs provided by the ME host interfaces. + +## Vendor ID Security + +The devices are not upgradable and thus require no vendor ID set. + +## External Interface Access + +This plugin requires `ioctl(IOCTL_MEI_CONNECT_CLIENT)` to `/dev/mei0`. diff --git a/plugins/intel-me/fu-intel-me-common.c b/plugins/intel-me/fu-intel-me-common.c new file mode 100644 index 000000000..2846760a7 --- /dev/null +++ b/plugins/intel-me/fu-intel-me-common.c @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2022 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include "fu-intel-me-common.h" + +gboolean +fu_intel_me_mkhi_result_to_error(FuMkhiResult result, GError **error) +{ + if (result == MKHI_STATUS_SUCCESS) + return TRUE; + + switch (result) { + case MKHI_STATUS_NOT_SUPPORTED: + case MKHI_STATUS_NOT_AVAILABLE: + case MKHI_STATUS_NOT_SET: + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "not supported [0x%x]", + result); + break; + default: + g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "generic failure [0x%x]", result); + break; + } + return FALSE; +} + +gboolean +fu_intel_me_mkhi_verify_header(const FuMkhiHeader hdr_req, + const FuMkhiHeader hdr_res, + GError **error) +{ + if (hdr_req.group_id != hdr_res.group_id) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "invalid response group ID, requested 0x%x and got 0x%x", + hdr_req.group_id, + hdr_res.group_id); + return FALSE; + } + if (hdr_req.command != hdr_res.command) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "invalid response command, requested 0x%x and got 0x%x", + (guint)hdr_req.command, + (guint)hdr_res.command); + return FALSE; + } + if (!hdr_res.is_resp) { + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "invalid response group ID, not a response!"); + return FALSE; + } + return fu_intel_me_mkhi_result_to_error(hdr_res.result, error); +} + +GString * +fu_intel_me_convert_checksum(GByteArray *buf, GError **error) +{ + gboolean seen_non00_data = FALSE; + gboolean seen_nonff_data = FALSE; + g_autoptr(GString) checksum = g_string_new(NULL); + + /* create checksum, but only if non-zero and set */ + for (gsize i = 0; i < buf->len; i++) { + if (!seen_non00_data && buf->data[i] != 0x00) + seen_non00_data = TRUE; + if (!seen_nonff_data && buf->data[i] != 0xFF) + seen_nonff_data = TRUE; + g_string_append_printf(checksum, "%02x", buf->data[i]); + } + if (!seen_non00_data) { + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_NOT_INITIALIZED, + "buffer was all 0x00"); + return NULL; + } + if (!seen_nonff_data) { + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_NOT_INITIALIZED, + "buffer was all 0xFF"); + return NULL; + } + + /* success */ + return g_steal_pointer(&checksum); +} diff --git a/plugins/intel-me/fu-intel-me-common.h b/plugins/intel-me/fu-intel-me-common.h new file mode 100644 index 000000000..d886cfa88 --- /dev/null +++ b/plugins/intel-me/fu-intel-me-common.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2022 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +typedef struct __attribute__((packed)) { + guint8 group_id; + guint8 command : 7; + guint8 is_resp : 1; + guint8 rsvd; + guint8 result; +} FuMkhiHeader; + +typedef enum { + MKHI_GROUP_ID_CBM, + MKHI_GROUP_ID_PM, /* no longer used */ + MKHI_GROUP_ID_PWD, + MKHI_GROUP_ID_FWCAPS, + MKHI_GROUP_ID_APP, /* no longer used */ + MKHI_GROUP_ID_FWUPDATE, /* for manufacturing downgrade */ + MKHI_GROUP_ID_FIRMWARE_UPDATE, + MKHI_GROUP_ID_BIST, + MKHI_GROUP_ID_MDES, + MKHI_GROUP_ID_ME_DBG, + MKHI_GROUP_ID_MCA, /* sometimes called "FPF" */ + MKHI_GROUP_ID_GEN = 0xFF +} FuMkhiGroupId; + +#define MCA_READ_FILE_EX 0x02 +#define MCA_READ_FILE_EX_CMD 0x0A + +typedef enum { + MKHI_STATUS_SUCCESS, + MKHI_STATUS_INVALID_STATE, + MKHI_STATUS_MESSAGE_SKIPPED, + MKHI_STATUS_SIZE_ERROR = 0x05, + MKHI_STATUS_NOT_SET = 0x0F, /* guessed */ + MKHI_STATUS_NOT_AVAILABLE = 0x18, /* guessed */ + MKHI_STATUS_INVALID_ACCESS = 0x84, + MKHI_STATUS_INVALID_PARAMS = 0x85, + MKHI_STATUS_NOT_READY = 0x88, + MKHI_STATUS_NOT_SUPPORTED = 0x89, + MKHI_STATUS_INVALID_ADDRESS = 0x8C, + MKHI_STATUS_INVALID_COMMAND = 0x8D, + MKHI_STATUS_FAILURE = 0x9E, + MKHI_STATUS_INVALID_RESOURCE = 0xE4, + MKHI_STATUS_RESOURCE_IN_USE = 0xE5, + MKHI_STATUS_NO_RESOURCE = 0xE6, + MKHI_STATUS_GENERAL_ERROR = 0xFF +} FuMkhiResult; + +GString * +fu_intel_me_convert_checksum(GByteArray *buf, GError **error); +gboolean +fu_intel_me_mkhi_result_to_error(FuMkhiResult result, GError **error); +gboolean +fu_intel_me_mkhi_verify_header(const FuMkhiHeader hdr_req, + const FuMkhiHeader hdr_res, + GError **error); diff --git a/plugins/intel-me/fu-intel-me-heci-device.c b/plugins/intel-me/fu-intel-me-heci-device.c new file mode 100644 index 000000000..89f17b109 --- /dev/null +++ b/plugins/intel-me/fu-intel-me-heci-device.c @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2022 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include + +#include "fu-intel-me-common.h" +#include "fu-intel-me-heci-device.h" + +G_DEFINE_TYPE(FuIntelMeHeciDevice, fu_intel_me_heci_device, FU_TYPE_MEI_DEVICE) + +#define FU_INTEL_ME_HECI_DEVICE_TIMEOUT 200 /* ms */ + +static gboolean +fu_intel_me_heci_device_open(FuDevice *device, GError **error) +{ + /* open then create context */ + if (!FU_DEVICE_CLASS(fu_intel_me_heci_device_parent_class)->open(device, error)) + return FALSE; + return fu_mei_device_connect(FU_MEI_DEVICE(device), 0, error); +} + +static GByteArray * +fu_intel_me_heci_device_read_file_response(GByteArray *buf_res, guint32 datasz_req, GError **error) +{ + guint32 datasz_res = 0; + g_autoptr(GByteArray) buf = g_byte_array_new(); + + /* verify payload size */ + if (!fu_memread_uint32_safe(buf_res->data, + buf_res->len, + sizeof(FuMkhiHeader), + &datasz_res, + G_LITTLE_ENDIAN, + error)) + return NULL; + if (datasz_res > datasz_req || datasz_res == 0x0) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "invalid response data size, requested 0x%x and got 0x%x", + datasz_req, + datasz_res); + return NULL; + } + + /* copy out payload */ + for (gsize i = 0; i < datasz_res; i++) { + guint8 tmp = 0; + if (!fu_memread_uint8_safe(buf_res->data, + buf_res->len, + sizeof(FuMkhiHeader) + sizeof(guint32) + i, + &tmp, + error)) + return NULL; + fu_byte_array_append_uint8(buf, tmp); + } + + /* success */ + return g_steal_pointer(&buf); +} + +GByteArray * +fu_intel_me_heci_device_read_file(FuIntelMeHeciDevice *self, const gchar *filename, GError **error) +{ + FuMkhiHeader hdr_res = {0}; + gsize filenamesz = strlen(filename); + guint datasz_req = 0x80; + g_autoptr(GByteArray) buf_fn = g_byte_array_new(); + g_autoptr(GByteArray) buf_req = g_byte_array_new(); + g_autoptr(GByteArray) buf_res = g_byte_array_new(); + const FuMkhiHeader hdr_req = {.group_id = MKHI_GROUP_ID_MCA, .command = MCA_READ_FILE_EX}; + + /* filename must be a specific size */ + fu_byte_array_set_size(buf_fn, 0x40, 0x0); + if (!fu_memcpy_safe(buf_fn->data, + buf_fn->len - 1, + 0x0, + (const guint8 *)filename, + filenamesz, + 0x0, + filenamesz, + error)) + return NULL; + + /* request */ + g_byte_array_append(buf_req, (const guint8 *)&hdr_req, sizeof(hdr_req)); + g_byte_array_append(buf_req, buf_fn->data, buf_fn->len); /* Filename */ + fu_byte_array_append_uint32(buf_req, 0x0, G_LITTLE_ENDIAN); /* Offset */ + fu_byte_array_append_uint32(buf_req, datasz_req, G_LITTLE_ENDIAN); /* DataSize */ + fu_byte_array_append_uint8(buf_req, (1 << 3)); /* Flags?? */ + if (!fu_mei_device_write(FU_MEI_DEVICE(self), + buf_req->data, + buf_req->len, + FU_INTEL_ME_HECI_DEVICE_TIMEOUT, + error)) + return NULL; + + /* response */ + fu_byte_array_set_size(buf_res, sizeof(hdr_res) + sizeof(guint32) + datasz_req, 0x0); + if (!fu_mei_device_read(FU_MEI_DEVICE(self), + buf_res->data, + buf_res->len, + NULL, + FU_INTEL_ME_HECI_DEVICE_TIMEOUT, + error)) + return NULL; + + /* verify header */ + if (!fu_memcpy_safe((guint8 *)&hdr_res, + sizeof(hdr_res), + 0x0, /* dst */ + buf_res->data, + buf_res->len, + 0x0, /* src */ + sizeof(hdr_req), + error)) + return NULL; + if (!fu_intel_me_mkhi_verify_header(hdr_req, hdr_res, error)) + return NULL; + return fu_intel_me_heci_device_read_file_response(buf_res, datasz_req, error); +} + +GByteArray * +fu_intel_me_heci_device_read_file_ex(FuIntelMeHeciDevice *self, + guint32 file_id, + guint32 section, + guint32 datasz_req, + GError **error) +{ + FuMkhiHeader hdr_res = {0}; + g_autoptr(GByteArray) buf_req = g_byte_array_new(); + g_autoptr(GByteArray) buf_res = g_byte_array_new(); + const FuMkhiHeader hdr_req = {.group_id = MKHI_GROUP_ID_MCA, + .command = MCA_READ_FILE_EX_CMD}; + + /* request */ + g_byte_array_append(buf_req, (const guint8 *)&hdr_req, sizeof(hdr_req)); + fu_byte_array_append_uint32(buf_req, file_id, G_LITTLE_ENDIAN); /* FileId */ + fu_byte_array_append_uint32(buf_req, 0x0, G_LITTLE_ENDIAN); /* Offset */ + fu_byte_array_append_uint32(buf_req, datasz_req, G_LITTLE_ENDIAN); /* DataSize */ + fu_byte_array_append_uint8(buf_req, section); /* Flags */ + if (!fu_mei_device_write(FU_MEI_DEVICE(self), + buf_req->data, + buf_req->len, + FU_INTEL_ME_HECI_DEVICE_TIMEOUT, + error)) + return NULL; + + /* response */ + fu_byte_array_set_size(buf_res, sizeof(hdr_res) + sizeof(guint32) + datasz_req, 0x0); + if (!fu_mei_device_read(FU_MEI_DEVICE(self), + buf_res->data, + buf_res->len, + NULL, + FU_INTEL_ME_HECI_DEVICE_TIMEOUT, + error)) + return NULL; + + /* verify header */ + if (!fu_memcpy_safe((guint8 *)&hdr_res, + sizeof(hdr_res), + 0x0, /* dst */ + buf_res->data, + buf_res->len, + 0x0, /* src */ + sizeof(hdr_req), + error)) + return NULL; + if (!fu_intel_me_mkhi_verify_header(hdr_req, hdr_res, error)) + return NULL; + return fu_intel_me_heci_device_read_file_response(buf_res, datasz_req, error); +} + +static void +fu_intel_me_heci_device_init(FuIntelMeHeciDevice *self) +{ + fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); + fu_device_add_icon(FU_DEVICE(self), "computer"); +} + +static void +fu_intel_me_heci_device_class_init(FuIntelMeHeciDeviceClass *klass) +{ + FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); + klass_device->open = fu_intel_me_heci_device_open; +} diff --git a/plugins/intel-me/fu-intel-me-heci-device.h b/plugins/intel-me/fu-intel-me-heci-device.h new file mode 100644 index 000000000..bf797335a --- /dev/null +++ b/plugins/intel-me/fu-intel-me-heci-device.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2022 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +#define FU_TYPE_INTEL_ME_HECI_DEVICE (fu_intel_me_heci_device_get_type()) +G_DECLARE_DERIVABLE_TYPE(FuIntelMeHeciDevice, + fu_intel_me_heci_device, + FU, + INTEL_ME_HECI_DEVICE, + FuMeiDevice) + +struct _FuIntelMeHeciDeviceClass { + FuMeiDeviceClass parent_class; +}; + +GByteArray * +fu_intel_me_heci_device_read_file(FuIntelMeHeciDevice *self, const gchar *filename, GError **error); +GByteArray * +fu_intel_me_heci_device_read_file_ex(FuIntelMeHeciDevice *self, + guint32 file_id, + guint32 section, + guint32 datasz_req, + GError **error); diff --git a/plugins/intel-me/fu-intel-me-mca-device.c b/plugins/intel-me/fu-intel-me-mca-device.c new file mode 100644 index 000000000..e3b74a4ae --- /dev/null +++ b/plugins/intel-me/fu-intel-me-mca-device.c @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2022 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include "fu-intel-me-common.h" +#include "fu-intel-me-mca-device.h" + +struct _FuIntelMeMcaDevice { + FuIntelMeHeciDevice parent_instance; + gboolean using_leaked_km; +}; + +G_DEFINE_TYPE(FuIntelMeMcaDevice, fu_intel_me_mca_device, FU_TYPE_INTEL_ME_HECI_DEVICE) + +#define MCA_SECTION_ME 0x00 /* OEM Public Key Hash ME FW */ +#define MCA_SECTION_UEP 0x04 /* OEM Public Key Hash UEP */ +#define MCA_SECTION_FPF 0x08 /* OEM Public Key Hash FPF */ + +static const gchar * +fu_intel_me_mca_device_section_to_string(guint8 section) +{ + if (section == MCA_SECTION_ME) + return "ME"; + if (section == MCA_SECTION_UEP) + return "UEP"; + if (section == MCA_SECTION_FPF) + return "FPF"; + return NULL; +} + +static gboolean +fu_intel_me_mca_device_add_checksum_for_id(FuIntelMeMcaDevice *self, + guint32 file_id, + guint32 section, + GError **error) +{ + g_autoptr(GByteArray) buf = NULL; + g_autoptr(GString) checksum = NULL; + + /* + * Call READ_FILE_EX with a larger-than-required data size -- which hopefully works when + * SHA512 results start being returned too. + * + * CometLake: 0x20 (SHA256) + * TigerLake: 0x30 (SHA384) + */ + buf = fu_intel_me_heci_device_read_file_ex(FU_INTEL_ME_HECI_DEVICE(self), + file_id, + section, + 0x40, + error); + if (buf == NULL) + return FALSE; + + /* convert into checksum, but only if non-zero and set */ + checksum = fu_intel_me_convert_checksum(buf, error); + if (checksum == NULL) + return FALSE; + fu_device_add_checksum(FU_DEVICE(self), checksum->str); + + /* success */ + return TRUE; +} + +static gboolean +fu_intel_me_mca_device_setup(FuDevice *device, GError **error) +{ + FuIntelMeMcaDevice *self = FU_INTEL_ME_MCA_DEVICE(device); + const guint32 sections[] = {MCA_SECTION_FPF, MCA_SECTION_UEP, MCA_SECTION_ME, G_MAXUINT32}; + const guint32 file_ids[] = {0x40002300, /* CometLake: OEM Public Key Hash */ + 0x40005B00, /* TigerLake: 1st OEM Public Key Hash */ + 0x40005C00 /* TigerLake: 2nd OEM Public Key Hash */, + G_MAXUINT32}; + const gchar *leaked_kms[] = {"05a92e16da51d10882bfa7e3ba449184ce48e94fa9903e07983d2112ab" + "54ecf20fbb07512cea2c13b167c0e252c6a704", + "2e357bca116cf3da637bb5803be3550873eddb5a4431a49df1770aca83" + "5d94853b458239d207653dce277910d9e5aa0b", + "b52a825cf0be60027f12a226226b055ed68efaa9273695d45d859c0ed3" + "3d063143974f4b4c59fabfc5afeadab0b00f09", + NULL}; + + /* look for all the possible OEM Public Key hashes using the CML+ method */ + for (guint i = 0; file_ids[i] != G_MAXUINT32; i++) { + for (guint j = 0; sections[j] != G_MAXUINT32; j++) { + g_autoptr(GError) error_local = NULL; + if (!fu_intel_me_mca_device_add_checksum_for_id(self, + file_ids[i], + sections[j], + &error_local)) { + if (g_error_matches(error_local, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED) || + g_error_matches(error_local, + G_IO_ERROR, + G_IO_ERROR_NOT_INITIALIZED)) { + continue; + } + g_warning("failed to get public key using file-id 0x%x, " + "section %s [0x%x]: %s", + file_ids[i], + fu_intel_me_mca_device_section_to_string(sections[j]), + sections[j], + error_local->message); + } + } + } + + /* no point even adding */ + if (fu_device_get_checksums(device)->len == 0) { + g_set_error_literal(error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "no OEM public keys found"); + return FALSE; + } + + /* check for any of the leaked keys */ + for (guint i = 0; leaked_kms[i] != NULL; i++) { + if (fu_device_has_checksum(self, leaked_kms[i])) { + self->using_leaked_km = TRUE; + break; + } + } + + /* success */ + return TRUE; +} + +static void +fu_intel_me_mca_device_add_security_attrs(FuDevice *device, FuSecurityAttrs *attrs) +{ + FuIntelMeMcaDevice *self = FU_INTEL_ME_MCA_DEVICE(device); + g_autoptr(FwupdSecurityAttr) attr = NULL; + + /* create attr */ + attr = + fu_device_security_attr_new(FU_DEVICE(self), FWUPD_SECURITY_ATTR_ID_MEI_KEY_MANIFEST); + fu_security_attrs_append(attrs, attr); + + /* verify keys */ + if (fu_device_get_checksums(device)->len == 0) { + fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_MISSING_DATA); + return; + } + if (self->using_leaked_km) { + fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID); + return; + } + + /* success */ + fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); + fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_VALID); +} + +static void +fu_intel_me_mca_device_init(FuIntelMeMcaDevice *self) +{ + fu_device_set_logical_id(FU_DEVICE(self), "MCA"); + fu_device_set_name(FU_DEVICE(self), "BootGuard Configuration"); + fu_device_add_parent_guid(FU_DEVICE(self), "main-system-firmware"); +} + +static void +fu_intel_me_mca_device_class_init(FuIntelMeMcaDeviceClass *klass) +{ + FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); + klass_device->setup = fu_intel_me_mca_device_setup; + klass_device->add_security_attrs = fu_intel_me_mca_device_add_security_attrs; +} diff --git a/plugins/intel-me/fu-intel-me-mca-device.h b/plugins/intel-me/fu-intel-me-mca-device.h new file mode 100644 index 000000000..36efebae6 --- /dev/null +++ b/plugins/intel-me/fu-intel-me-mca-device.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2022 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +#include "fu-intel-me-heci-device.h" + +#define FU_TYPE_INTEL_ME_MCA_DEVICE (fu_intel_me_mca_device_get_type()) +G_DECLARE_FINAL_TYPE(FuIntelMeMcaDevice, + fu_intel_me_mca_device, + FU, + INTEL_ME_MCA_DEVICE, + FuIntelMeHeciDevice) diff --git a/plugins/intel-me/fu-intel-me-mkhi-device.c b/plugins/intel-me/fu-intel-me-mkhi-device.c new file mode 100644 index 000000000..66f364531 --- /dev/null +++ b/plugins/intel-me/fu-intel-me-mkhi-device.c @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2022 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include "fu-intel-me-common.h" +#include "fu-intel-me-mkhi-device.h" + +struct _FuIntelMeMkhiDevice { + FuIntelMeHeciDevice parent_instance; +}; + +G_DEFINE_TYPE(FuIntelMeMkhiDevice, fu_intel_me_mkhi_device, FU_TYPE_INTEL_ME_HECI_DEVICE) + +static gboolean +fu_intel_me_mkhi_device_add_checksum_for_filename(FuIntelMeMkhiDevice *self, + const gchar *filename, + GError **error) +{ + g_autoptr(GByteArray) buf = NULL; + g_autoptr(GString) checksum = NULL; + + /* read from the MFS */ + buf = fu_intel_me_heci_device_read_file(FU_INTEL_ME_HECI_DEVICE(self), filename, error); + if (buf == NULL) + return FALSE; + + /* convert into checksum, but only if non-zero and set */ + checksum = fu_intel_me_convert_checksum(buf, error); + if (checksum == NULL) + return FALSE; + fu_device_add_checksum(FU_DEVICE(self), checksum->str); + + /* success */ + return TRUE; +} + +static gboolean +fu_intel_me_mkhi_device_setup(FuDevice *device, GError **error) +{ + FuIntelMeMkhiDevice *self = FU_INTEL_ME_MKHI_DEVICE(device); + const gchar *fns[] = {"/fpf/OemCred", NULL}; + + /* this is the legacy way to get the hash, which is removed in newer ME versions due to + * possible path traversal attacks */ + for (guint i = 0; fns[i] != NULL; i++) { + g_autoptr(GError) error_local = NULL; + g_autoptr(GByteArray) buf = NULL; + if (!fu_intel_me_mkhi_device_add_checksum_for_filename(self, + fns[i], + &error_local)) { + if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) { + continue; + } + g_warning("failed to get public key using %s: %s", + fns[i], + error_local->message); + } + } + + /* no point even adding */ + if (fu_device_get_checksums(device)->len == 0) { + g_set_error_literal(error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "no OEM public keys found"); + return FALSE; + } + + /* success */ + return TRUE; +} + +static void +fu_intel_me_mkhi_device_init(FuIntelMeMkhiDevice *self) +{ + fu_device_set_logical_id(FU_DEVICE(self), "MKHI"); + fu_device_set_name(FU_DEVICE(self), "BootGuard Configuration"); + fu_device_add_parent_guid(FU_DEVICE(self), "main-system-firmware"); +} + +static void +fu_intel_me_mkhi_device_class_init(FuIntelMeMkhiDeviceClass *klass) +{ + FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); + klass_device->setup = fu_intel_me_mkhi_device_setup; +} diff --git a/plugins/intel-me/fu-intel-me-mkhi-device.h b/plugins/intel-me/fu-intel-me-mkhi-device.h new file mode 100644 index 000000000..394030fed --- /dev/null +++ b/plugins/intel-me/fu-intel-me-mkhi-device.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2022 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +#include "fu-intel-me-heci-device.h" + +#define FU_TYPE_INTEL_ME_MKHI_DEVICE (fu_intel_me_mkhi_device_get_type()) +G_DECLARE_FINAL_TYPE(FuIntelMeMkhiDevice, + fu_intel_me_mkhi_device, + FU, + INTEL_ME_MKHI_DEVICE, + FuIntelMeHeciDevice) diff --git a/plugins/intel-me/fu-intel-me-plugin.c b/plugins/intel-me/fu-intel-me-plugin.c new file mode 100644 index 000000000..68f3139ac --- /dev/null +++ b/plugins/intel-me/fu-intel-me-plugin.c @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2022 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include "fu-intel-me-mca-device.h" +#include "fu-intel-me-mkhi-device.h" +#include "fu-intel-me-plugin.h" + +struct _FuIntelMePlugin { + FuPlugin parent_instance; +}; + +G_DEFINE_TYPE(FuIntelMePlugin, fu_intel_me_plugin, FU_TYPE_PLUGIN) + +static void +fu_intel_me_plugin_init(FuIntelMePlugin *self) +{ +} + +static void +fu_intel_me_plugin_constructed(GObject *obj) +{ + FuPlugin *plugin = FU_PLUGIN(obj); + fu_plugin_add_udev_subsystem(plugin, "mei"); + fu_plugin_add_device_gtype(plugin, FU_TYPE_INTEL_ME_MCA_DEVICE); + fu_plugin_add_device_gtype(plugin, FU_TYPE_INTEL_ME_MKHI_DEVICE); +} + +static void +fu_intel_me_plugin_class_init(FuIntelMePluginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + object_class->constructed = fu_intel_me_plugin_constructed; +} diff --git a/plugins/intel-me/fu-intel-me-plugin.h b/plugins/intel-me/fu-intel-me-plugin.h new file mode 100644 index 000000000..c5dc9c3b3 --- /dev/null +++ b/plugins/intel-me/fu-intel-me-plugin.h @@ -0,0 +1,11 @@ +/* + * Copyright (C) 2022 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +G_DECLARE_FINAL_TYPE(FuIntelMePlugin, fu_intel_me_plugin, FU, INTEL_ME_PLUGIN, FuPlugin) diff --git a/plugins/intel-me/intel-me.quirk b/plugins/intel-me/intel-me.quirk new file mode 100644 index 000000000..f65dc13ef --- /dev/null +++ b/plugins/intel-me/intel-me.quirk @@ -0,0 +1,9 @@ +# MKHI (legacy) +[8e6a6715-9abc-4043-88ef-9e39c6f63e0f] +Plugin = intel_me +GType = FuIntelMeMkhiDevice + +# MCA? +[dd17041c-09ea-4b17-a271-5b989867ec65] +Plugin = intel_me +GType = FuIntelMeMcaDevice diff --git a/plugins/intel-me/meson.build b/plugins/intel-me/meson.build new file mode 100644 index 000000000..2efa22c6f --- /dev/null +++ b/plugins/intel-me/meson.build @@ -0,0 +1,18 @@ +if get_option('plugin_amt').disable_auto_if(host_machine.system() != 'linux').allowed() +cargs = ['-DG_LOG_DOMAIN="FuPluginIntelMe"'] + +plugin_quirks += files('intel-me.quirk') +plugin_builtins += static_library('fu_plugin_intel_me', + sources: [ + 'fu-intel-me-common.c', + 'fu-intel-me-plugin.c', + 'fu-intel-me-heci-device.c', + 'fu-intel-me-mca-device.c', + 'fu-intel-me-mkhi-device.c', + ], + include_directories: plugin_incdirs, + link_with: plugin_libs, + c_args: cargs, + dependencies: plugin_deps, +) +endif diff --git a/plugins/meson.build b/plugins/meson.build index 703b20c43..39a759fe2 100644 --- a/plugins/meson.build +++ b/plugins/meson.build @@ -52,6 +52,7 @@ subdir('genesys') subdir('goodix-moc') subdir('gpio') subdir('hailuck') +subdir('intel-me') subdir('intel-spi') subdir('intel-usb4') subdir('iommu') diff --git a/src/fu-security-attr-common.c b/src/fu-security-attr-common.c index 361a5e321..11626c448 100644 --- a/src/fu-security-attr-common.c +++ b/src/fu-security-attr-common.c @@ -146,6 +146,11 @@ fu_security_attr_get_name(FwupdSecurityAttr *attr) * end users on consumer boards */ return g_strdup(_("MEI override")); } + if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_MEI_KEY_MANIFEST) == 0) { + /* TRANSLATORS: Title: MEI = Intel Management Engine, and key refer + * to the private/public key used to secure loading of firmware */ + return g_strdup(_("MEI key manifest")); + } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_MEI_VERSION) == 0) { /* TRANSLATORS: Title: MEI = Intel Management Engine */ const gchar *kind = fwupd_security_attr_get_metadata(attr, "kind"); @@ -322,6 +327,11 @@ fu_security_attr_get_title(FwupdSecurityAttr *attr) * boards */ return _("Intel Management Engine Override"); } + if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_MEI_KEY_MANIFEST) == 0) { + /* TRANSLATORS: Title: MEI = Intel Management Engine, and key refers + * to the private/public key used to secure loading of firmware */ + return g_strdup(_("MEI Key Manifest")); + } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_MEI_VERSION) == 0) { /* TRANSLATORS: Title: MEI = Intel Management Engine */ return _("Intel Management Engine Version"); @@ -496,6 +506,11 @@ fu_security_attr_get_description(FwupdSecurityAttr *attr) return _("Intel Management Engine Override disables checks for device software " "tampering."); } + if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_MEI_KEY_MANIFEST) == 0) { + /* TRANSLATORS: longer description */ + return _("The Intel Management Engine Key Manifest must be valid so " + "that the device firmware can be trusted by the CPU."); + } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_MEI_VERSION) == 0) { /* TRANSLATORS: longer description */ return _("The Intel Management Engine controls device components and needs "