2024-03-07 18:48:48

by Michael Kelley

[permalink] [raw]
Subject: [PATCH v2 1/1] x86/hyperv: Use Hyper-V entropy to seed guest random number generator

From: Michael Kelley <[email protected]>

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 is still provided in such VMs, though
it isn't needed.

But 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. 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
seed 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.

An equivalent change is *not* made for Linux VMs on Hyper-V for
ARM64. Such VMs are always Generation 2 and the rng is seeded
with entropy obtained via the EFI_RNG_PROTOCOL as described above.

[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 <[email protected]>
---
Changes in v2:
* Tweaked commit message [Wei Liu]
* Removed message when OEM0 table isn't found. Added debug-level
message when OEM0 is successfully used to add randomness. [Wei Liu]

arch/x86/kernel/cpu/mshyperv.c | 1 +
drivers/hv/hv_common.c | 64 ++++++++++++++++++++++++++++++++++
include/asm-generic/mshyperv.h | 2 ++
3 files changed, 67 insertions(+)

diff --git a/arch/x86/kernel/cpu/mshyperv.c b/arch/x86/kernel/cpu/mshyperv.c
index 303fef824167..65c9cbdd2282 100644
--- a/arch/x86/kernel/cpu/mshyperv.c
+++ b/arch/x86/kernel/cpu/mshyperv.c
@@ -648,6 +648,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,
diff --git a/drivers/hv/hv_common.c b/drivers/hv/hv_common.c
index 0285a74363b3..219c4371314d 100644
--- a/drivers/hv/hv_common.c
+++ b/drivers/hv/hv_common.c
@@ -20,6 +20,8 @@
#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/slab.h>
@@ -347,6 +349,68 @@ 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. It would be nice to do this
+ * even earlier in ms_hyperv_init_platform(), but the ACPI subsystem
+ * isn't set up at that point. Skip if booted via EFI as generic EFI
+ * code has already done some seeding using the EFI RNG protocol.
+ */
+ if (!IS_ENABLED(CONFIG_ACPI) || efi_enabled(EFI_BOOT))
+ 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 32 bytes and
+ * no more than 256 bytes is somewhat arbitrary. Hyper-V currently
+ * provides 64 bytes, but allow for a change in a later version.
+ */
+ if (header->length < sizeof(*header) + 32 ||
+ header->length > sizeof(*header) + 256)
+ 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.
+ */
+ for (i = 0; i < length; i++) {
+ header->checksum += randomdata[i];
+ randomdata[i] = 0;
+ }
+
+ acpi_put_table(header);
+ return;
+
+error:
+ pr_info("Hyper-V: Ignoring malformed ACPI table OEM0\n");
+ acpi_put_table(header);
+}
+
/*
* Hyper-V specific initialization and die code for
* individual CPUs that is common across all architectures.
diff --git a/include/asm-generic/mshyperv.h b/include/asm-generic/mshyperv.h
index 430f0ae0dde2..e861223093df 100644
--- a/include/asm-generic/mshyperv.h
+++ b/include/asm-generic/mshyperv.h
@@ -193,6 +193,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);

@@ -290,6 +291,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)
{
--
2.25.1



2024-03-13 04:51:20

by Long Li

[permalink] [raw]
Subject: RE: [PATCH v2 1/1] x86/hyperv: Use Hyper-V entropy to seed guest random number generator

> +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. It would be nice to do this
> + * even earlier in ms_hyperv_init_platform(), but the ACPI subsystem
> + * isn't set up at that point. Skip if booted via EFI as generic EFI
> + * code has already done some seeding using the EFI RNG protocol.
> + */
> + if (!IS_ENABLED(CONFIG_ACPI) || efi_enabled(EFI_BOOT))
> + return;
> +
> + status = acpi_get_table("OEM0", 0, &header);
> + if (ACPI_FAILURE(status) || !header)
> + return;

Should we call acpi_put_table() if header == 0? It will also be helpful doing a pr_info() here so user knows that hyper-v random number is not used.

2024-03-13 05:29:20

by Michael Kelley

[permalink] [raw]
Subject: RE: [PATCH v2 1/1] x86/hyperv: Use Hyper-V entropy to seed guest random number generator

