Commit b9640a28 authored by Richard Hughes's avatar Richard Hughes
Browse files

uefi-dbx: Add a plugin that analyses the UEFI dbx variable

This will be used for future functionality.
parent a84d7a7e
......@@ -125,3 +125,4 @@ There are several automated fuzzing tests in fwupd. These take some time to run:
ninja fuzz-synaptics-rmi
ninja fuzz-firmware
ninja fuzz-smbios
ninja fuzz-efidbx
......@@ -49,6 +49,7 @@ Depends: ${misc:Depends},
shared-mime-info
Recommends: python3,
bolt,
secureboot-db,
fwupd-signed
Provides: fwupdate
Conflicts: fwupdate-amd64-signed,
......
......@@ -20,6 +20,7 @@ ifeq (yes,$(shell dpkg-vendor --derives-from Ubuntu && echo yes))
SB_STYLE := ubuntu
tar_name := fwupd_$(deb_version)_$(DEB_HOST_ARCH).tar.gz
export FLASHROM=-Dplugin_flashrom=false
export DBX=-Defi_dbxdir=/usr/share/secureboot/updates/dbx
else
TMPLDIR := debian/fwupd-$(DEB_HOST_ARCH)-signed-template/usr/share/code-signing/fwupd-$(DEB_HOST_ARCH)-signed-template
export FLASHROM=-Dplugin_flashrom=true
......@@ -42,7 +43,7 @@ override_dh_auto_configure:
export DELL="-Dplugin_dell=false"; \
fi; \
if pkg-config --exists efivar; then \
export UEFI="-Dplugin_uefi=true -Dplugin_redfish=true -Dplugin_nvme=true"; \
export UEFI="-Dplugin_uefi=true -Dplugin_redfish=true -Dplugin_nvme=true $$DBX"; \
else \
export UEFI="-Dplugin_uefi=false -Dplugin_redfish=false -Dplugin_nvme=false"; \
fi; \
......
......@@ -117,6 +117,10 @@ Requires: libsoup%{?_isa} >= %{libsoup_version}
Requires: bubblewrap
Requires: shared-mime-info
%if 0%{?have_uefi}
Requires: dbxtool
%endif
%if 0%{?rhel} > 7 || 0%{?fedora} > 28
Recommends: python3
%endif
......@@ -163,6 +167,7 @@ Data files for installed tests.
--werror \
%endif
-Dgtkdoc=true \
-Defi_dbxdir=%{_datadir}/dbxtool \
%if 0%{?enable_tests}
-Dtests=true \
%else
......@@ -380,6 +385,7 @@ mkdir -p --mode=0700 $RPM_BUILD_ROOT%{_localstatedir}/lib/fwupd/gnupg
%{_libdir}/fwupd-plugins-3/libfu_plugin_tpm.so
%{_libdir}/fwupd-plugins-3/libfu_plugin_tpm_eventlog.so
%{_libdir}/fwupd-plugins-3/libfu_plugin_uefi.so
%{_libdir}/fwupd-plugins-3/libfu_plugin_uefi_dbx.so
%{_libdir}/fwupd-plugins-3/libfu_plugin_uefi_recovery.so
%endif
%{_libdir}/fwupd-plugins-3/libfu_plugin_logind.so
......
......@@ -1095,6 +1095,20 @@ fu_common_get_path (FuPathKind path_kind)
return g_strdup (EFI_APP_LOCATION);
#else
return NULL;
#endif
/* /usr/share/fwupd/dbx */
case FU_PATH_KIND_EFIDBXDIR:
tmp = g_getenv ("FWUPD_EFIDBXDIR");
if (tmp != NULL)
return g_strdup (tmp);
#ifdef FWUPD_EFI_DBXDIR
tmp = g_getenv ("SNAP");
if (tmp != NULL)
return g_build_filename (tmp, FWUPD_EFI_DBXDIR, NULL);
return g_strdup (FWUPD_EFI_DBXDIR);
#else
basedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR_PKG);
return g_build_filename (basedir, "dbx", NULL);
#endif
/* /etc/fwupd */
case FU_PATH_KIND_SYSCONFDIR_PKG:
......
......@@ -57,6 +57,7 @@ typedef guint FuEndianType;
* @FU_PATH_KIND_POLKIT_ACTIONS: The directory for policy kit actions (IE /usr/share/polkit-1/actions/)
* @FU_PATH_KIND_OFFLINE_TRIGGER: The file for the offline trigger (IE /system-update)
* @FU_PATH_KIND_SYSFSDIR_SECURITY: The sysfs security location (IE /sys/kernel/security)
* @FU_PATH_KIND_EFIDBXDIR: The location of the EFI dbx files
*
* Path types to use when dynamically determining a path at runtime
**/
......@@ -76,6 +77,7 @@ typedef enum {
FU_PATH_KIND_POLKIT_ACTIONS,
FU_PATH_KIND_OFFLINE_TRIGGER,
FU_PATH_KIND_SYSFSDIR_SECURITY,
FU_PATH_KIND_EFIDBXDIR,
/*< private >*/
FU_PATH_KIND_LAST
} FuPathKind;
......
......@@ -289,6 +289,14 @@ if build_standalone and get_option('plugin_uefi')
efi_app_location = join_paths(libexecdir, 'fwupd', 'efi')
conf.set_quoted ('EFI_APP_LOCATION', efi_app_location)
# e.g. could be:
# * /usr/share/dbxtool for Red Hat
# * /usr/share/secureboot/updates/dbx for Ubuntu
efi_dbxdir = get_option('efi_dbxdir')
if efi_dbxdir != ''
conf.set_quoted ('FWUPD_EFI_DBXDIR', efi_dbxdir)
endif
efi_arch = host_machine.cpu_family()
if efi_arch == 'x86'
EFI_MACHINE_TYPE_NAME = 'ia32'
......
......@@ -26,6 +26,7 @@ option('systemd_root_prefix', type: 'string', value: '', description: 'Directory
option('elogind', type : 'boolean', value : false, description : 'enable elogind support')
option('tests', type : 'boolean', value : true, description : 'enable tests')
option('udevdir', type: 'string', value: '', description: 'Directory for udev rules')
option('efi_dbxdir', type: 'string', value: '', description: 'Directory for UEFI dbx files')
option('efi-cc', type : 'string', value : 'gcc', description : 'the compiler to use for EFI modules')
option('efi-ld', type : 'string', value : 'ld', description : 'the linker to use for EFI modules')
option('efi-libdir', type : 'string', description : 'path to the EFI lib directory')
......
......@@ -101,6 +101,7 @@ endif
if get_option('plugin_uefi')
subdir('uefi')
subdir('uefi-recovery')
subdir('uefi-dbx')
endif
if get_option('plugin_flashrom')
......
UEFI dbx Support
================
Introduction
------------
This plugin checks if the UEFI dbx contains all the most recent blacklisted
checksums.
#!/usr/bin/python3
# SPDX-License-Identifier: LGPL-2.1+
import sys
import struct
if __name__ == '__main__':
# SignatureType
buf = b'0' * 16
# SignatureListSize
buf += struct.pack('<I', 16 + 4 + 4 + 4 + 16 + 32)
# SignatureHeaderSize
buf += struct.pack('<I', 0)
# SignatureSize
buf += struct.pack('<I', 16 + 32)
# SignatureOwner
buf += b'1' * 16
# SignatureData
buf += b'2' * 32
with open(sys.argv[1], 'wb') as f:
f.write(buf)
/*
* Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <stdlib.h>
#include "fu-uefi-dbx-file.h"
int
main (int argc, char *argv[])
{
gsize bufsz = 0;
g_autofree guint8 *buf = NULL;
g_autoptr(FuUefiDbxFile) uefi_dbx_file = NULL;
g_autoptr(GError) error = NULL;
if (argc < 2) {
g_printerr ("Not enough arguments, expected 'foo.bin'\n");
return EXIT_FAILURE;
}
if (!g_file_get_contents (argv[1], (gchar **) &buf, &bufsz, &error)) {
g_printerr ("Failed to load %s: %s\n", argv[1], error->message);
return EXIT_FAILURE;
}
uefi_dbx_file = fu_uefi_dbx_file_new (buf, bufsz,
FU_UEFI_DBX_FILE_PARSE_FLAGS_IGNORE_HEADER,
&error);
if (uefi_dbx_file == NULL) {
g_printerr ("Failed to parse %s: %s\n", argv[1], error->message);
return EXIT_FAILURE;
}
g_print ("%u checksums\n", fu_uefi_dbx_file_get_checksums(uefi_dbx_file)->len);
return EXIT_SUCCESS;
}
/*
* Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include "fu-plugin-vfuncs.h"
#include "fu-efivar.h"
#include "fu-hash.h"
#include "fu-uefi-dbx-common.h"
#include "fu-uefi-dbx-file.h"
struct FuPluginData {
gchar *fn;
};
void
fu_plugin_init (FuPlugin *plugin)
{
fu_plugin_alloc_data (plugin, sizeof (FuPluginData));
fu_plugin_set_build_hash (plugin, FU_BUILD_HASH);
}
void
fu_plugin_destroy (FuPlugin *plugin)
{
FuPluginData *data = fu_plugin_get_data (plugin);
g_free (data->fn);
}
gboolean
fu_plugin_startup (FuPlugin *plugin, GError **error)
{
FuPluginData *data = fu_plugin_get_data (plugin);
GPtrArray *checksums;
gsize bufsz = 0;
guint missing_cnt = 0;
g_autofree guint8 *buf_system = NULL;
g_autofree guint8 *buf_update = NULL;
g_autoptr(FuUefiDbxFile) dbx_system = NULL;
g_autoptr(FuUefiDbxFile) dbx_update = NULL;
g_autoptr(GError) error_local = NULL;
/* get binary blob */
data->fn = fu_uefi_dbx_get_dbxupdate (error);
if (data->fn == NULL) {
g_autofree gchar *dbxdir = NULL;
dbxdir = fu_common_get_path (FU_PATH_KIND_EFIDBXDIR);
g_prefix_error (error,
"file can be downloaded from %s and decompressed into %s: ",
FU_UEFI_DBX_DATA_URL, dbxdir);
return FALSE;
}
/* get update dbx */
if (!g_file_get_contents (data->fn, (gchar **) &buf_update, &bufsz, error)) {
g_prefix_error (error, "failed to load %s: ", data->fn);
return FALSE;
}
dbx_update = fu_uefi_dbx_file_new (buf_update, bufsz,
FU_UEFI_DBX_FILE_PARSE_FLAGS_IGNORE_HEADER,
error);
if (dbx_update == NULL) {
g_prefix_error (error, "could not parse %s: ", data->fn);
return FALSE;
}
/* get system dbx */
if (!fu_efivar_get_data ("d719b2cb-3d3a-4596-a3bc-dad00e67656f", "dbx",
&buf_system, &bufsz, NULL, error)) {
g_prefix_error (error, "failed to get dbx: ");
return FALSE;
}
dbx_system = fu_uefi_dbx_file_new (buf_system, bufsz,
FU_UEFI_DBX_FILE_PARSE_FLAGS_NONE,
error);
if (dbx_system == NULL) {
g_prefix_error (error, "could not parse variable: ");
return FALSE;
}
/* look for each checksum in the update in the system version */
checksums = fu_uefi_dbx_file_get_checksums (dbx_update);
for (guint i = 0; i < checksums->len; i++) {
const gchar *checksum = g_ptr_array_index (checksums, i);
if (!fu_uefi_dbx_file_has_checksum (dbx_system, checksum)) {
g_debug ("%s missing from the system dbx", checksum);
missing_cnt += 1;
}
}
if (missing_cnt > 0)
g_warning ("%u hashes missing", missing_cnt);
return TRUE;
}
/*
* Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <fwupd.h>
#include "fu-uefi-dbx-common.h"
#include "fu-uefi-dbx-file.h"
static void
fu_uefi_dbx_file_parse_func (void)
{
gboolean ret;
gsize bufsz = 0;
g_autofree gchar *fn = NULL;
g_autofree guint8 *buf = NULL;
g_autoptr(FuUefiDbxFile) uefi_dbx_file = NULL;
g_autoptr(GError) error = NULL;
/* load file */
fn = fu_uefi_dbx_get_dbxupdate (NULL);
if (fn == NULL) {
g_test_skip ("no dbx file, use -Defi_dbxdir=");
return;
}
ret = g_file_get_contents (fn, (gchar **) &buf, &bufsz, &error);
g_assert_no_error (error);
g_assert_true (ret);
/* parse the update */
uefi_dbx_file = fu_uefi_dbx_file_new (buf, bufsz,
FU_UEFI_DBX_FILE_PARSE_FLAGS_IGNORE_HEADER,
&error);
g_assert_no_error (error);
g_assert_nonnull (uefi_dbx_file);
g_assert_cmpint (fu_uefi_dbx_file_get_checksums(uefi_dbx_file)->len, ==, 77);
g_assert_true (fu_uefi_dbx_file_has_checksum (uefi_dbx_file, "72e0bd1867cf5d9d56ab158adf3bddbc82bf32a8d8aa1d8c5e2f6df29428d6d8"));
g_assert_false (fu_uefi_dbx_file_has_checksum (uefi_dbx_file, "dave"));
}
int
main (int argc, char **argv)
{
g_test_init (&argc, &argv, NULL);
/* only critical and error are fatal */
g_log_set_fatal_mask (NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL);
g_setenv ("G_MESSAGES_DEBUG", "all", TRUE);
/* tests go here */
g_test_add_func ("/uefi-dbx/file-parse", fu_uefi_dbx_file_parse_func);
return g_test_run ();
}
/*
* Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include "fu-common.h"
#include "fu-uefi-dbx-common.h"
gchar *
fu_uefi_dbx_get_dbxupdate (GError **error)
{
g_autofree gchar *dbxdir = NULL;
g_autofree gchar *glob = NULL;
g_autoptr(GPtrArray) files = NULL;
/* get the newest files from dbxtool, prefer the per-arch ones first */
dbxdir = fu_common_get_path (FU_PATH_KIND_EFIDBXDIR);
glob = g_strdup_printf ("*%s*.bin", EFI_MACHINE_TYPE_NAME);
files = fu_common_filename_glob (dbxdir, glob, NULL);
if (files == NULL)
files = fu_common_filename_glob (dbxdir, "*.bin", error);
if (files == NULL)
return NULL;
return g_strdup (g_ptr_array_index (files, 0));
}
/*
* Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#pragma once
#include <gio/gio.h>
#define FU_UEFI_DBX_DATA_URL "https://uefi.org/revocationlistfile"
gchar *fu_uefi_dbx_get_dbxupdate (GError **error);
/*
* Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <string.h>
#include <fwupd.h>
#include "fu-common.h"
#include "fu-uefi-dbx-common.h"
#include "fu-uefi-dbx-file.h"
struct _FuUefiDbxFile {
GObject parent_instance;
GPtrArray *checksums;
};
G_DEFINE_TYPE (FuUefiDbxFile, fu_uefi_dbx_file, G_TYPE_OBJECT)
static gboolean
fu_uefi_dbx_file_parse_sig_item (FuUefiDbxFile *self,
const guint8 *buf,
gsize bufsz,
gsize offset,
guint32 sig_size,
GError **error)
{
GString *sig_datastr;
fwupd_guid_t guid;
gsize sig_datasz = sig_size - sizeof(fwupd_guid_t);
g_autofree gchar *sig_owner = NULL;
g_autofree guint8 *sig_data = g_malloc0 (sig_datasz);
/* read both blocks of data */
if (!fu_memcpy_safe ((guint8 *) &guid, sizeof(guid), 0x0, /* dst */
buf, bufsz, offset, /* src */
sizeof(guid), error)) {
g_prefix_error (error, "failed to read signature GUID: ");
return FALSE;
}
if (!fu_memcpy_safe (sig_data, sig_datasz, 0x0, /* dst */
buf, bufsz, offset + sizeof(fwupd_guid_t), /* src */
sig_datasz, error)) {
g_prefix_error (error, "failed to read signature data: ");
return FALSE;
}
/* we don't care about the owner, so just store the checksum */
sig_owner = fwupd_guid_to_string (&guid, FWUPD_GUID_FLAG_MIXED_ENDIAN);
sig_datastr = g_string_new (NULL);
for (gsize j = 0; j < sig_datasz; j++)
g_string_append_printf (sig_datastr, "%02x", sig_data[j]);
g_debug ("Owner: %s, Data: %s", sig_owner, sig_datastr->str);
g_ptr_array_add (self->checksums, g_string_free (sig_datastr, FALSE));
return TRUE;
}
static gboolean
fu_uefi_dbx_file_parse_sig_list (FuUefiDbxFile *self,
const guint8 *buf,
gsize bufsz,
gsize *offset,
GError **error)
{
fwupd_guid_t guid;
gsize offset_tmp;
guint32 sig_header_size = 0;
guint32 sig_list_size = 0;
guint32 sig_size = 0;
g_autofree gchar *sig_type = NULL;
/* read EFI_SIGNATURE_LIST */
if (!fu_memcpy_safe ((guint8 *) &guid, sizeof(guid), 0x0, /* dst */
buf, bufsz, *offset, /* src */
sizeof(guid), error)) {
g_prefix_error (error, "failed to read GUID header: ");
return FALSE;
}
sig_type = fwupd_guid_to_string (&guid, FWUPD_GUID_FLAG_MIXED_ENDIAN);
if (g_strcmp0 (sig_type, "c1c41626-504c-4092-aca9-41f936934328") == 0)
g_debug ("EFI_SIGNATURE_LIST SHA256");
else if (g_strcmp0 (sig_type, "a5c059a1-94e4-4aa7-87b5-ab155c2bf072") == 0)
g_debug ("EFI_SIGNATURE_LIST X509");
else
g_debug ("EFI_SIGNATURE_LIST unknown: %s", sig_type);
if (!fu_common_read_uint32_safe (buf, bufsz, *offset + 0x10,
&sig_list_size, G_LITTLE_ENDIAN, error))
return FALSE;
if (sig_list_size < 0x1c || sig_list_size > 1024 * 1024) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"SignatureListSize invalid: 0x%x", sig_list_size);
return FALSE;
}
if (!fu_common_read_uint32_safe (buf, bufsz, *offset + 0x14,
&sig_header_size, G_LITTLE_ENDIAN, error))
return FALSE;
if (sig_header_size > 1024 * 1024) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"SignatureHeaderSize invalid: 0x%x", sig_size);
return FALSE;
}
if (!fu_common_read_uint32_safe (buf, bufsz, *offset + 0x18,
&sig_size, G_LITTLE_ENDIAN, error))
return FALSE;
if (sig_size < sizeof(fwupd_guid_t) || sig_size > 1024 * 1024) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"SignatureSize invalid: 0x%x", sig_size);
return FALSE;
}
/* header is typically unused */
offset_tmp = *offset + 0x1c + sig_header_size;
for (guint i = 0; i < (sig_list_size - 0x1c) / sig_size; i++) {
if (!fu_uefi_dbx_file_parse_sig_item (self, buf, bufsz,
offset_tmp, sig_size,
error))
return FALSE;
offset_tmp += sig_size;
}
*offset += sig_list_size;
return TRUE;
}
FuUefiDbxFile *
fu_uefi_dbx_file_new (const guint8 *buf, gsize bufsz,
FuUefiDbxFileParseFlags flags,
GError **error)
{
gsize offset_fs = 0;
g_autoptr(FuUefiDbxFile) self = g_object_new (FU_TYPE_UEFI_DBX_FILE, NULL);
/* this allows us to skip the efi permissions uint32_t or even the
* Microsoft PKCS-7 signature */
if (flags & FU_UEFI_DBX_FILE_PARSE_FLAGS_IGNORE_HEADER) {
for (gsize i = 0; i < bufsz - 5; i++) {
if (memcmp (buf + i, "\x26\x16\xc4\xc1\x4c", 5) == 0) {
g_debug ("found EFI_SIGNATURE_LIST @0x%x", (guint) i);
offset_fs = i;
break;
}
}
}
/* parse each EFI_SIGNATURE_LIST */
for (gsize offset = offset_fs; offset < bufsz;) {
if (!fu_uefi_dbx_file_parse_sig_list (self, buf, bufsz, &offset, error))
return NULL;
}
/* success */
return g_steal_pointer (&self);
}
gboolean
fu_uefi_dbx_file_has_checksum (FuUefiDbxFile *self, const gchar *checksum)
{
g_return_val_if_fail (FU_IS_UEFI_DBX_FILE (self), FALSE);
for (guint i = 0; i < self->checksums->len; i++) {
const gchar *checksums_tmp = g_ptr_array_index (self->checksums, i);
if (g_strcmp0 (checksums_tmp, checksum) == 0)
return TRUE;
}
return FALSE;
}
GPtrArray *
fu_uefi_dbx_file_get_checksums (FuUefiDbxFile *self)
{
g_return_val_if_fail (FU_IS_UEFI_DBX_FILE (self), FALSE);
return self->checksums;
}
static void
fu_uefi_dbx_file_finalize (GObject *obj)
{
FuUefiDbxFile *self = FU_UEFI_DBX_FILE (obj);
g_ptr_array_unref (self->checksums);
G_OBJECT_CLASS (fu_uefi_dbx_file_parent_class)->finalize (obj);
}
static void
fu_uefi_dbx_file_class_init (FuUefiDbxFileClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = fu_uefi_dbx_file_finalize;
}
static void
fu_uefi_dbx_file_init (FuUefiDbxFile *self)
{
self->checksums = g_ptr_array_new_with_free_func (g_free);
}