From 9f17d1116d27696ec76c48c5c77df34cba521380 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Fri, 17 Feb 2023 16:56:11 +0000 Subject: [rng] Allow entropy source to be selected at runtime As noted in commit 3c83843 ("[rng] Check for several functioning RTC interrupts"), experimentation shows that Hyper-V cannot be trusted to reliably generate RTC interrupts. (As noted in commit f3ba0fb ("[hyperv] Provide timer based on the 10MHz time reference count MSR"), Hyper-V appears to suffer from a general problem in reliably generating any legacy interrupts.) An alternative entropy source is therefore required for an image that may be used in a Hyper-V Gen1 virtual machine. The x86 RDRAND instruction provides a suitable alternative entropy source, but may not be supported by all CPUs. We must therefore allow for multiple entropy sources to be compiled in, with the single active entropy source selected only at runtime. Restructure the internal entropy API to allow a working entropy source to be detected and chosen at runtime. Enable the RDRAND entropy source for all x86 builds, since it is likely to be substantially faster than any other source. Signed-off-by: Michael Brown --- src/interface/efi/efi_entropy.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) (limited to 'src/interface/efi/efi_entropy.c') diff --git a/src/interface/efi/efi_entropy.c b/src/interface/efi/efi_entropy.c index 1e8ddfb68..e5c393562 100644 --- a/src/interface/efi/efi_entropy.c +++ b/src/interface/efi/efi_entropy.c @@ -36,6 +36,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); * */ +struct entropy_source efi_entropy __entropy_source ( ENTROPY_NORMAL ); + /** Random number generator protocol */ static EFI_RNG_PROTOCOL *efirng; EFI_REQUEST_PROTOCOL ( EFI_RNG_PROTOCOL, &efirng ); @@ -91,6 +93,12 @@ static int efi_entropy_enable ( void ) { return rc; } + /* We use essentially the same mechanism as for the BIOS + * RTC-based entropy source, and so assume the same + * min-entropy per sample. + */ + entropy_init ( &efi_entropy, MIN_ENTROPY ( 1.3 ) ); + return 0; } @@ -235,7 +243,10 @@ static int efi_get_noise ( noise_sample_t *noise ) { return 0; } -PROVIDE_ENTROPY_INLINE ( efi, min_entropy_per_sample ); -PROVIDE_ENTROPY ( efi, entropy_enable, efi_entropy_enable ); -PROVIDE_ENTROPY ( efi, entropy_disable, efi_entropy_disable ); -PROVIDE_ENTROPY ( efi, get_noise, efi_get_noise ); +/** EFI entropy source */ +struct entropy_source efi_entropy __entropy_source ( ENTROPY_NORMAL ) = { + .name = "efi", + .enable = efi_entropy_enable, + .disable = efi_entropy_disable, + .get_noise = efi_get_noise, +}; -- cgit v1.2.3-55-g7522 From 471599dc7721d454b6658062c901b52038a78be2 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Mon, 20 Feb 2023 14:08:49 +0000 Subject: [efi] Split out EFI_RNG_PROTOCOL as a separate entropy source Commit 7ca801d ("[efi] Use the EFI_RNG_PROTOCOL as an entropy source if available") added EFI_RNG_PROTOCOL as an alternative entropy source via an ad-hoc mechanism specific to efi_entropy.c. Split out EFI_RNG_PROTOCOL to a separate entropy source, and allow the entropy core to handle the selection of RDRAND, EFI_RNG_PROTOCOL, or timer ticks as the active source. The fault detection logic added in commit a87537d ("[efi] Detect and disable seriously broken EFI_RNG_PROTOCOL implementations") may be removed completely, since the failure will already be detected by the generic ANS X9.82-mandated repetition count test and will now be handled gracefully by the entropy core. Signed-off-by: Michael Brown --- src/config/config_entropy.c | 5 +- src/config/defaults/efi.h | 3 +- src/include/ipxe/errfile.h | 1 + src/interface/efi/efi_entropy.c | 95 ++------------------------------ src/interface/efi/efi_rng.c | 118 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 130 insertions(+), 92 deletions(-) create mode 100644 src/interface/efi/efi_rng.c (limited to 'src/interface/efi/efi_entropy.c') diff --git a/src/config/config_entropy.c b/src/config/config_entropy.c index e96019a58..9f12f1fa3 100644 --- a/src/config/config_entropy.c +++ b/src/config/config_entropy.c @@ -37,9 +37,12 @@ PROVIDE_REQUIRING_SYMBOL(); #ifdef ENTROPY_RTC REQUIRE_OBJECT ( rtc_entropy ); #endif -#ifdef ENTROPY_EFI +#ifdef ENTROPY_EFITICK REQUIRE_OBJECT ( efi_entropy ); #endif +#ifdef ENTROPY_EFIRNG +REQUIRE_OBJECT ( efi_rng ); +#endif #ifdef ENTROPY_LINUX REQUIRE_OBJECT ( linux_entropy ); #endif diff --git a/src/config/defaults/efi.h b/src/config/defaults/efi.h index 16c561660..8e53b9ab6 100644 --- a/src/config/defaults/efi.h +++ b/src/config/defaults/efi.h @@ -19,7 +19,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define SMBIOS_EFI #define SANBOOT_EFI #define BOFM_EFI -#define ENTROPY_EFI +#define ENTROPY_EFITICK +#define ENTROPY_EFIRNG #define TIME_EFI #define REBOOT_EFI #define ACPI_EFI diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index d7b6ea1bd..e6fd8524e 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -403,6 +403,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_pci_cmd ( ERRFILE_OTHER | 0x00590000 ) #define ERRFILE_dhe ( ERRFILE_OTHER | 0x005a0000 ) #define ERRFILE_efi_cmdline ( ERRFILE_OTHER | 0x005b0000 ) +#define ERRFILE_efi_rng ( ERRFILE_OTHER | 0x005c0000 ) /** @} */ diff --git a/src/interface/efi/efi_entropy.c b/src/interface/efi/efi_entropy.c index e5c393562..cda1c3640 100644 --- a/src/interface/efi/efi_entropy.c +++ b/src/interface/efi/efi_entropy.c @@ -25,10 +25,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include -#include #include #include -#include /** @file * @@ -36,24 +34,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); * */ -struct entropy_source efi_entropy __entropy_source ( ENTROPY_NORMAL ); - -/** Random number generator protocol */ -static EFI_RNG_PROTOCOL *efirng; -EFI_REQUEST_PROTOCOL ( EFI_RNG_PROTOCOL, &efirng ); - -/** Minimum number of bytes to request from RNG - * - * The UEFI spec states (for no apparently good reason) that "When a - * Deterministic Random Bit Generator (DRBG) is used on the output of - * a (raw) entropy source, its security level must be at least 256 - * bits." The EDK2 codebase (mis)interprets this to mean that the - * call to GetRNG() should fail if given a buffer less than 32 bytes. - * - * Incidentally, nothing in the EFI RNG protocol provides any way to - * report the actual amount of entropy returned by GetRNG(). - */ -#define EFI_ENTROPY_RNG_LEN 32 +struct entropy_source efitick_entropy __entropy_source ( ENTROPY_FALLBACK ); /** Time (in 100ns units) to delay waiting for timer tick * @@ -78,9 +59,6 @@ static int efi_entropy_enable ( void ) { EFI_STATUS efirc; int rc; - DBGC ( &tick, "ENTROPY %s RNG protocol\n", - ( efirng ? "has" : "has no" ) ); - /* Drop to external TPL to allow timer tick event to take place */ bs->RestoreTPL ( efi_external_tpl ); @@ -97,7 +75,7 @@ static int efi_entropy_enable ( void ) { * RTC-based entropy source, and so assume the same * min-entropy per sample. */ - entropy_init ( &efi_entropy, MIN_ENTROPY ( 1.3 ) ); + entropy_init ( &efitick_entropy, MIN_ENTROPY ( 1.3 ) ); return 0; } @@ -155,7 +133,7 @@ static int efi_entropy_tick ( void ) { * @ret noise Noise sample * @ret rc Return status code */ -static int efi_get_noise_ticks ( noise_sample_t *noise ) { +static int efi_get_noise ( noise_sample_t *noise ) { int before; int after; int rc; @@ -180,72 +158,9 @@ static int efi_get_noise_ticks ( noise_sample_t *noise ) { return 0; } -/** - * Get noise sample from RNG protocol - * - * @ret noise Noise sample - * @ret rc Return status code - */ -static int efi_get_noise_rng ( noise_sample_t *noise ) { - static uint8_t prev[EFI_ENTROPY_RNG_LEN]; - uint8_t buf[EFI_ENTROPY_RNG_LEN]; - EFI_STATUS efirc; - int rc; - - /* Fail if we have no EFI RNG protocol */ - if ( ! efirng ) - return -ENOTSUP; - - /* Get the minimum allowed number of random bytes */ - if ( ( efirc = efirng->GetRNG ( efirng, NULL, EFI_ENTROPY_RNG_LEN, - buf ) ) != 0 ) { - rc = -EEFI ( efirc ); - DBGC ( &tick, "ENTROPY could not read from RNG: %s\n", - strerror ( rc ) ); - return rc; - } - - /* Fail (and permanently disable the EFI RNG) if we get - * consecutive identical results. - */ - if ( memcmp ( buf, prev, sizeof ( buf ) ) == 0 ) { - DBGC ( &tick, "ENTROPY detected broken EFI RNG:\n" ); - DBGC_HDA ( &tick, 0, buf, sizeof ( buf ) ); - efirng = NULL; - return -EIO; - } - memcpy ( prev, buf, sizeof ( prev ) ); - - /* Reduce random bytes to a single noise sample. This seems - * like overkill, but we have no way of knowing how much - * entropy is actually present in the bytes returned by the - * RNG protocol. - */ - *noise = crc32_le ( 0, buf, sizeof ( buf ) ); - - return 0; -} - -/** - * Get noise sample - * - * @ret noise Noise sample - * @ret rc Return status code - */ -static int efi_get_noise ( noise_sample_t *noise ) { - int rc; - - /* Try RNG first, falling back to timer ticks */ - if ( ( ( rc = efi_get_noise_rng ( noise ) ) != 0 ) && - ( ( rc = efi_get_noise_ticks ( noise ) ) != 0 ) ) - return rc; - - return 0; -} - /** EFI entropy source */ -struct entropy_source efi_entropy __entropy_source ( ENTROPY_NORMAL ) = { - .name = "efi", +struct entropy_source efitick_entropy __entropy_source ( ENTROPY_FALLBACK ) = { + .name = "efitick", .enable = efi_entropy_enable, .disable = efi_entropy_disable, .get_noise = efi_get_noise, diff --git a/src/interface/efi/efi_rng.c b/src/interface/efi/efi_rng.c new file mode 100644 index 000000000..b76a6fc0d --- /dev/null +++ b/src/interface/efi/efi_rng.c @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2015 Michael Brown . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include +#include +#include +#include +#include + +/** @file + * + * EFI random number generator protocol entropy source + * + */ + +struct entropy_source efirng_entropy __entropy_source ( ENTROPY_NORMAL ); + +/** Random number generator protocol */ +static EFI_RNG_PROTOCOL *efirng; +EFI_REQUEST_PROTOCOL ( EFI_RNG_PROTOCOL, &efirng ); + +/** Minimum number of bytes to request from RNG + * + * The UEFI spec states (for no apparently good reason) that "When a + * Deterministic Random Bit Generator (DRBG) is used on the output of + * a (raw) entropy source, its security level must be at least 256 + * bits." The EDK2 codebase (mis)interprets this to mean that the + * call to GetRNG() should fail if given a buffer less than 32 bytes. + * + * Incidentally, nothing in the EFI RNG protocol provides any way to + * report the actual amount of entropy returned by GetRNG(). + */ +#define EFIRNG_LEN 32 + +/** + * Enable entropy gathering + * + * @ret rc Return status code + */ +static int efirng_enable ( void ) { + + /* Check for RNG protocol support */ + if ( ! efirng ) { + DBGC ( &efirng, "EFIRNG has no RNG protocol\n" ); + return -ENOTSUP; + } + + /* Nothing in the EFI specification provides any clue as to + * how much entropy will be returned by GetRNG(). Make a + * totally uninformed (and conservative guess) that each + * sample will contain at least one bit of entropy. + */ + entropy_init ( &efirng_entropy, MIN_ENTROPY ( 1.0 ) ); + + return 0; +} + +/** + * Get noise sample from RNG protocol + * + * @ret noise Noise sample + * @ret rc Return status code + */ +static int efirng_get_noise ( noise_sample_t *noise ) { + uint8_t buf[EFIRNG_LEN]; + EFI_STATUS efirc; + int rc; + + /* Sanity check */ + assert ( efirng != NULL ); + + /* Get the minimum allowed number of random bytes */ + if ( ( efirc = efirng->GetRNG ( efirng, NULL, sizeof ( buf ), + buf ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( &efirng, "ENTROPY could not read from RNG: %s\n", + strerror ( rc ) ); + return rc; + } + + /* Reduce random bytes to a single noise sample. This seems + * like overkill, but we have no way of knowing how much + * entropy is actually present in the bytes returned by the + * RNG protocol. + */ + *noise = crc32_le ( 0, buf, sizeof ( buf ) ); + + return 0; +} + +/** EFI random number generator protocol entropy source */ +struct entropy_source efirng_entropy __entropy_source ( ENTROPY_NORMAL ) = { + .name = "efirng", + .enable = efirng_enable, + .get_noise = efirng_get_noise, +}; -- cgit v1.2.3-55-g7522