1
0
mirror of https://github.com/systemd/systemd.git synced 2025-03-19 22:50:17 +03:00

Merge d57f81660f93a8a78594727b7d568dc88d85be8b into fdab24bf6acc62d3011f9b5abdf834b4886642b2

This commit is contained in:
Adrian Vovk 2025-03-13 06:18:47 +01:00 committed by GitHub
commit b3125f503b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 325 additions and 44 deletions

View File

@ -56,6 +56,14 @@ Specifically, the following concepts are available:
and adds `factory-reset-now.target` to the boot transaction, already in the
initial RAM disk (initrd).
* The
[`systemd-factory-reset-esp.service`](https://www.freedesktop.org/software/systemd/man/latest/systemd-factory-reset-esp.service.html)
unit is invoked via `factory-reset-now.target`, and deletes non-vendor system
extension images, UKI addons, and credentials from the EFI System and Extended
Bootloader Partitions. See the
[`systemd-stub(7)`](https://www.freedesktop.org/software/systemd/man/latest/systemd-stub.html)
man page.
* The
[`systemd-factory-reset-complete.service`](https://www.freedesktop.org/software/systemd/man/latest/systemd-factory-reset-complete.service.html)
unit is invoked after `factory-reset-now.target` and marks the factory reset
@ -118,8 +126,9 @@ order to execute the reset operation.
## Support for Resetting other Resources than Partitions + TPM
By default a factory reset implemented with systemd's tools can reset/erase
partitions (via `systemd-repart`, see above) and reset the TPM (via
`systemd-tpm2-clear.service`, see above).
partitions (via `systemd-repart`, see above), reset the TPM (via
`systemd-tpm2-clear.service`, see above), and delete non-vendor resources from
the ESP (via `systemd-factory-reset-esp.service`, see above).
In some cases other resources shall be reset/erased too. To support that,
define your own service and plug it into `factory-reset-now.target`, ensuring

View File

@ -942,6 +942,7 @@ manpages = [
['30-systemd-environment-d-generator'],
'ENABLE_ENVIRONMENT_D'],
['systemd-escape', '1', [], ''],
['systemd-factory-reset-esp.service', '8', [], ''],
['systemd-factory-reset-generator', '8', [], ''],
['systemd-factory-reset',
'8',

View File

@ -0,0 +1,50 @@
<?xml version="1.0"?>
<!--*-nxml-*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
<refentry id="systemd-factory-reset"
xmlns:xi="http://www.w3.org/2001/XInclude">
<refentryinfo>
<title>systemd-factory-reset-esp.service</title>
<productname>systemd</productname>
</refentryinfo>
<refmeta>
<refentrytitle>systemd-factory-reset-esp.service</refentrytitle>
<manvolnum>8</manvolnum>
</refmeta>
<refnamediv>
<refname>systemd-factory-reset-esp.service</refname>
<refpurpose>Delete non-vendor content from the ESP and XBOOTLDR</refpurpose>
</refnamediv>
<refsynopsisdiv>
<para><filename>/usr/lib/systemd/systemd-factory-reset-esp</filename></para>
<para><filename>systemd-factory-reset-esp.service</filename></para>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><filename>systemd-factory-reset-esp.service</filename> is a part of systemd's factory reset logic,
which deletes non-vendor system extension images, UKI addons, and credentials from the EFI System and
Extended Bootloader Partitions. The vendor and non-vendor versions of these resources are stored in
different directories, as described in <member><citerefentry><refentrytitle>systemd-stub</refentrytitle>
<manvolnum>7</manvolnum></citerefentry></member>.</para>
<para>See <ulink url="https://systemd.io/FACTORY_RESET">Factory Reset</ulink> for an overview of the
factory reset logic.</para>
</refsect1>
<refsect1>
<title>See Also</title>
<para><simplelist type="inline">
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-factory-reset</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
<member><ulink url="https://systemd.io/FACTORY_RESET">Factory Reset</ulink></member>
</simplelist></para>
</refsect1>
</refentry>

View File

@ -207,10 +207,17 @@
details on system extension images. The generated <command>cpio</command> archive containing these
system extension images is measured into TPM PCR 13 (if a TPM is present).</para></listitem>
<!-- Note: the actual suffix we look for for sysexts is just *.raw (not *.sysext.raw), for
<!-- Note: the actual suffix we look for for non-vendor sysexts is just *.raw (not *.sysext.raw), for
compatibility reasons with old versions. But we want people to name their system extensions
properly, hence we document the *.sysext.raw suffix only. -->
<listitem><para>Similarly, files
<filename><replaceable>foo</replaceable>.efi.extra.vendor.d/*.sysext.raw</filename> are packed up in a
<command>cpio</command> archive and placed in the <filename>/.extra/sysext/</filename> directory in the
initrd file heirarchy. This alternate directory exists for OS vendors to ship system extension
images that shall persist across an OS factory reset. This archive is similarly measured into TPM
PCR 13 (if a TPM is present).</para></listitem>
<listitem><para>Similarly, files
<filename><replaceable>foo</replaceable>.efi.extra.d/*.confext.raw</filename> are packed up in a
<command>cpio</command> archive and placed in the <filename>/.extra/confext/</filename> directory in
@ -221,13 +228,23 @@
these configuration extension images is measured into TPM PCR 12 (if a TPM is present).</para></listitem>
<listitem><para>Similarly, files
<filename><replaceable>foo</replaceable>.efi.extra.d/*.addon.efi</filename> are loaded and verified as
PE binaries and specific sections are loaded from them. Addons are used to pass additional kernel
command line parameters (<literal>.cmdline</literal> section), or DeviceTree blobs
<filename><replaceable>foo</replaceable>.efi.extra.vendor.d/*.confext.raw</filename> are packed up in a
<command>cpio</command> archive and placed in the <filename>/.extra/confext/</filename> directory in
the initrd file heirarchy. This archive is similarly measured into TPM PCR 12 (if a TPM is present).
</para></listitem>
<listitem><para>Similarly, files
<filename><replaceable>foo</replaceable>.efi.extra.d/*.addon.efi</filename>,
<filename><replaceable>foo</replaceable>.efi.extra.vendor.d/*.addon.efi</filename>,
<filename>/loader/addons/*.addon.efi</filename>, and
<filename>/loader/vendor-addons/*.addon.efi</filename> are loaded and
verified as PE binaries and specific sections are loaded from them. Addons are used to pass additional
kernel command line parameters (<literal>.cmdline</literal> section), or Devicetree blobs
(<literal>.dtb</literal> section), additional initrds (<literal>.initrd</literal> section),
and microcode updates (<literal>.ucode</literal> section). Addons allow those resources to be passed
regardless of the kernel version being booted, for example allowing platform vendors to ship
platform-specific configuration.</para>
and microcode updates (<literal>.ucode</literal> section). The addons found under
<filename>/loader/addons/</filename> or <filename>/loader/vendor-addons/</filename> allow those
resources to be passed regardless of the kernel version being booted, for example allowing platform
vendors to ship platform-specific configuration.</para>
<para>In case Secure Boot is enabled, these files will be validated using keys in UEFI DB, Shim's DB or
Shim's MOK, and only loaded if the check passes. Additionally, if both the addon and the UKI contain a
@ -239,8 +256,8 @@
<ulink url="https://github.com/rhboot/shim/blob/main/SBAT.md">Shim documentation</ulink>.</para>
<para>Addon files are sorted, loaded, and measured into TPM PCR 12 (if a TPM is present) and appended
to the kernel command line. UKI command line options are listed first, then options from addons in
<filename>/loader/addons/*.addon.efi</filename>, and finally UKI-specific addons. Device tree blobs are
to the kernel command line. UKI command line options are listed first, then options from global addons in
<filename>/loader/addons/</filename> and others, and finally UKI-specific addons. Device tree blobs are
loaded and measured following the same algorithm. Microcode addons are passed to the kernel in inverse
order (UKI specific addons, global addons, UKI embedded section). This is because the microcode update
driver stops on the first matching filename. Addons are always loaded in the same order based on
@ -252,17 +269,11 @@
an attestation service so that improper use of your signed addons can be detected and dealt with using
one of the aforementioned revocation mechanisms.</para></listitem>
<listitem><para>Files <filename>/loader/credentials/*.cred</filename> are packed up in a
<listitem><para>Similarly, files <filename>/loader/credentials/*.cred</filename> are packed up in a
<command>cpio</command> archive and placed in the <filename>/.extra/global_credentials/</filename>
directory of the initrd file hierarchy. This is supposed to be used to pass additional credentials to
the initrd, regardless of the kernel version being booted. The generated <command>cpio</command>
archive is measured into TPM PCR 12 (if a TPM is present).</para></listitem>
<listitem><para>Additionally, files <filename>/loader/addons/*.addon.efi</filename> are loaded and
verified as PE binaries, and <literal>.cmdline</literal>, <literal>.dtb</literal>,
<literal>.initrd</literal>, and <literal>.ucode</literal> sections are parsed from them.
This is supposed to be used to pass additional command line parameters, DeviceTree blobs, initrds,
and microcode updates to the kernel, regardless of the kernel version being booted.</para></listitem>
</itemizedlist>
<para>These mechanisms may be used to parameterize and extend trusted (i.e. signed), immutable initrd

View File

@ -316,7 +316,6 @@ EFI_STATUS pack_cpio(
_cleanup_file_close_ EFI_FILE *root = NULL, *extra_dir = NULL;
size_t dirent_size = 0, buffer_size = 0, n_items = 0, n_allocated = 0;
_cleanup_free_ char16_t *rel_dropin_dir = NULL;
_cleanup_free_ EFI_FILE_INFO *dirent = NULL;
_cleanup_strv_free_ char16_t **items = NULL;
_cleanup_free_ void *buffer = NULL;
@ -330,6 +329,9 @@ EFI_STATUS pack_cpio(
if (!loaded_image->DeviceHandle)
goto nothing;
if (!dropin_dir)
goto nothing;
err = open_volume(loaded_image->DeviceHandle, &root);
if (err == EFI_UNSUPPORTED)
/* Error will be unsupported if the bootloader doesn't implement the file system protocol on
@ -338,12 +340,6 @@ EFI_STATUS pack_cpio(
if (err != EFI_SUCCESS)
return log_error_status(err, "Unable to open root directory: %m");
if (!dropin_dir) {
dropin_dir = rel_dropin_dir = get_extra_dir(loaded_image->FilePath);
if (!dropin_dir)
goto nothing;
}
err = open_directory(root, dropin_dir, &extra_dir);
if (err == EFI_NOT_FOUND)
/* No extra subdir, that's totally OK */

View File

@ -37,7 +37,9 @@ enum {
INITRD_CREDENTIAL = _INITRD_DYNAMIC_FIRST,
INITRD_GLOBAL_CREDENTIAL,
INITRD_SYSEXT,
INITRD_VENDOR_SYSEXT,
INITRD_CONFEXT,
INITRD_VENDOR_CONFEXT,
INITRD_PCRSIG,
INITRD_PCRPKEY,
INITRD_OSREL,
@ -821,6 +823,7 @@ static void generate_sidecar_initrds(
int *sysext_measured,
int *confext_measured) {
_cleanup_free_ char16_t *dropin_dir = NULL, *vendor_dropin_dir = NULL;
bool m;
assert(loaded_image);
@ -829,8 +832,13 @@ static void generate_sidecar_initrds(
assert(sysext_measured);
assert(confext_measured);
dropin_dir = get_extra_dir(".extra.d", loaded_image->FilePath);
vendor_dropin_dir = get_extra_dir(".extra.vendor.d", loaded_image->FilePath);
/* Note: Either of these may be NULL, but that's handled by pack_cpio */
if (pack_cpio(loaded_image,
/* dropin_dir= */ NULL,
dropin_dir,
u".cred",
/* exclude_suffix= */ NULL,
".extra/credentials",
@ -856,7 +864,7 @@ static void generate_sidecar_initrds(
combine_measured_flag(parameters_measured, m);
if (pack_cpio(loaded_image,
/* dropin_dir= */ NULL,
dropin_dir,
u".raw", /* ideally we'd pick up only *.sysext.raw here, but for compat we pick up *.raw instead … */
u".confext.raw", /* … but then exclude *.confext.raw again */
".extra/sysext",
@ -869,7 +877,20 @@ static void generate_sidecar_initrds(
combine_measured_flag(sysext_measured, m);
if (pack_cpio(loaded_image,
/* dropin_dir= */ NULL,
vendor_dropin_dir,
u".sysext.raw", /* No compatibility to worry about in the new .vendor.d dirs */
/* exclude_suffix= */ NULL,
".extra/sysext",
/* dir_mode= */ 0555,
/* access_mode= */ 0444,
/* tpm_pcr= */ TPM2_PCR_SYSEXTS,
u"Vendor system extension initrd",
initrds + INITRD_VENDOR_SYSEXT,
&m) == EFI_SUCCESS)
combine_measured_flag(sysext_measured, m);
if (pack_cpio(loaded_image,
dropin_dir,
u".confext.raw",
/* exclude_suffix= */ NULL,
".extra/confext",
@ -880,6 +901,19 @@ static void generate_sidecar_initrds(
initrds + INITRD_CONFEXT,
&m) == EFI_SUCCESS)
combine_measured_flag(confext_measured, m);
if (pack_cpio(loaded_image,
vendor_dropin_dir,
u".confext.raw",
/* exclude_suffix= */ NULL,
".extra/confext",
/* dir_mode= */ 0555,
/* access_mode= */ 0444,
/* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG,
u"Vendor configuration extension initrd",
initrds + INITRD_VENDOR_CONFEXT,
&m) == EFI_SUCCESS)
combine_measured_flag(confext_measured, m);
}
static void generate_embedded_initrds(
@ -1042,8 +1076,23 @@ static void load_all_addons(
if (err != EFI_SUCCESS)
log_error_status(err, "Error loading global addons, ignoring: %m");
err = load_addons(
image,
loaded_image,
u"\\loader\\vendor-addons",
uname,
cmdline_addons,
dt_addons,
n_dt_addons,
initrd_addons,
n_initrd_addons,
ucode_addons,
n_ucode_addons);
if (err != EFI_SUCCESS)
log_error_status(err, "Error loading global vendor addons, ignoring: %m");
/* Some bootloaders always pass NULL in FilePath, so we need to check for it here. */
_cleanup_free_ char16_t *dropin_dir = get_extra_dir(loaded_image->FilePath);
_cleanup_free_ char16_t *dropin_dir = get_extra_dir(".extra.d", loaded_image->FilePath);
if (!dropin_dir)
return;
@ -1061,6 +1110,26 @@ static void load_all_addons(
n_ucode_addons);
if (err != EFI_SUCCESS)
log_error_status(err, "Error loading UKI-specific addons, ignoring: %m");
_cleanup_free_ char16_t *vendor_dropin_dir =
get_extra_dir(".extra.vendor.d", loaded_image->FilePath);
if (!vendor_dropin_dir)
return;
err = load_addons(
image,
loaded_image,
vendor_dropin_dir,
uname,
cmdline_addons,
dt_addons,
n_dt_addons,
initrd_addons,
n_initrd_addons,
ucode_addons,
n_ucode_addons);
if (err != EFI_SUCCESS)
log_error_status(err, "Error loading UKI-specific vendor addons, ignoring: %m");
}
static void display_splash(

View File

@ -462,7 +462,7 @@ static void remove_boot_count(char16_t *path) {
strcpy16(prefix_end, tail);
}
char16_t *get_extra_dir(const EFI_DEVICE_PATH *file_path) {
char16_t *get_extra_dir(const char *suffix, const EFI_DEVICE_PATH *file_path) {
if (!file_path)
return NULL;
@ -483,7 +483,7 @@ char16_t *get_extra_dir(const EFI_DEVICE_PATH *file_path) {
convert_efi_path(file_path_str);
remove_boot_count(file_path_str);
return xasprintf("%ls.extra.d", file_path_str);
return xasprintf("%ls%s", file_path_str, suffix);
}
void *xmalloc(size_t size) {

View File

@ -239,7 +239,7 @@ static inline bool efi_guid_equal(const EFI_GUID *a, const EFI_GUID *b) {
void *find_configuration_table(const EFI_GUID *guid);
char16_t *get_extra_dir(const EFI_DEVICE_PATH *file_path);
char16_t *get_extra_dir(const char *suffix, const EFI_DEVICE_PATH *file_path);
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
# define be32toh(x) __builtin_bswap32(x)

View File

@ -0,0 +1,95 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <getopt.h>
#include "ansi-color.h"
#include "bootspec.h"
#include "find-esp.h"
#include "main-func.h"
static int help(void) {
_cleanup_free_ char *link = NULL;
int r;
r = terminal_urlify_man("systemd-factory-reset-esp.service", "8", &link);
if (r < 0)
return log_oom();
printf("%1$s [OPTIONS...]\n"
"\n%5$sDelete non-vendor contents from ESP and XBOOTLDR.%6$s\n"
"\n%3$sOptions:%4$s\n"
" -h --help Show this help\n"
" --version Print version\n"
"\nSee the %2$s for details.\n",
program_invocation_short_name,
link,
ansi_underline(),
ansi_normal(),
ansi_highlight(),
ansi_normal());
return 0;
}
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{}
};
int r, c;
assert(argc >= 0);
assert(argv);
while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
switch (c) {
case 'h':
return help();
case ARG_VERSION:
return version();
case '?':
return -EINVAL;
default:
assert_not_reached();
}
return 1;
}
static int run(int argc, char *argv[]) {
int r;
log_setup();
r = parse_argv(argc, argv);
if (r <= 0)
return r;
FactoryResetMode f = factory_reset_mode();
if (f < 0)
return log_error_errno(f, "Failed to determine factory reset mode: %m");
if (f != FACTORY_RESET_ON)
return log_error("We are not currently in factory reset mode. Refusing operation.");
_cleanup_(boot_config_free) BootConfig bc = BOOT_CONFIG_NULL;
r = boot_config_load_auto(&bc, NULL, NULL);
if (r < 0)
log_error_errno(r, "Failed to load boot config: %m");
// TODO: Walk the ESP and XBOOTLDR, and delete the non-vendor stuff!
return 0;
}
DEFINE_MAIN_FUNCTION(run);

View File

@ -5,6 +5,10 @@ executables += [
'name' : 'systemd-factory-reset',
'sources' : files('factory-reset-tool.c'),
},
libexec_template + {
'name' : 'systemd-factory-reset-esp',
'sources' : files('factory-reset-esp.c'),
},
generator_template + {
'name' : 'systemd-factory-reset-generator',
'sources' : files('factory-reset-generator.c'),

View File

@ -1090,14 +1090,13 @@ static int boot_entries_find_unified_addons(
int d_fd,
const char *addon_dir,
const char *root,
BootEntryAddons *ret_addons) {
BootEntryAddons *addons) {
_cleanup_closedir_ DIR *d = NULL;
_cleanup_free_ char *full = NULL;
_cleanup_(boot_entry_addons_done) BootEntryAddons addons = {};
int r;
assert(ret_addons);
assert(addons);
assert(config);
r = chase_and_opendirat(d_fd, addon_dir, CHASE_AT_RESOLVE_IN_ROOT, &full, &d);
@ -1139,15 +1138,13 @@ static int boot_entries_find_unified_addons(
if (!location)
return log_oom();
r = insert_boot_entry_addon(&addons, location, cmdline);
r = insert_boot_entry_addon(addons, location, cmdline);
if (r < 0)
return r;
TAKE_PTR(location);
TAKE_PTR(cmdline);
}
*ret_addons = TAKE_STRUCT(addons);
return 0;
}
@ -1158,17 +1155,28 @@ static int boot_entries_find_unified_global_addons(
BootEntryAddons *ret_addons) {
int r;
_cleanup_free_ char *p = NULL;
_cleanup_closedir_ DIR *d = NULL;
_cleanup_(boot_entry_addons_done) BootEntryAddons addons = {};
assert(ret_addons);
r = chase_and_opendir(root, NULL, CHASE_PROHIBIT_SYMLINKS, NULL, &d);
r = chase_and_opendir(d_name, root, CHASE_PROHIBIT_SYMLINKS, &p, &d);
if (r == -ENOENT)
return 0;
if (r < 0)
return log_error_errno(r, "Failed to open '%s/%s': %m", root, d_name);
return boot_entries_find_unified_addons(config, dirfd(d), d_name, root, ret_addons);
r = boot_entries_find_unified_addons(config, dirfd(d), "addons", p, &addons);
if (r < 0)
return r;
r = boot_entries_find_unified_addons(config, dirfd(d), "vendor-addons", p, &addons);
if (r < 0)
return r;
*ret_addons = TAKE_STRUCT(addons);
return 0;
}
static int boot_entries_find_unified_local_addons(
@ -1178,7 +1186,8 @@ static int boot_entries_find_unified_local_addons(
const char *root,
BootEntry *ret) {
_cleanup_free_ char *addon_dir = NULL;
_cleanup_free_ char *addon_dir = NULL, *vendor_addon_dir = NULL;
int r;
assert(ret);
@ -1186,7 +1195,15 @@ static int boot_entries_find_unified_local_addons(
if (!addon_dir)
return log_oom();
return boot_entries_find_unified_addons(config, d_fd, addon_dir, root, &ret->local_addons);
vendor_addon_dir = strjoin(d_name, ".extra.vendor.d");
if (!vendor_addon_dir)
return log_oom();
r = boot_entries_find_unified_addons(config, d_fd, addon_dir, root, &ret->local_addons);
if (r < 0)
return r;
return boot_entries_find_unified_addons(config, d_fd, vendor_addon_dir, root, &ret->local_addons);
}
static int boot_entries_find_unified(
@ -1485,7 +1502,7 @@ int boot_config_load(
if (r < 0)
return r;
r = boot_entries_find_unified_global_addons(config, esp_path, "/loader/addons/",
r = boot_entries_find_unified_global_addons(config, esp_path, "/loader",
&config->global_addons[BOOT_ENTRY_ESP]);
if (r < 0)
return r;
@ -1500,7 +1517,7 @@ int boot_config_load(
if (r < 0)
return r;
r = boot_entries_find_unified_global_addons(config, xbootldr_path, "/loader/addons/",
r = boot_entries_find_unified_global_addons(config, xbootldr_path, "/loader",
&config->global_addons[BOOT_ENTRY_XBOOTLDR]);
if (r < 0)
return r;

View File

@ -329,6 +329,7 @@ units = [
'symlinks' : ['sockets.target.wants/'],
},
{ 'file' : 'systemd-factory-reset-complete.service.in' },
{ 'file' : 'systemd-factory-reset-esp.service' },
{ 'file' : 'systemd-factory-reset-reboot.service' },
{
'file' : 'systemd-factory-reset-request.service.in',

View File

@ -0,0 +1,28 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
##########################################################################################
# TODO: Edit this!
##########################################################################################
[Unit]
Description=Delete non-vendor content from the ESP and XBOOTLDR
Documentation=man:systemd-factory-reset-esp.service(8)
DefaultDependencies=no
Requires=factory-reset-now.target
After=factory-reset-now.target
Conflicts=shutdown.target
Before=shutdown.target
RefuseManualStart=yes
RefuseManualStop=yes
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart={{LIBEXECDIR}}/systemd-factory-reset complete --retrigger