From: Long Li <[email protected]> Sent: Tuesday, March 12, 2024 9:51 PM
>
> > +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. It would be nice to do this
> > + * even earlier in ms_hyperv_init_platform(), but the ACPI subsystem
> > + * isn't set up at that point. Skip if booted via EFI as generic EFI
> > + * code has already done some seeding using the EFI RNG protocol.
> > + */
> > + if (!IS_ENABLED(CONFIG_ACPI) || efi_enabled(EFI_BOOT))
> > + return;
> > +
> > + status = acpi_get_table("OEM0", 0, &header);
> > + if (ACPI_FAILURE(status) || !header)
> > + return;
>
> Should we call acpi_put_table() if header == 0?

No. acpi_get_table() setting header to NULL is equivalent to
returning a failure status, per a comment in the code for
acpi_get_table(). So checking header for NULL is probably
redundant, but it doesn't hurt.

> It will also be helpful doing a
> pr_info() here so user knows that hyper-v random number is not used.

In v1 of the patch, I had such a pr_info(), but Wei Liu recommended
removing it, and I agreed it wasn't really necessary. See the brief
discussion here:

https://lore.kernel.org/linux-hyperv/SN6PR02MB4157B61CA09C0DAF0BB994E1D4212@SN6PR02MB4157.namprd02.prod.outlook.com/

Michael



2024-03-13 23:33:11

by Jason A. Donenfeld

[permalink] [raw]
Subject: Re: [PATCH v2 1/1] x86/hyperv: Use Hyper-V entropy to seed guest random number generator

Hi Michael,

On Thu, Mar 07, 2024 at 10:48:20AM -0800, [email protected] wrote:
> + /*
> + * Seed the Linux random number generator with entropy provided by
> + * the Hyper-V host in ACPI table OEM0. It would be nice to do this
> + * even earlier in ms_hyperv_init_platform(), but the ACPI subsystem
> + * isn't set up at that point. Skip if booted via EFI as generic EFI
> + * code has already done some seeding using the EFI RNG protocol.
> + */
> + if (!IS_ENABLED(CONFIG_ACPI) || efi_enabled(EFI_BOOT))
> + return;

Even if EFI seeds the kernel using its own code, if this is available,
it should be used too. So I think you should remove the `|| efi_enabled(EFI_BOOT)`
part and let the add_bootloader_randomness() do what it wants with the
entropy.

> +
> + 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 32 bytes and
> + * no more than 256 bytes is somewhat arbitrary. Hyper-V currently
> + * provides 64 bytes, but allow for a change in a later version.
> + */
> + if (header->length < sizeof(*header) + 32 ||
> + header->length > sizeof(*header) + 256)

What's the point of the lower bound? Obviously skip for 0, but if
there's only 16 bytes, cool, 16 bytes is good and can't hurt.

For the upper bound, I understand you need some sanity check. Why not
put it a bit higher, though, at SZ_4K or something? Can't hurt.

> + 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.
> + */
> + for (i = 0; i < length; i++) {
> + header->checksum += randomdata[i];
> + randomdata[i] = 0;
> + }

Seems dangerous for kexec and such. What if, in addition to zeroing out
the actual data, you also set header->length to 0, so that it doesn't
get used again as 32 bytes of known zeros?

Thanks,
Jason

2024-03-14 00:32:56

by Michael Kelley

[permalink] [raw]
Subject: RE: [PATCH v2 1/1] x86/hyperv: Use Hyper-V entropy to seed guest random number generator

From: Jason A. Donenfeld <[email protected]> Sent: Wednesday, March 13, 2024 4:33 PM
>
> Hi Michael,
>
> On Thu, Mar 07, 2024 at 10:48:20AM -0800, [email protected] wrote:
> > + /*
> > + * Seed the Linux random number generator with entropy provided by
> > + * the Hyper-V host in ACPI table OEM0. It would be nice to do this
> > + * even earlier in ms_hyperv_init_platform(), but the ACPI subsystem
> > + * isn't set up at that point. Skip if booted via EFI as generic EFI
> > + * code has already done some seeding using the EFI RNG protocol.
> > + */
> > + if (!IS_ENABLED(CONFIG_ACPI) || efi_enabled(EFI_BOOT))
> > + return;
>
> Even if EFI seeds the kernel using its own code, if this is available,
> it should be used too. So I think you should remove the `|| efi_enabled(EFI_BOOT)`
> part and let the add_bootloader_randomness() do what it wants with the
> entropy.

