From 1abb32c623d14581452c3e11bf238b498347b25f Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Mon, 17 Aug 2020 14:13:54 +0100 Subject: [PATCH] uefi-dbx: Validate the dbx update is safe to apply To do this mount all ESP partitions and check all the binaries there to see if they match any entries in the new dbx. If we applied the update when a hash matched, we would unintentially 'brick' the users machine, as the grub and shim binaries *have* to be updated first. This functionality does reimplement the PE hashing functionality found in sbsigntools and pesign. This was done for 4 main reasons: * There were some memory safety issues found when fuzzing random binaries * Executing the tools hundreds of times was a lot of overhead * Operating from a blob of immutable mmap'd memory is much faster * We only need a very small amount of functionality from both tools --- plugins/uefi-dbx/fu-dbxtool.c | 12 + plugins/uefi-dbx/fu-efi-image.c | 332 +++++++++++++++++++++ plugins/uefi-dbx/fu-efi-image.h | 16 + plugins/uefi-dbx/fu-efi-signature-common.c | 2 +- plugins/uefi-dbx/fu-efi-signature-common.h | 2 + plugins/uefi-dbx/fu-self-test.c | 33 ++ plugins/uefi-dbx/fu-uefi-dbx-common.c | 86 ++++++ plugins/uefi-dbx/fu-uefi-dbx-common.h | 6 +- plugins/uefi-dbx/fu-uefi-dbx-device.c | 36 +++ plugins/uefi-dbx/meson.build | 8 + plugins/uefi-dbx/tests/fwupdx64.efi | Bin 0 -> 65640 bytes 11 files changed, 531 insertions(+), 2 deletions(-) create mode 100644 plugins/uefi-dbx/fu-efi-image.c create mode 100644 plugins/uefi-dbx/fu-efi-image.h create mode 100755 plugins/uefi-dbx/tests/fwupdx64.efi diff --git a/plugins/uefi-dbx/fu-dbxtool.c b/plugins/uefi-dbx/fu-dbxtool.c index a18665a7f..2a9601e79 100644 --- a/plugins/uefi-dbx/fu-dbxtool.c +++ b/plugins/uefi-dbx/fu-dbxtool.c @@ -221,6 +221,18 @@ main (int argc, char *argv[]) return EXIT_FAILURE; } + /* validate this is safe to apply */ + if (!force) { + /* TRANSLATORS: ESP refers to the EFI System Partition */ + g_print ("%s\n", _("Validating ESP contents…")); + if (!fu_uefi_dbx_signature_list_validate (dbx_update, &error)) { + /* TRANSLATORS: something with a blocked hash exists + * in the users ESP -- which would be bad! */ + g_printerr ("%s: %s\n", _("Failed to validate ESP contents"), error->message); + return EXIT_FAILURE; + } + } + /* TRANSLATORS: actually sending the update to the hardware */ g_print ("%s\n", _("Applying update…")); if (!fu_efivar_set_data (FU_EFIVAR_GUID_SECURITY_DATABASE, diff --git a/plugins/uefi-dbx/fu-efi-image.c b/plugins/uefi-dbx/fu-efi-image.c new file mode 100644 index 000000000..31229316b --- /dev/null +++ b/plugins/uefi-dbx/fu-efi-image.c @@ -0,0 +1,332 @@ +/* + * Copyright (C) 2020 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include "fu-common.h" +#include "fu-efi-image.h" + +struct _FuEfiImage { + GObject parent_instance; + gchar *checksum; +}; + +typedef struct { + gsize offset; + gsize size; + gchar *name; +} FuEfiImageRegion; + +typedef struct __attribute__((packed)) { + guint32 addr; + guint32 size; +} FuEfiImageDataDirEntry; + +G_DEFINE_TYPE (FuEfiImage, fu_efi_image, G_TYPE_OBJECT) + +#define _DOS_OFFSET_SIGNATURE 0x00 +#define _DOS_OFFSET_TO_PE_HEADER 0x3c + +#define _PEI_OFFSET_SIGNATURE 0x00 +#define _PEI_OFFSET_MACHINE 0x04 +#define _PEI_OFFSET_NUMBER_OF_SECTIONS 0x06 +#define _PEI_OFFSET_OPTIONAL_HEADER_SIZE 0x14 +#define _PEI_HEADER_SIZE 0x18 + +#define _PE_OFFSET_SIZE_OF_HEADERS 0x54 +#define _PE_OFFSET_CHECKSUM 0x58 +#define _PE_OFFSET_DEBUG_TABLE_OFFSET 0x98 + +#define _PEP_OFFSET_SIZE_OF_HEADERS 0x54 +#define _PEP_OFFSET_CHECKSUM 0x58 +#define _PEP_OFFSET_DEBUG_TABLE_OFFSET 0xa8 + +#define _SECTION_HEADER_OFFSET_NAME 0x0 +#define _SECTION_HEADER_OFFSET_SIZE 0x10 +#define _SECTION_HEADER_OFFSET_PTR 0x14 +#define _SECTION_HEADER_SIZE 0x28 + +#define IMAGE_FILE_MACHINE_AMD64 0x8664 +#define IMAGE_FILE_MACHINE_I386 0x014c +#define IMAGE_FILE_MACHINE_THUMB 0x01c2 +#define IMAGE_FILE_MACHINE_AARCH64 0xaa64 + +static gint +fu_efi_image_region_sort_cb (gconstpointer a, gconstpointer b) +{ + const FuEfiImageRegion *r1 = *((const FuEfiImageRegion **) a); + const FuEfiImageRegion *r2 = *((const FuEfiImageRegion **) b); + if (r1->offset < r2->offset) + return -1; + if (r1->offset > r2->offset) + return 1; + return 0; +} + +static FuEfiImageRegion * +fu_efi_image_add_region (GPtrArray *checksum_regions, + const gchar *name, + gsize offset_start, + gsize offset_end) +{ + FuEfiImageRegion *r = g_new0 (FuEfiImageRegion, 1); + r->name = g_strdup (name); + r->offset = offset_start; + r->size = offset_end - offset_start; + g_ptr_array_add (checksum_regions, r); + return r; +} + +static void +fu_efi_image_region_free (FuEfiImageRegion *r) +{ + g_free (r->name); + g_free (r); +} + +FuEfiImage * +fu_efi_image_new (GBytes *data, GError **error) +{ + FuEfiImageRegion *r; + const guint8 *buf; + gsize bufsz; + gsize image_bytes = 0; + gsize checksum_offset; + gsize data_dir_debug_offset; + gsize offset_tmp; + guint16 dos_sig = 0; + guint16 machine = 0; + guint16 opthdrsz; + guint16 sections; + guint32 baseaddr = 0; + guint32 cert_table_size; + guint32 header_size; + guint32 nt_sig = 0; + g_autoptr(FuEfiImage) self = g_object_new (FU_TYPE_EFI_IMAGE, NULL); + g_autoptr(GChecksum) checksum = g_checksum_new (G_CHECKSUM_SHA256); + g_autoptr(GPtrArray) checksum_regions = NULL; + + /* verify this is a DOS file */ + buf = g_bytes_get_data (data, &bufsz); + if (!fu_common_read_uint16_safe (buf, bufsz, + _DOS_OFFSET_SIGNATURE, + &dos_sig, G_LITTLE_ENDIAN, error)) + return NULL; + if (dos_sig != 0x5a4d) { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "Invalid DOS header magic %04x", dos_sig); + return NULL; + } + + /* verify the PE signature */ + if (!fu_common_read_uint32_safe (buf, bufsz, + _DOS_OFFSET_TO_PE_HEADER, + &baseaddr, G_LITTLE_ENDIAN, error)) + return NULL; + if (!fu_common_read_uint32_safe (buf, bufsz, + baseaddr + _PEI_OFFSET_SIGNATURE, + &nt_sig, G_LITTLE_ENDIAN, error)) + return NULL; + if (nt_sig != 0x4550) { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "Invalid PE header signature %08x", nt_sig); + return NULL; + } + + /* which machine type are we reading */ + if (!fu_common_read_uint16_safe (buf, bufsz, + baseaddr + _PEI_OFFSET_MACHINE, + &machine, G_LITTLE_ENDIAN, error)) + return NULL; + if (machine == IMAGE_FILE_MACHINE_AMD64 || + machine == IMAGE_FILE_MACHINE_AARCH64) { + + /* a.out header directly follows PE header */ + if (!fu_common_read_uint16_safe (buf, bufsz, + baseaddr + _PEI_HEADER_SIZE, + &machine, G_LITTLE_ENDIAN, error)) + return NULL; + if (machine != 0x020b) { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "Invalid a.out machine type %04x", machine); + return NULL; + } + if (!fu_common_read_uint32_safe (buf, bufsz, + baseaddr + _PEP_OFFSET_SIZE_OF_HEADERS, + &header_size, G_LITTLE_ENDIAN, error)) + return NULL; + + checksum_offset = baseaddr + _PEP_OFFSET_CHECKSUM; + + /* now, this is odd. sbsigntools seems to think that we're + * skipping the CertificateTable -- but we actually seems to be + * ignoring Debug instead */ + data_dir_debug_offset = baseaddr + _PEP_OFFSET_DEBUG_TABLE_OFFSET; + + } else if (machine == IMAGE_FILE_MACHINE_I386 || + machine == IMAGE_FILE_MACHINE_THUMB) { + + /* a.out header directly follows PE header */ + if (!fu_common_read_uint16_safe (buf, bufsz, + baseaddr + _PEI_HEADER_SIZE, + &machine, G_LITTLE_ENDIAN, error)) + return NULL; + if (machine != 0x010b) { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "Invalid a.out machine type %04x", machine); + return NULL; + } + if (!fu_common_read_uint32_safe (buf, bufsz, + baseaddr + _PE_OFFSET_SIZE_OF_HEADERS, + &header_size, G_LITTLE_ENDIAN, error)) + return NULL; + + checksum_offset = baseaddr + _PE_OFFSET_CHECKSUM; + data_dir_debug_offset = baseaddr + _PE_OFFSET_DEBUG_TABLE_OFFSET; + + } else { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "Invalid PE header machine %04x", machine); + return NULL; + } + + /* get sections */ + if (!fu_common_read_uint32_safe (buf, bufsz, + data_dir_debug_offset + sizeof(guint32), + &cert_table_size, G_LITTLE_ENDIAN, error)) + return NULL; + if (!fu_common_read_uint16_safe (buf, bufsz, + baseaddr + _PEI_OFFSET_NUMBER_OF_SECTIONS, + §ions, G_LITTLE_ENDIAN, error)) + return NULL; + g_debug ("number_of_sections: %u", sections); + + /* get header size */ + if (!fu_common_read_uint16_safe (buf, bufsz, + baseaddr + _PEI_OFFSET_OPTIONAL_HEADER_SIZE, + &opthdrsz, G_LITTLE_ENDIAN, error)) + return NULL; + g_debug ("optional_header_size: 0x%x", opthdrsz); + + /* first region: beginning to checksum_offset field */ + checksum_regions = g_ptr_array_new_with_free_func ((GDestroyNotify) fu_efi_image_region_free); + r = fu_efi_image_add_region (checksum_regions, "begin->cksum", 0x0, checksum_offset); + image_bytes += r->size + sizeof(guint32); + + /* second region: end of checksum_offset to certificate table entry */ + r = fu_efi_image_add_region (checksum_regions, "cksum->datadir[DEBUG]", + checksum_offset + sizeof(guint32), + data_dir_debug_offset); + image_bytes += r->size + sizeof(FuEfiImageDataDirEntry); + + /* third region: end of checksum_offset to end of headers */ + r = fu_efi_image_add_region (checksum_regions, "datadir[DEBUG]->headers", + data_dir_debug_offset + sizeof(FuEfiImageDataDirEntry), + header_size); + image_bytes += r->size; + + /* add COFF sections */ + offset_tmp = baseaddr + _PEI_HEADER_SIZE + opthdrsz; + for (guint i = 0; i < sections; i++) { + guint32 file_offset = 0; + guint32 file_size = 0; + gchar name[9] = { '\0' }; + + if (!fu_common_read_uint32_safe (buf, bufsz, + offset_tmp + _SECTION_HEADER_OFFSET_PTR, + &file_offset, G_LITTLE_ENDIAN, error)) + return NULL; + if (!fu_common_read_uint32_safe (buf, bufsz, + offset_tmp + _SECTION_HEADER_OFFSET_SIZE, + &file_size, G_LITTLE_ENDIAN, error)) + return NULL; + if (file_size == 0) + continue; + if (!fu_memcpy_safe ((guint8 *) name, sizeof(name), 0x0, /* dst */ + buf, bufsz, + offset_tmp + _SECTION_HEADER_OFFSET_NAME, /* src */ + sizeof(name) - 1, error)) + return NULL; + r = fu_efi_image_add_region (checksum_regions, name, file_offset, file_offset + file_size); + image_bytes += r->size; + + if (file_offset + r->size > bufsz) { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "file-aligned section %s extends beyond end of file", + r->name); + return NULL; + } + offset_tmp += _SECTION_HEADER_SIZE; + } + + /* make sure in order */ + g_ptr_array_sort (checksum_regions, fu_efi_image_region_sort_cb); + + /* for the data at the end of the image */ + if (image_bytes + cert_table_size < bufsz) { + fu_efi_image_add_region (checksum_regions, "endjunk", + image_bytes, bufsz - cert_table_size); + } else if (image_bytes + cert_table_size > bufsz) { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "checksum_offset areas outside image size"); + return NULL; + } + + /* calculate the checksum we would find in the dbx */ + for (guint i = 0; i < checksum_regions->len; i++) { + r = g_ptr_array_index (checksum_regions, i); + g_debug ("region %s: 0x%04x -> 0x%04x [0x%04x]", + r->name, + (guint) r->offset, + (guint) (r->offset + r->size - 1), + (guint) r->size); + g_checksum_update (checksum, + (const guchar *) buf + r->offset, + (gssize) r->size); + } + self->checksum = g_strdup (g_checksum_get_string (checksum)); + return g_steal_pointer (&self); +} + +const gchar * +fu_efi_image_get_checksum (FuEfiImage *self) +{ + return self->checksum; +} + +static void +fu_efi_image_finalize (GObject *obj) +{ + FuEfiImage *self = FU_EFI_IMAGE (obj); + g_free (self->checksum); + G_OBJECT_CLASS (fu_efi_image_parent_class)->finalize (obj); +} + +static void +fu_efi_image_class_init (FuEfiImageClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + object_class->finalize = fu_efi_image_finalize; +} + +static void +fu_efi_image_init (FuEfiImage *self) +{ +} diff --git a/plugins/uefi-dbx/fu-efi-image.h b/plugins/uefi-dbx/fu-efi-image.h new file mode 100644 index 000000000..64aebad42 --- /dev/null +++ b/plugins/uefi-dbx/fu-efi-image.h @@ -0,0 +1,16 @@ +/* + * Copyright (C) 2020 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +#define FU_TYPE_EFI_IMAGE (fu_efi_image_get_type ()) +G_DECLARE_FINAL_TYPE (FuEfiImage, fu_efi_image, FU, EFI_IMAGE, GObject) + +FuEfiImage *fu_efi_image_new (GBytes *data, + GError **error); +const gchar *fu_efi_image_get_checksum (FuEfiImage *self); diff --git a/plugins/uefi-dbx/fu-efi-signature-common.c b/plugins/uefi-dbx/fu-efi-signature-common.c index 00d43fe7f..825ac3266 100644 --- a/plugins/uefi-dbx/fu-efi-signature-common.c +++ b/plugins/uefi-dbx/fu-efi-signature-common.c @@ -9,7 +9,7 @@ #include "fu-efi-signature-common.h" #include "fu-efi-signature-list.h" -static gboolean +gboolean fu_efi_signature_list_array_has_checksum (GPtrArray *siglists, const gchar *checksum) { for (guint i = 0; i < siglists->len; i++) { diff --git a/plugins/uefi-dbx/fu-efi-signature-common.h b/plugins/uefi-dbx/fu-efi-signature-common.h index 48a89c64b..fe9511d1d 100644 --- a/plugins/uefi-dbx/fu-efi-signature-common.h +++ b/plugins/uefi-dbx/fu-efi-signature-common.h @@ -11,3 +11,5 @@ gboolean fu_efi_signature_list_array_inclusive (GPtrArray *outer, GPtrArray *inner); guint fu_efi_signature_list_array_version (GPtrArray *siglists); +gboolean fu_efi_signature_list_array_has_checksum (GPtrArray *siglists, + const gchar *checksum); diff --git a/plugins/uefi-dbx/fu-self-test.c b/plugins/uefi-dbx/fu-self-test.c index de8657b55..fb310e30f 100644 --- a/plugins/uefi-dbx/fu-self-test.c +++ b/plugins/uefi-dbx/fu-self-test.c @@ -8,9 +8,41 @@ #include +#include "fu-common.h" #include "fu-uefi-dbx-common.h" +#include "fu-efi-image.h" #include "fu-efi-signature-parser.h" +static gchar * +fu_test_get_filename (const gchar *filename) +{ + g_autofree gchar *path = NULL; + path = g_build_filename (TESTDATADIR, filename, NULL); + return fu_common_realpath (path, NULL); +} + +static void +fu_efi_image_func (void) +{ + const gchar *csum = NULL; + g_autofree gchar *fn = NULL; + g_autoptr(FuEfiImage) img = NULL; + g_autoptr(GBytes) bytes = NULL; + g_autoptr(GError) error = NULL; + + fn = fu_test_get_filename ("fwupdx64.efi"); + g_assert_nonnull (fn); + bytes = fu_common_get_contents_bytes (fn, &error); + g_assert_no_error (error); + g_assert_nonnull (bytes); + + img = fu_efi_image_new (bytes, &error); + g_assert_no_error (error); + g_assert_nonnull (img); + csum = fu_efi_image_get_checksum (img); + g_assert_cmpstr (csum, ==, "e99707d4378140c01eb3f867240d5cc9e237b126d3db0c3b4bbcd3da1720ddff"); +} + static void fu_efi_signature_list_parse_func (void) { @@ -55,6 +87,7 @@ main (int argc, char **argv) g_setenv ("G_MESSAGES_DEBUG", "all", TRUE); /* tests go here */ + g_test_add_func ("/uefi-dbx/image", fu_efi_image_func); g_test_add_func ("/uefi-dbx/file-parse", fu_efi_signature_list_parse_func); return g_test_run (); } diff --git a/plugins/uefi-dbx/fu-uefi-dbx-common.c b/plugins/uefi-dbx/fu-uefi-dbx-common.c index 2971e81a6..bdcd0a44a 100644 --- a/plugins/uefi-dbx/fu-uefi-dbx-common.c +++ b/plugins/uefi-dbx/fu-uefi-dbx-common.c @@ -7,6 +7,10 @@ #include "config.h" #include "fu-common.h" +#include "fu-efi-image.h" +#include "fu-efi-signature-common.h" +#include "fu-volume.h" + #include "fu-uefi-dbx-common.h" gchar * @@ -26,3 +30,85 @@ fu_uefi_dbx_get_dbxupdate (GError **error) return NULL; return g_strdup (g_ptr_array_index (files, 0)); } + +gchar * +fu_uefi_dbx_get_authenticode_hash (const gchar *fn, GError **error) +{ + g_autoptr(FuEfiImage) img = NULL; + g_autoptr(GBytes) bytes = NULL; + g_autoptr(GMappedFile) mmap = NULL; + + g_debug ("getting Authenticode hash of %s", fn); + mmap = g_mapped_file_new (fn, FALSE, error); + if (mmap == NULL) + return NULL; + bytes = g_mapped_file_get_bytes (mmap); + + img = fu_efi_image_new (bytes, error); + if (img == NULL) + return NULL; + g_debug ("SHA256 was %s", fu_efi_image_get_checksum (img)); + return g_strdup (fu_efi_image_get_checksum (img)); +} + +static gboolean +fu_uefi_dbx_signature_list_validate_volume (GPtrArray *siglists, FuVolume *esp, GError **error) +{ + g_autofree gchar *esp_path = NULL; + g_autoptr(GPtrArray) files = NULL; + + /* get list of files contained in the ESP */ + esp_path = fu_volume_get_mount_point (esp); + if (esp_path == NULL) + return TRUE; + files = fu_common_get_files_recursive (esp_path, error); + if (files == NULL) + return FALSE; + + /* verify each file does not exist in the ESP */ + for (guint i = 0; i < files->len; i++) { + const gchar *fn = g_ptr_array_index (files, i); + g_autofree gchar *checksum = NULL; + g_autoptr(GError) error_local = NULL; + + /* get checksum of file */ + checksum = fu_uefi_dbx_get_authenticode_hash (fn, &error_local); + if (checksum == NULL) { + g_debug ("failed to get checksum for %s: %s", fn, error_local->message); + continue; + } + + /* Authenticode signature is present in dbx! */ + g_debug ("fn=%s, checksum=%s", fn, checksum); + if (fu_efi_signature_list_array_has_checksum (siglists, checksum)) { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "%s Authenticode checksum [%s] is present in dbx", + fn, checksum); + return FALSE; + } + } + + /* success */ + return TRUE; +} + +gboolean +fu_uefi_dbx_signature_list_validate (GPtrArray *siglists, GError **error) +{ + g_autoptr(GPtrArray) volumes = NULL; + volumes = fu_common_get_volumes_by_kind (FU_VOLUME_KIND_ESP, error); + if (volumes == NULL) + return FALSE; + for (guint i = 0; i < volumes->len; i++) { + FuVolume *esp = g_ptr_array_index (volumes, i); + g_autoptr(FuDeviceLocker) locker = NULL; + locker = fu_volume_locker (esp, error); + if (locker == NULL) + return FALSE; + if (!fu_uefi_dbx_signature_list_validate_volume (siglists, esp, error)) + return FALSE; + } + return TRUE; +} diff --git a/plugins/uefi-dbx/fu-uefi-dbx-common.h b/plugins/uefi-dbx/fu-uefi-dbx-common.h index fd4243217..de33f8805 100644 --- a/plugins/uefi-dbx/fu-uefi-dbx-common.h +++ b/plugins/uefi-dbx/fu-uefi-dbx-common.h @@ -8,4 +8,8 @@ #include -gchar *fu_uefi_dbx_get_dbxupdate (GError **error); +gchar *fu_uefi_dbx_get_dbxupdate (GError **error); +gchar *fu_uefi_dbx_get_authenticode_hash (const gchar *fn, + GError **error); +gboolean fu_uefi_dbx_signature_list_validate (GPtrArray *siglists, + GError **error); diff --git a/plugins/uefi-dbx/fu-uefi-dbx-device.c b/plugins/uefi-dbx/fu-uefi-dbx-device.c index 92e4c6d42..3236a1be2 100644 --- a/plugins/uefi-dbx/fu-uefi-dbx-device.c +++ b/plugins/uefi-dbx/fu-uefi-dbx-device.c @@ -10,6 +10,7 @@ #include "fu-efi-signature-common.h" #include "fu-efi-signature-parser.h" +#include "fu-uefi-dbx-common.h" #include "fu-uefi-dbx-device.h" struct _FuUefiDbxDevice { @@ -75,6 +76,40 @@ fu_uefi_dbx_device_set_version_number (FuDevice *device, GError **error) return TRUE; } +static FuFirmware * +fu_uefi_dbx_prepare_firmware (FuDevice *device, + GBytes *fw, + FwupdInstallFlags flags, + GError **error) +{ + const guint8 *buf; + gsize bufsz = 0; + g_autoptr(GPtrArray) siglists = NULL; + + /* parse dbx */ + fu_device_set_status (device, FWUPD_STATUS_DECOMPRESSING); + buf = g_bytes_get_data (fw, &bufsz); + siglists = fu_efi_signature_parser_new (buf, bufsz, + FU_EFI_SIGNATURE_PARSER_FLAGS_IGNORE_HEADER, + error); + if (siglists == NULL) + return NULL; + + /* validate this is safe to apply */ + if ((flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { + fu_device_set_status (device, FWUPD_STATUS_DEVICE_VERIFY); + if (!fu_uefi_dbx_signature_list_validate (siglists, error)) { + g_prefix_error (error, + "Blocked executable in the ESP, " + "ensure grub and shim are up to date: "); + return NULL; + } + } + + /* default blob */ + return fu_firmware_new_from_bytes (fw); +} + static gboolean fu_uefi_dbx_device_probe (FuDevice *device, GError **error) { @@ -137,6 +172,7 @@ fu_uefi_dbx_device_class_init (FuUefiDbxDeviceClass *klass) FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); klass_device->probe = fu_uefi_dbx_device_probe; klass_device->write_firmware = fu_uefi_dbx_device_write_firmware; + klass_device->prepare_firmware = fu_uefi_dbx_prepare_firmware; } FuUefiDbxDevice * diff --git a/plugins/uefi-dbx/meson.build b/plugins/uefi-dbx/meson.build index a257c6bdd..6961dfab2 100644 --- a/plugins/uefi-dbx/meson.build +++ b/plugins/uefi-dbx/meson.build @@ -6,6 +6,7 @@ shared_module('fu_plugin_uefi_dbx', 'fu-plugin-uefi-dbx.c', 'fu-uefi-dbx-common.c', 'fu-uefi-dbx-device.c', + 'fu-efi-image.c', 'fu-efi-signature.c', 'fu-efi-signature-common.c', 'fu-efi-signature-list.c', @@ -29,12 +30,15 @@ shared_module('fu_plugin_uefi_dbx', ) if get_option('tests') + testdatadir = join_paths(meson.current_source_dir(), 'tests') + cargs += '-DTESTDATADIR="' + testdatadir + '"' e = executable( 'uefi-dbx-self-test', fu_hash, sources : [ 'fu-self-test.c', 'fu-uefi-dbx-common.c', + 'fu-efi-image.c', 'fu-efi-signature.c', 'fu-efi-signature-common.c', 'fu-efi-signature-list.c', @@ -52,6 +56,7 @@ if get_option('tests') fwupd, fwupdplugin, ], + c_args : cargs, ) test('uefi-dbx-self-test', e) endif @@ -60,10 +65,12 @@ uefi_dbx_fuzzer = executable( 'uefi-dbx-fuzzer', sources : [ 'fu-fuzzer.c', + 'fu-efi-image.c', 'fu-efi-signature.c', 'fu-efi-signature-common.c', 'fu-efi-signature-list.c', 'fu-efi-signature-parser.c', + 'fu-uefi-dbx-common.c', ], include_directories : [ root_incdir, @@ -84,6 +91,7 @@ dbxtool = executable( sources : [ 'fu-dbxtool.c', 'fu-uefi-dbx-common.c', + 'fu-efi-image.c', 'fu-efi-signature.c', 'fu-efi-signature-common.c', 'fu-efi-signature-list.c', diff --git a/plugins/uefi-dbx/tests/fwupdx64.efi b/plugins/uefi-dbx/tests/fwupdx64.efi new file mode 100755 index 0000000000000000000000000000000000000000..8d38a5e8ede05b4e81f69b8749fcb3f44cfec933 GIT binary patch literal 65640 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 SJ0x0gAccE1z|`n(RTIBV>cA}OPW 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 KBfp?eSN{hcgPmLe literal 0 HcmV?d00001