diff mbox series

[U-Boot,RFC,3/3] add infrastructure for collecting entropy

Message ID 20190918220140.838-4-rasmus.villemoes@prevas.dk
State RFC
Delegated to: Tom Rini
Headers show
Series collect entropy, populate /chosen/rng-seed | expand

Commit Message

Rasmus Villemoes Sept. 18, 2019, 10:01 p.m. UTC
A recurring theme on LKML is the boot process deadlocking due to some
process blocking waiting for random numbers, while the kernel's
Cryptographic Random Number Generator (crng) is not initalized yet,
but that very blocking means no activity happens that would generate
the entropy necessary to finalize seeding the crng.

This is not a problem on boards that have a good hwrng (when the
kernel is configured to trust it), whether in the CPU or in a TPM or
elsewhere. However, that's far from all boards out there.

Moreover, when booting with an initrd, all the "disk activity" that
would otherwise generate some timing variances has already been done
by U-boot. Hence it makes sense to try to collect that entropy in
U-boot and pass it on to the kernel. On the kernel side, support for
that has just landed in master (commit 428826f5358c "fdt: add support
for rng-seed").

By itself, this does not help with the initialization of the crng,
since the kernel only considers the rng-seed "trustworthy" if
CONFIG_RANDOM_TRUST_BOOTLOADER is set (it is always fed into the crng,
but entropy is only accounted when that config option is set).

This adds some basic infrastructure for collecting entropy in U-boot,
and then it's up to the BSP developer to decide if
CONFIG_RANDOM_TRUST_BOOTLOADER should be enabled in the kernel.

If this is accepted, I think we should add entropy() calls before and
after most disk and network activities. Moreover, at least some boards
seem to have a rather reliable source of randomness in the contents of
RAM after a cold boot [*], so I imagine exposing the entropy_mix()
either via a command "entropy <addr> <len>" that can be run during a
boot script, or perhaps to be done automatically (and as early as
possible to reduce risk of "tainting") via some CONFIG_ENTROPY_RAM_{ADDR,LEN}.

There's probably good sources of entropy to be had from the SPL phase,
but I couldn't find a good way of passing that on other than by
putting the sha256_context inside global_data, which would bloat that
by over 100 bytes.

[*] Looking at a slightly arbitrary place in the middle of physical
memory I got these

40d07670: 34081000 400a8020 00002040 20024400    ...4 ..@@ ...D.
40d07670: 343c1000 640a9120 00036040 6006e400    ..<4 ..d@`.....`
40d07670: 353c1040 600a9120 00026041 6246e400    @.<5 ..`A`....Fb
40d07670: 34281000 600a9100 00026040 2046e400    ..(4...`@`....F

So some bits are always the same, but there's quite a few that flip
randomly between boots - so mixing in a MB or two seems that it should
provide plenty of real entropy.

Signed-off-by: Rasmus Villemoes <rasmus.villemoes@prevas.dk>
---
 common/fdt_support.c | 12 +++++++++
 include/common.h     |  3 +++
 include/entropy.h    | 42 +++++++++++++++++++++++++++++
 lib/Kconfig          | 10 +++++++
 lib/Makefile         |  1 +
 lib/entropy.c        | 63 ++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 131 insertions(+)
 create mode 100644 include/entropy.h
 create mode 100644 lib/entropy.c
diff mbox series

Patch

diff --git a/common/fdt_support.c b/common/fdt_support.c
index baf7924ff6..e071abaabb 100644
--- a/common/fdt_support.c
+++ b/common/fdt_support.c
@@ -274,6 +274,7 @@  int fdt_initrd(void *fdt, ulong initrd_start, ulong initrd_end)
 
 int fdt_chosen(void *fdt)
 {
+	uint8_t digest[SHA256_SUM_LEN];
 	int   nodeoffset;
 	int   err;
 	char  *str;		/* used to set string properties */
@@ -299,6 +300,17 @@  int fdt_chosen(void *fdt)
 			return err;
 		}
 	}
+	if (IS_ENABLED(CONFIG_ENTROPY)) {
+		entropy_digest(digest);
+
+		err = fdt_setprop(fdt, nodeoffset, "rng-seed", digest,
+				  sizeof(digest));
+		if (err < 0) {
+			printf("WARNING: could not set rng-seed %s.\n",
+			       fdt_strerror(err));
+			return err;
+		}
+	}
 
 	return fdt_fixup_stdout(fdt, nodeoffset);
 }
diff --git a/include/common.h b/include/common.h
index d8f302ea92..55df86b6e7 100644
--- a/include/common.h
+++ b/include/common.h
@@ -331,6 +331,9 @@  void srand(unsigned int seed);
 unsigned int rand(void);
 unsigned int rand_r(unsigned int *seedp);
 
