[PATCH tip v10] x86/setup: Use rng seeds from setup_data

From: Jason A. Donenfeld
Date: Sun Jul 10 2022 - 13:29:41 EST

Currently the only way x86 can get an early boot RNG seed is via EFI,
which is generally always used now for physical machines, but is very
rarely used in VMs, especially VMs that are optimized for starting
"instantaneously", such as Firecracker's MicroVM. For tiny fast booting
VMs, EFI is not something you generally need or want.

Rather, here we want the ability for the image loader or firmware to
pass a single random seed, exactly as device tree platforms do with the
"rng-seed" property. Additionally, this is something that bootloaders
can append, with their own seed file management, which is something
every other major OS ecosystem has that we do not (yet).

This patch adds SETUP_RNG_SEED, similar to the other seven setup_data
entries that are parsed at boot. It also takes care to zero out the seed
immediately after using, in order to retain forward secrecy. This all
takes about 7 trivial lines of code.

Then, on kexec_file_load(), a new fresh seed is generated and passed to
the next kernel, just as is done on device tree architectures when
using kexec. And, importantly, I've tested that QEMU is able to properly
pass SETUP_RNG_SEED as well, making this work for every step of the way.
This code too is pretty straight forward.

Together these measures ensure that VMs and nested kexec()'d kernels
always receive a proper boot time RNG seed at the earliest possible
stage from their parents:

- Host [already has strongly initialized RNG]
- QEMU [passes fresh seed in SETUP_RNG_SEED field]
- Linux [uses parent's seed and gathers entropy of its own]
- kexec [passes this in SETUP_RNG_SEED field]
- Linux [uses parent's seed and gathers entropy of its own]
- kexec [passes this in SETUP_RNG_SEED field]
- Linux [uses parent's seed and gathers entropy of its own]
- kexec [passes this in SETUP_RNG_SEED field]
- ...

I've verified in several scenarios that this works quite well from a
host kernel to QEMU and down inwards, mixing and matching loaders, with
every layer providing a seed to the next.

Acked-by: H. Peter Anvin (Intel) <hpa@xxxxxxxxx>
Signed-off-by: Jason A. Donenfeld <Jason@xxxxxxxxx>
Changes v9->v10:
- Don't remove IMA_KEXEC ifdef guard.
- Rebase on top of x86/urgent for cb8a4beac39b90cd6.
Changes v8->v9:
- [hpa] Update SETUP_TYPE_MAX and add SETUP_ENUM_MAX.
Changes v7->v8:
- Rebase against tip.
Changes v6->v7:
- [amluto] Add comment about zeroing fields - data for forward secrecy, len in
case of accidental reset-to-entry-jump bug.
Changes v5->v6:
- [hpa] Rework commit message to be less confusing and not improperly
mention e820.
Changes v4->v5:
- Populate field when loading bzimages for kexec, just like device tree
platforms do.
Changes v3->v4:
- Zero out data after using, for forward secrecy.
Changes v2->v3:
- Actually memmap the right area with the random bytes in it. This
worked before because of page sizes, but the code wasn't right. Now
it's right.
Changes v1->v2:
- Fix small typo of data_len -> data->len.

arch/x86/include/uapi/asm/bootparam.h | 6 ++---
arch/x86/kernel/kexec-bzimage64.c | 38 ++++++++++++++++++++++++---
arch/x86/kernel/setup.c | 10 +++++++
3 files changed, 47 insertions(+), 7 deletions(-)

diff --git a/arch/x86/include/uapi/asm/bootparam.h b/arch/x86/include/uapi/asm/bootparam.h
index be2b9ce52c76..342290624040 100644
--- a/arch/x86/include/uapi/asm/bootparam.h
+++ b/arch/x86/include/uapi/asm/bootparam.h
@@ -12,11 +12,11 @@
#define SETUP_CC_BLOB 7
#define SETUP_IMA 8
+#define SETUP_RNG_SEED 9

#define SETUP_INDIRECT (1<<31)
-/* SETUP_INDIRECT | max(SETUP_*) */

/* ram_size flags */
diff --git a/arch/x86/kernel/kexec-bzimage64.c b/arch/x86/kernel/kexec-bzimage64.c
index c63974e94272..b9bdb40364a6 100644
--- a/arch/x86/kernel/kexec-bzimage64.c
+++ b/arch/x86/kernel/kexec-bzimage64.c
@@ -18,6 +18,7 @@
#include <linux/mm.h>
#include <linux/efi.h>
#include <linux/verification.h>
+#include <linux/random.h>

#include <asm/bootparam.h>
#include <asm/setup.h>
@@ -110,6 +111,26 @@ static int setup_e820_entries(struct boot_params *params)
return 0;

+enum { RNG_SEED_LENGTH = 32 };
+static void
+setup_rng_seed(struct boot_params *params, unsigned long params_load_addr,
+ unsigned int rng_seed_setup_data_offset)
+ struct setup_data *sd = (void *)params + rng_seed_setup_data_offset;
+ unsigned long setup_data_phys;
+ if (!rng_is_initialized())
+ return;
+ sd->type = SETUP_RNG_SEED;
+ sd->len = RNG_SEED_LENGTH;
+ get_random_bytes(sd->data, RNG_SEED_LENGTH);
+ setup_data_phys = params_load_addr + rng_seed_setup_data_offset;
+ sd->next = params->hdr.setup_data;
+ params->hdr.setup_data = setup_data_phys;
static int setup_efi_info_memmap(struct boot_params *params,
unsigned long params_load_addr,
@@ -277,9 +298,16 @@ setup_boot_parameters(struct kimage *image, struct boot_params *params,
sizeof(struct efi_setup_data);

- /* Setup IMA log buffer state */
- setup_ima_state(image, params, params_load_addr,
- setup_data_offset);
+ /* Setup IMA log buffer state */
+ setup_ima_state(image, params, params_load_addr,
+ setup_data_offset);
+ setup_data_offset += sizeof(struct setup_data) +
+ sizeof(struct ima_setup_data);
+ }
+ /* Setup RNG seed */
+ setup_rng_seed(params, params_load_addr, setup_data_offset);

/* Setup EDD info */
memcpy(params->eddbuf, boot_params.eddbuf,
@@ -435,7 +463,9 @@ static void *bzImage64_load(struct kimage *image, char *kernel,
params_cmdline_sz = ALIGN(params_cmdline_sz, 16);
kbuf.bufsz = params_cmdline_sz + ALIGN(efi_map_sz, 16) +
sizeof(struct setup_data) +
- sizeof(struct efi_setup_data);
+ sizeof(struct efi_setup_data) +
+ sizeof(struct setup_data) +

kbuf.bufsz += sizeof(struct setup_data) +
diff --git a/arch/x86/kernel/setup.c b/arch/x86/kernel/setup.c
index 53f863f28b4c..216fee7144ee 100644
--- a/arch/x86/kernel/setup.c
+++ b/arch/x86/kernel/setup.c
@@ -24,6 +24,7 @@
#include <linux/usb/xhci-dbgp.h>
#include <linux/static_call.h>
#include <linux/swiotlb.h>
+#include <linux/random.h>

#include <uapi/linux/mount.h>

@@ -418,6 +419,15 @@ static void __init parse_setup_data(void)
+ data = early_memremap(pa_data, data_len);
+ add_bootloader_randomness(data->data, data->len);
+ /* Zero seed for forward secrecy. */
+ memzero_explicit(data->data, data->len);
+ /* Zero length in case we find ourselves back here by accident. */
+ memzero_explicit(&data->len, sizeof(data->len));
+ early_memunmap(data, data_len);
+ break;