From 1e7d74160167d7e0c280d30fdc57205cca8b3005 Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Wed, 19 Oct 2022 13:57:32 +0100 Subject: [PATCH] Check system integrity when installing UEFI updates Recently we had an update that changed the system-defined Platform Key, and we've certainly had updates in the past that changed the Boot#### variables. Store some core ACPI and UEFI system integrity state from before and after the update which can be used to mark (waivable) test failures on the LVFS. --- libfwupd/fwupd-enums.c | 4 + libfwupd/fwupd-enums.h | 8 ++ libfwupd/fwupd-self-test.c | 2 +- plugins/flashrom/fu-flashrom-plugin.c | 1 + plugins/uefi-capsule/fu-uefi-capsule-plugin.c | 1 + src/fu-engine-helper.c | 104 ++++++++++++++++++ src/fu-engine-helper.h | 6 + src/fu-engine.c | 28 ++++- src/fu-util-common.c | 4 + 9 files changed, 154 insertions(+), 4 deletions(-) diff --git a/libfwupd/fwupd-enums.c b/libfwupd/fwupd-enums.c index 4d457b5ed..f81ff0b8d 100644 --- a/libfwupd/fwupd-enums.c +++ b/libfwupd/fwupd-enums.c @@ -448,6 +448,8 @@ fwupd_plugin_flag_to_string(FwupdPluginFlags plugin_flag) return "secure-config"; if (plugin_flag == FWUPD_PLUGIN_FLAG_MODULAR) return "modular"; + if (plugin_flag == FWUPD_PLUGIN_FLAG_MEASURE_SYSTEM_INTEGRITY) + return "measure-system-integrity"; return NULL; } @@ -496,6 +498,8 @@ fwupd_plugin_flag_from_string(const gchar *plugin_flag) return FWUPD_PLUGIN_FLAG_SECURE_CONFIG; if (g_strcmp0(plugin_flag, "modular") == 0) return FWUPD_PLUGIN_FLAG_MODULAR; + if (g_strcmp0(plugin_flag, "measure-system-integrity") == 0) + return FWUPD_PLUGIN_FLAG_MEASURE_SYSTEM_INTEGRITY; return FWUPD_DEVICE_FLAG_UNKNOWN; } diff --git a/libfwupd/fwupd-enums.h b/libfwupd/fwupd-enums.h index 6593455c9..b9aa39829 100644 --- a/libfwupd/fwupd-enums.h +++ b/libfwupd/fwupd-enums.h @@ -879,6 +879,14 @@ typedef enum { * Since: 1.8.6 */ #define FWUPD_PLUGIN_FLAG_MODULAR (1u << 14) +/** + * FWUPD_PLUGIN_FLAG_MEASURE_SYSTEM_INTEGRITY: + * + * The plugin will be checked that it preserves system state such as `KEK`, `PK`, `BOOT####` etc. + * + * Since: 1.8.7 + */ +#define FWUPD_PLUGIN_FLAG_MEASURE_SYSTEM_INTEGRITY (1u << 15) /** * FWUPD_PLUGIN_FLAG_UNKNOWN: * diff --git a/libfwupd/fwupd-self-test.c b/libfwupd/fwupd-self-test.c index a7d5fc3f2..236ddf6aa 100644 --- a/libfwupd/fwupd-self-test.c +++ b/libfwupd/fwupd-self-test.c @@ -170,7 +170,7 @@ fwupd_enums_func(void) g_assert_cmpstr(tmp, !=, NULL); g_assert_cmpint(fwupd_device_problem_from_string(tmp), ==, i); } - for (guint64 i = 1; i <= FWUPD_PLUGIN_FLAG_MODULAR; i *= 2) { + for (guint64 i = 1; i <= FWUPD_PLUGIN_FLAG_MEASURE_SYSTEM_INTEGRITY; i *= 2) { const gchar *tmp = fwupd_plugin_flag_to_string(i); if (tmp == NULL) g_warning("missing plugin flag 0x%x", (guint)i); diff --git a/plugins/flashrom/fu-flashrom-plugin.c b/plugins/flashrom/fu-flashrom-plugin.c index 190e2055d..353514b96 100644 --- a/plugins/flashrom/fu-flashrom-plugin.c +++ b/plugins/flashrom/fu-flashrom-plugin.c @@ -339,6 +339,7 @@ fu_flashrom_plugin_constructed(GObject *obj) fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_METADATA_SOURCE, "linux_lockdown"); fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_CONFLICTS, "coreboot"); /* obsoleted */ fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_REQUIRE_HWID); + fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_MEASURE_SYSTEM_INTEGRITY); } static void diff --git a/plugins/uefi-capsule/fu-uefi-capsule-plugin.c b/plugins/uefi-capsule/fu-uefi-capsule-plugin.c index 177392f96..bf116f142 100644 --- a/plugins/uefi-capsule/fu-uefi-capsule-plugin.c +++ b/plugins/uefi-capsule/fu-uefi-capsule-plugin.c @@ -997,6 +997,7 @@ static void fu_uefi_capsule_plugin_init(FuUefiCapsulePlugin *self) { self->bgrt = fu_uefi_bgrt_new(); + fu_plugin_add_flag(FU_PLUGIN(self), FWUPD_PLUGIN_FLAG_MEASURE_SYSTEM_INTEGRITY); } static void diff --git a/src/fu-engine-helper.c b/src/fu-engine-helper.c index a9b3d05fc..e48b73395 100644 --- a/src/fu-engine-helper.c +++ b/src/fu-engine-helper.c @@ -1,5 +1,6 @@ /* * Copyright (C) 2020 Mario Limonciello + * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ @@ -187,3 +188,106 @@ fu_engine_update_devices_file(FuEngine *self, GError **error) target = g_build_filename(directory, "devices.json", NULL); return g_file_set_contents(target, data, (gssize)len, error); } + +static void +fu_engine_integrity_add_measurement(GHashTable *self, const gchar *id, GBytes *blob) +{ + g_autofree gchar *csum = g_compute_checksum_for_bytes(G_CHECKSUM_SHA256, blob); + g_hash_table_insert(self, g_strdup(id), g_steal_pointer(&csum)); +} + +static void +fu_engine_integrity_measure_acpi(GHashTable *self) +{ + g_autofree gchar *path = fu_path_from_kind(FU_PATH_KIND_ACPI_TABLES); + const gchar *tables[] = {"SLIC", "MSDM", "TPM2", NULL}; + + for (guint i = 0; tables[i] != NULL; i++) { + g_autofree gchar *fn = g_build_filename(path, tables[i], NULL); + g_autoptr(GBytes) blob = NULL; + + blob = fu_bytes_get_contents(fn, NULL); + if (blob != NULL && g_bytes_get_size(blob) > 0) { + g_autofree gchar *id = g_strdup_printf("ACPI:%s", tables[i]); + fu_engine_integrity_add_measurement(self, id, blob); + } + } +} + +static void +fu_engine_integrity_measure_uefi(GHashTable *self) +{ + struct { + const gchar *guid; + const gchar *name; + } keys[] = {{FU_EFIVAR_GUID_EFI_GLOBAL, "BootOrder"}, + {FU_EFIVAR_GUID_EFI_GLOBAL, "BootCurrent"}, + {FU_EFIVAR_GUID_EFI_GLOBAL, "KEK"}, + {FU_EFIVAR_GUID_EFI_GLOBAL, "PK"}, + {FU_EFIVAR_GUID_SECURITY_DATABASE, "db"}, + {FU_EFIVAR_GUID_SECURITY_DATABASE, "dbx"}, + {NULL, NULL}}; + + /* important keys */ + for (guint i = 0; keys[i].guid != NULL; i++) { + g_autoptr(GBytes) blob = + fu_efivar_get_data_bytes(keys[i].guid, keys[i].name, NULL, NULL); + if (blob != NULL) { + g_autofree gchar *id = g_strdup_printf("UEFI:%s", keys[i].name); + fu_engine_integrity_add_measurement(self, id, blob); + } + } + + /* Boot#### */ + for (guint i = 0; i < 0xFF; i++) { + g_autofree gchar *name = g_strdup_printf("Boot%04X", i); + g_autoptr(GBytes) blob = + fu_efivar_get_data_bytes(FU_EFIVAR_GUID_EFI_GLOBAL, name, NULL, NULL); + if (blob != NULL && g_bytes_get_size(blob) > 0) { + g_autofree gchar *id = g_strdup_printf("UEFI:%s", name); + fu_engine_integrity_add_measurement(self, id, blob); + } + } +} + +GHashTable * +fu_engine_integrity_new(GError **error) +{ + g_autoptr(GHashTable) self = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + + g_return_val_if_fail(error == NULL || *error == NULL, NULL); + + fu_engine_integrity_measure_uefi(self); + fu_engine_integrity_measure_acpi(self); + + /* nothing of use */ + if (g_hash_table_size(self) == 0) { + g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no measurements"); + return NULL; + } + + /* success */ + return g_steal_pointer(&self); +} + +gchar * +fu_engine_integrity_to_string(GHashTable *self) +{ + GHashTableIter iter; + gpointer key, value; + g_autoptr(GPtrArray) array = g_ptr_array_new_with_free_func(g_free); + + g_return_val_if_fail(self != NULL, NULL); + + /* sanity check */ + if (g_hash_table_size(self) == 0) + return NULL; + + /* build into KV array */ + g_hash_table_iter_init(&iter, self); + while (g_hash_table_iter_next(&iter, &key, &value)) { + g_ptr_array_add(array, + g_strdup_printf("%s=%s", (const gchar *)key, (const gchar *)value)); + } + return fu_strjoin("\n", array); +} diff --git a/src/fu-engine-helper.h b/src/fu-engine-helper.h index ce24ca585..be9b79b33 100644 --- a/src/fu-engine-helper.h +++ b/src/fu-engine-helper.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2020 Mario Limonciello + * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ @@ -11,3 +12,8 @@ gboolean fu_engine_update_motd(FuEngine *self, GError **error); gboolean fu_engine_update_devices_file(FuEngine *self, GError **error); + +GHashTable * +fu_engine_integrity_new(GError **error); +gchar * +fu_engine_integrity_to_string(GHashTable *self); diff --git a/src/fu-engine.c b/src/fu-engine.c index 2d8c1df25..91b8bb584 100644 --- a/src/fu-engine.c +++ b/src/fu-engine.c @@ -2413,6 +2413,16 @@ fu_engine_install_releases(FuEngine *self, return TRUE; } +static void +fu_engine_update_release_integrity(FuEngine *self, FwupdRelease *release, const gchar *key) +{ + g_autoptr(GHashTable) integrity = fu_engine_integrity_new(NULL); + if (integrity != NULL) { + g_autofree gchar *str = fu_engine_integrity_to_string(integrity); + fwupd_release_add_metadata_item(FWUPD_RELEASE(release), key, str); + } +} + static gboolean fu_engine_add_release_metadata(FuEngine *self, FuRelease *release, FuPlugin *plugin, GError **error) { @@ -2455,6 +2465,14 @@ fu_engine_add_release_metadata(FuEngine *self, FuRelease *release, FuPlugin *plu } } } + + /* measure the "old" system state */ + if (fu_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_MEASURE_SYSTEM_INTEGRITY)) { + fu_engine_update_release_integrity(self, + FWUPD_RELEASE(release), + "SystemIntegrityOld"); + } + return TRUE; } @@ -7315,6 +7333,13 @@ fu_engine_update_history_device(FuEngine *self, FuDevice *dev_history, GError ** } } + /* measure the "new" system state */ + plugin = fu_plugin_list_find_by_name(self->plugin_list, fu_device_get_plugin(dev), error); + if (plugin == NULL) + return FALSE; + if (fu_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_MEASURE_SYSTEM_INTEGRITY)) + fu_engine_update_release_integrity(self, rel_history, "SystemIntegrityNew"); + /* the system is running with the new firmware version */ if (fu_version_compare(fu_device_get_version(dev), fwupd_release_get_version(rel_history), @@ -7338,9 +7363,6 @@ fu_engine_update_history_device(FuEngine *self, FuDevice *dev_history, GError ** } /* does the plugin know the update failure */ - plugin = fu_plugin_list_find_by_name(self->plugin_list, fu_device_get_plugin(dev), error); - if (plugin == NULL) - return FALSE; if (!fu_plugin_runner_get_results(plugin, dev, error)) return FALSE; diff --git a/src/fu-util-common.c b/src/fu-util-common.c index 7af9e1c20..b9de0038a 100644 --- a/src/fu-util-common.c +++ b/src/fu-util-common.c @@ -1702,6 +1702,10 @@ fu_util_plugin_flag_to_string(FwupdPluginFlags plugin_flag) /* TRANSLATORS: the plugin was created from a .so object, and was not built-in */ return _("Loaded from an external module"); } + if (plugin_flag == FWUPD_PLUGIN_FLAG_MEASURE_SYSTEM_INTEGRITY) { + /* TRANSLATORS: check various UEFI and ACPI tables are unchanged after the update */ + return _("Will measure elements of system integrity around an update"); + } if (plugin_flag == FWUPD_PLUGIN_FLAG_EFIVAR_NOT_MOUNTED) { /* TRANSLATORS: the user is using Gentoo/Arch and has screwed something up */ return _("Required efivarfs filesystem was not found");