x86/hyperv: Use Hyper-V entropy to seed guest random number generator
authorMichael Kelley <mhklinux@outlook.com>
Mon, 18 Mar 2024 15:54:08 +0000 (08:54 -0700)
committerWei Liu <wei.liu@kernel.org>
Mon, 18 Mar 2024 22:01:52 +0000 (22:01 +0000)
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>

arch/arm64/hyperv/mshyperv.c
arch/x86/kernel/cpu/mshyperv.c
drivers/hv/hv_common.c
include/asm-generic/mshyperv.h

index 03ac88bb9d10e53ae3669981a48a326c3b8a8d9c..b1a4de4eee2930f622409b7e68a7c82d50e27496 100644 (file)
@@ -72,6 +72,8 @@ static int __init hyperv_init(void)
                return ret;
        }
 
+       ms_hyperv_late_init();
+
        hyperv_initialized = true;
        return 0;
 }
index 909a6236a4c0d1ff8716d533a20d9c5c7f1136fd..faf438dce9db468edf3c1e5a5b195b0096a2c00a 100644 (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,
index 5d64cb0a709d111f898314aa159fa223fc0fba14..dde3f9b6871af9eb68f1cd8931651a0250943fe0 100644 (file)
 #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.
index 452b7c089b7168e5232d0b62bf82f8123acdfd2b..99935779682dc29180f556469c4487603b082740 100644 (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)
 {