mirror of
https://github.com/systemd/systemd.git
synced 2026-11-06 05:17:24 +00:00
boot: erase our EFI variables on request
If the new LoaderFactoryResetVariables EFI variable is set to true, systemd-boot will now (after asking the user for confirmation) erase all EFI variables in the Boot Loader Interface vendor UUID namespace, and in systemd's own vendor UUID namespace, and then reboot. The FactoryResetRequest variable is retained, since the OS needs it on the subsequent boot to execute its part of the factory reset. The request variable itself is erased last, so that an interrupted operation is resumed on the next boot. Support for this is advertised via a new LoaderFeatures bit.
This commit is contained in:
1 parent
4d349f9657
commit
66a760e541
8 files changed
+239
-1
No files matched your search
@@ -10,6 +10,7 @@
|
||||
#include "efi-efivars.h"
|
||||
#include "efi-log.h"
|
||||
#include "efi-string-table.h"
|
||||
#include "efivar-erase.h"
|
||||
#include "efivars.h"
|
||||
#include "export-vars.h"
|
||||
#include "graphics.h"
|
||||
@@ -3287,6 +3288,7 @@ static void export_loader_variables(
|
||||
EFI_LOADER_FEATURE_TPM2_ACTIVE_PCR_BANKS |
|
||||
EFI_LOADER_FEATURE_KEYBOARD_LAYOUT |
|
||||
EFI_LOADER_FEATURE_SMBIOS_MEASURED |
|
||||
EFI_LOADER_FEATURE_FACTORY_RESET_VARIABLES |
|
||||
0;
|
||||
|
||||
assert(loaded_image);
|
||||
@@ -3465,6 +3467,10 @@ static EFI_STATUS run(EFI_HANDLE image) {
|
||||
config_load_all_entries(&config, loaded_image, loaded_image_path, root_dir);
|
||||
(void) sysfail_process(&config);
|
||||
|
||||
/* If a factory reset of our EFI variables has been requested, do so now, after asking the user. This
|
||||
* reboots if the variables are erased. */
|
||||
(void) factory_reset_variables_process();
|
||||
|
||||
if (config.n_entries == 0)
|
||||
return log_error_status(
|
||||
EFI_NOT_FOUND,
|
||||
|
||||
@@ -12,6 +12,11 @@
|
||||
#define LOADER_GUID \
|
||||
{ 0x4a67b082, 0x0a4c, 0x41cf, { 0xb6, 0xc7, 0x44, 0x0b, 0x29, 0xbb, 0x8c, 0x4f } }
|
||||
|
||||
/* Allocated random UUID, for systemd's own EFI variables that are not part of the Boot Loader Interface,
|
||||
* such as FactoryResetRequest. */
|
||||
#define SYSTEMD_GUID \
|
||||
{ 0x8cf2644b, 0x4b0b, 0x428f, { 0x93, 0x87, 0x6d, 0x87, 0x60, 0x50, 0xdc, 0x67 } }
|
||||
|
||||
EFI_STATUS efivar_set_str16(const EFI_GUID *vendor, const char16_t *name, const char16_t *value, uint32_t flags);
|
||||
EFI_STATUS efivar_set_raw(const EFI_GUID *vendor, const char16_t *name, const void *buf, size_t size, uint32_t flags);
|
||||
EFI_STATUS efivar_set_uint64_str16(const EFI_GUID *vendor, const char16_t *name, uint64_t i, uint32_t flags);
|
||||
|
||||
+4
-1
@@ -409,7 +409,10 @@ typedef struct {
|
||||
uint32_t *Attributes,
|
||||
size_t *DataSize,
|
||||
void *Data);
|
||||
void *GetNextVariableName;
|
||||
EFI_STATUS (EFIAPI *GetNextVariableName)(
|
||||
size_t *VariableNameSize,
|
||||
char16_t *VariableName,
|
||||
EFI_GUID *VendorGuid);
|
||||
EFI_STATUS (EFIAPI *SetVariable)(
|
||||
char16_t *VariableName,
|
||||
EFI_GUID *VendorGuid,
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#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;
|
||||
|
||||
assert(vendor);
|
||||
assert(name);
|
||||
|
||||
/* Some firmware implementations refuse deletion if the attributes don't match the existing
|
||||
* variable, hence query them first, and pass them along. */
|
||||
err = efivar_get_raw_full(vendor, name, &attributes, /* ret_data= */ NULL, /* ret_size= */ NULL);
|
||||
if (err == EFI_NOT_FOUND)
|
||||
return EFI_SUCCESS;
|
||||
if (err != EFI_SUCCESS)
|
||||
return log_error_status(err, "Failed to query EFI variable %ls: %m", name);
|
||||
|
||||
err = RT->SetVariable((char16_t *) name, (EFI_GUID *) vendor, attributes, 0, NULL);
|
||||
if (err != EFI_SUCCESS)
|
||||
return log_error_status(err, "Failed to erase EFI variable %ls: %m", name);
|
||||
|
||||
log_debug("Erased EFI variable %ls.", name);
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
EFI_STATUS efivar_erase_namespace(const EFI_GUID *vendor, const char16_t *const *keep) {
|
||||
_cleanup_strv_free_ char16_t **l = NULL;
|
||||
EFI_STATUS err, ret = EFI_SUCCESS;
|
||||
|
||||
assert(vendor);
|
||||
|
||||
err = collect_variables(vendor, &l);
|
||||
if (err != EFI_SUCCESS)
|
||||
return err;
|
||||
|
||||
STRV_FOREACH(i, l) {
|
||||
if (strv_contains((char16_t**) keep, *i))
|
||||
continue;
|
||||
|
||||
err = erase_variable(vendor, *i);
|
||||
if (err != EFI_SUCCESS && ret == EFI_SUCCESS)
|
||||
ret = err;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static EFI_STATUS ask_confirmation(bool *ret) {
|
||||
EFI_STATUS err;
|
||||
|
||||
assert(ret);
|
||||
|
||||
clear_screen(COLOR_NORMAL);
|
||||
|
||||
printf("A factory reset has been requested.\n"
|
||||
"\n"
|
||||
"All EFI variables owned by the boot loader and by systemd are about to be erased,\n"
|
||||
"including boot menu defaults, one-shot entries and the system token. The system\n"
|
||||
"will reboot afterwards.\n"
|
||||
"\n"
|
||||
"Erase these EFI variables? (y/n) ");
|
||||
|
||||
for (;;) {
|
||||
uint64_t key;
|
||||
|
||||
err = console_key_read(&key, /* timeout_usec= */ UINT64_MAX);
|
||||
if (err == EFI_NOT_READY)
|
||||
continue;
|
||||
if (err != EFI_SUCCESS) {
|
||||
printf("\n");
|
||||
return log_error_status(err, "Error waiting for user input: %m");
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
|
||||
case KEYPRESS(0, 0, 'y'):
|
||||
case KEYPRESS(0, 0, 'Y'):
|
||||
printf("y\n");
|
||||
*ret = true;
|
||||
return EFI_SUCCESS;
|
||||
|
||||
case KEYPRESS(0, 0, 'n'):
|
||||
case KEYPRESS(0, 0, 'N'):
|
||||
case KEYPRESS(0, SCAN_ESC, 0):
|
||||
printf("n\n");
|
||||
*ret = false;
|
||||
return EFI_SUCCESS;
|
||||
|
||||
default:
|
||||
/* Ignore anything else, the user has to make an explicit choice */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EFI_STATUS factory_reset_variables_process(void) {
|
||||
static const char16_t *const keep_loader[] = {
|
||||
u"LoaderFactoryResetVariables", /* erased last, see below */
|
||||
NULL,
|
||||
};
|
||||
static const char16_t *const keep_systemd[] = {
|
||||
/* The OS needs this on the next boot to reset its partitions, hence don't erase it */
|
||||
u"FactoryResetRequest",
|
||||
NULL,
|
||||
};
|
||||
bool b, confirmed;
|
||||
EFI_STATUS err;
|
||||
|
||||
err = efivar_get_boolean_str16(MAKE_GUID_PTR(LOADER), u"LoaderFactoryResetVariables", &b);
|
||||
if (err == EFI_NOT_FOUND)
|
||||
return EFI_SUCCESS;
|
||||
if (err == EFI_INVALID_PARAMETER) {
|
||||
/* Remove the invalid request, so that we don't complain about it on every boot */
|
||||
log_warning("Invalid LoaderFactoryResetVariables EFI variable, removing.");
|
||||
(void) erase_variable(MAKE_GUID_PTR(LOADER), u"LoaderFactoryResetVariables");
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
if (err != EFI_SUCCESS) {
|
||||
log_warning_status(err, "Failed to read LoaderFactoryResetVariables EFI variable, ignoring: %m");
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
if (!b)
|
||||
return EFI_SUCCESS;
|
||||
|
||||
err = ask_confirmation(&confirmed);
|
||||
if (err != EFI_SUCCESS)
|
||||
/* Leave the request in place, so that we ask again on the next boot */
|
||||
return err;
|
||||
if (!confirmed) {
|
||||
log_info("Erasing EFI variables declined by user.");
|
||||
|
||||
/* Drop the request, so that we don't ask again on every boot */
|
||||
(void) erase_variable(MAKE_GUID_PTR(LOADER), u"LoaderFactoryResetVariables");
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
log_info("Erasing EFI variables of boot loader and systemd.");
|
||||
|
||||
(void) efivar_erase_namespace(MAKE_GUID_PTR(LOADER), keep_loader);
|
||||
(void) efivar_erase_namespace(MAKE_GUID_PTR(SYSTEMD), keep_systemd);
|
||||
|
||||
/* Erase the request itself last, so that if we are interrupted half-way we'll try again on the next
|
||||
* boot. Do this also if erasing some variables failed, so that we don't end up in a reboot loop. */
|
||||
(void) erase_variable(MAKE_GUID_PTR(LOADER), u"LoaderFactoryResetVariables");
|
||||
|
||||
log_wait();
|
||||
RT->ResetSystem(EfiResetCold, EFI_SUCCESS, 0, NULL);
|
||||
assert_not_reached();
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "efi.h"
|
||||
|
||||
EFI_STATUS efivar_erase_namespace(const EFI_GUID *vendor, const char16_t *const *keep);
|
||||
|
||||
/* Checks the LoaderFactoryResetVariables EFI variable, and if set erases all EFI variables in the LOADER
|
||||
* and SYSTEMD namespaces (with a few exceptions) and reboots, after asking the user for confirmation. Returns
|
||||
* only if no erasing took place. */
|
||||
EFI_STATUS factory_reset_variables_process(void);
|
||||
@@ -320,6 +320,7 @@ libefi_sources = files(
|
||||
'efi-firmware.c',
|
||||
'efi-log.c',
|
||||
'efi-string.c',
|
||||
'efivar-erase.c',
|
||||
'export-vars.c',
|
||||
'graphics.c',
|
||||
'hii.c',
|
||||
|
||||
@@ -411,6 +411,7 @@ int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
{ EFI_LOADER_FEATURE_TPM2_ACTIVE_PCR_BANKS, "Loader reports active TPM2 PCR banks" },
|
||||
{ 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" },
|
||||
};
|
||||
static const struct {
|
||||
uint64_t flag;
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#define EFI_LOADER_FEATURE_ENTRY_PREFERRED (UINT64_C(1) << 19)
|
||||
#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)
|
||||
|
||||
/* 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