You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
864 lines
40 KiB
864 lines
40 KiB
From bd5fbc8566124744d94d5f040016b9f4404dc2dc Mon Sep 17 00:00:00 2001
|
|
From: "Jason A. Donenfeld" <Jason@zx2c4.com>
|
|
Date: Wed, 9 Nov 2022 12:44:37 +0100
|
|
Subject: [PATCH] boot: implement kernel EFI RNG seed protocol with proper
|
|
hashing
|
|
|
|
Rather than passing seeds up to userspace via EFI variables, pass seeds
|
|
directly to the kernel's EFI stub loader, via LINUX_EFI_RANDOM_SEED_TABLE_GUID.
|
|
EFI variables can potentially leak and suffer from forward secrecy
|
|
issues, and processing these with userspace means that they are
|
|
initialized much too late in boot to be useful. In contrast,
|
|
LINUX_EFI_RANDOM_SEED_TABLE_GUID uses EFI configuration tables, and so
|
|
is hidden from userspace entirely, and is parsed extremely early on by
|
|
the kernel, so that every single call to get_random_bytes() by the
|
|
kernel is seeded.
|
|
|
|
In order to do this properly, we use a bit more robust hashing scheme,
|
|
and make sure that each input is properly memzeroed out after use. The
|
|
scheme is:
|
|
|
|
key = HASH(LABEL || sizeof(input1) || input1 || ... || sizeof(inputN) || inputN)
|
|
new_disk_seed = HASH(key || 0)
|
|
seed_for_linux = HASH(key || 1)
|
|
|
|
The various inputs are:
|
|
- LINUX_EFI_RANDOM_SEED_TABLE_GUID from prior bootloaders
|
|
- 256 bits of seed from EFI's RNG
|
|
- The (immutable) system token, from its EFI variable
|
|
- The prior on-disk seed
|
|
- The UEFI monotonic counter
|
|
- A timestamp
|
|
|
|
This also adjusts the secure boot semantics, so that the operation is
|
|
only aborted if it's not possible to get random bytes from EFI's RNG or
|
|
a prior boot stage. With the proper hashing scheme, this should make
|
|
boot seeds safe even on secure boot.
|
|
|
|
There is currently a bug in Linux's EFI stub in which if the EFI stub
|
|
manages to generate random bytes on its own using EFI's RNG, it will
|
|
ignore what the bootloader passes. That's annoying, but it means that
|
|
either way, via systemd-boot or via EFI stub's mechanism, the RNG *does*
|
|
get initialized in a good safe way. And this bug is now fixed in the
|
|
efi.git tree, and will hopefully be backported to older kernels.
|
|
|
|
As the kernel recommends, the resultant seeds are 256 bits and are
|
|
allocated using pool memory of type EfiACPIReclaimMemory, so that it
|
|
gets freed at the right moment in boot.
|
|
|
|
(cherry picked from commit 0be72218f1c90af5755ab40f94d047ee6864aea8)
|
|
|
|
Related: RHEL-16952
|
|
---
|
|
.../UninitializedVariableWithCleanup.ql | 2 +-
|
|
docs/BOOT_LOADER_INTERFACE.md | 9 +-
|
|
docs/RANDOM_SEEDS.md | 51 +--
|
|
man/systemd-boot.xml | 22 --
|
|
src/basic/random-util.h | 1 +
|
|
src/boot/bootctl.c | 30 +-
|
|
src/boot/efi/efi-string.h | 1 +
|
|
src/boot/efi/random-seed.c | 305 +++++++++---------
|
|
src/core/efi-random.c | 82 +----
|
|
src/core/efi-random.h | 2 +-
|
|
src/core/main.c | 4 +-
|
|
units/systemd-boot-system-token.service | 3 -
|
|
12 files changed, 218 insertions(+), 294 deletions(-)
|
|
|
|
diff --git a/.github/codeql-queries/UninitializedVariableWithCleanup.ql b/.github/codeql-queries/UninitializedVariableWithCleanup.ql
|
|
index e514111f28..dadc6cb1b5 100644
|
|
--- a/.github/codeql-queries/UninitializedVariableWithCleanup.ql
|
|
+++ b/.github/codeql-queries/UninitializedVariableWithCleanup.ql
|
|
@@ -20,7 +20,7 @@ import semmle.code.cpp.controlflow.StackVariableReachability
|
|
* since they don't do anything illegal even when the variable is uninitialized
|
|
*/
|
|
predicate cleanupFunctionDenyList(string fun) {
|
|
- fun = "erase_char"
|
|
+ fun = "erase_char" or fun = "erase_obj"
|
|
}
|
|
|
|
/**
|
|
diff --git a/docs/BOOT_LOADER_INTERFACE.md b/docs/BOOT_LOADER_INTERFACE.md
|
|
index fc9336085b..5be4d1ad17 100644
|
|
--- a/docs/BOOT_LOADER_INTERFACE.md
|
|
+++ b/docs/BOOT_LOADER_INTERFACE.md
|
|
@@ -80,12 +80,6 @@ variables. All EFI variables use the vendor UUID
|
|
* `1 << 5` → The boot loader supports looking for boot menu entries in the Extended Boot Loader Partition.
|
|
* `1 << 6` → The boot loader supports passing a random seed to the OS.
|
|
|
|
-* The EFI variable `LoaderRandomSeed` contains a binary random seed if set. It
|
|
- is set by the boot loader to pass an entropy seed read from the ESP to the OS.
|
|
- The system manager then credits this seed to the kernel's entropy pool. It is
|
|
- the responsibility of the boot loader to ensure the quality and integrity of
|
|
- the random seed.
|
|
-
|
|
* The EFI variable `LoaderSystemToken` contains binary random data,
|
|
persistently set by the OS installer. Boot loaders that support passing
|
|
random seeds to the OS should use this data and combine it with the random
|
|
@@ -107,8 +101,7 @@ that directory is empty, and only if no other file systems are mounted
|
|
there. The `systemctl reboot --boot-loader-entry=…` and `systemctl reboot
|
|
--boot-loader-menu=…` commands rely on the `LoaderFeatures` ,
|
|
`LoaderConfigTimeoutOneShot`, `LoaderEntries`, `LoaderEntryOneShot`
|
|
-variables. `LoaderRandomSeed` is read by PID during early boot and credited to
|
|
-the kernel's random pool.
|
|
+variables.
|
|
|
|
## Boot Loader Entry Identifiers
|
|
|
|
diff --git a/docs/RANDOM_SEEDS.md b/docs/RANDOM_SEEDS.md
|
|
index 3dc27f5552..b7240f0d89 100644
|
|
--- a/docs/RANDOM_SEEDS.md
|
|
+++ b/docs/RANDOM_SEEDS.md
|
|
@@ -197,28 +197,39 @@ boot, in order to ensure the entropy pool is filled up quickly.
|
|
generate sufficient data), to generate a new random seed file to store in
|
|
the ESP as well as a random seed to pass to the OS kernel. The new random
|
|
seed file for the ESP is then written to the ESP, ensuring this is completed
|
|
- before the OS is invoked. Very early during initialization PID 1 will read
|
|
- the random seed provided in the EFI variable and credit it fully to the
|
|
- kernel's entropy pool.
|
|
-
|
|
- This mechanism is able to safely provide an initialized entropy pool already
|
|
- in the `initrd` and guarantees that different seeds are passed from the boot
|
|
- loader to the OS on every boot (in a way that does not allow regeneration of
|
|
- an old seed file from a new seed file). Moreover, when an OS image is
|
|
- replicated between multiple images and the random seed is not reset, this
|
|
- will still result in different random seeds being passed to the OS, as the
|
|
- per-machine 'system token' is specific to the physical host, and not
|
|
- included in OS disk images. If the 'system token' is properly initialized
|
|
- and kept sufficiently secret it should not be possible to regenerate the
|
|
- entropy pool of different machines, even if this seed is the only source of
|
|
- entropy.
|
|
+ before the OS is invoked.
|
|
+
|
|
+ The kernel then reads the random seed that the boot loader passes to it, via
|
|
+ the EFI configuration table entry, `LINUX_EFI_RANDOM_SEED_TABLE_GUID`
|
|
+ (1ce1e5bc-7ceb-42f2-81e5-8aadf180f57b), which is allocated with pool memory
|
|
+ of type `EfiACPIReclaimMemory`. Its contents have the form:
|
|
+ ```
|
|
+ struct linux_efi_random_seed {
|
|
+ u32 size; // of the 'seed' array in bytes
|
|
+ u8 seed[];
|
|
+ };
|
|
+ ```
|
|
+ The size field is generally set to 32 bytes, and the seed field includes a
|
|
+ hashed representation of any prior seed in `LINUX_EFI_RANDOM_SEED_TABLE_GUID`
|
|
+ together with the new seed.
|
|
+
|
|
+ This mechanism is able to safely provide an initialized entropy pool before
|
|
+ userspace even starts and guarantees that different seeds are passed from
|
|
+ the boot loader to the OS on every boot (in a way that does not allow
|
|
+ regeneration of an old seed file from a new seed file). Moreover, when an OS
|
|
+ image is replicated between multiple images and the random seed is not
|
|
+ reset, this will still result in different random seeds being passed to the
|
|
+ OS, as the per-machine 'system token' is specific to the physical host, and
|
|
+ not included in OS disk images. If the 'system token' is properly
|
|
+ initialized and kept sufficiently secret it should not be possible to
|
|
+ regenerate the entropy pool of different machines, even if this seed is the
|
|
+ only source of entropy.
|
|
|
|
Note that the writes to the ESP needed to maintain the random seed should be
|
|
- minimal. The size of the random seed file is directly derived from the Linux
|
|
- kernel's entropy pool size, which defaults to 512 bytes. This means updating
|
|
- the random seed in the ESP should be doable safely with a single sector
|
|
- write (since hard-disk sectors typically happen to be 512 bytes long, too),
|
|
- which should be safe even with FAT file system drivers built into
|
|
+ minimal. Because the size of the random seed file is generally set to 32 bytes,
|
|
+ updating the random seed in the ESP should be doable safely with a single
|
|
+ sector write (since hard-disk sectors typically happen to be 512 bytes long,
|
|
+ too), which should be safe even with FAT file system drivers built into
|
|
low-quality EFI firmwares.
|
|
|
|
As a special restriction: in virtualized environments PID 1 will refrain
|
|
diff --git a/man/systemd-boot.xml b/man/systemd-boot.xml
|
|
index 57b66803fa..f96c4c6512 100644
|
|
--- a/man/systemd-boot.xml
|
|
+++ b/man/systemd-boot.xml
|
|
@@ -435,28 +435,6 @@
|
|
to view this data. </para></listitem>
|
|
</varlistentry>
|
|
|
|
- <varlistentry>
|
|
- <term><varname>LoaderRandomSeed</varname></term>
|
|
-
|
|
- <listitem><para>A binary random seed <command>systemd-boot</command> may optionally pass to the
|
|
- OS. This is a volatile EFI variable that is hashed at boot from the combination of a random seed
|
|
- stored in the ESP (in <filename>/loader/random-seed</filename>) and a "system token" persistently
|
|
- stored in the EFI variable <varname>LoaderSystemToken</varname> (see below). During early OS boot the
|
|
- system manager reads this variable and passes it to the OS kernel's random pool, crediting the full
|
|
- entropy it contains. This is an efficient way to ensure the system starts up with a fully initialized
|
|
- kernel random pool — as early as the initrd phase. <command>systemd-boot</command> reads
|
|
- the random seed from the ESP, combines it with the "system token", and both derives a new random seed
|
|
- to update in-place the seed stored in the ESP, and the random seed to pass to the OS from it via
|
|
- SHA256 hashing in counter mode. This ensures that different physical systems that boot the same
|
|
- "golden" OS image — i.e. containing the same random seed file in the ESP — will still pass a
|
|
- different random seed to the OS. It is made sure the random seed stored in the ESP is fully
|
|
- overwritten before the OS is booted, to ensure different random seed data is used between subsequent
|
|
- boots.</para>
|
|
-
|
|
- <para>See <ulink url="https://systemd.io/RANDOM_SEEDS">Random Seeds</ulink> for
|
|
- further information.</para></listitem>
|
|
- </varlistentry>
|
|
-
|
|
<varlistentry>
|
|
<term><varname>LoaderSystemToken</varname></term>
|
|
|
|
diff --git a/src/basic/random-util.h b/src/basic/random-util.h
|
|
index 7e6f66df4d..08b1a3599a 100644
|
|
--- a/src/basic/random-util.h
|
|
+++ b/src/basic/random-util.h
|
|
@@ -23,6 +23,7 @@ static inline uint32_t random_u32(void) {
|
|
/* Some limits on the pool sizes when we deal with the kernel random pool */
|
|
#define RANDOM_POOL_SIZE_MIN 1024U
|
|
#define RANDOM_POOL_SIZE_MAX (10U*1024U*1024U)
|
|
+#define RANDOM_EFI_SEED_SIZE 32U
|
|
|
|
size_t random_pool_size(void);
|
|
|
|
diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c
|
|
index e830d45486..e23a72fd38 100644
|
|
--- a/src/boot/bootctl.c
|
|
+++ b/src/boot/bootctl.c
|
|
@@ -1894,8 +1894,6 @@ static int verb_status(int argc, char *argv[], void *userdata) {
|
|
printf("\n");
|
|
|
|
printf("%sRandom Seed:%s\n", ansi_underline(), ansi_normal());
|
|
- have = access(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderRandomSeed)), F_OK) >= 0;
|
|
- printf(" Passed to OS: %s\n", yes_no(have));
|
|
have = access(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderSystemToken)), F_OK) >= 0;
|
|
printf(" System Token: %s\n", have ? "set" : "not set");
|
|
|
|
@@ -1985,10 +1983,10 @@ static int verb_list(int argc, char *argv[], void *userdata) {
|
|
|
|
static int install_random_seed(const char *esp) {
|
|
_cleanup_(unlink_and_freep) char *tmp = NULL;
|
|
- _cleanup_free_ void *buffer = NULL;
|
|
+ unsigned char buffer[RANDOM_EFI_SEED_SIZE];
|
|
_cleanup_free_ char *path = NULL;
|
|
_cleanup_close_ int fd = -1;
|
|
- size_t sz, token_size;
|
|
+ size_t token_size;
|
|
ssize_t n;
|
|
int r;
|
|
|
|
@@ -1998,13 +1996,7 @@ static int install_random_seed(const char *esp) {
|
|
if (!path)
|
|
return log_oom();
|
|
|
|
- sz = random_pool_size();
|
|
-
|
|
- buffer = malloc(sz);
|
|
- if (!buffer)
|
|
- return log_oom();
|
|
-
|
|
- r = crypto_random_bytes(buffer, sz);
|
|
+ r = crypto_random_bytes(buffer, sizeof(buffer));
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to acquire random seed: %m");
|
|
|
|
@@ -2025,10 +2017,10 @@ static int install_random_seed(const char *esp) {
|
|
return log_error_errno(fd, "Failed to open random seed file for writing: %m");
|
|
}
|
|
|
|
- n = write(fd, buffer, sz);
|
|
+ n = write(fd, buffer, sizeof(buffer));
|
|
if (n < 0)
|
|
return log_error_errno(errno, "Failed to write random seed file: %m");
|
|
- if ((size_t) n != sz)
|
|
+ if ((size_t) n != sizeof(buffer))
|
|
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short write while writing random seed file.");
|
|
|
|
if (rename(tmp, path) < 0)
|
|
@@ -2036,7 +2028,7 @@ static int install_random_seed(const char *esp) {
|
|
|
|
tmp = mfree(tmp);
|
|
|
|
- log_info("Random seed file %s successfully written (%zu bytes).", path, sz);
|
|
+ log_info("Random seed file %s successfully written (%zu bytes).", path, sizeof(buffer));
|
|
|
|
if (!arg_touch_variables)
|
|
return 0;
|
|
@@ -2088,16 +2080,16 @@ static int install_random_seed(const char *esp) {
|
|
if (r != -ENOENT)
|
|
return log_error_errno(r, "Failed to test system token validity: %m");
|
|
} else {
|
|
- if (token_size >= sz) {
|
|
+ if (token_size >= sizeof(buffer)) {
|
|
/* Let's avoid writes if we can, and initialize this only once. */
|
|
log_debug("System token already written, not updating.");
|
|
return 0;
|
|
}
|
|
|
|
- log_debug("Existing system token size (%zu) does not match our expectations (%zu), replacing.", token_size, sz);
|
|
+ log_debug("Existing system token size (%zu) does not match our expectations (%zu), replacing.", token_size, sizeof(buffer));
|
|
}
|
|
|
|
- r = crypto_random_bytes(buffer, sz);
|
|
+ r = crypto_random_bytes(buffer, sizeof(buffer));
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to acquire random seed: %m");
|
|
|
|
@@ -2105,7 +2097,7 @@ static int install_random_seed(const char *esp) {
|
|
* and possibly get identification information or too much insight into the kernel's entropy pool
|
|
* state. */
|
|
RUN_WITH_UMASK(0077) {
|
|
- r = efi_set_variable(EFI_LOADER_VARIABLE(LoaderSystemToken), buffer, sz);
|
|
+ r = efi_set_variable(EFI_LOADER_VARIABLE(LoaderSystemToken), buffer, sizeof(buffer));
|
|
if (r < 0) {
|
|
if (!arg_graceful)
|
|
return log_error_errno(r, "Failed to write 'LoaderSystemToken' EFI variable: %m");
|
|
@@ -2115,7 +2107,7 @@ static int install_random_seed(const char *esp) {
|
|
else
|
|
log_warning_errno(r, "Unable to write 'LoaderSystemToken' EFI variable, ignoring: %m");
|
|
} else
|
|
- log_info("Successfully initialized system token in EFI variable with %zu bytes.", sz);
|
|
+ log_info("Successfully initialized system token in EFI variable with %zu bytes.", sizeof(buffer));
|
|
}
|
|
|
|
return 0;
|
|
diff --git a/src/boot/efi/efi-string.h b/src/boot/efi/efi-string.h
|
|
index 9b2a9ad1c5..25931a7d6e 100644
|
|
--- a/src/boot/efi/efi-string.h
|
|
+++ b/src/boot/efi/efi-string.h
|
|
@@ -124,6 +124,7 @@ static inline void *mempcpy(void * restrict dest, const void * restrict src, siz
|
|
memcpy(dest, src, n);
|
|
return (uint8_t *) dest + n;
|
|
}
|
|
+
|
|
#else
|
|
/* For unit testing. */
|
|
int efi_memcmp(const void *p1, const void *p2, size_t n);
|
|
diff --git a/src/boot/efi/random-seed.c b/src/boot/efi/random-seed.c
|
|
index 3c9df5bb54..c723160c0f 100644
|
|
--- a/src/boot/efi/random-seed.c
|
|
+++ b/src/boot/efi/random-seed.c
|
|
@@ -15,11 +15,24 @@
|
|
|
|
#define EFI_RNG_GUID &(const EFI_GUID) EFI_RNG_PROTOCOL_GUID
|
|
|
|
+struct linux_efi_random_seed {
|
|
+ uint32_t size;
|
|
+ uint8_t seed[];
|
|
+};
|
|
+
|
|
+#define LINUX_EFI_RANDOM_SEED_TABLE_GUID \
|
|
+ { 0x1ce1e5bc, 0x7ceb, 0x42f2, { 0x81, 0xe5, 0x8a, 0xad, 0xf1, 0x80, 0xf5, 0x7b } }
|
|
+
|
|
/* SHA256 gives us 256/8=32 bytes */
|
|
#define HASH_VALUE_SIZE 32
|
|
|
|
-static EFI_STATUS acquire_rng(UINTN size, void **ret) {
|
|
- _cleanup_free_ void *data = NULL;
|
|
+/* Linux's RNG is 256 bits, so let's provide this much */
|
|
+#define DESIRED_SEED_SIZE 32
|
|
+
|
|
+/* Some basic domain separation in case somebody uses this data elsewhere */
|
|
+#define HASH_LABEL "systemd-boot random seed label v1"
|
|
+
|
|
+static EFI_STATUS acquire_rng(void *ret, UINTN size) {
|
|
EFI_RNG_PROTOCOL *rng;
|
|
EFI_STATUS err;
|
|
|
|
@@ -33,126 +46,9 @@ static EFI_STATUS acquire_rng(UINTN size, void **ret) {
|
|
if (!rng)
|
|
return EFI_UNSUPPORTED;
|
|
|
|
- data = xmalloc(size);
|
|
-
|
|
- err = rng->GetRNG(rng, NULL, size, data);
|
|
+ err = rng->GetRNG(rng, NULL, size, ret);
|
|
if (err != EFI_SUCCESS)
|
|
return log_error_status_stall(err, L"Failed to acquire RNG data: %r", err);
|
|
-
|
|
- *ret = TAKE_PTR(data);
|
|
- return EFI_SUCCESS;
|
|
-}
|
|
-
|
|
-static void hash_once(
|
|
- const void *old_seed,
|
|
- const void *rng,
|
|
- UINTN size,
|
|
- const void *system_token,
|
|
- UINTN system_token_size,
|
|
- uint64_t uefi_monotonic_counter,
|
|
- UINTN counter,
|
|
- uint8_t ret[static HASH_VALUE_SIZE]) {
|
|
-
|
|
- /* This hashes together:
|
|
- *
|
|
- * 1. The contents of the old seed file
|
|
- * 2. Some random data acquired from the UEFI RNG (optional)
|
|
- * 3. Some 'system token' the installer installed as EFI variable (optional)
|
|
- * 4. The UEFI "monotonic counter" that increases with each boot
|
|
- * 5. A supplied counter value
|
|
- *
|
|
- * And writes the result to the specified buffer.
|
|
- */
|
|
-
|
|
- struct sha256_ctx hash;
|
|
-
|
|
- assert(old_seed);
|
|
- assert(system_token_size == 0 || system_token);
|
|
-
|
|
- sha256_init_ctx(&hash);
|
|
- sha256_process_bytes(old_seed, size, &hash);
|
|
- if (rng)
|
|
- sha256_process_bytes(rng, size, &hash);
|
|
- if (system_token_size > 0)
|
|
- sha256_process_bytes(system_token, system_token_size, &hash);
|
|
- sha256_process_bytes(&uefi_monotonic_counter, sizeof(uefi_monotonic_counter), &hash);
|
|
- sha256_process_bytes(&counter, sizeof(counter), &hash);
|
|
- sha256_finish_ctx(&hash, ret);
|
|
-}
|
|
-
|
|
-static EFI_STATUS hash_many(
|
|
- const void *old_seed,
|
|
- const void *rng,
|
|
- UINTN size,
|
|
- const void *system_token,
|
|
- UINTN system_token_size,
|
|
- uint64_t uefi_monotonic_counter,
|
|
- UINTN counter_start,
|
|
- UINTN n,
|
|
- void **ret) {
|
|
-
|
|
- _cleanup_free_ void *output = NULL;
|
|
-
|
|
- assert(old_seed);
|
|
- assert(system_token_size == 0 || system_token);
|
|
- assert(ret);
|
|
-
|
|
- /* Hashes the specified parameters in counter mode, generating n hash values, with the counter in the
|
|
- * range counter_start…counter_start+n-1. */
|
|
-
|
|
- output = xmalloc_multiply(HASH_VALUE_SIZE, n);
|
|
-
|
|
- for (UINTN i = 0; i < n; i++)
|
|
- hash_once(old_seed, rng, size,
|
|
- system_token, system_token_size,
|
|
- uefi_monotonic_counter,
|
|
- counter_start + i,
|
|
- (uint8_t*) output + (i * HASH_VALUE_SIZE));
|
|
-
|
|
- *ret = TAKE_PTR(output);
|
|
- return EFI_SUCCESS;
|
|
-}
|
|
-
|
|
-static EFI_STATUS mangle_random_seed(
|
|
- const void *old_seed,
|
|
- const void *rng,
|
|
- UINTN size,
|
|
- const void *system_token,
|
|
- UINTN system_token_size,
|
|
- uint64_t uefi_monotonic_counter,
|
|
- void **ret_new_seed,
|
|
- void **ret_for_kernel) {
|
|
-
|
|
- _cleanup_free_ void *new_seed = NULL, *for_kernel = NULL;
|
|
- EFI_STATUS err;
|
|
- UINTN n;
|
|
-
|
|
- assert(old_seed);
|
|
- assert(system_token_size == 0 || system_token);
|
|
- assert(ret_new_seed);
|
|
- assert(ret_for_kernel);
|
|
-
|
|
- /* This takes the old seed file contents, an (optional) random number acquired from the UEFI RNG, an
|
|
- * (optional) system 'token' installed once by the OS installer in an EFI variable, and hashes them
|
|
- * together in counter mode, generating a new seed (to replace the file on disk) and the seed for the
|
|
- * kernel. To keep things simple, the new seed and kernel data have the same size as the old seed and
|
|
- * RNG data. */
|
|
-
|
|
- n = (size + HASH_VALUE_SIZE - 1) / HASH_VALUE_SIZE;
|
|
-
|
|
- /* Begin hashing in counter mode at counter 0 for the new seed for the disk */
|
|
- err = hash_many(old_seed, rng, size, system_token, system_token_size, uefi_monotonic_counter, 0, n, &new_seed);
|
|
- if (err != EFI_SUCCESS)
|
|
- return err;
|
|
-
|
|
- /* Continue counting at 'n' for the seed for the kernel */
|
|
- err = hash_many(old_seed, rng, size, system_token, system_token_size, uefi_monotonic_counter, n, n, &for_kernel);
|
|
- if (err != EFI_SUCCESS)
|
|
- return err;
|
|
-
|
|
- *ret_new_seed = TAKE_PTR(new_seed);
|
|
- *ret_for_kernel = TAKE_PTR(for_kernel);
|
|
-
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
@@ -164,6 +60,7 @@ static EFI_STATUS acquire_system_token(void **ret, UINTN *ret_size) {
|
|
assert(ret);
|
|
assert(ret_size);
|
|
|
|
+ *ret_size = 0;
|
|
err = efivar_get_raw(LOADER_GUID, L"LoaderSystemToken", &data, &size);
|
|
if (err != EFI_SUCCESS) {
|
|
if (err != EFI_NOT_FOUND)
|
|
@@ -221,32 +118,88 @@ static void validate_sha256(void) {
|
|
#endif
|
|
}
|
|
|
|
-EFI_STATUS process_random_seed(EFI_FILE *root_dir, RandomSeedMode mode) {
|
|
- _cleanup_free_ void *seed = NULL, *new_seed = NULL, *rng = NULL, *for_kernel = NULL, *system_token = NULL;
|
|
+EFI_STATUS process_random_seed(EFI_FILE *root_dir) {
|
|
+ uint8_t random_bytes[DESIRED_SEED_SIZE], hash_key[HASH_VALUE_SIZE];
|
|
+ _cleanup_free_ struct linux_efi_random_seed *new_seed_table = NULL;
|
|
+ struct linux_efi_random_seed *previous_seed_table = NULL;
|
|
+ _cleanup_free_ void *seed = NULL, *system_token = NULL;
|
|
_cleanup_(file_closep) EFI_FILE *handle = NULL;
|
|
- UINTN size, rsize, wsize, system_token_size = 0;
|
|
_cleanup_free_ EFI_FILE_INFO *info = NULL;
|
|
+ struct sha256_ctx hash;
|
|
uint64_t uefi_monotonic_counter = 0;
|
|
+ size_t size, rsize, wsize;
|
|
+ bool seeded_by_efi = false;
|
|
EFI_STATUS err;
|
|
+ EFI_TIME now;
|
|
+
|
|
+ CLEANUP_ERASE(random_bytes);
|
|
+ CLEANUP_ERASE(hash_key);
|
|
+ CLEANUP_ERASE(hash);
|
|
|
|
assert(root_dir);
|
|
+ assert_cc(DESIRED_SEED_SIZE == HASH_VALUE_SIZE);
|
|
|
|
validate_sha256();
|
|
|
|
if (mode == RANDOM_SEED_OFF)
|
|
return EFI_NOT_FOUND;
|
|
|
|
- /* Let's better be safe than sorry, and for now disable this logic in SecureBoot mode, so that we
|
|
- * don't credit a random seed that is not authenticated. */
|
|
- if (secure_boot_enabled())
|
|
- return EFI_NOT_FOUND;
|
|
+ /* hash = LABEL || sizeof(input1) || input1 || ... || sizeof(inputN) || inputN */
|
|
+ sha256_init_ctx(&hash);
|
|
+
|
|
+ /* Some basic domain separation in case somebody uses this data elsewhere */
|
|
+ sha256_process_bytes(HASH_LABEL, sizeof(HASH_LABEL) - 1, &hash);
|
|
+
|
|
+ for (size_t i = 0; i < ST->NumberOfTableEntries; ++i)
|
|
+ if (memcmp(&(const EFI_GUID)LINUX_EFI_RANDOM_SEED_TABLE_GUID,
|
|
+ &ST->ConfigurationTable[i].VendorGuid, sizeof(EFI_GUID)) == 0) {
|
|
+ previous_seed_table = ST->ConfigurationTable[i].VendorTable;
|
|
+ break;
|
|
+ }
|
|
+ if (!previous_seed_table) {
|
|
+ size = 0;
|
|
+ sha256_process_bytes(&size, sizeof(size), &hash);
|
|
+ } else {
|
|
+ size = previous_seed_table->size;
|
|
+ seeded_by_efi = size >= DESIRED_SEED_SIZE;
|
|
+ sha256_process_bytes(&size, sizeof(size), &hash);
|
|
+ sha256_process_bytes(previous_seed_table->seed, size, &hash);
|
|
+
|
|
+ /* Zero and free the previous seed table only at the end after we've managed to install a new
|
|
+ * one, so that in case this function fails or aborts, Linux still receives whatever the
|
|
+ * previous bootloader chain set. So, the next line of this block is not an explicit_bzero()
|
|
+ * call. */
|
|
+ }
|
|
+
|
|
+ /* Request some random data from the UEFI RNG. We don't need this to work safely, but it's a good
|
|
+ * idea to use it because it helps us for cases where users mistakenly include a random seed in
|
|
+ * golden master images that are replicated many times. */
|
|
+ err = acquire_rng(random_bytes, sizeof(random_bytes));
|
|
+ if (err != EFI_SUCCESS) {
|
|
+ size = 0;
|
|
+ /* If we can't get any randomness from EFI itself, then we'll only be relying on what's in
|
|
+ * ESP. But ESP is mutable, so if secure boot is enabled, we probably shouldn't trust that
|
|
+ * alone, in which case we bail out early. */
|
|
+ if (!seeded_by_efi && secure_boot_enabled())
|
|
+ return EFI_NOT_FOUND;
|
|
+ } else {
|
|
+ seeded_by_efi = true;
|
|
+ size = sizeof(random_bytes);
|
|
+ }
|
|
+ sha256_process_bytes(&size, sizeof(size), &hash);
|
|
+ sha256_process_bytes(random_bytes, size, &hash);
|
|
|
|
/* Get some system specific seed that the installer might have placed in an EFI variable. We include
|
|
* it in our hash. This is protection against golden master image sloppiness, and it remains on the
|
|
* system, even when disk images are duplicated or swapped out. */
|
|
- err = acquire_system_token(&system_token, &system_token_size);
|
|
- if (mode != RANDOM_SEED_ALWAYS && err != EFI_SUCCESS)
|
|
+ err = acquire_system_token(&system_token, &size);
|
|
+ if (mode != RANDOM_SEED_ALWAYS && (err != EFI_SUCCESS || size < DESIRED_SEED_SIZE) && !seeded_by_efi)
|
|
return err;
|
|
+ sha256_process_bytes(&size, sizeof(size), &hash);
|
|
+ if (system_token) {
|
|
+ sha256_process_bytes(system_token, size, &hash);
|
|
+ explicit_bzero_safe(system_token, size);
|
|
+ }
|
|
|
|
err = root_dir->Open(
|
|
root_dir,
|
|
@@ -262,7 +215,7 @@ EFI_STATUS process_random_seed(EFI_FILE *root_dir, RandomSeedMode mode) {
|
|
|
|
err = get_file_info_harder(handle, &info, NULL);
|
|
if (err != EFI_SUCCESS)
|
|
- return log_error_status_stall(err, L"Failed to get file info for random seed: %r");
|
|
+ return log_error_status_stall(err, L"Failed to get file info for random seed: %r", err);
|
|
|
|
size = info->FileSize;
|
|
if (size < RANDOM_MAX_SIZE_MIN)
|
|
@@ -272,51 +225,105 @@ EFI_STATUS process_random_seed(EFI_FILE *root_dir, RandomSeedMode mode) {
|
|
return log_error_status_stall(EFI_INVALID_PARAMETER, L"Random seed file is too large.");
|
|
|
|
seed = xmalloc(size);
|
|
-
|
|
rsize = size;
|
|
err = handle->Read(handle, &rsize, seed);
|
|
if (err != EFI_SUCCESS)
|
|
return log_error_status_stall(err, L"Failed to read random seed file: %r", err);
|
|
- if (rsize != size)
|
|
+ if (rsize != size) {
|
|
+ explicit_bzero_safe(seed, rsize);
|
|
return log_error_status_stall(EFI_PROTOCOL_ERROR, L"Short read on random seed file.");
|
|
+ }
|
|
+
|
|
+ sha256_process_bytes(&size, sizeof(size), &hash);
|
|
+ sha256_process_bytes(seed, size, &hash);
|
|
+ explicit_bzero_safe(seed, size);
|
|
|
|
err = handle->SetPosition(handle, 0);
|
|
if (err != EFI_SUCCESS)
|
|
return log_error_status_stall(err, L"Failed to seek to beginning of random seed file: %r", err);
|
|
|
|
- /* Request some random data from the UEFI RNG. We don't need this to work safely, but it's a good
|
|
- * idea to use it because it helps us for cases where users mistakenly include a random seed in
|
|
- * golden master images that are replicated many times. */
|
|
- (void) acquire_rng(size, &rng); /* It's fine if this fails */
|
|
-
|
|
/* Let's also include the UEFI monotonic counter (which is supposedly increasing on every single
|
|
* boot) in the hash, so that even if the changes to the ESP for some reason should not be
|
|
* persistent, the random seed we generate will still be different on every single boot. */
|
|
err = BS->GetNextMonotonicCount(&uefi_monotonic_counter);
|
|
- if (err != EFI_SUCCESS)
|
|
+ if (err != EFI_SUCCESS && !seeded_by_efi)
|
|
return log_error_status_stall(err, L"Failed to acquire UEFI monotonic counter: %r", err);
|
|
-
|
|
- /* Calculate new random seed for the disk and what to pass to the kernel */
|
|
- err = mangle_random_seed(seed, rng, size, system_token, system_token_size, uefi_monotonic_counter, &new_seed, &for_kernel);
|
|
- if (err != EFI_SUCCESS)
|
|
- return err;
|
|
-
|
|
+ size = sizeof(uefi_monotonic_counter);
|
|
+ sha256_process_bytes(&size, sizeof(size), &hash);
|
|
+ sha256_process_bytes(&uefi_monotonic_counter, size, &hash);
|
|
+ err = RT->GetTime(&now, NULL);
|
|
+ size = err == EFI_SUCCESS ? sizeof(now) : 0; /* Known to be flaky, so don't bark on error. */
|
|
+ sha256_process_bytes(&size, sizeof(size), &hash);
|
|
+ sha256_process_bytes(&now, size, &hash);
|
|
+
|
|
+ /* hash_key = HASH(hash) */
|
|
+ sha256_finish_ctx(&hash, hash_key);
|
|
+
|
|
+ /* hash = hash_key || 0 */
|
|
+ sha256_init_ctx(&hash);
|
|
+ sha256_process_bytes(hash_key, sizeof(hash_key), &hash);
|
|
+ sha256_process_bytes(&(const uint8_t){ 0 }, sizeof(uint8_t), &hash);
|
|
+ /* random_bytes = HASH(hash) */
|
|
+ sha256_finish_ctx(&hash, random_bytes);
|
|
+
|
|
+ size = sizeof(random_bytes);
|
|
+ /* If the file size is too large, zero out the remaining bytes on disk, and then truncate. */
|
|
+ if (size < info->FileSize) {
|
|
+ err = handle->SetPosition(handle, size);
|
|
+ if (err != EFI_SUCCESS)
|
|
+ return log_error_status_stall(err, L"Failed to seek to offset of random seed file: %r", err);
|
|
+ wsize = info->FileSize - size;
|
|
+ err = handle->Write(handle, &wsize, seed /* All zeros now */);
|
|
+ if (err != EFI_SUCCESS)
|
|
+ return log_error_status_stall(err, L"Failed to write random seed file: %r", err);
|
|
+ if (wsize != info->FileSize - size)
|
|
+ return log_error_status_stall(EFI_PROTOCOL_ERROR, L"Short write on random seed file.");
|
|
+ err = handle->Flush(handle);
|
|
+ if (err != EFI_SUCCESS)
|
|
+ return log_error_status_stall(err, L"Failed to flush random seed file: %r", err);
|
|
+ err = handle->SetPosition(handle, 0);
|
|
+ if (err != EFI_SUCCESS)
|
|
+ return log_error_status_stall(err, L"Failed to seek to beginning of random seed file: %r", err);
|
|
+ info->FileSize = size;
|
|
+ err = handle->SetInfo(handle, &GenericFileInfo, info->Size, info);
|
|
+ if (err != EFI_SUCCESS)
|
|
+ return log_error_status_stall(err, L"Failed to truncate random seed file: %r", err);
|
|
+ }
|
|
/* Update the random seed on disk before we use it */
|
|
wsize = size;
|
|
- err = handle->Write(handle, &wsize, new_seed);
|
|
+ err = handle->Write(handle, &wsize, random_bytes);
|
|
if (err != EFI_SUCCESS)
|
|
return log_error_status_stall(err, L"Failed to write random seed file: %r", err);
|
|
if (wsize != size)
|
|
return log_error_status_stall(EFI_PROTOCOL_ERROR, L"Short write on random seed file.");
|
|
-
|
|
err = handle->Flush(handle);
|
|
if (err != EFI_SUCCESS)
|
|
return log_error_status_stall(err, L"Failed to flush random seed file: %r", err);
|
|
|
|
- /* We are good to go */
|
|
- err = efivar_set_raw(LOADER_GUID, L"LoaderRandomSeed", for_kernel, size, 0);
|
|
+ err = BS->AllocatePool(EfiACPIReclaimMemory, sizeof(*new_seed_table) + DESIRED_SEED_SIZE,
|
|
+ (void **) &new_seed_table);
|
|
+ if (err != EFI_SUCCESS)
|
|
+ return log_error_status_stall(err, L"Failed to allocate EFI table for random seed: %r", err);
|
|
+ new_seed_table->size = DESIRED_SEED_SIZE;
|
|
+
|
|
+ /* hash = hash_key || 1 */
|
|
+ sha256_init_ctx(&hash);
|
|
+ sha256_process_bytes(hash_key, sizeof(hash_key), &hash);
|
|
+ sha256_process_bytes(&(const uint8_t){ 1 }, sizeof(uint8_t), &hash);
|
|
+ /* new_seed_table->seed = HASH(hash) */
|
|
+ sha256_finish_ctx(&hash, new_seed_table->seed);
|
|
+
|
|
+ err = BS->InstallConfigurationTable(&(EFI_GUID)LINUX_EFI_RANDOM_SEED_TABLE_GUID, new_seed_table);
|
|
if (err != EFI_SUCCESS)
|
|
- return log_error_status_stall(err, L"Failed to write random seed to EFI variable: %r", err);
|
|
+ return log_error_status_stall(err, L"Failed to install EFI table for random seed: %r", err);
|
|
+ TAKE_PTR(new_seed_table);
|
|
+
|
|
+ if (previous_seed_table) {
|
|
+ /* Now that we've succeeded in installing the new table, we can safely nuke the old one. */
|
|
+ explicit_bzero_safe(previous_seed_table->seed, previous_seed_table->size);
|
|
+ explicit_bzero_safe(previous_seed_table, sizeof(*previous_seed_table));
|
|
+ free(previous_seed_table);
|
|
+ }
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
diff --git a/src/core/efi-random.c b/src/core/efi-random.c
|
|
index 4086b12739..61516775fc 100644
|
|
--- a/src/core/efi-random.c
|
|
+++ b/src/core/efi-random.c
|
|
@@ -12,79 +12,23 @@
|
|
#include "random-util.h"
|
|
#include "strv.h"
|
|
|
|
-/* If a random seed was passed by the boot loader in the LoaderRandomSeed EFI variable, let's credit it to
|
|
- * the kernel's random pool, but only once per boot. If this is run very early during initialization we can
|
|
- * instantly boot up with a filled random pool.
|
|
- *
|
|
- * This makes no judgement on the entropy passed, it's the job of the boot loader to only pass us a seed that
|
|
- * is suitably validated. */
|
|
-
|
|
-static void lock_down_efi_variables(void) {
|
|
+void lock_down_efi_variables(void) {
|
|
+ _cleanup_close_ int fd = -1;
|
|
int r;
|
|
|
|
+ fd = open(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderSystemToken)), O_RDONLY|O_CLOEXEC);
|
|
+ if (fd < 0) {
|
|
+ if (errno != ENOENT)
|
|
+ log_warning_errno(errno, "Unable to open LoaderSystemToken EFI variable, ignoring: %m");
|
|
+ return;
|
|
+ }
|
|
+
|
|
/* Paranoia: let's restrict access modes of these a bit, so that unprivileged users can't use them to
|
|
* identify the system or gain too much insight into what we might have credited to the entropy
|
|
* pool. */
|
|
- FOREACH_STRING(path,
|
|
- EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderRandomSeed)),
|
|
- EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderSystemToken))) {
|
|
-
|
|
- r = chattr_path(path, 0, FS_IMMUTABLE_FL, NULL);
|
|
- if (r == -ENOENT)
|
|
- continue;
|
|
- if (r < 0)
|
|
- log_warning_errno(r, "Failed to drop FS_IMMUTABLE_FL from %s, ignoring: %m", path);
|
|
-
|
|
- if (chmod(path, 0600) < 0)
|
|
- log_warning_errno(errno, "Failed to reduce access mode of %s, ignoring: %m", path);
|
|
- }
|
|
-}
|
|
-
|
|
-int efi_take_random_seed(void) {
|
|
- _cleanup_free_ void *value = NULL;
|
|
- size_t size;
|
|
- int r;
|
|
-
|
|
- /* Paranoia comes first. */
|
|
- lock_down_efi_variables();
|
|
-
|
|
- if (access("/run/systemd/efi-random-seed-taken", F_OK) < 0) {
|
|
- if (errno != ENOENT) {
|
|
- log_warning_errno(errno, "Failed to determine whether we already used the random seed token, not using it.");
|
|
- return 0;
|
|
- }
|
|
-
|
|
- /* ENOENT means we haven't used it yet. */
|
|
- } else {
|
|
- log_debug("EFI random seed already used, not using again.");
|
|
- return 0;
|
|
- }
|
|
-
|
|
- r = efi_get_variable(EFI_LOADER_VARIABLE(LoaderRandomSeed), NULL, &value, &size);
|
|
- if (r == -EOPNOTSUPP) {
|
|
- log_debug_errno(r, "System lacks EFI support, not initializing random seed from EFI variable.");
|
|
- return 0;
|
|
- }
|
|
- if (r == -ENOENT) {
|
|
- log_debug_errno(r, "Boot loader did not pass LoaderRandomSeed EFI variable, not crediting any entropy.");
|
|
- return 0;
|
|
- }
|
|
+ r = chattr_fd(fd, 0, FS_IMMUTABLE_FL, NULL);
|
|
if (r < 0)
|
|
- return log_warning_errno(r, "Failed to read LoaderRandomSeed EFI variable, ignoring: %m");
|
|
-
|
|
- if (size == 0)
|
|
- return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Random seed passed from boot loader has zero size? Ignoring.");
|
|
-
|
|
- /* Before we use the seed, let's mark it as used, so that we never credit it twice. Also, it's a nice
|
|
- * way to let users known that we successfully acquired entropy from the boot loader. */
|
|
- r = touch("/run/systemd/efi-random-seed-taken");
|
|
- if (r < 0)
|
|
- return log_warning_errno(r, "Unable to mark EFI random seed as used, not using it: %m");
|
|
-
|
|
- r = random_write_entropy(-1, value, size, true);
|
|
- if (r < 0)
|
|
- return log_warning_errno(errno, "Failed to credit entropy, ignoring: %m");
|
|
-
|
|
- log_info("Successfully credited entropy passed from boot loader.");
|
|
- return 1;
|
|
+ log_warning_errno(r, "Failed to drop FS_IMMUTABLE_FL from LoaderSystemToken EFI variable, ignoring: %m");
|
|
+ if (fchmod(fd, 0600) < 0)
|
|
+ log_warning_errno(errno, "Failed to reduce access mode of LoaderSystemToken EFI variable, ignoring: %m");
|
|
}
|
|
diff --git a/src/core/efi-random.h b/src/core/efi-random.h
|
|
index 7d20fff57d..87166c9e3f 100644
|
|
--- a/src/core/efi-random.h
|
|
+++ b/src/core/efi-random.h
|
|
@@ -1,4 +1,4 @@
|
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
#pragma once
|
|
|
|
-int efi_take_random_seed(void);
|
|
+void lock_down_efi_variables(void);
|
|
diff --git a/src/core/main.c b/src/core/main.c
|
|
index 126a4bce8c..e7b8e98bca 100644
|
|
--- a/src/core/main.c
|
|
+++ b/src/core/main.c
|
|
@@ -2840,8 +2840,8 @@ int main(int argc, char *argv[]) {
|
|
goto finish;
|
|
}
|
|
|
|
- /* The efivarfs is now mounted, let's read the random seed off it */
|
|
- (void) efi_take_random_seed();
|
|
+ /* The efivarfs is now mounted, let's lock down the system token. */
|
|
+ lock_down_efi_variables();
|
|
|
|
/* Cache command-line options passed from EFI variables */
|
|
if (!skip_setup)
|
|
diff --git a/units/systemd-boot-system-token.service b/units/systemd-boot-system-token.service
|
|
index 662a1fda04..5a56d7c331 100644
|
|
--- a/units/systemd-boot-system-token.service
|
|
+++ b/units/systemd-boot-system-token.service
|
|
@@ -26,9 +26,6 @@ ConditionPathExists=/sys/firmware/efi/efivars/LoaderFeatures-4a67b082-0a4c-41cf-
|
|
# Only run this if there is no system token defined yet, or …
|
|
ConditionPathExists=|!/sys/firmware/efi/efivars/LoaderSystemToken-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f
|
|
|
|
-# … if the boot loader didn't pass the OS a random seed (and thus probably was missing the random seed file)
|
|
-ConditionPathExists=|!/sys/firmware/efi/efivars/LoaderRandomSeed-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f
|
|
-
|
|
[Service]
|
|
Type=oneshot
|
|
RemainAfterExit=yes
|