mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-10 18:23:43 +00:00

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
262 lines
8.3 KiB
C
262 lines
8.3 KiB
C
/*
|
|
* Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <fwupd.h>
|
|
#include <glib/gi18n.h>
|
|
#include <glib-unix.h>
|
|
#include <locale.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
|
|
#include "fu-common.h"
|
|
#include "fu-efivar.h"
|
|
#include "fu-uefi-dbx-common.h"
|
|
#include "fu-efi-signature-common.h"
|
|
#include "fu-efi-signature-parser.h"
|
|
|
|
/* custom return code */
|
|
#define EXIT_NOTHING_TO_DO 2
|
|
|
|
static void
|
|
fu_util_ignore_cb (const gchar *log_domain, GLogLevelFlags log_level,
|
|
const gchar *message, gpointer user_data)
|
|
{
|
|
}
|
|
|
|
static GPtrArray *
|
|
fu_dbxtool_get_siglist_system (GError **error)
|
|
{
|
|
gsize bufsz = 0;
|
|
g_autofree guint8 *buf = NULL;
|
|
if (!fu_efivar_get_data (FU_EFIVAR_GUID_SECURITY_DATABASE, "dbx",
|
|
&buf, &bufsz, NULL, error))
|
|
return NULL;
|
|
return fu_efi_signature_parser_new (buf, bufsz,
|
|
FU_EFI_SIGNATURE_PARSER_FLAGS_NONE,
|
|
error);
|
|
}
|
|
|
|
static GPtrArray *
|
|
fu_dbxtool_get_siglist_local (const gchar *filename, GError **error)
|
|
{
|
|
gsize bufsz = 0;
|
|
g_autofree guint8 *buf = NULL;
|
|
if (!g_file_get_contents (filename, (gchar **) &buf, &bufsz, error))
|
|
return NULL;
|
|
return fu_efi_signature_parser_new (buf, bufsz,
|
|
FU_EFI_SIGNATURE_PARSER_FLAGS_IGNORE_HEADER,
|
|
error);
|
|
}
|
|
|
|
int
|
|
main (int argc, char *argv[])
|
|
{
|
|
gboolean action_apply = FALSE;
|
|
gboolean action_list = FALSE;
|
|
gboolean action_version = FALSE;
|
|
gboolean force = FALSE;
|
|
gboolean verbose = FALSE;
|
|
g_autofree gchar *dbxfile = NULL;
|
|
g_autoptr(GError) error = NULL;
|
|
g_autoptr(GOptionContext) context = NULL;
|
|
g_autofree gchar *tmp = NULL;
|
|
const GOptionEntry options[] = {
|
|
{ "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose,
|
|
/* TRANSLATORS: command line option */
|
|
_("Show extra debugging information"), NULL },
|
|
{ "version", '\0', 0, G_OPTION_ARG_NONE, &action_version,
|
|
/* TRANSLATORS: command line option */
|
|
_("Show the calculated version of the dbx"), NULL },
|
|
{ "list", 'l', 0, G_OPTION_ARG_NONE, &action_list,
|
|
/* TRANSLATORS: command line option */
|
|
_("List entries in dbx"), NULL },
|
|
{ "apply", 'a', 0, G_OPTION_ARG_NONE, &action_apply,
|
|
/* TRANSLATORS: command line option */
|
|
_("Apply update files"), NULL },
|
|
{ "dbx", 'd', 0, G_OPTION_ARG_STRING, &dbxfile,
|
|
/* TRANSLATORS: command line option */
|
|
_("Specify the dbx database file"), "FILENAME" },
|
|
{ "force", 'f', 0, G_OPTION_ARG_NONE, &force,
|
|
/* TRANSLATORS: command line option */
|
|
_("Apply update even when not advised"), NULL },
|
|
{ NULL}
|
|
};
|
|
|
|
setlocale (LC_ALL, "");
|
|
|
|
bindtextdomain (GETTEXT_PACKAGE, FWUPD_LOCALEDIR);
|
|
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
|
|
textdomain (GETTEXT_PACKAGE);
|
|
|
|
/* get a action_list of the commands */
|
|
context = g_option_context_new (NULL);
|
|
g_option_context_set_description (context,
|
|
/* TRANSLATORS: description of dbxtool */
|
|
_("This tool allows an administrator to apply UEFI dbx updates."));
|
|
|
|
/* TRANSLATORS: program name */
|
|
g_set_application_name (_("UEFI dbx Utility"));
|
|
g_option_context_add_main_entries (context, options, NULL);
|
|
if (!g_option_context_parse (context, &argc, &argv, &error)) {
|
|
/* TRANSLATORS: the user didn't read the man page */
|
|
g_print ("%s: %s\n", _("Failed to parse arguments"),
|
|
error->message);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
/* set verbose? */
|
|
if (verbose) {
|
|
g_setenv ("G_MESSAGES_DEBUG", "all", FALSE);
|
|
} else {
|
|
g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG,
|
|
fu_util_ignore_cb, NULL);
|
|
}
|
|
|
|
/* list contents, either of the existing system, or an update */
|
|
if (action_list || action_version) {
|
|
guint cnt = 1;
|
|
g_autoptr(GPtrArray) dbx = NULL;
|
|
if (dbxfile != NULL) {
|
|
dbx = fu_dbxtool_get_siglist_local (dbxfile, &error);
|
|
if (dbx == NULL) {
|
|
/* TRANSLATORS: could not read existing system data */
|
|
g_printerr ("%s: %s\n", _("Failed to load local dbx"), error->message);
|
|
return EXIT_FAILURE;
|
|
}
|
|
} else {
|
|
dbx = fu_dbxtool_get_siglist_system (&error);
|
|
if (dbx == NULL) {
|
|
/* TRANSLATORS: could not read existing system data */
|
|
g_printerr ("%s: %s\n", _("Failed to load system dbx"), error->message);
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
if (action_version) {
|
|
/* TRANSLATORS: the detected version number of the dbx */
|
|
g_print ("%s: %u\n", _("Version"), fu_efi_signature_list_array_version (dbx));
|
|
return EXIT_SUCCESS;
|
|
}
|
|
for (guint j = 0; j < dbx->len; j++) {
|
|
FuEfiSignatureList *siglist = g_ptr_array_index (dbx, j);
|
|
GPtrArray *sigs = fu_efi_signature_list_get_all (siglist);
|
|
for (guint i = 0; i < sigs->len; i++) {
|
|
FuEfiSignature *sig = g_ptr_array_index (sigs, i);
|
|
g_print ("%4u: {%s} {%s} %s\n",
|
|
cnt++,
|
|
fu_efi_signature_guid_to_string (fu_efi_signature_get_owner (sig)),
|
|
fu_efi_signature_kind_to_string (fu_efi_signature_get_kind (sig)),
|
|
fu_efi_signature_get_checksum (sig));
|
|
}
|
|
}
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
#ifdef HAVE_GETUID
|
|
/* ensure root user */
|
|
if (getuid () != 0 || geteuid () != 0) {
|
|
/* TRANSLATORS: we're poking around as a power user */
|
|
g_printerr ("%s\n", _("This program may only work correctly as root"));
|
|
}
|
|
#endif
|
|
|
|
/* apply update */
|
|
if (action_apply) {
|
|
gsize bufsz = 0;
|
|
g_autofree guint8 *buf = NULL;
|
|
g_autoptr(GPtrArray) dbx_system = NULL;
|
|
g_autoptr(GPtrArray) dbx_update = NULL;
|
|
|
|
if (dbxfile == NULL) {
|
|
/* TRANSLATORS: user did not include a filename parameter */
|
|
g_printerr ("%s\n", _("Filename required"));
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
/* TRANSLATORS: reading existing dbx from the system */
|
|
g_print ("%s\n", _("Parsing system dbx…"));
|
|
dbx_system = fu_dbxtool_get_siglist_system (&error);
|
|
if (dbx_system == NULL) {
|
|
/* TRANSLATORS: could not read existing system data */
|
|
g_printerr ("%s: %s\n", _("Failed to load system dbx"), error->message);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
/* TRANSLATORS: reading new dbx from the update */
|
|
g_print ("%s\n", _("Parsing dbx update…"));
|
|
if (!g_file_get_contents (dbxfile, (gchar **) &buf, &bufsz, &error)) {
|
|
/* TRANSLATORS: could not read file */
|
|
g_printerr ("%s: %s\n", _("Failed to load local dbx"), error->message);
|
|
return EXIT_FAILURE;
|
|
}
|
|
dbx_update = fu_efi_signature_parser_new (buf, bufsz,
|
|
FU_EFI_SIGNATURE_PARSER_FLAGS_IGNORE_HEADER,
|
|
&error);
|
|
if (dbx_update == NULL) {
|
|
/* TRANSLATORS: could not parse file */
|
|
g_printerr ("%s: %s\n", _("Failed to parse local dbx"), error->message);
|
|
return EXIT_FAILURE;
|
|
}
|
|
if (dbx_update->len != 1) {
|
|
/* TRANSLATORS: could not parse file */
|
|
g_printerr ("%s: %s\n", _("Failed to extract local dbx "), error->message);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
/* check this is a newer dbx update */
|
|
if (!force && fu_efi_signature_list_array_inclusive (dbx_system, dbx_update)) {
|
|
/* TRANSLATORS: same or newer update already applied */
|
|
g_printerr ("%s\n", _("Cannot apply as dbx update has already been applied."));
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
/* check if on live media */
|
|
if (fu_common_is_live_media () && !force) {
|
|
/* TRANSLATORS: the user is using a LiveCD or LiveUSB install disk */
|
|
g_printerr ("%s\n", _("Cannot apply updates on live media"));
|
|
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,
|
|
"dbx", buf, bufsz,
|
|
FU_EFIVAR_ATTR_APPEND_WRITE |
|
|
FU_EFIVAR_ATTR_TIME_BASED_AUTHENTICATED_WRITE_ACCESS |
|
|
FU_EFIVAR_ATTR_RUNTIME_ACCESS |
|
|
FU_EFIVAR_ATTR_BOOTSERVICE_ACCESS |
|
|
FU_EFIVAR_ATTR_NON_VOLATILE,
|
|
&error)) {
|
|
/* TRANSLATORS: dbx file failed to be applied as an update */
|
|
g_printerr ("%s: %s\n", _("Failed to apply update"), error->message);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
/* TRANSLATORS: success */
|
|
g_print ("%s\n", _("Done!"));
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
/* nothing specified */
|
|
tmp = g_option_context_get_help (context, TRUE, NULL);
|
|
/* TRANSLATORS: user did not tell the tool what to do */
|
|
g_printerr ("%s\n\n%s", _("No action specified!"), tmp);
|
|
return EXIT_FAILURE;
|
|
}
|