OK, fair enough. But just to double-check: When this is called,
the EFI RNG protocol has already invoked add_bootloader_randomness(),
and this line has been output:

[ 0.000000] random: crng init done

I don't see an obvious problem with calling add_bootloader_randomness()
again, but wanted to confirm.

Also, if we're adding this ACPI-based randomness for VMs that
boot via EFI, then for consistency we should use it on Hyper-V
based ARM64 VMs as well.

>
> > +
> > + 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 32 bytes and
> > + * no more than 256 bytes is somewhat arbitrary. Hyper-V currently
> > + * provides 64 bytes, but allow for a change in a later version.
> > + */
> > + if (header->length < sizeof(*header) + 32 ||
> > + header->length > sizeof(*header) + 256)
>
> What's the point of the lower bound? Obviously skip for 0, but if
> there's only 16 bytes, cool, 16 bytes is good and can't hurt.
>
> For the upper bound, I understand you need some sanity check. Why not
> put it a bit higher, though, at SZ_4K or something? Can't hurt.

Both bounds are just a check for bogusness. Having the hypervisor
provide just 4 bytes (for example) of randomness seems like
there might be something weird going on. But widening the bounds
is fine with me. I'll use "8" and "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.
> > + */
> > + for (i = 0; i < length; i++) {
> > + header->checksum += randomdata[i];
> > + randomdata[i] = 0;
> > + }
>
> Seems dangerous for kexec and such. What if, in addition to zeroing out
> the actual data, you also set header->length to 0, so that it doesn't
> get used again as 32 bytes of known zeros?

What's your take on the whole idea of zero'ing the random data? I
saw the EFI RNG protocol handling was doing something roughly
similiar. But yes, good point about kexec(). Zeroing the header->length
would make sense to prevent any re-use.

Thanks for reviewing -- I wanted to get the benefit of your expertise
in this area. :-)

Michael

2024-03-14 03:06:29

by Jason A. Donenfeld

[permalink] [raw]
Subject: Re: [PATCH v2 1/1] x86/hyperv: Use Hyper-V entropy to seed guest random number generator

Hi Michael,

On Thu, Mar 14, 2024 at 12:30:06AM +0000, Michael Kelley wrote:
> OK, fair enough. But just to double-check: When this is called,
> the EFI RNG protocol has already invoked add_bootloader_randomness(),
> and this line has been output:
>
> [ 0.000000] random: crng init done
>
> I don't see an obvious problem with calling add_bootloader_randomness()
> again, but wanted to confirm.

Yea, that's very much okay. It'll just get added as extra, which can't
hurt.

> Also, if we're adding this ACPI-based randomness for VMs that
> boot via EFI, then for consistency we should use it on Hyper-V
> based ARM64 VMs as well.

Agreed.

> Both bounds are just a check for bogusness. Having the hypervisor
> provide just 4 bytes (for example) of randomness seems like
> there might be something weird going on. But widening the bounds
> is fine with me. I'll use "8" and "SZ_4K".

Ahh, as a sanity check that seems like a reasonable heuristic.

> > > + for (i = 0; i < length; i++) {
> > > + header->checksum += randomdata[i];
> > > + randomdata[i] = 0;
> > > + }
> >
> > Seems dangerous for kexec and such. What if, in addition to zeroing out
> > the actual data, you also set header->length to 0, so that it doesn't
> > get used again as 32 bytes of known zeros?
>
> What's your take on the whole idea of zero'ing the random data? I
> saw the EFI RNG protocol handling was doing something roughly
> similiar. But yes, good point about kexec(). Zeroing the header->length
> would make sense to prevent any re-use.

Yes, I think zeroing it out is the right call. I wonder, though, what's
the deal with the checksum adjustment? Should we be checking the
checksum before using the random data? And do we have to adjust it like
that at the end, or can we just zero out the entire header (including
length) along with the random data?

Jason

2024-03-14 04:31:04

by Michael Kelley

[permalink] [raw]
Subject: RE: [PATCH v2 1/1] x86/hyperv: Use Hyper-V entropy to seed guest random number generator

