random: do not use input pool from hard IRQs
authorJason A. Donenfeld <Jason@zx2c4.com>
Fri, 6 May 2022 16:30:51 +0000 (18:30 +0200)
committerJason A. Donenfeld <Jason@zx2c4.com>
Wed, 18 May 2022 13:53:52 +0000 (15:53 +0200)
Years ago, a separate fast pool was added for interrupts, so that the
cost associated with taking the input pool spinlocks and mixing into it
would be avoided in places where latency is critical. However, one
oversight was that add_input_randomness() and add_disk_randomness()
still sometimes are called directly from the interrupt handler, rather
than being deferred to a thread. This means that some unlucky interrupts
will be caught doing a blake2s_compress() call and potentially spinning
on input_pool.lock, which can also be taken by unprivileged users by
writing into /dev/urandom.

In order to fix this, add_timer_randomness() now checks whether it is
being called from a hard IRQ and if so, just mixes into the per-cpu IRQ
fast pool using fast_mix(), which is much faster and can be done
lock-free. A nice consequence of this, as well, is that it means hard
IRQ context FPU support is likely no longer useful.

The entropy estimation algorithm used by add_timer_randomness() is also
somewhat different than the one used for add_interrupt_randomness(). The
former looks at deltas of deltas of deltas, while the latter just waits
for 64 interrupts for one bit or for one second since the last bit. In
order to bridge these, and since add_interrupt_randomness() runs after
an add_timer_randomness() that's called from hard IRQ, we add to the
fast pool credit the related amount, and then subtract one to account
for add_interrupt_randomness()'s contribution.

A downside of this, however, is that the num argument is potentially
attacker controlled, which puts a bit more pressure on the fast_mix()
sponge to do more than it's really intended to do. As a mitigating
factor, the first 96 bits of input aren't attacker controlled (a cycle
counter followed by zeros), which means it's essentially two rounds of
siphash rather than one, which is somewhat better. It's also not that
much different from add_interrupt_randomness()'s use of the irq stack
instruction pointer register.

Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Filipe Manana <fdmanana@suse.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Borislav Petkov <bp@alien8.de>
Cc: Theodore Ts'o <tytso@mit.edu>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
drivers/char/random.c

index fcdd108c9d057225472bf6f99b235c7f33ffbc53..e5d9739b81f5e7f8d72356e99e9d8cd8b657215f 100644 (file)
@@ -1156,6 +1156,7 @@ static void mix_interrupt_randomness(struct work_struct *work)
         * we don't wind up "losing" some.
         */
        unsigned long pool[2];
+       unsigned int count;
 
        /* Check to see if we're running on the wrong CPU due to hotplug. */
        local_irq_disable();
@@ -1169,12 +1170,13 @@ static void mix_interrupt_randomness(struct work_struct *work)
         * consistent view, before we reenable irqs again.
         */
        memcpy(pool, fast_pool->pool, sizeof(pool));
+       count = fast_pool->count;
        fast_pool->count = 0;
        fast_pool->last = jiffies;
        local_irq_enable();
 
        mix_pool_bytes(pool, sizeof(pool));
-       credit_init_bits(1);
+       credit_init_bits(max(1u, (count & U16_MAX) / 64));
 
        memzero_explicit(pool, sizeof(pool));
 }
@@ -1214,22 +1216,30 @@ struct timer_rand_state {
 
 /*
  * This function adds entropy to the entropy "pool" by using timing
- * delays.  It uses the timer_rand_state structure to make an estimate
- * of how many bits of entropy this call has added to the pool.
- *
- * The number "num" is also added to the pool - it should somehow describe
- * the type of event which just happened.  This is currently 0-255 for
- * keyboard scan codes, and 256 upwards for interrupts.
+ * delays. It uses the timer_rand_state structure to make an estimate
+ * of how many bits of entropy this call has added to the pool. The
+ * value "num" is also added to the pool; it should somehow describe
+ * the type of event that just happened.
  */
 static void add_timer_randomness(struct timer_rand_state *state, unsigned int num)
 {
        unsigned long entropy = random_get_entropy(), now = jiffies, flags;
        long delta, delta2, delta3;
+       unsigned int bits;
 
-       spin_lock_irqsave(&input_pool.lock, flags);
-       _mix_pool_bytes(&entropy, sizeof(entropy));
-       _mix_pool_bytes(&num, sizeof(num));
-       spin_unlock_irqrestore(&input_pool.lock, flags);
+       /*
+        * If we're in a hard IRQ, add_interrupt_randomness() will be called
+        * sometime after, so mix into the fast pool.
+        */
+       if (in_hardirq()) {
+               fast_mix(this_cpu_ptr(&irq_randomness)->pool,
+                        (unsigned long[2]){ entropy, num });
+       } else {
+               spin_lock_irqsave(&input_pool.lock, flags);
+               _mix_pool_bytes(&entropy, sizeof(entropy));
+               _mix_pool_bytes(&num, sizeof(num));
+               spin_unlock_irqrestore(&input_pool.lock, flags);
+       }
 
        if (crng_ready())
                return;
@@ -1260,11 +1270,22 @@ static void add_timer_randomness(struct timer_rand_state *state, unsigned int nu
                delta = delta3;
 
        /*
-        * delta is now minimum absolute delta.
-        * Round down by 1 bit on general principles,
-        * and limit entropy estimate to 12 bits.
+        * delta is now minimum absolute delta. Round down by 1 bit
+        * on general principles, and limit entropy estimate to 11 bits.
+        */
+       bits = min(fls(delta >> 1), 11);
+
+       /*
+        * As mentioned above, if we're in a hard IRQ, add_interrupt_randomness()
+        * will run after this, which uses a different crediting scheme of 1 bit
+        * per every 64 interrupts. In order to let that function do accounting
+        * close to the one in this function, we credit a full 64/64 bit per bit,
+        * and then subtract one to account for the extra one added.
         */
-       credit_init_bits(min_t(unsigned int, fls(delta >> 1), 11));
+       if (in_hardirq())
+               this_cpu_ptr(&irq_randomness)->count += max(1u, bits * 64) - 1;
+       else
+               credit_init_bits(bits);
 }
 
 void add_input_randomness(unsigned int type, unsigned int code,