mirror of
https://github.com/systemd/systemd.git
synced 2026-11-06 05:17:24 +00:00
boot: add support for installing fwupd capsules
fwupd prepares capsules in the ESP and corresponding non-volatile variables that describe them. It currently ships an EFI binary that parses and applies them, but the intention is to deprecate it and move the functionality into bootloaders. A new volatile variable can be set by a bootloader that supports this workflow, and if fwupd finds it, it will no longer use its own EFI binary. Port the implementation from https://github.com/fwupd/fwupd-efi/blob/main/efi/fwupdate.c and adapt it. The high level flow is defined at https://github.com/fwupd/fwupd-efi/blob/main/SECURITY.md Fixes https://github.com/systemd/systemd/issues/36643
This commit is contained in:
1 parent
ca50a3cd2f
commit
45b53d7fbf
14 files changed
+726
-60
No files matched your search
@@ -148,6 +148,7 @@ Variables will be listed below using the Linux efivarfs naming,
|
||||
* `1 << 21` → The boot loader measures SMBIOS information into a TPM2 PCR and reports the PCR index in the
|
||||
EFI variable `LoaderPcrSMBIOS-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f`.
|
||||
* `1 << 22` → The boot loader honours `LoaderFactoryResetVariables` when set.
|
||||
* `1 << 23` → The boot loader supports installing firmware update capsules prepared by fwupd.
|
||||
|
||||
* The EFI variable `LoaderFactoryResetVariables-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f`
|
||||
contains a boolean, formatted as a NUL-terminated UTF-16 string
|
||||
|
||||
@@ -98,6 +98,14 @@
|
||||
|
||||
<listitem><para>The boot manager allows for Secure Boot variables to be enrolled if the UEFI firmware is
|
||||
in setup mode. Additionally, variables can be automatically enrolled if configured.</para></listitem>
|
||||
|
||||
<listitem><para>The boot manager can apply UEFI firmware update capsules prepared by
|
||||
<command>fwupd</command> at boot, without a separate EFI updater. Capsule updates require UEFI 2.0 or
|
||||
newer firmware. The boot manager sets the
|
||||
<varname>BootloaderSupportsFwupd</varname> EFI variable to advertise support for the
|
||||
<ulink url="https://github.com/fwupd/fwupd/tree/main/plugins/uefi-capsule#3rd-party-bootloader">fwupd
|
||||
bootloader protocol</ulink>. Only capsules on the disk containing the boot manager are processed,
|
||||
update variables and capsules on other disks are left untouched.</para></listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para><citerefentry><refentrytitle>bootctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||
|
||||
+19
-3
@@ -13,6 +13,7 @@
|
||||
#include "efivar-erase.h"
|
||||
#include "efivars.h"
|
||||
#include "export-vars.h"
|
||||
#include "fwupdate.h"
|
||||
#include "graphics.h"
|
||||
#include "initrd.h"
|
||||
#include "iovec-util.h"
|
||||
@@ -3263,9 +3264,10 @@ static EFI_STATUS secure_boot_discover_keys(Config *config, EFI_FILE *root_dir)
|
||||
|
||||
static void export_loader_variables(
|
||||
EFI_LOADED_IMAGE_PROTOCOL *loaded_image,
|
||||
uint64_t init_usec) {
|
||||
uint64_t init_usec,
|
||||
bool fwupdate_available) {
|
||||
|
||||
static const uint64_t loader_features =
|
||||
uint64_t loader_features =
|
||||
EFI_LOADER_FEATURE_CONFIG_TIMEOUT |
|
||||
EFI_LOADER_FEATURE_CONFIG_TIMEOUT_ONE_SHOT |
|
||||
EFI_LOADER_FEATURE_ENTRY_DEFAULT |
|
||||
@@ -3289,6 +3291,7 @@ static void export_loader_variables(
|
||||
EFI_LOADER_FEATURE_KEYBOARD_LAYOUT |
|
||||
EFI_LOADER_FEATURE_SMBIOS_MEASURED |
|
||||
EFI_LOADER_FEATURE_FACTORY_RESET_VARIABLES |
|
||||
(fwupdate_available ? EFI_LOADER_FEATURE_FIRMWARE_UPDATE : 0) |
|
||||
0;
|
||||
|
||||
assert(loaded_image);
|
||||
@@ -3296,6 +3299,13 @@ static void export_loader_variables(
|
||||
efivar_set_time_usec(MAKE_GUID_PTR(LOADER), u"LoaderTimeInitUSec", init_usec);
|
||||
(void) efivar_set_str16(MAKE_GUID_PTR(LOADER), u"LoaderInfo", u"systemd-boot " GIT_VERSION, /* flags= */ 0);
|
||||
(void) efivar_set_uint64_le(MAKE_GUID_PTR(LOADER), u"LoaderFeatures", loader_features, /* flags= */ 0);
|
||||
|
||||
/* Defined by fwupd https://github.com/fwupd/fwupd/tree/main/plugins/uefi-capsule#3rd-party-bootloader */
|
||||
if (FLAGS_SET(loader_features, EFI_LOADER_FEATURE_FIRMWARE_UPDATE)) {
|
||||
uint8_t value = 1;
|
||||
(void) efivar_set_raw(MAKE_GUID_PTR(FWUPDATE), u"BootloaderSupportsFwupd", &value, sizeof(value), /* flags= */ 0);
|
||||
} else
|
||||
(void) efivar_unset(MAKE_GUID_PTR(FWUPDATE), u"BootloaderSupportsFwupd", /* flags= */ 0);
|
||||
}
|
||||
|
||||
static void config_add_system_entries(Config *config) {
|
||||
@@ -3418,6 +3428,7 @@ static EFI_STATUS discover_root_dir(EFI_LOADED_IMAGE_PROTOCOL *loaded_image, EFI
|
||||
static EFI_STATUS run(EFI_HANDLE image) {
|
||||
EFI_LOADED_IMAGE_PROTOCOL *loaded_image;
|
||||
_cleanup_file_close_ EFI_FILE *root_dir = NULL;
|
||||
_cleanup_free_ EFI_DEVICE_PATH *fwupdate_disk_path = NULL;
|
||||
_cleanup_(config_free) Config config = {};
|
||||
EFI_STATUS err;
|
||||
uint64_t init_usec;
|
||||
@@ -3432,6 +3443,11 @@ static EFI_STATUS run(EFI_HANDLE image) {
|
||||
if (err != EFI_SUCCESS)
|
||||
return log_error_status(err, "Error getting a LoadedImageProtocol handle: %m");
|
||||
|
||||
/* First, apply any pending fwupd capsules from the boot disk. */
|
||||
err = fwupdate_get_boot_disk_path(loaded_image->DeviceHandle, &fwupdate_disk_path);
|
||||
if (err == EFI_SUCCESS)
|
||||
fwupdate_install(fwupdate_disk_path);
|
||||
|
||||
err = discover_root_dir(loaded_image, &root_dir);
|
||||
if (err != EFI_SUCCESS) {
|
||||
log_error_status(err, "Unable to open root directory: %m");
|
||||
@@ -3454,7 +3470,7 @@ static EFI_STATUS run(EFI_HANDLE image) {
|
||||
shim_retain_protocol();
|
||||
|
||||
export_common_variables(loaded_image);
|
||||
export_loader_variables(loaded_image, init_usec);
|
||||
export_loader_variables(loaded_image, init_usec, fwupdate_disk_path != NULL);
|
||||
|
||||
/* Measure SMBIOS data into PCR 1. This is done early, and suppressed if sd-stub later runs in
|
||||
* the same boot (and vice versa), via the LoaderPcrSMBIOS EFI variable. */
|
||||
|
||||
@@ -213,3 +213,25 @@ size_t device_path_size(const EFI_DEVICE_PATH *dp) {
|
||||
EFI_DEVICE_PATH *device_path_dup(const EFI_DEVICE_PATH *dp) {
|
||||
return xmemdup(ASSERT_PTR(dp), device_path_size(dp));
|
||||
}
|
||||
|
||||
EFI_STATUS device_path_split_file_path(
|
||||
const EFI_DEVICE_PATH *dp,
|
||||
EFI_DEVICE_PATH **ret_device_path,
|
||||
const EFI_DEVICE_PATH **ret_file_path) {
|
||||
|
||||
assert(dp);
|
||||
assert(ret_device_path);
|
||||
assert(ret_file_path);
|
||||
|
||||
/* Return an owned, terminated copy of the device prefix and a borrowed pointer to the file suffix. */
|
||||
for (const EFI_DEVICE_PATH *node = dp; !device_path_is_end(node); node = device_path_next_node(node)) {
|
||||
if (node->Type != MEDIA_DEVICE_PATH || node->SubType != MEDIA_FILEPATH_DP)
|
||||
continue;
|
||||
|
||||
*ret_device_path = device_path_replace_node(dp, node, /* new_node= */ NULL);
|
||||
*ret_file_path = node;
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
return EFI_NOT_FOUND;
|
||||
}
|
||||
@@ -38,3 +38,8 @@ size_t device_path_size(const EFI_DEVICE_PATH *dp);
|
||||
bool device_path_is_valid(const EFI_DEVICE_PATH *dp, size_t size);
|
||||
|
||||
EFI_DEVICE_PATH *device_path_dup(const EFI_DEVICE_PATH *dp);
|
||||
|
||||
EFI_STATUS device_path_split_file_path(
|
||||
const EFI_DEVICE_PATH *dp,
|
||||
EFI_DEVICE_PATH **ret_device_path,
|
||||
const EFI_DEVICE_PATH **ret_file_path);
|
||||
@@ -1,10 +1,14 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "efi-efivars.h"
|
||||
#include "efi-log.h"
|
||||
#include "efi-string.h"
|
||||
#include "ticks.h"
|
||||
#include "util.h"
|
||||
|
||||
/* Bound enumeration in case firmware never returns EFI_NOT_FOUND. */
|
||||
#define ENUMERATE_VARIABLES_MAX 65536U
|
||||
|
||||
EFI_STATUS efivar_set_raw(const EFI_GUID *vendor, const char16_t *name, const void *buf, size_t size, uint32_t flags) {
|
||||
assert(vendor);
|
||||
assert(name);
|
||||
@@ -275,6 +279,53 @@ void efivar_set_time_usec(const EFI_GUID *vendor, const char16_t *name, uint64_t
|
||||
efivar_set_str16(vendor, name, str, /* flags= */ 0);
|
||||
}
|
||||
|
||||
EFI_STATUS efivar_get_names(const EFI_GUID *vendor, char16_t ***ret) {
|
||||
_cleanup_strv_free_ char16_t **names = NULL;
|
||||
size_t n = 0, buf_size = 256 * sizeof(char16_t);
|
||||
_cleanup_free_ char16_t *buf = NULL;
|
||||
EFI_GUID guid = {};
|
||||
EFI_STATUS err;
|
||||
|
||||
assert(vendor);
|
||||
assert(ret);
|
||||
|
||||
/* Deleting variables while enumerating them is undefined. Collect the names first so callers can
|
||||
* modify their variables afterwards. */
|
||||
buf = xmalloc(buf_size);
|
||||
buf[0] = 0;
|
||||
|
||||
for (unsigned i = 0;; i++) {
|
||||
if (i >= ENUMERATE_VARIABLES_MAX)
|
||||
return log_debug_status(EFI_ABORTED, "Too many EFI variables, refusing to enumerate further.");
|
||||
|
||||
size_t size = buf_size;
|
||||
err = RT->GetNextVariableName(&size, buf, &guid);
|
||||
if (err == EFI_NOT_FOUND)
|
||||
break;
|
||||
if (err == EFI_BUFFER_TOO_SMALL) {
|
||||
if (size <= buf_size)
|
||||
return log_debug_status(err, "Firmware reported invalid EFI variable name size.");
|
||||
|
||||
/* Retain the current name and GUID: enumeration continues from them. */
|
||||
buf = xrealloc(buf, buf_size, size);
|
||||
buf_size = size;
|
||||
continue;
|
||||
}
|
||||
if (err != EFI_SUCCESS)
|
||||
return err;
|
||||
|
||||
if (!efi_guid_equal(&guid, vendor))
|
||||
continue;
|
||||
|
||||
names = xrealloc(names, n == 0 ? 0 : (n + 1) * sizeof(char16_t*), (n + 2) * sizeof(char16_t*));
|
||||
names[n++] = xstrdup16(buf);
|
||||
names[n] = NULL;
|
||||
}
|
||||
|
||||
*ret = TAKE_PTR(names);
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
uint64_t get_os_indications_supported(void) {
|
||||
uint64_t osind;
|
||||
EFI_STATUS err;
|
||||
|
||||
@@ -36,5 +36,6 @@ EFI_STATUS efivar_get_boolean_str16(const EFI_GUID *vendor, const char16_t *name
|
||||
EFI_STATUS efivar_get_uint32_le(const EFI_GUID *vendor, const char16_t *name, uint32_t *ret);
|
||||
EFI_STATUS efivar_get_uint64_le(const EFI_GUID *vendor, const char16_t *name, uint64_t *ret);
|
||||
EFI_STATUS efivar_get_boolean_u8(const EFI_GUID *vendor, const char16_t *name, bool *ret);
|
||||
EFI_STATUS efivar_get_names(const EFI_GUID *vendor, char16_t ***ret);
|
||||
|
||||
uint64_t get_os_indications_supported(void);
|
||||
+24
-2
@@ -254,6 +254,21 @@ typedef struct {
|
||||
uint32_t Reserved;
|
||||
} EFI_TABLE_HEADER;
|
||||
|
||||
typedef struct {
|
||||
EFI_GUID CapsuleGuid;
|
||||
uint32_t HeaderSize;
|
||||
uint32_t Flags;
|
||||
uint32_t CapsuleImageSize;
|
||||
} EFI_CAPSULE_HEADER;
|
||||
|
||||
typedef struct {
|
||||
uint64_t Length;
|
||||
union {
|
||||
EFI_PHYSICAL_ADDRESS DataBlock;
|
||||
EFI_PHYSICAL_ADDRESS ContinuationPointer;
|
||||
};
|
||||
} EFI_CAPSULE_BLOCK_DESCRIPTOR;
|
||||
|
||||
typedef struct {
|
||||
EFI_TABLE_HEADER Hdr;
|
||||
void *RaiseTPL;
|
||||
@@ -425,8 +440,15 @@ typedef struct {
|
||||
EFI_STATUS ResetStatus,
|
||||
size_t DataSize,
|
||||
void *ResetData);
|
||||
void *UpdateCapsule;
|
||||
void *QueryCapsuleCapabilities;
|
||||
EFI_STATUS (EFIAPI *UpdateCapsule)(
|
||||
EFI_CAPSULE_HEADER **CapsuleHeaderArray,
|
||||
size_t CapsuleCount,
|
||||
EFI_PHYSICAL_ADDRESS ScatterGatherList);
|
||||
EFI_STATUS (EFIAPI *QueryCapsuleCapabilities)(
|
||||
EFI_CAPSULE_HEADER **CapsuleHeaderArray,
|
||||
size_t CapsuleCount,
|
||||
uint64_t *MaximumCapsuleSize,
|
||||
EFI_RESET_TYPE *ResetType);
|
||||
void *QueryVariableInfo;
|
||||
} EFI_RUNTIME_SERVICES;
|
||||
|
||||
|
||||
+2
-55
@@ -3,64 +3,11 @@
|
||||
#include "console.h"
|
||||
#include "efi-efivars.h"
|
||||
#include "efi-log.h"
|
||||
#include "efi-string.h"
|
||||
#include "efivar-erase.h"
|
||||
#include "proto/simple-text-io.h"
|
||||
#include "strv.h"
|
||||
#include "util.h"
|
||||
|
||||
/* Upper limit on the number of variables we enumerate, as protection against firmware that never returns
|
||||
* EFI_NOT_FOUND from GetNextVariableName() */
|
||||
#define ENUMERATE_VARIABLES_MAX 65536U
|
||||
|
||||
static EFI_STATUS collect_variables(const EFI_GUID *vendor, char16_t ***ret) {
|
||||
_cleanup_strv_free_ char16_t **l = NULL;
|
||||
size_t n = 0, buf_size = 256 * sizeof(char16_t);
|
||||
_cleanup_free_ char16_t *buf = NULL;
|
||||
EFI_GUID guid = {};
|
||||
EFI_STATUS err;
|
||||
|
||||
assert(vendor);
|
||||
assert(ret);
|
||||
|
||||
/* Note that the UEFI spec leaves undefined what happens if variables are deleted while they are
|
||||
* enumerated. Hence we first collect all names, and only delete them afterwards. */
|
||||
|
||||
buf = xmalloc(buf_size);
|
||||
buf[0] = 0; /* Empty string starts enumeration */
|
||||
|
||||
for (unsigned i = 0;; i++) {
|
||||
if (i >= ENUMERATE_VARIABLES_MAX)
|
||||
return log_error_status(EFI_ABORTED, "Too many EFI variables, refusing to enumerate further.");
|
||||
|
||||
size_t size = buf_size;
|
||||
err = RT->GetNextVariableName(&size, buf, &guid);
|
||||
if (err == EFI_NOT_FOUND)
|
||||
break;
|
||||
if (err == EFI_BUFFER_TOO_SMALL) {
|
||||
if (size <= buf_size)
|
||||
return log_error_status(err, "Firmware reported invalid EFI variable name size.");
|
||||
|
||||
/* Retain the current name, enumeration continues from it */
|
||||
buf = xrealloc(buf, buf_size, size);
|
||||
buf_size = size;
|
||||
continue;
|
||||
}
|
||||
if (err != EFI_SUCCESS)
|
||||
return log_error_status(err, "Failed to enumerate EFI variables: %m");
|
||||
|
||||
if (!efi_guid_equal(&guid, vendor))
|
||||
continue;
|
||||
|
||||
l = xrealloc(l, n == 0 ? 0 : (n + 1) * sizeof(char16_t*), (n + 2) * sizeof(char16_t*));
|
||||
l[n++] = xstrdup16(buf);
|
||||
l[n] = NULL;
|
||||
}
|
||||
|
||||
*ret = TAKE_PTR(l);
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
static EFI_STATUS erase_variable(const EFI_GUID *vendor, const char16_t *name) {
|
||||
uint32_t attributes;
|
||||
EFI_STATUS err;
|
||||
@@ -90,9 +37,9 @@ EFI_STATUS efivar_erase_namespace(const EFI_GUID *vendor, const char16_t *const
|
||||
|
||||
assert(vendor);
|
||||
|
||||
err = collect_variables(vendor, &l);
|
||||
err = efivar_get_names(vendor, &l);
|
||||
if (err != EFI_SUCCESS)
|
||||
return err;
|
||||
return log_error_status(err, "Failed to enumerate EFI variables: %m");
|
||||
|
||||
STRV_FOREACH(i, l) {
|
||||
if (strv_contains((char16_t**) keep, *i))
|
||||
|
||||
@@ -0,0 +1,581 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
/* Ported from:
|
||||
* https://github.com/fwupd/fwupd-efi/blob/1a794fc7ce481176926687f4e88e6e0c8d5e9699/efi/fwupdate.c
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*/
|
||||
|
||||
#include "device-path-util.h"
|
||||
#include "efi-efivars.h"
|
||||
#include "efi-log.h"
|
||||
#include "efi-string.h"
|
||||
#include "efi.h"
|
||||
#include "fwupdate.h"
|
||||
#include "memory-util.h"
|
||||
#include "proto/block-io.h"
|
||||
#include "proto/graphics-output.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
#include "util.h"
|
||||
|
||||
/* The following types implement the protocol for windows compatible graphical firmware updates. This
|
||||
* extended header is inserted right after the capsule header when the indicated GUID is used.
|
||||
* https://learn.microsoft.com/en-us/windows-hardware/drivers/bringup/boot-screen-components
|
||||
*/
|
||||
#define UX_CAPSULE_GUID {0x3b8c8162, 0x188c, 0x46a4, {0xae, 0xc9, 0xbe, 0x43, 0xf1, 0xd6, 0x56, 0x97} }
|
||||
|
||||
typedef struct {
|
||||
uint8_t version;
|
||||
uint8_t checksum;
|
||||
uint8_t image_type;
|
||||
uint8_t reserved;
|
||||
uint32_t mode;
|
||||
uint32_t x_offset;
|
||||
uint32_t y_offset;
|
||||
} _packed_ UX_CAPSULE_HEADER;
|
||||
|
||||
/* The following types are defined by fwupd:
|
||||
* https://github.com/fwupd/fwupd-efi/blob/1a794fc7ce481176926687f4e88e6e0c8d5e9699/efi/fwup-efi.h
|
||||
* fwupd uses this struct for the EFI variables it creates in userspace.
|
||||
*/
|
||||
#define FWUPDATE_INFO_VERSION 7U
|
||||
#define FWUPDATE_ATTEMPT_UPDATE 0x00000001
|
||||
#define FWUPDATE_ATTEMPTED 0x00000002
|
||||
|
||||
typedef struct {
|
||||
uint32_t update_info_version;
|
||||
EFI_GUID guid;
|
||||
uint32_t capsule_flags;
|
||||
uint64_t hw_inst;
|
||||
EFI_TIME time_attempted;
|
||||
uint32_t status;
|
||||
EFI_DEVICE_PATH dp;
|
||||
} _packed_ FWUP_UPDATE_INFO;
|
||||
|
||||
typedef struct {
|
||||
char16_t *name;
|
||||
uint32_t attrs;
|
||||
size_t size;
|
||||
FWUP_UPDATE_INFO *info;
|
||||
EFI_HANDLE device_handle;
|
||||
const EFI_DEVICE_PATH *file_dp; /* Borrowed from info. */
|
||||
} FwupUpdateTable;
|
||||
|
||||
static bool fwupdate_supported(void) {
|
||||
/* EFI 1.x runtime tables end at ResetSystem; capsule services were added in UEFI 2.0. */
|
||||
return RT->Hdr.Revision >= (2U << 16) &&
|
||||
RT->Hdr.HeaderSize >= offsetof(EFI_RUNTIME_SERVICES, QueryCapsuleCapabilities) +
|
||||
sizeof(RT->QueryCapsuleCapabilities) &&
|
||||
RT->UpdateCapsule && RT->QueryCapsuleCapabilities;
|
||||
}
|
||||
|
||||
static FwupUpdateTable *fwup_update_table_free(FwupUpdateTable *update) {
|
||||
if (!update)
|
||||
return NULL;
|
||||
|
||||
free(update->name);
|
||||
free(update->info);
|
||||
|
||||
return mfree(update);
|
||||
}
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(FwupUpdateTable *, fwup_update_table_free);
|
||||
|
||||
static void fwup_update_table_free_many(FwupUpdateTable **a, size_t n) {
|
||||
assert(a || n == 0);
|
||||
|
||||
FOREACH_ARRAY(i, a, n)
|
||||
fwup_update_table_free(*i);
|
||||
|
||||
free(a);
|
||||
}
|
||||
|
||||
static void capsule_free_many(EFI_CAPSULE_HEADER **a, size_t n) {
|
||||
assert(a || n == 0);
|
||||
|
||||
FOREACH_ARRAY(i, a, n)
|
||||
free(*i);
|
||||
|
||||
free(a);
|
||||
}
|
||||
|
||||
EFI_STATUS fwupdate_get_boot_disk_path(EFI_HANDLE boot_device, EFI_DEVICE_PATH **ret_disk_path) {
|
||||
EFI_DEVICE_PATH *path = NULL;
|
||||
EFI_STATUS err;
|
||||
|
||||
assert(ret_disk_path);
|
||||
|
||||
if (!fwupdate_supported())
|
||||
return EFI_UNSUPPORTED;
|
||||
if (!boot_device)
|
||||
return EFI_NOT_FOUND;
|
||||
|
||||
err = BS->HandleProtocol(boot_device, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void **) &path);
|
||||
if (err != EFI_SUCCESS)
|
||||
return err;
|
||||
if (!path)
|
||||
return EFI_NOT_FOUND;
|
||||
|
||||
/* Remove the last partition node, as partition discovery does, to identify the whole disk rather
|
||||
* than just the ESP. An empty prefix cannot identify a disk. */
|
||||
const EFI_DEVICE_PATH *partition = NULL;
|
||||
for (const EFI_DEVICE_PATH *node = path; !device_path_is_end(node); node = device_path_next_node(node))
|
||||
if (node->Type == MEDIA_DEVICE_PATH && IN_SET(node->SubType, MEDIA_HARDDRIVE_DP, MEDIA_CDROM_DP))
|
||||
partition = node;
|
||||
if (!partition || partition == path)
|
||||
return EFI_NOT_FOUND;
|
||||
|
||||
_cleanup_free_ EFI_DEVICE_PATH *disk_path = device_path_replace_node(path, partition, /* new_node= */ NULL);
|
||||
EFI_DEVICE_PATH *remaining = disk_path;
|
||||
EFI_HANDLE disk_handle = NULL;
|
||||
|
||||
err = BS->LocateDevicePath(MAKE_GUID_PTR(EFI_BLOCK_IO_PROTOCOL), &remaining, &disk_handle);
|
||||
if (err != EFI_SUCCESS)
|
||||
return err;
|
||||
if (!disk_handle || !remaining || !device_path_is_end(remaining))
|
||||
return EFI_NOT_FOUND;
|
||||
|
||||
EFI_BLOCK_IO_PROTOCOL *block_io = NULL;
|
||||
err = BS->HandleProtocol(disk_handle, MAKE_GUID_PTR(EFI_BLOCK_IO_PROTOCOL), (void **) &block_io);
|
||||
if (err != EFI_SUCCESS)
|
||||
return err;
|
||||
if (!block_io || !block_io->Media || block_io->Media->LogicalPartition || !block_io->Media->MediaPresent)
|
||||
return EFI_NOT_FOUND;
|
||||
|
||||
/* Fastboot firmware may not have connected the other partitions on this disk yet. */
|
||||
(void) BS->ConnectController(disk_handle, /* DriverImageHandle= */ NULL, /* RemainingDevicePath= */ NULL, /* Recursive= */ true);
|
||||
|
||||
*ret_disk_path = TAKE_PTR(disk_path);
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
static EFI_STATUS parse_info_var(const char16_t *name, FwupUpdateTable **ret_update) {
|
||||
_cleanup_free_ FWUP_UPDATE_INFO *info = NULL;
|
||||
size_t size = 0;
|
||||
uint32_t flags = 0;
|
||||
EFI_STATUS err;
|
||||
|
||||
assert(name);
|
||||
assert(ret_update);
|
||||
|
||||
err = efivar_get_raw_full(MAKE_GUID_PTR(FWUPDATE), name, &flags, (void **) &info, &size);
|
||||
if (err != EFI_SUCCESS)
|
||||
return log_debug_status(err, "Failed to get variable '%ls': %m", name);
|
||||
|
||||
if (size < sizeof(FWUP_UPDATE_INFO))
|
||||
return log_debug_status(EFI_INVALID_PARAMETER, "Variable '%ls' is too small: %zu < %zu", name, size, sizeof(FWUP_UPDATE_INFO));
|
||||
if (info->update_info_version != FWUPDATE_INFO_VERSION)
|
||||
return log_debug_status(EFI_UNSUPPORTED, "Variable '%ls' has unsupported update info version %" PRIu32,
|
||||
name, info->update_info_version);
|
||||
|
||||
EFI_DEVICE_PATH *hdr = &info->dp;
|
||||
size_t dp_size = size - offsetof(FWUP_UPDATE_INFO, dp);
|
||||
|
||||
if (!device_path_is_valid(hdr, dp_size) || device_path_size(hdr) != dp_size)
|
||||
return log_debug_status(EFI_INVALID_PARAMETER, "Variable '%ls' has an invalid EFI device path", name);
|
||||
|
||||
for (const EFI_DEVICE_PATH *node = hdr; !device_path_is_end(node); node = device_path_next_node(node)) {
|
||||
if (node->Type != MEDIA_DEVICE_PATH || node->SubType != MEDIA_FILEPATH_DP)
|
||||
continue;
|
||||
|
||||
if (node->Length < sizeof(FILEPATH_DEVICE_PATH) + sizeof(char16_t) ||
|
||||
(node->Length - sizeof(FILEPATH_DEVICE_PATH)) % sizeof(char16_t) != 0 ||
|
||||
!memeqzero((const uint8_t *) node + node->Length - sizeof(char16_t), sizeof(char16_t)))
|
||||
return log_debug_status(EFI_INVALID_PARAMETER, "Variable '%ls' has a malformed or unterminated file-path node", name);
|
||||
|
||||
/* Only the file path is passed to the firmware's text converter. Do not expose it to
|
||||
* trailing nodes with unvalidated, type-specific payloads. */
|
||||
if (!device_path_is_end(device_path_next_node(node)))
|
||||
return log_debug_status(EFI_INVALID_PARAMETER, "Variable '%ls' has unexpected nodes after its file path", name);
|
||||
}
|
||||
|
||||
log_debug("Parsed UEFI update variable '%ls': flags=0x%08x, hw_inst=%" PRIu64 ", status=%" PRIu32,
|
||||
name,
|
||||
info->capsule_flags,
|
||||
info->hw_inst,
|
||||
info->status);
|
||||
|
||||
*ret_update = xnew(FwupUpdateTable, 1);
|
||||
**ret_update = (FwupUpdateTable) {
|
||||
.name = xstrdup16(name),
|
||||
.attrs = flags,
|
||||
.size = size,
|
||||
.info = TAKE_PTR(info),
|
||||
};
|
||||
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
/* Locates the capsule's filesystem on the boot disk as provided by fwupd.
|
||||
*
|
||||
* fwupd's fu_uefi_capsule_device_write_update_info() stores the path built by
|
||||
* fu_uefi_capsule_device_build_dp_buf(): an HD node describing the ESP partition, followed by a File node.
|
||||
* The ESP mount point is stripped from the filename, which is stored as NUL-terminated UTF-16 with
|
||||
* backslashes. fu_efi_device_path_list_write() appends End. No controller/disk hardware prefix is stored.
|
||||
*
|
||||
* An example, with the capsule's ESP mounted at /efi on partition 1 of the PCI disk
|
||||
* (HD(1,...) abbreviates the unchanged ESP partition node, and $F denotes the File node):
|
||||
* filename = /efi/EFI/fedora/fw/fwupd-697bd920-12cf-4da9-8385-996909bc6559.cap
|
||||
* UEFI file path = File(\EFI\fedora\fw\fwupd-697bd920-12cf-4da9-8385-996909bc6559.cap)
|
||||
* update_dp = HD(1,...)/File(\EFI\fedora\fw\fwupd-697bd920-12cf-4da9-8385-996909bc6559.cap)/End
|
||||
* boot_disk_path = PciRoot(0)/Pci(0x4,0)/End
|
||||
* lookup_dp = PciRoot(0)/Pci(0x4,0)/HD(1,...)/End
|
||||
*
|
||||
* Prepending the boot disk's hardware path scopes the partition lookup to that disk, to stop the firmware
|
||||
* from finding the partition on another disk. A full input path must already start with boot_disk_path.
|
||||
* Requires the filesystem lookup to consume lookup_dp completely, rather than accepting a partial match.
|
||||
*
|
||||
* Returns the filesystem handle and, via ret_file_dp, a borrowed pointer to the original file-path nodes
|
||||
* in update_dp (File(\EFI\fedora\fw\fwupd-697bd920-12cf-4da9-8385-996909bc6559.cap)/End in the example). */
|
||||
static EFI_STATUS find_capsule(
|
||||
const EFI_DEVICE_PATH *update_dp,
|
||||
const EFI_DEVICE_PATH *boot_disk_path,
|
||||
EFI_HANDLE *ret_device_handle,
|
||||
const EFI_DEVICE_PATH **ret_file_dp) {
|
||||
|
||||
_cleanup_free_ EFI_DEVICE_PATH *device_dp = NULL, *lookup_dp = NULL;
|
||||
const EFI_DEVICE_PATH *file_dp;
|
||||
EFI_HANDLE device_handle = NULL;
|
||||
EFI_STATUS err;
|
||||
|
||||
assert(update_dp);
|
||||
assert(boot_disk_path);
|
||||
assert(!device_path_is_end(boot_disk_path));
|
||||
assert(ret_device_handle);
|
||||
assert(ret_file_dp);
|
||||
|
||||
err = device_path_split_file_path(update_dp, &device_dp, &file_dp);
|
||||
if (err != EFI_SUCCESS)
|
||||
return err;
|
||||
|
||||
if (device_dp->Type == MEDIA_DEVICE_PATH && device_dp->SubType == MEDIA_HARDDRIVE_DP) {
|
||||
if (device_dp->Length != sizeof(HARDDRIVE_DEVICE_PATH) ||
|
||||
!device_path_is_end(device_path_next_node(device_dp)))
|
||||
return EFI_NOT_FOUND;
|
||||
|
||||
lookup_dp = device_path_replace_node(boot_disk_path, /* node= */ NULL, device_dp);
|
||||
} else {
|
||||
if (!device_path_startswith(device_dp, boot_disk_path))
|
||||
return EFI_NOT_FOUND;
|
||||
|
||||
lookup_dp = TAKE_PTR(device_dp);
|
||||
}
|
||||
|
||||
EFI_DEVICE_PATH *remaining = lookup_dp;
|
||||
err = BS->LocateDevicePath(MAKE_GUID_PTR(EFI_SIMPLE_FILE_SYSTEM_PROTOCOL), &remaining, &device_handle);
|
||||
if (err != EFI_SUCCESS)
|
||||
return err;
|
||||
if (!device_handle || !remaining || !device_path_is_end(remaining))
|
||||
return EFI_NOT_FOUND;
|
||||
|
||||
*ret_device_handle = device_handle;
|
||||
*ret_file_dp = file_dp;
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
static EFI_STATUS parse_update_vars(
|
||||
const EFI_DEVICE_PATH *boot_disk_path,
|
||||
FwupUpdateTable ***updates,
|
||||
size_t *n_updates) {
|
||||
|
||||
_cleanup_strv_free_ char16_t **names = NULL;
|
||||
EFI_STATUS err;
|
||||
|
||||
assert(boot_disk_path);
|
||||
assert(updates);
|
||||
assert(n_updates);
|
||||
|
||||
/* A series of fwupd-* named variables will be prepared by userspace, but there is no fixed name,
|
||||
* so we need to iterate over the full table of variables and find them. Unfortunately there are no
|
||||
* APIs to search by GUID, so it's really the entire table that has to be walked.
|
||||
* As per the protocol, start with a NUL string, and then pass the previous name as input to the
|
||||
* next search, as the protocol implementation is stateless. */
|
||||
err = efivar_get_names(MAKE_GUID_PTR(FWUPDATE), &names);
|
||||
if (err != EFI_SUCCESS)
|
||||
return log_error_status(err, "Failed to enumerate firmware update variables: %m");
|
||||
|
||||
STRV_FOREACH(name, names) {
|
||||
if (!startswith_no_case(*name, u"fwupd-"))
|
||||
continue;
|
||||
|
||||
log_debug("Found UEFI update variable '%ls'", *name);
|
||||
|
||||
_cleanup_(fwup_update_table_freep) FwupUpdateTable *update = NULL;
|
||||
err = parse_info_var(*name, &update);
|
||||
if (err != EFI_SUCCESS)
|
||||
/* We cannot establish which disk owns a malformed entry, so leave it untouched. */
|
||||
continue;
|
||||
if (!FLAGS_SET(update->info->status, FWUPDATE_ATTEMPT_UPDATE)) {
|
||||
log_debug("Skipping UEFI update variable '%ls', lacks FWUPDATE_ATTEMPT_UPDATE flag", *name);
|
||||
continue;
|
||||
}
|
||||
|
||||
err = find_capsule(&update->info->dp, boot_disk_path, &update->device_handle, &update->file_dp);
|
||||
if (err != EFI_SUCCESS) {
|
||||
log_debug_status(err, "Ignoring update '%ls', cannot resolve it on the boot disk: %m", *name);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (*n_updates == SIZE_MAX)
|
||||
return log_error_status(EFI_OUT_OF_RESOURCES, "Too many UEFI update variables found");
|
||||
|
||||
*updates = xrealloc(*updates,
|
||||
*n_updates * sizeof(FwupUpdateTable *),
|
||||
(*n_updates + 1) * sizeof(FwupUpdateTable *));
|
||||
(*updates)[(*n_updates)++] = TAKE_PTR(update);
|
||||
}
|
||||
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
static uint8_t capsule_checksum(const uint8_t *buffer, size_t size) {
|
||||
uint8_t checksum = 0;
|
||||
|
||||
assert(buffer || size == 0);
|
||||
|
||||
FOREACH_ARRAY(c, buffer, size)
|
||||
checksum += *c;
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
static EFI_STATUS prepare_capsules(
|
||||
FwupUpdateTable **updates,
|
||||
size_t n_updates,
|
||||
EFI_CAPSULE_HEADER ***ret_capsules,
|
||||
size_t *ret_n_capsules,
|
||||
EFI_CAPSULE_BLOCK_DESCRIPTOR **ret_capsule_blocks) {
|
||||
|
||||
_cleanup_free_ EFI_CAPSULE_BLOCK_DESCRIPTOR *capsule_blocks = NULL;
|
||||
EFI_CAPSULE_HEADER **capsules = NULL;
|
||||
size_t n_capsules = 0;
|
||||
EFI_STATUS err;
|
||||
|
||||
CLEANUP_ARRAY(capsules, n_capsules, capsule_free_many);
|
||||
|
||||
assert(updates || n_updates == 0);
|
||||
assert(ret_capsules);
|
||||
assert(ret_n_capsules);
|
||||
assert(ret_capsule_blocks);
|
||||
|
||||
log_debug("Preparing %zu UEFI capsules...", n_updates);
|
||||
|
||||
FOREACH_ARRAY(u, updates, n_updates) {
|
||||
_cleanup_file_close_ EFI_FILE *root = NULL;
|
||||
err = open_volume((*u)->device_handle, &root);
|
||||
if (err != EFI_SUCCESS) {
|
||||
log_warning_status(err, "Failed to open volume for '%ls': %m, skipping", (*u)->name);
|
||||
continue;
|
||||
}
|
||||
|
||||
_cleanup_free_ char16_t *dp_str = NULL;
|
||||
err = device_path_to_str((*u)->file_dp, &dp_str);
|
||||
if (err != EFI_SUCCESS) {
|
||||
log_warning_status(err, "Failed to convert device path for '%ls' to string: %m, skipping", (*u)->name);
|
||||
continue;
|
||||
}
|
||||
|
||||
_cleanup_free_ char *file_buffer = NULL;
|
||||
size_t file_size = 0;
|
||||
err = file_read(root, dp_str, /* offset= */ 0, /* size= */ 0, &file_buffer, &file_size);
|
||||
if (err != EFI_SUCCESS) {
|
||||
log_warning_status(err, "Failed to read file for '%ls': %m, skipping", (*u)->name);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (file_size < sizeof(EFI_CAPSULE_HEADER)) {
|
||||
log_warning("Capsule for '%ls' is too small for EFI_CAPSULE_HEADER: %zu < %zu, skipping", (*u)->name, file_size, sizeof(EFI_CAPSULE_HEADER));
|
||||
continue;
|
||||
}
|
||||
EFI_CAPSULE_HEADER *capsule = (EFI_CAPSULE_HEADER *)file_buffer;
|
||||
|
||||
if (capsule->HeaderSize < sizeof(EFI_CAPSULE_HEADER) ||
|
||||
capsule->HeaderSize > capsule->CapsuleImageSize ||
|
||||
capsule->CapsuleImageSize > file_size) {
|
||||
log_warning("Invalid capsule sizes for '%ls': header=%" PRIu32 ", image=%" PRIu32 ", file=%zu, skipping",
|
||||
(*u)->name, capsule->HeaderSize, capsule->CapsuleImageSize, file_size);
|
||||
continue;
|
||||
}
|
||||
|
||||
EFI_GUID guid = (*u)->info->guid; /* Avoid unaligned access warning, since info is packed */
|
||||
/* Is this a Microsoft UX capsule? Then let's do special processing for it */
|
||||
if (efi_guid_equal(&guid, MAKE_GUID_PTR(UX_CAPSULE))) {
|
||||
if (capsule->CapsuleImageSize - capsule->HeaderSize < sizeof(UX_CAPSULE_HEADER)) {
|
||||
log_warning("Capsule for '%ls' has too little payload for UX_CAPSULE_HEADER: %" PRIu32 " < %zu, skipping",
|
||||
(*u)->name, capsule->CapsuleImageSize - capsule->HeaderSize, sizeof(UX_CAPSULE_HEADER));
|
||||
continue;
|
||||
}
|
||||
|
||||
EFI_GRAPHICS_OUTPUT_PROTOCOL *graphics = NULL;
|
||||
err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_GRAPHICS_OUTPUT_PROTOCOL), /* Registration= */ NULL, (void **) &graphics);
|
||||
if (err != EFI_SUCCESS) {
|
||||
log_warning_status(err, "Failed to get graphics output protocol for UX capsule '%ls', skipping: %m", (*u)->name);
|
||||
continue;
|
||||
}
|
||||
if (!graphics || !graphics->Mode || !graphics->Mode->Info) {
|
||||
log_warning("No graphics output mode for UX capsule '%ls', skipping", (*u)->name);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* struct is packed, so no alignment check is necessary */
|
||||
UX_CAPSULE_HEADER *hdr = (UX_CAPSULE_HEADER *)((uint8_t *)capsule + capsule->HeaderSize);
|
||||
hdr->mode = graphics->Mode->Mode;
|
||||
|
||||
/* Since the mode is updated, need to recalculate the checksum */
|
||||
hdr->checksum = 0;
|
||||
hdr->checksum = capsule_checksum((uint8_t *) hdr, sizeof(UX_CAPSULE_HEADER));
|
||||
} else if (capsule->Flags == 0)
|
||||
/* fwupd supplies any required flags; do not impose platform-specific reset behavior. */
|
||||
capsule->Flags = (*u)->info->capsule_flags;
|
||||
|
||||
if (n_capsules == SIZE_MAX)
|
||||
return log_error_status(EFI_OUT_OF_RESOURCES, "Too many UEFI capsules");
|
||||
|
||||
EFI_CAPSULE_BLOCK_DESCRIPTOR block_descriptor = {
|
||||
.Length = capsule->CapsuleImageSize,
|
||||
.DataBlock = POINTER_TO_PHYSICAL_ADDRESS(file_buffer),
|
||||
};
|
||||
|
||||
capsule_blocks = xrealloc(capsule_blocks,
|
||||
n_capsules * sizeof(EFI_CAPSULE_BLOCK_DESCRIPTOR),
|
||||
(n_capsules + 1) * sizeof(EFI_CAPSULE_BLOCK_DESCRIPTOR));
|
||||
capsule_blocks[n_capsules] = block_descriptor;
|
||||
|
||||
capsules = xrealloc(capsules,
|
||||
n_capsules * sizeof(EFI_CAPSULE_HEADER *),
|
||||
(n_capsules + 1) * sizeof(EFI_CAPSULE_HEADER *));
|
||||
capsules[n_capsules++] = (EFI_CAPSULE_HEADER *) TAKE_PTR(file_buffer);
|
||||
}
|
||||
|
||||
/* Need to terminate the lists with NULL/empty elements as per spec */
|
||||
capsule_blocks = xrealloc(capsule_blocks,
|
||||
n_capsules * sizeof(EFI_CAPSULE_BLOCK_DESCRIPTOR),
|
||||
(n_capsules + 1) * sizeof(EFI_CAPSULE_BLOCK_DESCRIPTOR));
|
||||
capsule_blocks[n_capsules] = (EFI_CAPSULE_BLOCK_DESCRIPTOR) { };
|
||||
capsules = xrealloc(capsules,
|
||||
n_capsules * sizeof(EFI_CAPSULE_HEADER *),
|
||||
(n_capsules + 1) * sizeof(EFI_CAPSULE_HEADER *));
|
||||
capsules[n_capsules] = NULL;
|
||||
|
||||
*ret_capsule_blocks = TAKE_PTR(capsule_blocks);
|
||||
*ret_capsules = TAKE_PTR(capsules);
|
||||
*ret_n_capsules = n_capsules;
|
||||
n_capsules = 0;
|
||||
|
||||
log_debug("Prepared %zu UEFI capsules for update.", *ret_n_capsules);
|
||||
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
static EFI_STATUS apply_updates(
|
||||
EFI_CAPSULE_HEADER **capsules,
|
||||
size_t n_capsules,
|
||||
const EFI_CAPSULE_BLOCK_DESCRIPTOR *capsule_blocks,
|
||||
EFI_RESET_TYPE *ret_reset_type) {
|
||||
|
||||
EFI_RESET_TYPE reset_type;
|
||||
uint64_t max_capsule_size;
|
||||
EFI_STATUS err;
|
||||
|
||||
assert(capsules && n_capsules > 0);
|
||||
assert(capsule_blocks);
|
||||
assert(ret_reset_type);
|
||||
|
||||
err = RT->QueryCapsuleCapabilities(capsules, n_capsules, &max_capsule_size, &reset_type);
|
||||
if (err != EFI_SUCCESS) {
|
||||
log_warning_status(err, "Failed to query capsule capabilities, assuming EfiResetWarm: %m");
|
||||
reset_type = EfiResetWarm;
|
||||
}
|
||||
|
||||
err = RT->UpdateCapsule(capsules, n_capsules, POINTER_TO_PHYSICAL_ADDRESS(capsule_blocks));
|
||||
if (err != EFI_SUCCESS)
|
||||
return log_error_status(err, "Failed to update capsules: %m");
|
||||
|
||||
*ret_reset_type = reset_type;
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
static EFI_STATUS mark_capsules_as_attempted(FwupUpdateTable **updates, size_t n_updates) {
|
||||
EFI_TIME now;
|
||||
EFI_STATUS err;
|
||||
|
||||
if (n_updates == 0)
|
||||
return EFI_SUCCESS;
|
||||
|
||||
err = RT->GetTime(&now, NULL);
|
||||
if (err != EFI_SUCCESS) /* Should not happen, but sometimes clocks are flaky */
|
||||
now = (EFI_TIME) { };
|
||||
|
||||
FOREACH_ARRAY(u, updates, n_updates) {
|
||||
log_debug("Marking UEFI update variable '%ls' as attempted", (*u)->name);
|
||||
|
||||
(*u)->info->time_attempted = now;
|
||||
(*u)->info->status = FWUPDATE_ATTEMPTED;
|
||||
|
||||
err = efivar_set_raw(MAKE_GUID_PTR(FWUPDATE), (*u)->name, (*u)->info, (*u)->size, (*u)->attrs);
|
||||
if (err != EFI_SUCCESS)
|
||||
return log_error_status(err, "Failed to set variable '%ls': %m", (*u)->name);
|
||||
}
|
||||
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
/* Implement the flow defined at https://github.com/fwupd/fwupd-efi/blob/main/SECURITY.md */
|
||||
void fwupdate_install(const EFI_DEVICE_PATH *boot_disk_path) {
|
||||
EFI_STATUS err;
|
||||
|
||||
assert(boot_disk_path);
|
||||
assert(!device_path_is_end(boot_disk_path));
|
||||
|
||||
if (!fwupdate_supported()) {
|
||||
log_debug("UEFI capsule update services are unavailable, skipping firmware updates.");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Collect only variables whose capsules resolve on the boot disk. Variables for other disks must
|
||||
* neither be loaded nor have their status changed. */
|
||||
FwupUpdateTable **updates = NULL;
|
||||
size_t n_updates = 0;
|
||||
CLEANUP_ARRAY(updates, n_updates, fwup_update_table_free_many);
|
||||
err = parse_update_vars(boot_disk_path, &updates, &n_updates);
|
||||
if (err != EFI_SUCCESS || n_updates == 0)
|
||||
return;
|
||||
|
||||
/* Now prepare the capsules by loading them in memory. This is best-effort: if there are multiple
|
||||
* capsules queued for installation, but some cannot be loaded for any reason, skip them and install
|
||||
* what we can. */
|
||||
_cleanup_free_ EFI_CAPSULE_BLOCK_DESCRIPTOR *capsule_blocks = NULL;
|
||||
EFI_CAPSULE_HEADER **capsules = NULL;
|
||||
size_t n_capsules = 0;
|
||||
CLEANUP_ARRAY(capsules, n_capsules, capsule_free_many);
|
||||
err = prepare_capsules(updates, n_updates, &capsules, &n_capsules, &capsule_blocks);
|
||||
if (err != EFI_SUCCESS)
|
||||
return;
|
||||
|
||||
/* Mark all capsules from the boot disk as "attempted", including the ones that could not be loaded,
|
||||
* otherwise we would just loop forever. */
|
||||
err = mark_capsules_as_attempted(updates, n_updates);
|
||||
if (err != EFI_SUCCESS)
|
||||
return;
|
||||
|
||||
/* If no capsules could be prepared, log and bail out, no need to reset.
|
||||
* NB: this check is placed after the efi var update, since not all efi vars might reference
|
||||
* existing or valid capsules, but the variables need to be marked as processed anyway in order
|
||||
* to avoid getting stuck in a loop. */
|
||||
if (n_capsules == 0)
|
||||
return (void) log_error("No capsules could be prepared out of %zu updates.", n_updates);
|
||||
|
||||
log_info("Applying %zu updates, and then resetting system.", n_capsules);
|
||||
log_wait();
|
||||
|
||||
EFI_RESET_TYPE reset_type = EfiResetWarm;
|
||||
err = apply_updates(capsules, n_capsules, capsule_blocks, &reset_type);
|
||||
if (err != EFI_SUCCESS)
|
||||
return;
|
||||
|
||||
/* The update might already reboot, so might not get to this point. */
|
||||
log_info("Applied %zu updates, resetting system.", n_capsules);
|
||||
log_wait();
|
||||
|
||||
RT->ResetSystem(reset_type, EFI_SUCCESS, /* DataSize= */ 0, /* ResetData= */ NULL);
|
||||
assert_not_reached();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "efi.h"
|
||||
|
||||
#define FWUPDATE_GUID { 0x0abba7dc, 0xe516, 0x4167, { 0xbb, 0xf5, 0x4d, 0x9d, 0x1c, 0x73, 0x94, 0x16 } }
|
||||
|
||||
EFI_STATUS fwupdate_get_boot_disk_path(EFI_HANDLE boot_device, EFI_DEVICE_PATH **ret_disk_path);
|
||||
void fwupdate_install(const EFI_DEVICE_PATH *boot_disk_path);
|
||||
@@ -342,6 +342,7 @@ libefi_sources = files(
|
||||
|
||||
systemd_boot_sources = files(
|
||||
'boot.c',
|
||||
'fwupdate.c',
|
||||
'line-edit.c',
|
||||
)
|
||||
|
||||
|
||||
@@ -412,6 +412,7 @@ int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
{ EFI_LOADER_FEATURE_KEYBOARD_LAYOUT, "Loader reports firmware keyboard layout" },
|
||||
{ EFI_LOADER_FEATURE_SMBIOS_MEASURED, "Loader measures SMBIOS information" },
|
||||
{ EFI_LOADER_FEATURE_FACTORY_RESET_VARIABLES, "Loader erases EFI variables on request" },
|
||||
{ EFI_LOADER_FEATURE_FIRMWARE_UPDATE, "Support for installing fwupd updates" },
|
||||
};
|
||||
static const struct {
|
||||
uint64_t flag;
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#define EFI_LOADER_FEATURE_KEYBOARD_LAYOUT (UINT64_C(1) << 20)
|
||||
#define EFI_LOADER_FEATURE_SMBIOS_MEASURED (UINT64_C(1) << 21)
|
||||
#define EFI_LOADER_FEATURE_FACTORY_RESET_VARIABLES (UINT64_C(1) << 22)
|
||||
#define EFI_LOADER_FEATURE_FIRMWARE_UPDATE (UINT64_C(1) << 23)
|
||||
|
||||
/* Features of the stub, i.e. systemd-stub */
|
||||
#define EFI_STUB_FEATURE_REPORT_BOOT_PARTITION (UINT64_C(1) << 0)
|
||||
|
||||
Reference in new issue
Block a user