CVE-2020-16166: Use of Insufficiently Random Values (CWE-330)
value: The Linux kernel through 5.7.11 allows remote attackers to make observations that help to obtain sensitive information about the internal state of the network RNG, aka CID-f227e3ec3b5c. This is related to drivers/char/random.c and kernel/time/timer.c.
CWE description: The product uses insufficiently random numbers or values in a security context that depends on unpredictable numbers.
CVSS v3 base score: 3.7
Published: 2020-07-30T21:15Z
Repository: https://github.com/torvalds/linux
Language: C
Files changed: drivers/char/random.c, include/linux/random.h, kernel/time/timer.c, lib/random32.c
Commit date: 2020-07-29 10:35:37 -0700
Commit message: random32: update the net random state on interrupt and activity

This modifies the first 32 bits out of the 128 bits of a random CPU's
net_rand_state on interrupt or CPU activity to complicate remote
observations that could lead to guessing the network RNG's internal
state.

Note that depending on some network devices' interrupt rate moderation
or binding, this re-seeding might happen on every packet or even almost
never.

In addition, with NOHZ some CPUs might not even get timer interrupts,
leaving their local state rarely updated, while they are running
networked processes making use of the random state.  For this reason, we
also perform this update in update_process_times() in order to at least
update the state when there is user or system activity, since it's the
only case we care about.

Reported-by: Amit Klein <aksecurity@gmail.com>
Suggested-by: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Eric Dumazet <edumazet@google.com>
Cc: "Jason A. Donenfeld" <Jason@zx2c4.com>
Cc: Andy Lutomirski <luto@kernel.org>
Cc: Kees Cook <keescook@chromium.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: <stable@vger.kernel.org>
Signed-off-by: Willy Tarreau <w@1wt.eu>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

Diff: diff --git a/drivers/char/random.c b/drivers/char/random.c
index 2a41b21623ae..d20ba1b104ca 100644
--- a/drivers/char/random.c
+++ b/drivers/char/random.c
@@ -1275,10 +1275,11 @@ void add_interrupt_randomness(int irq, int irq_flags)
 	fast_pool->pool[3] ^= (sizeof(ip) > 4) ? ip >> 32 :
 		get_reg(fast_pool, regs);
 
 	fast_mix(fast_pool);
 	add_interrupt_bench(cycles);
+	this_cpu_add(net_rand_state.s1, fast_pool->pool[cycles & 3]);
 
 	if (unlikely(crng_init == 0)) {
 		if ((fast_pool->count >= 64) &&
 		    crng_fast_load((char *) fast_pool->pool,
 				   sizeof(fast_pool->pool))) {
diff --git a/include/linux/random.h b/include/linux/random.h
index 45e1f8fa742b..39aaa1f78f9d 100644
--- a/include/linux/random.h
+++ b/include/linux/random.h
@@ -9,10 +9,11 @@
 
 #include <linux/bug.h>
 #include <linux/kernel.h>
 #include <linux/list.h>
 #include <linux/once.h>
+#include <linux/percpu.h>
 
 #include <uapi/linux/random.h>
 
 struct random_ready_callback {
 	struct list_head list;
@@ -117,10 +118,12 @@ void prandom_reseed_late(void);
 
 struct rnd_state {
 	__u32 s1, s2, s3, s4;
 };
 
+DECLARE_PER_CPU(struct rnd_state, net_rand_state) __latent_entropy;
+
 u32 prandom_u32_state(struct rnd_state *state);
 void prandom_bytes_state(struct rnd_state *state, void *buf, size_t nbytes);
 void prandom_seed_full_state(struct rnd_state __percpu *pcpu_state);
 
 #define prandom_init_once(pcpu_state)			\
diff --git a/kernel/time/timer.c b/kernel/time/timer.c
index df1ff803acc4..026ac01af9da 100644
--- a/kernel/time/timer.c
+++ b/kernel/time/timer.c
@@ -41,10 +41,11 @@
 #include <linux/sched/sysctl.h>
 #include <linux/sched/nohz.h>
 #include <linux/sched/debug.h>
 #include <linux/slab.h>
 #include <linux/compat.h>
+#include <linux/random.h>
 
 #include <linux/uaccess.h>
 #include <asm/unistd.h>
 #include <asm/div64.h>
 #include <asm/timex.h>
@@ -1740,10 +1741,17 @@ void update_process_times(int user_tick)
 		irq_work_tick();
 #endif
 	scheduler_tick();
 	if (IS_ENABLED(CONFIG_POSIX_TIMERS))
 		run_posix_cpu_timers();
+
+	/* The current CPU might make use of net randoms without receiving IRQs
+	 * to renew them often enough. Let's update the net_rand_state from a
+	 * non-constant value that's not affine to the number of calls to make
+	 * sure it's updated when there's some activity (we don't care in idle).
+	 */
+	this_cpu_add(net_rand_state.s1, rol32(jiffies, 24) + user_tick);
 }
 
 /**
  * __run_timers - run all expired timers (if any) on this CPU.
  * @base: the timer vector to be processed.
diff --git a/lib/random32.c b/lib/random32.c
index 763b920a6206..c4d317be2997 100644
--- a/lib/random32.c
+++ b/lib/random32.c
@@ -46,11 +46,11 @@ static void __init prandom_state_selftest(void);
 static inline void prandom_state_selftest(void)
 {
 }
 #endif
 
-static DEFINE_PER_CPU(struct rnd_state, net_rand_state) __latent_entropy;
+DEFINE_PER_CPU(struct rnd_state, net_rand_state) __latent_entropy;
 
 /**
  *	prandom_u32_state - seeded pseudo-random number generator.
  *	@state: pointer to state structure holding seeded state.
  *

Vulnerable code: static DEFINE_PER_CPU(struct rnd_state, net_rand_state) __latent_entropy;
Fixed code: 	this_cpu_add(net_rand_state.s1, fast_pool->pool[cycles & 3]);
#include <linux/percpu.h>
DECLARE_PER_CPU(struct rnd_state, net_rand_state) __latent_entropy;

#include <linux/random.h>

	/* The current CPU might make use of net randoms without receiving IRQs
	 * to renew them often enough. Let's update the net_rand_state from a
	 * non-constant value that's not affine to the number of calls to make
	 * sure it's updated when there's some activity (we don't care in idle).
	 */
	this_cpu_add(net_rand_state.s1, rol32(jiffies, 24) + user_tick);
DEFINE_PER_CPU(struct rnd_state, net_rand_state) __latent_entropy;