From: Jason A. Donenfeld <[email protected]>
>
> Hi Michael,
>
> On Thu, Mar 14, 2024 at 12:30:06AM +0000, Michael Kelley wrote:
> > OK, fair enough. But just to double-check: When this is called,
> > the EFI RNG protocol has already invoked add_bootloader_randomness(),
> > and this line has been output:
> >
> > [ 0.000000] random: crng init done
> >
> > I don't see an obvious problem with calling add_bootloader_randomness()
> > again, but wanted to confirm.
>
> Yea, that's very much okay. It'll just get added as extra, which can't
> hurt.
>
> > Also, if we're adding this ACPI-based randomness for VMs that
> > boot via EFI, then for consistency we should use it on Hyper-V
> > based ARM64 VMs as well.
>
> Agreed.
>
> > Both bounds are just a check for bogusness. Having the hypervisor
> > provide just 4 bytes (for example) of randomness seems like
> > there might be something weird going on. But widening the bounds
> > is fine with me. I'll use "8" and "SZ_4K".
>
> Ahh, as a sanity check that seems like a reasonable heuristic.
>
> > > > + for (i = 0; i < length; i++) {
> > > > + header->checksum += randomdata[i];
> > > > + randomdata[i] = 0;
> > > > + }
> > >
> > > Seems dangerous for kexec and such. What if, in addition to zeroing out
> > > the actual data, you also set header->length to 0, so that it doesn't
> > > get used again as 32 bytes of known zeros?
> >
> > What's your take on the whole idea of zero'ing the random data? I
> > saw the EFI RNG protocol handling was doing something roughly
> > similiar. But yes, good point about kexec(). Zeroing the header->length
> > would make sense to prevent any re-use.
>
> Yes, I think zeroing it out is the right call. I wonder, though, what's
> the deal with the checksum adjustment? Should we be checking the
> checksum before using the random data? And do we have to adjust it like
> that at the end, or can we just zero out the entire header (including
> length) along with the random data?
>

By default, Linux doesn't verify checksums when accessing ACPI tables
during early boot, though you can add "acpi_force_table_verification"
to the kernel boot line. The default is shown in dmesg like this:

[ 0.004419] ACPI: Early table checksum verification disabled

The checksum of all tables is checked slightly later in boot, though
it's after my entropy code has run. Without the checksum fixup,
this error is output:

[ 0.053752] ACPI BIOS Warning (bug): Incorrect checksum in table
[OEM0] - 0x8B, should be 0x82 (20230628/utcksum-58)

At this point, the checksum error doesn't really matter, but I
don't want the warning showing up. I need to experiment a
bit, but probably the best approach is to set the data length to
zero (and adjust the checksum) while leaving the rest of the ACPI
table header intact. It will be more difficult to make the table
disappear entirely as it appears in a global list of ACPI tables.

Michael

2024-03-14 04:33:29

by Jason A. Donenfeld

[permalink] [raw]
Subject: Re: [PATCH v2 1/1] x86/hyperv: Use Hyper-V entropy to seed guest random number generator

Hi Michael,

On Wed, Mar 13, 2024 at 10:30 PM Michael Kelley <[email protected]> wrote:
> By default, Linux doesn't verify checksums when accessing ACPI tables
> during early boot, though you can add "acpi_force_table_verification"
> to the kernel boot line. The default is shown in dmesg like this:
>
> [ 0.004419] ACPI: Early table checksum verification disabled
>
> The checksum of all tables is checked slightly later in boot, though
> it's after my entropy code has run. Without the checksum fixup,
> this error is output:
>
> [ 0.053752] ACPI BIOS Warning (bug): Incorrect checksum in table
> [OEM0] - 0x8B, should be 0x82 (20230628/utcksum-58)
>
> At this point, the checksum error doesn't really matter, but I
> don't want the warning showing up. I need to experiment a
> bit, but probably the best approach is to set the data length to
> zero (and adjust the checksum) while leaving the rest of the ACPI
> table header intact. It will be more difficult to make the table
> disappear entirely as it appears in a global list of ACPI tables.

