[PATCH] random: ensure mix_interrupt_randomness() is consistent

From: Jason A. Donenfeld
Date: Thu Feb 10 2022 - 20:15:07 EST


This addresses two issues alluded to in an earlier commit.

The first issue is that mix_interrupt_randomness() might be migrated to
another CPU during CPU hotplug. This issue is rectified by checking that
it hasn't been migrated (after disabling migration). If it has been
migrated, then we set the count to zero, so that when the CPU comes
online again, it can requeue the work. As part of this, we switch to
using an atomic_t, so that the increment in the irq handler doesn't wipe
out the zeroing if the CPU comes back online while this worker is
running.

The second issue is that, though relatively minor in effect, we probably
do after all want to make sure we get a consistent view of the pool onto
the stack, in case it's interrupted by an irq while reading. To do this,
we simply read count before and after the memcpy and make sure they're
the same. If they're not, we try again. The likelihood of actually
hitting this is very low, as we're talking about a 2 or 4 word mov, and
we're executing on the same CPU as the potential interruption.
Nonetheless, it's easy enough to fix, so we do here.

If we only were concerned with the first issue rather than the second,
we could fix this entirely with just using an atomic_t. But in order to
fix them both, it requires a bit of coordination, which this patch
tackles.

Cc: Sebastian Andrzej Siewior <bigeasy@xxxxxxxxxxxxx>
Cc: Thomas Gleixner <tglx@xxxxxxxxxxxxx>
Cc: Theodore Ts'o <tytso@xxxxxxx>
Cc: Dominik Brodowski <linux@xxxxxxxxxxxxxxxxxxxx>
Cc: Sultan Alsawaf <sultan@xxxxxxxxxxxxxxx>
Signed-off-by: Jason A. Donenfeld <Jason@xxxxxxxxx>
---
drivers/char/random.c | 42 ++++++++++++++++++++++++++++++++++--------
1 file changed, 34 insertions(+), 8 deletions(-)

diff --git a/drivers/char/random.c b/drivers/char/random.c
index 9c779f1bda34..caaf3c33bb38 100644
--- a/drivers/char/random.c
+++ b/drivers/char/random.c
@@ -1152,10 +1152,11 @@ struct fast_pool {
union {
u64 pool64[2];
u32 pool32[4];
+ unsigned long pool_long[16 / sizeof(long)];
};
struct work_struct mix;
unsigned long last;
- unsigned int count;
+ atomic_t count;
u16 reg_idx;
};
#define FAST_POOL_MIX_INFLIGHT (1U << 31)
@@ -1210,14 +1211,39 @@ static u32 get_reg(struct fast_pool *f, struct pt_regs *regs)
static void mix_interrupt_randomness(struct work_struct *work)
{
struct fast_pool *fast_pool = container_of(work, struct fast_pool, mix);
- u32 pool[ARRAY_SIZE(fast_pool->pool32)];
+ unsigned long pool[ARRAY_SIZE(fast_pool->pool_long)];
+ unsigned int count_snapshot;
+ size_t i;

- /* Copy the pool to the stack so that the mixer always has a consistent view. */
- memcpy(pool, fast_pool->pool32, sizeof(pool));
+ /* Check to see if we're running on the wrong CPU due to hotplug. */
+ migrate_disable();
+ if (fast_pool != this_cpu_ptr(&irq_randomness)) {
+ migrate_enable();
+ /*
+ * If we are unlucky enough to have been moved to another CPU,
+ * then we set our count to zero atomically so that when the
+ * CPU comes back online, it can enqueue work again.
+ */
+ atomic_set_release(&fast_pool->count, 0);
+ return;
+ }
+
+ /*
+ * Copy the pool to the stack so that the mixer always has a
+ * consistent view. It's extremely unlikely but possible that
+ * this 2 or 4 word read is interrupted by an irq, but in case
+ * it is, we double check that count stays the same.
+ */
+ do {
+ count_snapshot = (unsigned int)atomic_read(&fast_pool->count);
+ for (i = 0; i < ARRAY_SIZE(pool); ++i)
+ pool[i] = READ_ONCE(fast_pool->pool_long[i]);
+ } while (count_snapshot != (unsigned int)atomic_read(&fast_pool->count));

/* We take care to zero out the count only after we're done reading the pool. */
- WRITE_ONCE(fast_pool->count, 0);
+ atomic_set(&fast_pool->count, 0);
fast_pool->last = jiffies;
+ migrate_enable();

mix_pool_bytes(pool, sizeof(pool));
credit_entropy_bits(1);
@@ -1246,12 +1272,12 @@ void add_interrupt_randomness(int irq)
}

fast_mix(fast_pool->pool32);
- new_count = ++fast_pool->count;
+ new_count = (unsigned int)atomic_inc_return_acquire(&fast_pool->count);

if (unlikely(crng_init == 0)) {
if (new_count >= 64 &&
crng_fast_load(fast_pool->pool32, sizeof(fast_pool->pool32)) > 0) {
- fast_pool->count = 0;
+ atomic_set(&fast_pool->count, 0);
fast_pool->last = now;

/*
@@ -1273,7 +1299,7 @@ void add_interrupt_randomness(int irq)

if (unlikely(!fast_pool->mix.func))
INIT_WORK(&fast_pool->mix, mix_interrupt_randomness);
- fast_pool->count |= FAST_POOL_MIX_INFLIGHT;
+ atomic_or(FAST_POOL_MIX_INFLIGHT, &fast_pool->count);
queue_work_on(raw_smp_processor_id(), system_highpri_wq, &fast_pool->mix);
}
EXPORT_SYMBOL_GPL(add_interrupt_randomness);
--
2.35.0