fwupd/plugins/uefi/fu-uefi-bootmgr.c
Hsieh-Tseng Shen b47eee9a8a uefi: a new option for uefi configuration to use UEFI removable path
Per discussion of #2513, Ubuntu Core was going to use UEFI removable
path as default esp path, and it needs to change some parts around
getting the esp path and searching the shim app path. Also, a new option
"FallbacktoRemovablePath" is added into uefi.conf to be applied in this
case, and it will be false by default.
2020-11-11 13:04:46 -06:00

392 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 <gio/gio.h>
#include <stdio.h>
#include "fwupd-error.h"
#include "fu-ucs2.h"
#include "fu-uefi-bootmgr.h"
#include "fu-uefi-common.h"
#include "fu-efivar.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;
guint i = 0;
guint32 attr = 0;
g_autofree guint16 *boot_order = NULL;
g_autofree guint16 *new_boot_order = NULL;
/* get the current boot order */
if (!fu_efivar_get_data (FU_EFIVAR_GUID_EFI_GLOBAL, "BootOrder",
(guint8 **) &boot_order, &boot_order_size,
&attr, error))
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);
attr |= FU_EFIVAR_ATTR_NON_VOLATILE |
FU_EFIVAR_ATTR_BOOTSERVICE_ACCESS |
FU_EFIVAR_ATTR_RUNTIME_ACCESS;
i = boot_order_size / sizeof (guint16);
new_boot_order[i] = boot_entry;
boot_order_size += sizeof (guint16);
return fu_efivar_set_data (FU_EFIVAR_GUID_EFI_GLOBAL, "BootOrder",
(guint8 *)new_boot_order, boot_order_size,
attr, error);
}
static gboolean
fu_uefi_setup_bootnext_with_dp (const guint8 *dp_buf, guint8 *opt, gssize opt_size, GError **error)
{
const gchar *desc;
const gchar *name;
efi_load_option *loadopt = NULL;
gint rc;
gsize var_data_size = 0;
guint32 attr;
guint16 boot_next = G_MAXUINT16;
g_autofree guint8 *var_data = NULL;
g_autofree guint8 *set_entries = g_malloc0 (G_MAXUINT16);
g_autoptr(GPtrArray) names = NULL;
names = fu_efivar_get_names (FU_EFIVAR_GUID_EFI_GLOBAL, error);
if (names == NULL)
return FALSE;
for (guint i = 0; i < names->len; i++) {
gint scanned = 0;
guint16 entry = 0;
g_autofree guint8 *var_data_tmp = NULL;
g_autoptr(GError) error_local = NULL;
name = g_ptr_array_index (names, i);
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;
/* mark this as used */
set_entries[entry] = 1;
if (!fu_efivar_get_data (FU_EFIVAR_GUID_EFI_GLOBAL, name,
&var_data_tmp, &var_data_size,
&attr, &error_local)) {
g_debug ("failed to get data for name %s: %s",
name, error_local->message);
continue;
}
loadopt = (efi_load_option *)var_data_tmp;
if (!efi_loadopt_is_valid(loadopt, var_data_size)) {
g_debug ("%s -> load option was invalid", name);
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 ("%s -> '%s' : does not match", name, desc);
continue;
}
var_data = g_steal_pointer (&var_data_tmp);
boot_next = entry;
break;
}
/* already exists */
if (var_data != NULL) {
/* is different than before */
if (var_data_size != (gsize) opt_size ||
memcmp (var_data, opt, opt_size) != 0) {
g_debug ("%s -> '%s' : updating existing boot entry", name, desc);
efi_loadopt_attr_set (loadopt, LOAD_OPTION_ACTIVE);
if (!fu_efivar_set_data (FU_EFIVAR_GUID_EFI_GLOBAL,
name, opt, opt_size, attr, error)) {
g_prefix_error (error,
"could not set boot variable active: ");
return FALSE;
}
} else {
g_debug ("%s -> %s : re-using existing boot entry", name, desc);
}
/* create a new one */
} else {
g_autofree gchar *boot_next_name = NULL;
for (guint16 value = 0; value < G_MAXUINT16; value++) {
if (set_entries[value])
continue;
boot_next = value;
break;
}
if (boot_next == G_MAXUINT16) {
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);
g_debug ("%s -> creating new entry", boot_next_name);
if (!fu_efivar_set_data (FU_EFIVAR_GUID_EFI_GLOBAL,
boot_next_name, opt, opt_size,
FU_EFIVAR_ATTR_NON_VOLATILE |
FU_EFIVAR_ATTR_BOOTSERVICE_ACCESS |
FU_EFIVAR_ATTR_RUNTIME_ACCESS,
error)) {
g_prefix_error (error,
"could not set boot variable %s: ",
boot_next_name);
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 */
if (!fu_efivar_set_data (FU_EFIVAR_GUID_EFI_GLOBAL,
"BootNext", (guint8 *)&boot_next, 2,
FU_EFIVAR_ATTR_NON_VOLATILE |
FU_EFIVAR_ATTR_BOOTSERVICE_ACCESS |
FU_EFIVAR_ATTR_RUNTIME_ACCESS,
error)) {
g_prefix_error (error,
"could not set BootNext(%" G_GUINT16_FORMAT "): ",
boot_next);
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 (FuDevice *device,
const gchar *esp_path,
const gchar *description,
FuUefiBootmgrFlags flags,
GError **error)
{
const gchar *filepath;
gboolean use_fwup_path = TRUE;
gboolean secure_boot = 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_TEST") != 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 if we should use shim */
secure_boot = fu_efivar_secure_boot_enabled ();
if (secure_boot) {
/* test to make sure shim is there if we need it */
shim_app = fu_uefi_get_esp_app_path (device, esp_path, "shim", error);
if (shim_app == NULL)
return FALSE;
/* try to fallback to use UEFI removable path if the shim path doesn't exist */
if (!g_file_test (shim_app, G_FILE_TEST_EXISTS)) {
if (fu_device_get_metadata_boolean (device, "FallbacktoRemovablePath")) {
shim_app = fu_uefi_get_esp_app_path (device, esp_path, "boot", 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 (device, 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;
}
use_fwup_path = FALSE;
} else if ((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;
}
}
/* test if correct asset in place */
target_app = fu_uefi_get_esp_app_path (device, 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;
}
return fu_uefi_setup_bootnext_with_dp (dp_buf, opt, opt_size, error);
}