mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-18 00:31:54 +00:00

If we add or remove a quirk from a machine using the uefi plugin then the new setting is not used until the BootXXXX EFI key is manually removed. Rather than just writing the contents of the found key back to the key (doh) just write the new DP buffer contents if it is different from the previous buffer.
426 lines
11 KiB
C
426 lines
11 KiB
C
/*
|
|
* Copyright (C) 2018 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <efivar/efiboot.h>
|
|
#include <efivar/efivar.h>
|
|
#include <gio/gio.h>
|
|
#include <stdio.h>
|
|
|
|
#include "fwupd-error.h"
|
|
|
|
#include "fu-ucs2.h"
|
|
#include "fu-uefi-bootmgr.h"
|
|
#include "fu-uefi-common.h"
|
|
|
|
/* XXX PJFIX: this should be in efiboot-loadopt.h in efivar */
|
|
#define LOAD_OPTION_ACTIVE 0x00000001
|
|
|
|
static gboolean
|
|
fu_uefi_bootmgr_add_to_boot_order (guint16 boot_entry, GError **error)
|
|
{
|
|
gsize boot_order_size = 0;
|
|
gint rc;
|
|
guint i = 0;
|
|
guint32 attr = EFI_VARIABLE_NON_VOLATILE |
|
|
EFI_VARIABLE_BOOTSERVICE_ACCESS |
|
|
EFI_VARIABLE_RUNTIME_ACCESS;
|
|
g_autofree guint16 *boot_order = NULL;
|
|
g_autofree guint16 *new_boot_order = NULL;
|
|
|
|
/* get size of the BootOrder */
|
|
rc = efi_get_variable_size (efi_guid_global, "BootOrder", &boot_order_size);
|
|
if (rc == ENOENT) {
|
|
boot_order_size = 0;
|
|
efi_error_clear ();
|
|
} else if (rc < 0) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"efi_get_variable_size() failed");
|
|
return rc;
|
|
}
|
|
|
|
/* get the current boot order */
|
|
if (boot_order_size != 0) {
|
|
rc = efi_get_variable (efi_guid_global, "BootOrder",
|
|
(guint8 **)&boot_order, &boot_order_size,
|
|
&attr);
|
|
if (rc < 0) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"efi_get_variable(BootOrder) failed");
|
|
return FALSE;
|
|
}
|
|
|
|
/* already set next */
|
|
for (i = 0; i < boot_order_size / sizeof (guint16); i++) {
|
|
guint16 val = boot_order[i];
|
|
if (val == boot_entry)
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
/* add the new boot index to the end of the list */
|
|
new_boot_order = g_malloc0 (boot_order_size + sizeof (guint16));
|
|
if (boot_order_size != 0)
|
|
memcpy (new_boot_order, boot_order, boot_order_size);
|
|
|
|
i = boot_order_size / sizeof (guint16);
|
|
new_boot_order[i] = boot_entry;
|
|
boot_order_size += sizeof (guint16);
|
|
rc = efi_set_variable(efi_guid_global, "BootOrder",
|
|
(guint8 *)new_boot_order, boot_order_size,
|
|
attr, 0644);
|
|
if (rc < 0) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"efi_set_variable(BootOrder) failed");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_uefi_setup_bootnext_with_dp (const guint8 *dp_buf, guint8 *opt, gssize opt_size, GError **error)
|
|
{
|
|
efi_guid_t *guid = NULL;
|
|
efi_load_option *loadopt = NULL;
|
|
gchar *name = NULL;
|
|
gint rc;
|
|
gint set_entries[0x10000 / sizeof(gint)] = {0,};
|
|
gsize var_data_size = 0;
|
|
guint16 real_boot16;
|
|
guint32 attr;
|
|
guint32 boot_next = 0x10000;
|
|
g_autofree guint8 *var_data = NULL;
|
|
|
|
while ((rc = efi_get_next_variable_name (&guid, &name)) > 0) {
|
|
const gchar *desc;
|
|
gint div, mod;
|
|
gint scanned = 0;
|
|
guint16 entry = 0;
|
|
g_autofree guint8 *var_data_tmp = NULL;
|
|
|
|
if (efi_guid_cmp (guid, &efi_guid_global) != 0)
|
|
continue;
|
|
rc = sscanf (name, "Boot%hX%n", &entry, &scanned);
|
|
if (rc < 0) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"failed to parse Boot entry %s", name);
|
|
return FALSE;
|
|
}
|
|
if (rc != 1)
|
|
continue;
|
|
if (scanned != 8)
|
|
continue;
|
|
|
|
div = entry / (sizeof(set_entries[0]) * 8);
|
|
mod = entry % (sizeof(set_entries[0]) * 8);
|
|
|
|
set_entries[div] |= 1 << mod;
|
|
|
|
rc = efi_get_variable (*guid, name, &var_data_tmp, &var_data_size, &attr);
|
|
if (rc < 0) {
|
|
g_debug ("efi_get_variable(%s) failed", name);
|
|
continue;
|
|
}
|
|
|
|
loadopt = (efi_load_option *)var_data_tmp;
|
|
if (!efi_loadopt_is_valid(loadopt, var_data_size)) {
|
|
g_debug ("load option was invalid");
|
|
continue;
|
|
}
|
|
|
|
desc = (const gchar *) efi_loadopt_desc (loadopt, var_data_size);
|
|
if (g_strcmp0 (desc, "Linux Firmware Updater") != 0 &&
|
|
g_strcmp0 (desc, "Linux-Firmware-Updater") != 0) {
|
|
g_debug ("description does not match");
|
|
continue;
|
|
}
|
|
|
|
var_data = g_steal_pointer (&var_data_tmp);
|
|
boot_next = entry;
|
|
efi_error_clear ();
|
|
break;
|
|
}
|
|
if (rc < 0) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"failed to find boot variable");
|
|
return FALSE;
|
|
}
|
|
|
|
/* already exists */
|
|
if (var_data != NULL) {
|
|
|
|
/* is different than before */
|
|
if (var_data_size != (gsize) opt_size ||
|
|
memcmp (var_data, opt, opt_size) != 0) {
|
|
efi_loadopt_attr_set (loadopt, LOAD_OPTION_ACTIVE);
|
|
rc = efi_set_variable (*guid, name, opt, opt_size, attr, 0644);
|
|
if (rc < 0) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"could not set boot variable active");
|
|
return FALSE;
|
|
}
|
|
}
|
|
/* create a new one */
|
|
} else {
|
|
g_autofree gchar *boot_next_name = NULL;
|
|
for (guint32 value = 0; value < 0x10000; value++) {
|
|
gint div = value / (sizeof(set_entries[0]) * 8);
|
|
gint mod = value % (sizeof(set_entries[0]) * 8);
|
|
if (set_entries[div] & (1 << mod))
|
|
continue;
|
|
boot_next = value;
|
|
break;
|
|
}
|
|
if (boot_next >= 0x10000) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"no free boot variables (tried %x)",
|
|
boot_next);
|
|
return FALSE;
|
|
}
|
|
boot_next_name = g_strdup_printf ("Boot%04X",
|
|
(guint) (boot_next & 0xffff));
|
|
rc = efi_set_variable (efi_guid_global, boot_next_name, opt, opt_size,
|
|
EFI_VARIABLE_NON_VOLATILE |
|
|
EFI_VARIABLE_BOOTSERVICE_ACCESS |
|
|
EFI_VARIABLE_RUNTIME_ACCESS,
|
|
0644);
|
|
if (rc < 0) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"could not set boot variable %s: %d",
|
|
boot_next_name, rc);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* TODO: conditionalize this on the UEFI version? */
|
|
if(!fu_uefi_bootmgr_add_to_boot_order (boot_next, error))
|
|
return FALSE;
|
|
|
|
/* set the boot next */
|
|
real_boot16 = boot_next;
|
|
rc = efi_set_variable (efi_guid_global, "BootNext", (guint8 *)&real_boot16, 2,
|
|
EFI_VARIABLE_NON_VOLATILE |
|
|
EFI_VARIABLE_BOOTSERVICE_ACCESS |
|
|
EFI_VARIABLE_RUNTIME_ACCESS,
|
|
0644);
|
|
if (rc < 0) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"could not set BootNext(%" G_GUINT16_FORMAT ")",
|
|
real_boot16);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_uefi_cmp_asset (const gchar *source, const gchar *target)
|
|
{
|
|
gsize len = 0;
|
|
g_autofree gchar *source_checksum = NULL;
|
|
g_autofree gchar *source_data = NULL;
|
|
g_autofree gchar *target_checksum = NULL;
|
|
g_autofree gchar *target_data = NULL;
|
|
|
|
/* nothing in target yet */
|
|
if (!g_file_test (target, G_FILE_TEST_EXISTS))
|
|
return FALSE;
|
|
|
|
/* test if the file needs to be updated */
|
|
if (!g_file_get_contents (source, &source_data, &len, NULL))
|
|
return FALSE;
|
|
source_checksum = g_compute_checksum_for_data (G_CHECKSUM_SHA256,
|
|
(guchar *) source_data, len);
|
|
if (!g_file_get_contents (target, &target_data, &len, NULL))
|
|
return FALSE;
|
|
target_checksum = g_compute_checksum_for_data (G_CHECKSUM_SHA256,
|
|
(guchar *) target_data, len);
|
|
return g_strcmp0 (target_checksum, source_checksum) == 0;
|
|
}
|
|
|
|
static gboolean
|
|
fu_uefi_copy_asset (const gchar *source, const gchar *target, GError **error)
|
|
{
|
|
g_autoptr(GFile) source_file = g_file_new_for_path (source);
|
|
g_autoptr(GFile) target_file = g_file_new_for_path (target);
|
|
|
|
if (!g_file_copy (source_file,
|
|
target_file,
|
|
G_FILE_COPY_OVERWRITE,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
error)) {
|
|
g_prefix_error (error, "Failed to copy %s to %s: ",
|
|
source, target);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_uefi_bootmgr_bootnext (const gchar *esp_path,
|
|
const gchar *description,
|
|
FuUefiBootmgrFlags flags,
|
|
GError **error)
|
|
{
|
|
const gchar *filepath;
|
|
gboolean use_fwup_path = FALSE;
|
|
gsize loader_sz = 0;
|
|
gssize opt_size = 0;
|
|
gssize sz, dp_size = 0;
|
|
guint32 attributes = LOAD_OPTION_ACTIVE;
|
|
g_autofree guint16 *loader_str = NULL;
|
|
g_autofree gchar *label = NULL;
|
|
g_autofree gchar *shim_app = NULL;
|
|
g_autofree gchar *shim_cpy = NULL;
|
|
g_autofree guint8 *dp_buf = NULL;
|
|
g_autofree guint8 *opt = NULL;
|
|
g_autofree gchar *source_app = NULL;
|
|
g_autofree gchar *target_app = NULL;
|
|
|
|
/* skip for self tests */
|
|
if (g_getenv ("FWUPD_UEFI_ESP_PATH") != NULL)
|
|
return TRUE;
|
|
|
|
/* if secure boot was turned on this might need to be installed separately */
|
|
source_app = fu_uefi_get_built_app_path (error);
|
|
if (source_app == NULL)
|
|
return FALSE;
|
|
|
|
/* test to make sure shim is there if we need it */
|
|
shim_app = fu_uefi_get_esp_app_path (esp_path, "shim", error);
|
|
if (shim_app == NULL)
|
|
return FALSE;
|
|
if (g_file_test (shim_app, G_FILE_TEST_EXISTS)) {
|
|
/* use a custom copy of shim for firmware updates */
|
|
if (flags & FU_UEFI_BOOTMGR_FLAG_USE_SHIM_UNIQUE) {
|
|
shim_cpy = fu_uefi_get_esp_app_path (esp_path, "shimfwupd", error);
|
|
if (shim_cpy == NULL)
|
|
return FALSE;
|
|
if (!fu_uefi_cmp_asset (shim_app, shim_cpy)) {
|
|
if (!fu_uefi_copy_asset (shim_app, shim_cpy, error))
|
|
return FALSE;
|
|
}
|
|
filepath = shim_cpy;
|
|
} else {
|
|
filepath = shim_app;
|
|
}
|
|
} else {
|
|
if (fu_uefi_secure_boot_enabled () &&
|
|
(flags & FU_UEFI_BOOTMGR_FLAG_USE_SHIM_FOR_SB) > 0) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_BROKEN_SYSTEM,
|
|
"Secure boot is enabled, but shim isn't installed to the EFI system partition");
|
|
return FALSE;
|
|
}
|
|
use_fwup_path = TRUE;
|
|
}
|
|
|
|
/* test if correct asset in place */
|
|
target_app = fu_uefi_get_esp_app_path (esp_path, "fwupd", error);
|
|
if (target_app == NULL)
|
|
return FALSE;
|
|
if (!fu_uefi_cmp_asset (source_app, target_app)) {
|
|
if (!fu_uefi_copy_asset (source_app, target_app, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* no shim, so use this directly */
|
|
if (use_fwup_path)
|
|
filepath = target_app;
|
|
|
|
/* generate device path for target */
|
|
sz = efi_generate_file_device_path (dp_buf, dp_size, filepath,
|
|
EFIBOOT_OPTIONS_IGNORE_FS_ERROR|
|
|
EFIBOOT_ABBREV_HD);
|
|
if (sz < 0) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"efi_generate_file_device_path(%s) failed",
|
|
filepath);
|
|
return FALSE;
|
|
}
|
|
|
|
/* add the fwupdx64.efi ESP path as the shim loadopt data */
|
|
dp_size = sz;
|
|
dp_buf = g_malloc0 (dp_size);
|
|
if (!use_fwup_path) {
|
|
g_autofree gchar *fwup_fs_basename = g_path_get_basename (target_app);
|
|
g_autofree gchar *fwup_esp_path = g_strdup_printf ("\\%s", fwup_fs_basename);
|
|
loader_str = fu_uft8_to_ucs2 (fwup_esp_path, -1);
|
|
loader_sz = fu_ucs2_strlen (loader_str, -1) * 2;
|
|
if (loader_sz)
|
|
loader_sz += 2;
|
|
}
|
|
|
|
sz = efi_generate_file_device_path (dp_buf, dp_size, filepath,
|
|
EFIBOOT_OPTIONS_IGNORE_FS_ERROR|
|
|
EFIBOOT_ABBREV_HD);
|
|
if (sz != dp_size) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"efi_generate_file_device_path(%s) failed",
|
|
filepath);
|
|
return FALSE;
|
|
}
|
|
|
|
label = g_strdup (description);
|
|
sz = efi_loadopt_create (opt, opt_size, attributes,
|
|
(efidp)dp_buf, dp_size,
|
|
(guint8 *)label,
|
|
(guint8 *)loader_str, loader_sz);
|
|
if (sz < 0) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"efi_loadopt_create(%s) failed",
|
|
label);
|
|
return FALSE;
|
|
}
|
|
opt = g_malloc0 (sz);
|
|
opt_size = sz;
|
|
sz = efi_loadopt_create (opt, opt_size, attributes,
|
|
(efidp)dp_buf, dp_size,
|
|
(guint8 *)label,
|
|
(guint8 *)loader_str, loader_sz);
|
|
if (sz != opt_size) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"loadopt size was unreasonable.");
|
|
return FALSE;
|
|
}
|
|
if (!fu_uefi_setup_bootnext_with_dp (dp_buf, opt, opt_size, error))
|
|
return FALSE;
|
|
efi_error_clear();
|
|
|
|
return TRUE;
|
|
}
|