That makes sense. If the length is getting set to zero and the data
itself zeroed, I would assume that the checksum evaluates to some
constant value (0? ~0? dunno how it's computed), so that should ease
things a bit.

Jason

2024-03-13 16:52:18

by Long Li

[permalink] [raw]
Subject: RE: [PATCH v2 1/1] x86/hyperv: Use Hyper-V entropy to seed guest random number generator

> Subject: [PATCH v2 1/1] x86/hyperv: Use Hyper-V entropy to seed guest random
> number generator
>
> [You don't often get email from [email protected]. Learn why this is
> important at https://aka.ms/LearnAboutSenderIdentification ]
>
> From: Michael Kelley <[email protected]>
>
> 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 is still provided in such VMs, though it isn't needed.
>
> But 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. 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 seed 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.
>
> An equivalent change is *not* made for Linux VMs on Hyper-V for ARM64. Such
> VMs are always Generation 2 and the rng is seeded with entropy obtained via the
> EFI_RNG_PROTOCOL as described above.
>
> [1]
> https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fdownload
> .microsoft.com%2Fdownload%2F1%2Fc%2F9%2F1c9813b8-089c-4fef-b2ad-
> ad80e79403ba%2FWhitepaper%2520-
> %2520The%2520Windows%252010%2520random%2520number%2520generatio
> n%2520infrastructure.pdf&data=05%7C02%7Clongli%40microsoft.com%7C9ecf1
> 997333f4461292108dc3ed7371b%7C72f988bf86f141af91ab2d7cd011db47%7C1
> %7C0%7C638454341537898325%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4
> wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C0%7C%7C
> %7C&sdata=jluLa8BFJ0wxQ1m0jcuKQO9t%2FdFVFSsfHiiSLoJviAo%3D&reserved=
> 0
>
> Signed-off-by: Michael Kelley <[email protected]>

Reviewed-by: Long Li <[email protected]>

> ---
> Changes in v2:
> * Tweaked commit message [Wei Liu]
> * Removed message when OEM0 table isn't found. Added debug-level
> message when OEM0 is successfully used to add randomness. [Wei Liu]
>
> arch/x86/kernel/cpu/mshyperv.c | 1 +
> drivers/hv/hv_common.c | 64 ++++++++++++++++++++++++++++++++++
> include/asm-generic/mshyperv.h | 2 ++
> 3 files changed, 67 insertions(+)
>
> diff --git a/arch/x86/kernel/cpu/mshyperv.c b/arch/x86/kernel/cpu/mshyperv.c
> index 303fef824167..65c9cbdd2282 100644
> --- a/arch/x86/kernel/cpu/mshyperv.c
> +++ b/arch/x86/kernel/cpu/mshyperv.c
> @@ -648,6 +648,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, diff --git
> a/drivers/hv/hv_common.c b/drivers/hv/hv_common.c index
> 0285a74363b3..219c4371314d 100644
> --- a/drivers/hv/hv_common.c
> +++ b/drivers/hv/hv_common.c
> @@ -20,6 +20,8 @@
> #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/slab.h>
> @@ -347,6 +349,68 @@ 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. It would be nice to do this
> + * even earlier in ms_hyperv_init_platform(), but the ACPI subsystem
> + * isn't set up at that point. Skip if booted via EFI as generic EFI
> + * code has already done some seeding using the EFI RNG protocol.
> + */
> + if (!IS_ENABLED(CONFIG_ACPI) || efi_enabled(EFI_BOOT))
> + 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 32 bytes and
> + * no more than 256 bytes is somewhat arbitrary. Hyper-V currently
> + * provides 64 bytes, but allow for a change in a later version.
> + */
> + if (header->length < sizeof(*header) + 32 ||
> + header->length > sizeof(*header) + 256)
> + 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.
> + */
> + for (i = 0; i < length; i++) {
> + header->checksum += randomdata[i];
> + randomdata[i] = 0;
> + }
> +
> + acpi_put_table(header);
> + return;
> +
> +error:
> + pr_info("Hyper-V: Ignoring malformed ACPI table OEM0\n");
> + acpi_put_table(header);
> +}
> +
> /*
> * Hyper-V specific initialization and die code for
> * individual CPUs that is common across all architectures.
> diff --git a/include/asm-generic/mshyperv.h b/include/asm-generic/mshyperv.h
> index 430f0ae0dde2..e861223093df 100644
> --- a/include/asm-generic/mshyperv.h
> +++ b/include/asm-generic/mshyperv.h
> @@ -193,6 +193,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);
>
> @@ -290,6 +291,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) {
> --
> 2.25.1
>