Skip to content

CVE‐2026‐8798

David Hook edited this page Aug 8, 2026 · 2 revisions

Title: Native entropy source retries the CPU entropy instructions without limit.

Issue affecting: BC-FJA before bc-fips 2.1.3 (from 2.1.0), on Intel platforms with native support enabled.

Fixed versions: BC-FJA 2.1.3.

Platform affected: Java 8 and later, x86 / x86-64 with the native library in use.

Bouncy Castle for Java (bcprov) is not affected — it has no native entropy source. The 1.0.X and 2.0.X FIPS series are not affected.

RDSEED and RDRAND do not always succeed. Both report the outcome through the carry flag, and both are specified as returning failure under load or transient conditions: the shared on-chip DRBG can be exhausted by contention across cores, and RDSEED in particular is expected to fail more often than RDRAND because it draws on the raw entropy conditioner. Intel's Digital Random Number Generator software implementation guide therefore tells callers to retry a bounded number of times — a baseline of 10 for RDRAND and 100 for RDSEED — and to treat exhaustion as a hardware failure rather than continuing.

The native entropy source in rand_jni.c (backing org.bouncycastle.crypto.fips.NativeEntropySource) instead retried without limit, spinning for as long as the carry flag stayed clear. A persistent failure of the instruction — a hardware fault, sustained contention, or a hypervisor that does not expose it — left the calling thread looping inside the JNI call indefinitely. Because the loop is in native code it cannot be interrupted or timed out from Java, so the thread is unrecoverable, and any operation that draws from the native entropy source hangs with it.

The retry loops are now bounded at 200 attempts for RDSEED and 20 for RDRAND — twice the Intel baselines, for margin — with a _mm_pause() between attempts. On exhaustion the routine clears any partially written buffer and throws, rather than continuing to spin.

The clear matters here, and it is why the fix also touches the compiler directives: the buffer is erased with an un-elidable memzero — a volatile-qualified pointer plus an __asm__ __volatile__ memory barrier — so that a compiler cannot discard the erase as a dead store on a buffer that is about to go out of scope. Without that, the caller could be left observing a partly-filled entropy buffer alongside the exception.

Clone this wiki locally