[RFC] Randomness on confidential computing platforms
From: Kirill A. Shutemov
Date: Fri Jan 26 2024 - 08:44:56 EST
Problem Statement
Currently Linux RNG uses the random inputs obtained from x86
RDRAND/RDSEED instructions (if present) during early initialization
stage (by mixing the obtained input into the random pool via
_mix_pool_bytes()), as well as for seeding/reseeding ChaCha-based CRNG.
When the calls to both RDRAND/RDSEED fail (including RDRAND internal
retries), the timing-based fallbacks are used in the latter case, and
during the early boot case this source of entropy input is simply
skipped. Overall Linux RNG has many other sources of entropy that it
uses (also depending on what HW is used), but the dominating one is
interrupts.
In a Confidential Computing Guest threat model, given the absence of any
special trusted HW for the secure entropy input, RDRAND/RDSEED
instructions is the only entropy source that is unobservable outside of
Confidential Computing Guest TCB. However, with enough pressure on these
instructions from multiple cores (see Intel SDM, Volume 1, Section
7.3.17, “Random Number Generator Instructions”), they can be made to
fail on purpose and force the Confidential Computing Guest Linux RNG to
use only Host/VMM controlled entropy sources.
Solution options
There are several possible solutions to this problem and the intention
of this RFC is to initiate a joined discussion. Here are some options
that has been considered:
1. Do nothing and accept the risk.
2. Force endless looping on RDRAND/RDSEED instructions when run in a
Confidential Computing Guest (this patch). This option turns the
attack against the quality of cryptographic randomness provided by
Confidential Computing Guest’s Linux RNG into a DoS attack against
the Confidential Computing Guest itself (DoS attack is out of scope
for the Confidential Computing threat model).
3. Panic after enough re-tries of RDRAND/RDSEED instructions fail.
Another DoS variant against the Guest.
4. Exit to the host/VMM with an error indication after a Confidential
Computing Guest failed to obtain random input from RDRAND/RDSEED
instructions after reasonable number of retries. This option allows
host/VMM to take some correction action for cases when the load on
RDRAND/RDSEED instructions has been put by another actor, i.e. the
other guest VM. The exit to host/VMM in such cases can be made
transparent for the Confidential Computing Guest in the TDX case with
the assistance of the TDX module component.
5. Anything other better option?
The patch below implements the second option. I believe the problem is
common for Intel TDX and AMD SEV. The patch cover both.
---
arch/x86/boot/compressed/kaslr.c | 6 ++++++
arch/x86/boot/compressed/mem.c | 26 -------------------------
arch/x86/boot/compressed/misc.h | 3 +++
arch/x86/boot/compressed/sev.c | 5 +++++
arch/x86/boot/compressed/sev.h | 2 ++
arch/x86/boot/compressed/tdx.c | 32 ++++++++++++++++++++++++++-----
arch/x86/boot/compressed/tdx.h | 2 ++
arch/x86/coco/core.c | 2 ++
arch/x86/include/asm/archrandom.h | 22 ++++++++++++++++-----
include/linux/cc_platform.h | 11 +++++++++++
10 files changed, 75 insertions(+), 36 deletions(-)
diff --git a/arch/x86/boot/compressed/kaslr.c b/arch/x86/boot/compressed/kaslr.c
index dec961c6d16a..a7bba37c7539 100644
--- a/arch/x86/boot/compressed/kaslr.c
+++ b/arch/x86/boot/compressed/kaslr.c
@@ -23,6 +23,7 @@
#include "error.h"
#include "../string.h"
#include "efi.h"
+#include "sev.h"
#include <generated/compile.h>
#include <linux/module.h>
@@ -304,6 +305,11 @@ static void handle_mem_options(void)
return;
}
+bool rd_loop(void)
+{
+ return early_is_tdx_guest() || sev_enabled();
+}
+
/*
* In theory, KASLR can put the kernel anywhere in the range of [16M, MAXMEM)
* on 64-bit, and [16M, KERNEL_IMAGE_SIZE) on 32-bit.
diff --git a/arch/x86/boot/compressed/mem.c b/arch/x86/boot/compressed/mem.c
index dbba332e4a12..84a9d9ad98b2 100644
--- a/arch/x86/boot/compressed/mem.c
+++ b/arch/x86/boot/compressed/mem.c
@@ -6,32 +6,6 @@
#include "sev.h"
#include <asm/shared/tdx.h>
-/*
- * accept_memory() and process_unaccepted_memory() called from EFI stub which
- * runs before decompressor and its early_tdx_detect().
- *
- * Enumerate TDX directly from the early users.
- */
-static bool early_is_tdx_guest(void)
-{
- static bool once;
- static bool is_tdx;
-
- if (!IS_ENABLED(CONFIG_INTEL_TDX_GUEST))
- return false;
-
- if (!once) {
- u32 eax, sig[3];
-
- cpuid_count(TDX_CPUID_LEAF_ID, 0, &eax,
- &sig[0], &sig[2], &sig[1]);
- is_tdx = !memcmp(TDX_IDENT, sig, sizeof(sig));
- once = true;
- }
-
- return is_tdx;
-}
-
void arch_accept_memory(phys_addr_t start, phys_addr_t end)
{
/* Platform-specific memory-acceptance call goes here */
diff --git a/arch/x86/boot/compressed/misc.h b/arch/x86/boot/compressed/misc.h
index bc2f0f17fb90..3fd0aba836e7 100644
--- a/arch/x86/boot/compressed/misc.h
+++ b/arch/x86/boot/compressed/misc.h
@@ -255,4 +255,7 @@ static inline bool init_unaccepted_memory(void) { return false; }
extern struct efi_unaccepted_memory *unaccepted_table;
void accept_memory(phys_addr_t start, phys_addr_t end);
+#define rd_loop rd_loop
+extern bool rd_loop(void);
+
#endif /* BOOT_COMPRESSED_MISC_H */
diff --git a/arch/x86/boot/compressed/sev.c b/arch/x86/boot/compressed/sev.c
index 454acd7a2daf..5e7fb31e630b 100644
--- a/arch/x86/boot/compressed/sev.c
+++ b/arch/x86/boot/compressed/sev.c
@@ -125,6 +125,11 @@ static bool fault_in_kernel_space(unsigned long address)
/* Include code for early handlers */
#include "../../kernel/sev-shared.c"
+bool sev_enabled(void)
+{
+ return sev_status & MSR_AMD64_SEV_ENABLED;
+}
+
bool sev_snp_enabled(void)
{
return sev_status & MSR_AMD64_SEV_SNP_ENABLED;
diff --git a/arch/x86/boot/compressed/sev.h b/arch/x86/boot/compressed/sev.h
index fc725a981b09..ec99e0390324 100644
--- a/arch/x86/boot/compressed/sev.h
+++ b/arch/x86/boot/compressed/sev.h
@@ -10,11 +10,13 @@
#ifdef CONFIG_AMD_MEM_ENCRYPT
+bool sev_enabled(void);
bool sev_snp_enabled(void);
void snp_accept_memory(phys_addr_t start, phys_addr_t end);
#else
+static inline bool sev_enabled(void) { return false; }
static inline bool sev_snp_enabled(void) { return false; }
static inline void snp_accept_memory(phys_addr_t start, phys_addr_t end) { }
diff --git a/arch/x86/boot/compressed/tdx.c b/arch/x86/boot/compressed/tdx.c
index 8451d6a1030c..90dcfb9e82bf 100644
--- a/arch/x86/boot/compressed/tdx.c
+++ b/arch/x86/boot/compressed/tdx.c
@@ -61,13 +61,35 @@ static inline void tdx_outw(u16 value, u16 port)
tdx_io_out(2, port, value);
}
+/*
+ * accept_memory() and process_unaccepted_memory() called from EFI stub which
+ * runs before decompressor and its early_tdx_detect().
+ *
+ * Enumerate TDX directly from the early users.
+ */
+bool early_is_tdx_guest(void)
+{
+ static bool once;
+ static bool is_tdx;
+
+ if (!IS_ENABLED(CONFIG_INTEL_TDX_GUEST))
+ return false;
+
+ if (!once) {
+ u32 eax, sig[3];
+
+ cpuid_count(TDX_CPUID_LEAF_ID, 0, &eax,
+ &sig[0], &sig[2], &sig[1]);
+ is_tdx = !memcmp(TDX_IDENT, sig, sizeof(sig));
+ once = true;
+ }
+
+ return is_tdx;
+}
+
void early_tdx_detect(void)
{
- u32 eax, sig[3];
-
- cpuid_count(TDX_CPUID_LEAF_ID, 0, &eax, &sig[0], &sig[2], &sig[1]);
-
- if (memcmp(TDX_IDENT, sig, sizeof(sig)))
+ if (!early_is_tdx_guest())
return;
/* Use hypercalls instead of I/O instructions */
diff --git a/arch/x86/boot/compressed/tdx.h b/arch/x86/boot/compressed/tdx.h
index 9055482cd35c..6c097de8392e 100644
--- a/arch/x86/boot/compressed/tdx.h
+++ b/arch/x86/boot/compressed/tdx.h
@@ -5,8 +5,10 @@
#include <linux/types.h>
#ifdef CONFIG_INTEL_TDX_GUEST
+bool early_is_tdx_guest(void);
void early_tdx_detect(void);
#else
+bool early_is_tdx_guest(void) { return false; }
static inline void early_tdx_detect(void) { };
#endif
diff --git a/arch/x86/coco/core.c b/arch/x86/coco/core.c
index f07c3bb7deab..655d881a9cfa 100644
--- a/arch/x86/coco/core.c
+++ b/arch/x86/coco/core.c
@@ -22,6 +22,7 @@ static bool noinstr intel_cc_platform_has(enum cc_attr attr)
case CC_ATTR_GUEST_UNROLL_STRING_IO:
case CC_ATTR_GUEST_MEM_ENCRYPT:
case CC_ATTR_MEM_ENCRYPT:
+ case CC_ATTR_GUEST_RAND_LOOP:
return true;
default:
return false;
@@ -72,6 +73,7 @@ static bool noinstr amd_cc_platform_has(enum cc_attr attr)
return sme_me_mask && !(sev_status & MSR_AMD64_SEV_ENABLED);
case CC_ATTR_GUEST_MEM_ENCRYPT:
+ case CC_ATTR_GUEST_RAND_LOOP:
return sev_status & MSR_AMD64_SEV_ENABLED;
case CC_ATTR_GUEST_STATE_ENCRYPT:
diff --git a/arch/x86/include/asm/archrandom.h b/arch/x86/include/asm/archrandom.h
index 02bae8e0758b..63368227c9d6 100644
--- a/arch/x86/include/asm/archrandom.h
+++ b/arch/x86/include/asm/archrandom.h
@@ -10,6 +10,7 @@
#ifndef ASM_X86_ARCHRANDOM_H
#define ASM_X86_ARCHRANDOM_H
+#include <linux/cc_platform.h>
#include <asm/processor.h>
#include <asm/cpufeature.h>
@@ -17,6 +18,13 @@
/* Unconditional execution of RDRAND and RDSEED */
+#ifndef rd_loop
+static inline bool rd_loop(void)
+{
+ return cc_platform_has(CC_ATTR_GUEST_RAND_LOOP);
+}
+#endif
+
static inline bool __must_check rdrand_long(unsigned long *v)
{
bool ok;
@@ -27,17 +35,21 @@ static inline bool __must_check rdrand_long(unsigned long *v)
: CC_OUT(c) (ok), [out] "=r" (*v));
if (ok)
return true;
- } while (--retry);
+ } while (rd_loop() || --retry);
return false;
}
static inline bool __must_check rdseed_long(unsigned long *v)
{
bool ok;
- asm volatile("rdseed %[out]"
- CC_SET(c)
- : CC_OUT(c) (ok), [out] "=r" (*v));
- return ok;
+ do {
+ asm volatile("rdseed %[out]"
+ CC_SET(c)
+ : CC_OUT(c) (ok), [out] "=r" (*v));
+ if (ok)
+ return ok;
+ } while (rd_loop());
+ return false;
}
/*
diff --git a/include/linux/cc_platform.h b/include/linux/cc_platform.h
index d08dd65b5c43..e554e8919eb0 100644
--- a/include/linux/cc_platform.h
+++ b/include/linux/cc_platform.h
@@ -80,6 +80,17 @@ enum cc_attr {
* using AMD SEV-SNP features.
*/
CC_ATTR_GUEST_SEV_SNP,
+
+ /**
+ * @CC_ATTR_GUEST_RAND_LOOP: Make RDRAND/RDSEED loop forever to
+ * harden the random number generation.
+ *
+ * The platform/OS is running as a guest/virtual machine and
+ * harden the random number generation.
+ *
+ * Examples include TDX guest & SEV.
+ */
+ CC_ATTR_GUEST_RAND_LOOP,
};
#ifdef CONFIG_ARCH_HAS_CC_PLATFORM
--
2.43.0