+/* lib/entropy.c */
+#include <entropy.h>
+
 /*
  * STDIO based functions (can always be used)
  */
diff --git a/include/entropy.h b/include/entropy.h
new file mode 100644
index 0000000000..47fa73a3a3
--- /dev/null
+++ b/include/entropy.h
@@ -0,0 +1,42 @@ 
+#ifndef __ENTROPY_H
+#define __ENTROPY_H
+
+#include <u-boot/sha256.h>
+
+#if defined(CONFIG_ENTROPY) && !defined(CONFIG_SPL_BUILD)
+
+/* Overridable, should preferably include some timer with microsecond-or-better resolution. */
+uint64_t entropy_token(void);
+
+/*
+ * Call entropy_token() and mix in the return value - this is the main
+ * function for adding entropy from timing variances. Call it before
+ * and after any operation that may have some variation in its
+ * execution time.
+ */
+void entropy(void);
+
+/*
+ * Mix in some buffer which one has some reason to believe contains at
+ * least some randomness. On some boards, that might be the contents
+ * of some never-touched part of RAM.
+ */
+void entropy_mix(const void *input, unsigned int len);
+
+void entropy_digest(uint8_t digest[SHA256_SUM_LEN]);
+
+#else
+
+static inline void entropy(void)
+{
+}
+static inline void entropy_mix(const void *input, unsigned int len)
+{
+}
+static inline void entropy_digest(uint8_t *digest)
+{
+}
+
+#endif
+
+#endif /* __ENTROPY_H */
diff --git a/lib/Kconfig b/lib/Kconfig
index 3da45a5ec3..f0db0589d3 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -287,6 +287,16 @@  config TPL_TPM
 	  for the low-level TPM interface, but only one TPM is supported at
 	  a time by the TPM library.
 
+config ENTROPY
+	bool "Enable collecting entropy"
+	select SHA256
+	help
+	  This enables collection of entropy (randomness) from
+	  variations in execution time of various functions, as well
+	  as other sources. A digest of this is then passed on to the
+	  kernel in the "rng-seed" property of the "chosen" node,
+	  which in turn is then used to seed the kernel's crng.
+
 endmenu
 
 menu "Android Verified Boot"
diff --git a/lib/Makefile b/lib/Makefile
index 2fffd68f94..e8edb74c8a 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -19,6 +19,7 @@  obj-$(CONFIG_ARCH_AT91) += at91/
 obj-$(CONFIG_OPTEE) += optee/
 
 obj-$(CONFIG_AES) += aes.o
+obj-$(CONFIG_ENTROPY) += entropy.o
 
 ifndef API_BUILD
 ifneq ($(CONFIG_UT_UNICODE)$(CONFIG_EFI_LOADER),)
diff --git a/lib/entropy.c b/lib/entropy.c
new file mode 100644
index 0000000000..5379c54da1
--- /dev/null
+++ b/lib/entropy.c
@@ -0,0 +1,63 @@ 
+#include <common.h>
+#include <u-boot/sha256.h>
+#include <watchdog.h>
+
+static sha256_context entropy_ctx = SHA256_INIT;
+
+__weak uint64_t entropy_token(void)
+{
+	return timer_get_us();
+}
+
+static void __entropy(sha256_context *ctx)
+{
+	uint64_t x = entropy_token();
+
+	sha256_update(ctx, (void*)&x, sizeof(x));
+}
+
+void entropy(void)
+{
+	sha256_context *ctx = &entropy_ctx;
+
+	__entropy(ctx);
+}
+
+/*
+ * This mixes in the @len bytes starting at @input, taking care to
+ * reset the watchdog every once in a while. Whether or not there is a
+ * watchdog to handle, we also use the opportunity to periodically mix
+ * in a timestamp or whatever other small ever-changing token
+ * get_entropy() provides.
+ */
+void entropy_mix(const void *input, unsigned int len)
+{
+	sha256_context *ctx = &entropy_ctx;
+	unsigned int chunk;
+	
+	while (len) {
+		chunk = min_t(unsigned, len, CHUNKSZ_SHA256);
+		sha256_update(ctx, input, chunk);
+		input += chunk;
+		len -= chunk;
+		      
+		__entropy(ctx);
+		WATCHDOG_RESET();
+	}
+}
+
+void entropy_digest(uint8_t digest[32])
+{
+	sha256_context ctx;
+
+	/*
+	 * Allow this to be called more than once by calling
+	 * sha256_finish() on a copy of the context (this is for
+	 * example necessary to allow the command "fdt chosen" to be
+	 * run more than once). And make sure each call provides a
+	 * different answer by updating the global context first.
+	 */
+	entropy();
+	memcpy(&ctx, &entropy_ctx, sizeof(ctx));
+	sha256_finish(&ctx, digest);
+}