x86/hyperv: Use Hyper-V entropy to seed guest random number generator

A Hyper-V host provides its guest VMs with entropy in a custom ACPI
table named "OEM0".  The entropy bits are updated each time Hyper-V
boots the VM, and are suitable for seeding the Linux guest random
number generator (rng). See a brief description of OEM0 in [1].

Generation 2 VMs on Hyper-V use UEFI to boot. Existing EFI code in
Linux seeds the rng with entropy bits from the EFI_RNG_PROTOCOL.
Via this path, the rng is seeded very early during boot with good
entropy. The ACPI OEM0 table provided in such VMs is an additional
source of entropy.

Generation 1 VMs on Hyper-V boot from BIOS. For these VMs, Linux
doesn't currently get any entropy from the Hyper-V host. While this
is not fundamentally broken because Linux can generate its own entropy,
using the Hyper-V host provided entropy would get the rng off to a
better start and would do so earlier in the boot process.

Improve the rng seeding for Generation 1 VMs by having Hyper-V specific
code in Linux take advantage of the OEM0 table to seed the rng. For
Generation 2 VMs, use the OEM0 table to provide additional entropy
beyond the EFI_RNG_PROTOCOL. Because the OEM0 table is custom to
Hyper-V, parse it directly in the Hyper-V code in the Linux kernel
and use add_bootloader_randomness() to add it to the rng. Once the
entropy bits are read from OEM0, zero them out in the table so
they don't appear in /sys/firmware/acpi/tables/OEM0 in the running
VM. The zero'ing is done out of an abundance of caution to avoid
potential security risks to the rng. Also set the OEM0 data length
to zero so a kexec or other subsequent use of the table won't try
to use the zero'ed bits.

[1] https://download.microsoft.com/download/1/c/9/1c9813b8-089c-4fef-b2ad-ad80e79403ba/Whitepaper%20-%20The%20Windows%2010%20random%20number%20generation%20infrastructure.pdf

Signed-off-by: Michael Kelley <mhklinux@outlook.com>
Reviewed-by: Jason A. Donenfeld <Jason@zx2c4.com>
Link: https://lore.kernel.org/r/20240318155408.216851-1-mhklinux@outlook.com
Signed-off-by: Wei Liu <wei.liu@kernel.org>
Message-ID: <20240318155408.216851-1-mhklinux@outlook.com>
This commit is contained in:
Michael Kelley 2024-03-18 08:54:08 -07:00 committed by Wei Liu
parent eac03d81cd
commit f2580a907e
4 changed files with 74 additions and 0 deletions

View file

@ -72,6 +72,8 @@ static int __init hyperv_init(void)
return ret;
}
ms_hyperv_late_init();
hyperv_initialized = true;
return 0;
}

View file

@ -639,6 +639,7 @@ const __initconst struct hypervisor_x86 x86_hyper_ms_hyperv = {
.init.x2apic_available = ms_hyperv_x2apic_available,
.init.msi_ext_dest_id = ms_hyperv_msi_ext_dest_id,
.init.init_platform = ms_hyperv_init_platform,
.init.guest_late_init = ms_hyperv_late_init,
#ifdef CONFIG_AMD_MEM_ENCRYPT
.runtime.sev_es_hcall_prepare = hv_sev_es_hcall_prepare,
.runtime.sev_es_hcall_finish = hv_sev_es_hcall_finish,

View file

@ -20,8 +20,11 @@
#include <linux/sched/task_stack.h>
#include <linux/panic_notifier.h>
#include <linux/ptrace.h>
#include <linux/random.h>
#include <linux/efi.h>
#include <linux/kdebug.h>
#include <linux/kmsg_dump.h>
#include <linux/sizes.h>
#include <linux/slab.h>
#include <linux/dma-map-ops.h>
#include <linux/set_memory.h>
@ -355,6 +358,72 @@ int __init hv_common_init(void)
return 0;
}
void __init ms_hyperv_late_init(void)
{
struct acpi_table_header *header;
acpi_status status;
u8 *randomdata;
u32 length, i;
/*
* Seed the Linux random number generator with entropy provided by
* the Hyper-V host in ACPI table OEM0.
*/
if (!IS_ENABLED(CONFIG_ACPI))
return;
status = acpi_get_table("OEM0", 0, &header);
if (ACPI_FAILURE(status) || !header)
return;
/*
* Since the "OEM0" table name is for OEM specific usage, verify
* that what we're seeing purports to be from Microsoft.
*/
if (strncmp(header->oem_table_id, "MICROSFT", 8))
goto error;
/*
* Ensure the length is reasonable. Requiring at least 8 bytes and
* no more than 4K bytes is somewhat arbitrary and just protects
* against a malformed table. Hyper-V currently provides 64 bytes,
* but allow for a change in a later version.
*/
if (header->length < sizeof(*header) + 8 ||
header->length > sizeof(*header) + SZ_4K)
goto error;
length = header->length - sizeof(*header);
randomdata = (u8 *)(header + 1);
pr_debug("Hyper-V: Seeding rng with %d random bytes from ACPI table OEM0\n",
length);
add_bootloader_randomness(randomdata, length);
/*
* To prevent the seed data from being visible in /sys/firmware/acpi,
* zero out the random data in the ACPI table and fixup the checksum.
* The zero'ing is done out of an abundance of caution in avoiding
* potential security risks to the rng. Similarly, reset the table
* length to just the header size so that a subsequent kexec doesn't
* try to use the zero'ed out random data.
*/
for (i = 0; i < length; i++) {
header->checksum += randomdata[i];
randomdata[i] = 0;
}
for (i = 0; i < sizeof(header->length); i++)
header->checksum += ((u8 *)&header->length)[i];
header->length = sizeof(*header);
for (i = 0; i < sizeof(header->length); i++)
header->checksum -= ((u8 *)&header->length)[i];
error:
acpi_put_table(header);
}
/*
* Hyper-V specific initialization and die code for
* individual CPUs that is common across all architectures.

View file

@ -195,6 +195,7 @@ extern u64 (*hv_read_reference_counter)(void);
int __init hv_common_init(void);
void __init hv_common_free(void);
void __init ms_hyperv_late_init(void);
int hv_common_cpu_init(unsigned int cpu);
int hv_common_cpu_die(unsigned int cpu);
@ -292,6 +293,7 @@ void hv_setup_dma_ops(struct device *dev, bool coherent);
static inline bool hv_is_hyperv_initialized(void) { return false; }
static inline bool hv_is_hibernation_supported(void) { return false; }
static inline void hyperv_cleanup(void) {}
static inline void ms_hyperv_late_init(void) {}
static inline bool hv_is_isolation_supported(void) { return false; }
static inline enum hv_isolation_type hv_get_isolation_type(void)
{