1 file changed
Hi Jason and maintainer team,
I'm very impressed with random.c, it is a huge and clever application of
ChaCha20 with rekeying. I was reading it to see if there are failure modes
from hardware errors. (I wanted to know if entropy sources that were stuck
high such as due to a wiring fault could cause an infinite loop freeze if
non-dyadic (not a power of two denominator) random value ranges are chosen,
since the usual algorithm rejects the excess, for example if one of three
values are needed with uniform randomness then it could read two bits,
assign 00, 01, and 10 to the three values and discard 11 and try again - so
getting stuck on 11 could freeze a system.)
Along the way I learned that there are some NIST recommendations for
testing against just such problems (stuck entropy sources), these are here:
https://csrc.nist.gov/pubs/sp/800/90/b/final
under Health Tests in Section 4 starting on page 22 of the document (page
30 of the PDF file.), the reasoning/discussion is provided on page 6 under
General Discussion.
These seem like really good recommendations.
I asked Grok to take a shot at proposing the changes, it produced the
attached patch. I asked it to try to encapsulate this guard behavior
correctly (so it doesn't repeat the code) and while respecting the layout
and architecture of the code. I've read the diff and it seems to make
sense, it has also run some tests on it, but since this code is also used
in high security applications, I would submit it for a review process to
see if it is correct. I don't know the details of the IRQ and other
entropy sources.
I think it would be nice to add this compliance with SP 800-90B which is a
thoughtful and good standard, though I don't know if it would really guard
code that much. It's hard to see how these tests could ever fail, since
there are so many entropy sources in the pool. But if they did fail, I
guess people would want to know!
Let me know what you think, and I hope you will consider carefully
reviewing and potentially adding the code below or a version of it.
Grok's notes are below.
Robert Viragh
Grok's notes:
This adds SP800-90B 4.3-4.4 startup (1024-sample discard) and continuous
RCT/APT on the raw symbols of credited sources only:
add_interrupt_randomness
add_timer_randomness (input + disk)
add_hwgenerator_randomness
Not on add_device_randomness / add_vmfork_randomness / bootloader seed.
A failed source is still mixed, but not credited. Other sources still
credit. getrandom(2) is unchanged.
Helper was unit-tested in userspace (startup discard, RCT C=31, APT
C=325, no credit after fail). Not booted in a kernel.
From: Robert Viragh <rviragh@gmail.com>
Date: Wed, 16 Sep 2026 20:00:00 +0000
Subject: [PATCH] random.c: add SP800-90B RCT/APT health tests before crediting sources
Hi Jason and maintainer team,
I'm very impressed with random.c, it is a huge and clever application
of ChaCha20 with rekeying. I was reading it to see if there are
failure modes from hardware errors. (I wanted to know if entropy
sources that were stuck high such as due to a wiring fault could cause
an infinite loop freeze if non-dyadic (not a power of two denominator)
random value ranges are chosen, since the usual algorithm rejects the
excess, for example if one of three values are needed with uniform
randomness then it could read two bits, assign 00, 01, and 10 to the
three values and discard 11 and try again - so getting stuck on 11
could freeze a system.)
Along the way I learned that there are some NIST recommendations
for testing against just such problems (stuck entropy sources),
these are here:
https://csrc.nist.gov/pubs/sp/800/90/b/final
under Health Tests in Section 4 starting on page 22 of the
document (page 30 of the PDF file.), the reasoning/discussion
is provided on page 6 under General Discussion.
These seem like really good recommendations.
I asked Grok to take a shot at proposing the changes, it produced
the attached patch. I asked it to try to encapsulate this guard
behavior correctly (so it doesn't repeat the code) and while
respecting the layout and architecture of the code. I've read the
diff and it seems to make sense, it has also run some tests on it,
but since this code is also used in high security applications,
I would submit it for a review process to see if it is correct.
I don't know the details of the IRQ and other entropy sources.
I think it would be nice to add this compliance with SP 800-90B
which is a thoughtful and good standard, though I don't know if
it would really guard code that much. It's hard to see how these
tests could ever fail, since there are so many entropy sources in
the pool. But if they did fail, I guess people would want to know!
Let me know what you think, and I hope you will consider carefully
reviewing and potentially adding the code below or a version of it.
Grok's notes are below.
Robert Viragh
Grok's notes:
Summary:
This adds SP800-90B 4.3-4.4 startup (1024-sample discard) and continuous
RCT/APT on the raw symbols of credited sources only:
add_interrupt_randomness
add_timer_randomness (input + disk)
add_hwgenerator_randomness
Not on add_device_randomness / add_vmfork_randomness / bootloader seed.
A failed source is still mixed, but not credited. Other sources still
credit. getrandom(2) is unchanged.
Helper was unit-tested in userspace (startup discard, RCT C=31, APT
C=325, no credit after fail). Not booted in a kernel.
Details:
Add SP800-90B section 4.3-4.4 startup and continuous health tests
(repetition count and adaptive proportion, alpha=2^-30) on the raw
symbols of credited noise sources: IRQ cycle counters, timer/input/disk
cycle counters, and hwrng bytes.
The first 1024 samples per source are discarded. A source that fails is
still mixed but not credited; other sources still credit.
getrandom(2) and CRNG_READY are unchanged: 256 credited bits from the
union of healthy sources.
Cutoffs use H=1 bit per IRQ/timer sample so the mandatory non-binary
APT window W=512 can meet alpha (H=1/64 pool accounting cannot: C>W).
hwrng uses H = min(8, entropy/len) bits per byte.
Untested on a booted kernel. Helper logic unit-tested in userspace.
Link: https://csrc.nist.gov/pubs/sp/800/90/b/final
Signed-off-by: ROBERT VIRAGH <rviragh@gmail.com>
---
drivers/char/random.c | 1 file
1 file changed
diff --git a/drivers/char/random.c b/drivers/char/random.c
index 000000000000..111111111111 100644
--- a/drivers/char/random.c 2026-09-16 20:20:44.539663592 +0000
+++ b/drivers/char/random.c 2026-09-16 20:20:44.539663592 +0000
@@ -937,6 +937,100 @@
}
EXPORT_SYMBOL(add_device_randomness);
+
+/*
+ * These health checks attempt to implement section 4 of
+ * NIST SP 800-90B
+ * Recommendation for the Entropy Sources Used for Random Bit Generation
+ * available at: https://csrc.nist.gov/pubs/sp/800/90/b/final
+ *
+ * SP800-90B 4.3-4.4 health tests on raw noise symbols (alpha = 2^-30).
+ * H = 1 bit/sample for IRQ and timer cycle counters (non-binary APT
+ * W=512 needs C <= W). First 1024 samples are discarded. A failed source
+ * is still included but not credited; other sources still credit.
+ */
+#define EH_STARTUP 1024
+#define EH_RCT_H1 31
+#define EH_APT_W 512
+#define EH_APT_H1 325
+
+struct entropy_health {
+ unsigned long last, apt_base;
+ u16 rct, apt_count, apt_obs, startup;
+ u8 apt_set, failed;
+};
+
+static struct entropy_health hwgen_health;
+
+static unsigned int rct_cutoff_h(unsigned int h)
+{
+ if (h < 1)
+ h = 1;
+ return 1 + (30 + h - 1) / h;
+}
+
+static unsigned int apt_cutoff_h(unsigned int h)
+{
+ static const u16 t[] = { 325, 190, 122, 71, 50, 38, 29, 16 };
+
+ if (h < 1)
+ h = 1;
+ if (h > 8)
+ h = 8;
+ return t[h - 1];
+}
+
+/* Returns true if this sample may be credited. */
+static bool entropy_health_observe(struct entropy_health *h, unsigned long sample,
+ unsigned int rct_c, unsigned int apt_c)
+{
+ if (!h->failed) {
+ if (!h->rct) {
+ h->rct = 1;
+ } else if (sample == h->last) {
+ if (++h->rct >= rct_c)
+ goto fail;
+ } else {
+ h->rct = 1;
+ }
+ h->last = sample;
+
+ if (!h->apt_set) {
+ h->apt_base = sample;
+ h->apt_set = 1;
+ h->apt_count = 1;
+ h->apt_obs = 1;
+ } else {
+ if (sample == h->apt_base)
+ h->apt_count++;
+ if (++h->apt_obs >= EH_APT_W) {
+ if (h->apt_count >= apt_c)
+ goto fail;
+ h->apt_set = 0;
+ h->apt_count = 0;
+ h->apt_obs = 0;
+ }
+ }
+ }
+
+ if (h->startup < EH_STARTUP) {
+ h->startup++;
+ return false;
+ }
+ return !h->failed;
+fail:
+ h->failed = 1;
+ pr_warn_ratelimited("random: entropy source failed SP800-90B health test\n");
+ if (h->startup < EH_STARTUP)
+ h->startup++;
+ return false;
+}
+
+static bool entropy_health_discarding(const struct entropy_health *h)
+{
+ return h->startup < EH_STARTUP;
+}
+
/*
* Interface for in-kernel drivers of true hardware RNGs. Those devices
* may produce endless random bits, so this function will sleep for
@@ -944,8 +1038,24 @@
*/
void add_hwgenerator_randomness(const void *buf, size_t len, size_t entropy, bool sleep_after)
{
- mix_pool_bytes(buf, len);
- credit_init_bits(entropy);
+ unsigned int h = (len && entropy) ? min_t(unsigned int, 8, entropy / len) : 0;
+ size_t i;
+ bool credit = true;
+
+ if (h) {
+ unsigned int rct_c = rct_cutoff_h(h), apt_c = apt_cutoff_h(h);
+
+ for (i = 0; i < len; i++) {
+ if (!entropy_health_observe(&hwgen_health, ((const u8 *)buf)[i],
+ rct_c, apt_c))
+ credit = false;
+ }
+ }
+ if (!entropy_health_discarding(&hwgen_health)) {
+ mix_pool_bytes(buf, len);
+ if (credit)
+ credit_init_bits(entropy);
+ }
/*
* Throttle writing to once every reseed interval, unless we're not yet
@@ -1021,6 +1131,8 @@
.mix = __TIMER_INITIALIZER(mix_interrupt_randomness, 0)
};
+static DEFINE_PER_CPU(struct entropy_health, irq_health);
+
/*
* This is [Half]SipHash-1-x, starting from an empty key. Because
* the key is fixed, it assumes that its inputs are non-malicious,
@@ -1072,6 +1184,8 @@
*/
unsigned long pool[2];
unsigned int count;
+ bool credit;
+ struct entropy_health *eh;
/* Check to see if we're running on the wrong CPU due to hotplug. */
local_irq_disable();
@@ -1088,10 +1202,13 @@
count = fast_pool->count;
fast_pool->count = 0;
fast_pool->last = jiffies;
+ eh = this_cpu_ptr(&irq_health);
+ credit = !eh->failed && eh->startup >= EH_STARTUP;
local_irq_enable();
mix_pool_bytes(pool, sizeof(pool));
- credit_init_bits(clamp_t(unsigned int, (count & U16_MAX) / 64, 1, sizeof(pool) * 8));
+ if (credit)
+ credit_init_bits(clamp_t(unsigned int, (count & U16_MAX) / 64, 1, sizeof(pool) * 8));
memzero_explicit(pool, sizeof(pool));
}
@@ -1103,6 +1220,11 @@
struct fast_pool *fast_pool = this_cpu_ptr(&irq_randomness);
struct pt_regs *regs = get_irq_regs();
unsigned int new_count;
+ struct entropy_health *eh = this_cpu_ptr(&irq_health);
+
+ entropy_health_observe(eh, entropy, EH_RCT_H1, EH_APT_H1);
+ if (entropy_health_discarding(eh))
+ return;
fast_mix(fast_pool->pool, entropy,
(regs ? instruction_pointer(regs) : _RET_IP_) ^ swab(irq));
@@ -1126,6 +1248,7 @@
struct timer_rand_state {
unsigned long last_time;
long last_delta, last_delta2;
+ struct entropy_health health;
};
/*
@@ -1140,6 +1263,11 @@
unsigned long entropy = random_get_entropy(), now = jiffies, flags;
long delta, delta2, delta3;
unsigned int bits;
+ bool credit;
+
+ credit = entropy_health_observe(&state->health, entropy, EH_RCT_H1, EH_APT_H1);
+ if (entropy_health_discarding(&state->health))
+ return;
/*
* If we're in a hard IRQ, add_interrupt_randomness() will be called
@@ -1154,7 +1282,7 @@
spin_unlock_irqrestore(&input_pool.lock, flags);
}
- if (crng_ready())
+ if (crng_ready() || !credit)
return;
/*
© 2016 - 2026 Red Hat, Inc.