]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
boot: implement kernel EFI RNG seed protocol with proper hashing
authorJason A. Donenfeld <Jason@zx2c4.com>
Wed, 9 Nov 2022 11:44:37 +0000 (12:44 +0100)
committerJason A. Donenfeld <Jason@zx2c4.com>
Mon, 14 Nov 2022 14:21:58 +0000 (15:21 +0100)
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.

13 files changed:
.github/codeql-queries/UninitializedVariableWithCleanup.ql
docs/BOOT_LOADER_INTERFACE.md
docs/RANDOM_SEEDS.md
man/systemd-boot.xml
src/basic/random-util.h
src/boot/bootctl.c
src/boot/efi/efi-string.h
src/boot/efi/random-seed.c
src/boot/efi/util.h
src/core/efi-random.c
src/core/efi-random.h
src/core/main.c
units/systemd-boot-system-token.service

index e514111f282c0f2639a72f8eb3fda06a43f61293..dadc6cb1b5ee3c45360dc870cb95958be2f4e90d 100644 (file)
@@ -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"
 }
 
 /**
index fc9336085bd70d1d6818025903f422399da55da7..5be4d1ad17dec71b2b0803b3c3c93eae83a92f8c 100644 (file)
@@ -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
 
index 3dc27f55525c5b233f71f60ddbbff2e053673f07..b7240f0d896f4bb5b5dd5adbb336d70d18fe644b 100644 (file)
@@ -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
index 0eee532f90af00114537add52de23ec9cf119cca..081deb8d6c20902f8c7050c668929974d6d24444 100644 (file)
         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>
 
index 2d99807272f3002baa06a0f301093af10a503de6..b1a4d10971fce86fe54161a3f0db2dfff8640dde 100644 (file)
@@ -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 32U
 #define RANDOM_POOL_SIZE_MAX (10U*1024U*1024U)
+#define RANDOM_EFI_SEED_SIZE 32U
 
 size_t random_pool_size(void);
 
index 0f142bff87ee9bda60aa0aefb39542dc7ff993de..984b7e3a6d4d1db1382aecdad6a083fb5832729b 100644 (file)
@@ -1886,8 +1886,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");
 
@@ -1977,10 +1975,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;
 
@@ -1990,13 +1988,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");
 
@@ -2017,10 +2009,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)
@@ -2028,7 +2020,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;
@@ -2080,16 +2072,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");
 
@@ -2097,7 +2089,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");
@@ -2107,7 +2099,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;
index 1ebd5fd6b7d42b3c0821af465f085706b483f070..d4d76a7c18875a087c4687683b8bd0ead6387737 100644 (file)
@@ -119,6 +119,13 @@ static inline void *mempcpy(void * restrict dest, const void * restrict src, siz
         memcpy(dest, src, n);
         return (uint8_t *) dest + n;
 }
+
+static inline void explicit_bzero_safe(void *bytes, size_t len) {
+        if (!bytes || len == 0)
+                return;
+        memset(bytes, 0, len);
+        __asm__ __volatile__("": :"r"(bytes) :"memory");
+}
 #else
 /* For unit testing. */
 int efi_memcmp(const void *p1, const void *p2, size_t n);
index aea4f7e532624372976f3568f9b1c4c8c111092f..04bfd526f8179914af81be40904459ae038a9b85 100644 (file)
 
 #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;
 
@@ -32,126 +45,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;
 }
 
@@ -163,6 +59,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,31 +118,83 @@ static void validate_sha256(void) {
 }
 
 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;
+        _cleanup_erase_ 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;
+        _cleanup_erase_ 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;
 
         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,
@@ -261,7 +210,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)
@@ -271,51 +220,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;
 }
index b33c50f9fc7e27ddf701f3f641d18cd06c4bf432..15dd87f7747cc7231f9eebf50174989037bfd801 100644 (file)
 #define UINTN_MAX (~(UINTN)0)
 #define INTN_MAX ((INTN)(UINTN_MAX>>1))
 
+#ifdef __OPTIMIZE__
+#ifndef __has_attribute
+#define __has_attribute(x) 0
+#endif
+#if __has_attribute(__error__)
+__attribute__((noreturn)) extern void __assert_cl_failure__(void) __attribute__((__error__("compile-time assertion failed")));
+#else
+__attribute__((noreturn)) extern void __assert_cl_failure__(void);
+#endif
+/* assert_cl generates a later-stage compile-time assertion when constant folding occurs. */
+#define assert_cl(condition) if (!(condition)) __assert_cl_failure__()
+#else
+#define assert_cl(condition) assert(condition)
+#endif
+
 /* gnu-efi format specifiers for integers are fixed to either 64bit with 'l' and 32bit without a size prefix.
  * We rely on %u/%d/%x to format regular ints, so ensure the size is what we expect. At the same time, we also
  * need specifiers for (U)INTN which are native (pointer) sized. */
@@ -43,6 +58,16 @@ static inline void freep(void *p) {
 
 #define _cleanup_free_ _cleanup_(freep)
 
+static __always_inline void erase_obj(void *p) {
+        size_t l;
+        assert_cl(p != NULL);
+        l = __builtin_object_size(p, 0);
+        assert_cl(l != (size_t) -1);
+        explicit_bzero_safe(p, l);
+}
+
+#define _cleanup_erase_ _cleanup_(erase_obj)
+
 _malloc_ _alloc_(1) _returns_nonnull_ _warn_unused_result_
 static inline void *xmalloc(size_t size) {
         void *p;
index 4086b12739db86620f18ddcbf46d16e2519e3477..61516775fc705f5681ab6e6a4c8150c58fecab07 100644 (file)
 #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");
 }
index 7d20fff57da317d4d950cffa2f0927c352fe0340..87166c9e3f35e244e6c1464fe24834b0aae3548f 100644 (file)
@@ -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);
index cc725e6c42af33d20ceed618413d1303fedac07a..119c518664ff78877bebd87a4a055ae11e2d7971 100644 (file)
@@ -2831,8 +2831,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)
index 662a1fda04ba4fcf7c77acc99da5852181f737c6..5a56d7c3317e33c5f2705bd9d014687b13d14e90 100644 (file)
@@ -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