/dev/ufootgun
The Linux randomness API has quite a bit of historical baggage and nuance to it. Arguably, the two most recognizable
components of the API, being the special files /dev/random and /dev/urandom, are often misunderstood, and in specific contexts, this can lead
to severe cryptographic vulnerabilities.
Note: This blog post was motivated by an investigation resulting in CVE-2026-34871, an entropy overestimation vulnerability in mbedTLS’s TF-PSA-Crypto cryptography library. A more in-depth analysis is available in the mbedTLS documentation.
Background
Why do we Care About Randomness?
Beyond the obvious desire to occasionally have seemingly random behavior (e.g., a simulated dice roll), randomness forms the basis of modern cryptography. The concept of $n$-bit security, where $n$ is some natural ( $n \in \mathbb{N}$ ), comes from the problem of finding a random bitstring of length $n$, presuming that the bitstring is sampled i.i.d. from a uniform distribution. So, for a more concrete example, if we state that something has “128-bit security”, that means that the problem is considered at least as hard as finding a unique random number in the range $[0, 2^{128})$. As one would expect, the average number of guesses to do so is $2^{127}$. This guarantee is how we are able to use tools such as Computationally Secure Pseudorandom Number Generators (CSPRNGs), alternatively referred to as Deterministic Random Bit Generators (DRBGs) when discussing the NIST-specific generators. We understand that the assumption is that these generators, provided that they are initialized with truly random data behaving according to the previously discussed distribution, provide a secure, deterministic source of pseudorandomness. However, if that statement is untrue, then those generators are only as secure as the amount of entropy used to initialize them, leading to drastic consequences.
…Like What?
CSPRNGs are directly used to feed things like RSA factor generation (often by requesting a certain number of random bytes, which are then put through something like Miller-Rabin probabilistic primality testing, or ECDH secrets. By knowing the outputs of such a generator beforehand, it is trivial to break these cryptosystems. For example, if we are given an RSA public key of $(N, e)$, knowing a factor $p$ of $N$, we can arrive at $q$ simply by the result of $\frac{N}{p}$. From there, it is trivial to derive $d$ as $e^{-1} \mod \phi(N)$, breaking the cryptosystem. Similarly, given a public key of the chosen curve $C$ with base point $P$, and we are aware of the secret $x$ for a given user, we can simply take the intercepted point of their conversation partner $Q$, multiply it by $x$, and arrive at the same shared secret. From there, it is clear that we are able to break symmetric communication, since we have broken the establishment of a secure channel.
The Linux Random Subsystem
Not For Export, Unless…
First, for a bit of history. In 1994, Theodore Ts’o first implemented the /dev/random/ special file, a mechanism for forming an entropy pool that would output cryptographically secure bitstreams. At the time, however,
export of strong cryptography was considered exportation of munitions under US law, save cryptographic hashes. This exception later formed the basis for the Snuffle cipher (which went on to inspire Salsa20), which in turn resulted in Bernstein v.
United States, which found that code could be considered speech, but this is a digression. As this had not yet happened, however, Ts’o’s generator made use of a cryptographic hash in order to process environmental noise into an entropy pool, which could then be outputted to
userspace via the /dev/urandom/ or /dev/random/ special files. Additionally, the pool maintained a count of how much estimated entropy had been taken in, which leads to the historical semantic difference between the generators. /dev/random/ was a blocking device, meaning
that if it did not have enough entropy to output strongly entropic material, it would halt until it did. /dev/urandom/, on the other hand, would output regardless of the entropy pool’s stored entropy.
2006: A Kernel Odyssey
In 2006, Zvi Gutterman and Benny Pinkas authored a paper describing the Linux randomness subsystem, finding several flaws. Most notably, they found a particularly concerning issue with the generator as used by OpenWRT systems, which was that between shut down and restart,
no entropy was saved, meaning that the entropy pool had to reacquire a meaningful amount of entropy every startup. An additional flaw was noting that /dev/random could force a Denial of Service attack, should an attacker make a particularly large read from the file,
as the device would block until it could be satisfied. This, however, did not particularly motivate change within the system.
2012: Heninger et al. Go Mining
In 2012, Gutterman and Pinkas’ critiques were realized by a practical attack. A research team consisting of Nadia Heninger, Zakir Durumeric, Eric Wustrow, and J. Alex Halderman presented a paper entitled “Mining Your P’s and Q’s: Detection of Widespread Weak Keys in Network Devices”
at USENIX ‘12, in which they described a practical attack on low-entropy keys, attributed to use of /dev/urandom before the entropy pool had collected sufficient amounts of entropy, particularly in
embedded devices. This was sufficient to motivate change, and come Linux 3.17, the getrandom() syscall was introduced, which allows for block-until-init semantics, ensuring that appropriate amounts of entropy are used.
2016: Let’s Go Dancing
In 2016, with Linux 4.8, /dev/urandom/ was swapped to make use of Bernstein’s ChaCha20 cipher, which enabled long, secure pseudorandom streams of data once initialized, moving away from the previously employed whitening methods. However, /dev/random preserved its semantics
at this point. This would change with Linux 5.6 in 2020, which made /dev/random/ act more like /dev/urandom/, in the sense that entropy would be used to initialize a ChaCha20-based CSPRNG, which would then be used to generate
pseudorandom streams outputted through the file. However, do note that /dev/random/ preserved its pre-init blocking semantics, while /dev/urandom/ has also preserved its non-blocking semantics pre-init.
There was additionally an update in Linux 5.17 that swapped SHA-1 for BLAKE2s, although since this doesn’t really provide much for the sake of discussion, we shall leave it as a footnote.
So, What’s Your Point?
While Heninger et al.’s work inspired change within the kernel, the footgun that is the semantics of /dev/urandom/ persists. This means that if a read is made pre-init, data that is not quite yet at the full entropy requirements will be produced. It is especially of note
that on embedded devices that lack a hardware random number generators or in virtualized environments, sufficient entropy can take “tens of minutes” to generate. If we consult Heninger et al.’s
paper, we see that they advise use of /dev/random/ over /dev/urandom/ for use in cryptographic applications. This was rather controversial at the time, as this could allow for DoS attacks. However, if you must use a legacy device, with modern semantics being that /dev/random/
will not block after init, it is senseless to risk use of /dev/urandom/, where early-boot use may result in weak keys. Similarly, if one is unable to validate that sufficient entropy is being provided, which by definition /dev/urandom cannot do, as it will always output and offers
no indication of if said output is full-entropy or not, it does not enforce the requirements set forth by NIST SP-800-90A for the seeding of a DRBG. That is, each DRBG construction must have a minimum of
security_strength bits of entropy for seeding, and if that is not met, than the DRBG cannot be considered to be operating in a safe manner. As such, in design of these systems, entropy provenance is paramount - if one is unsure of their randomness, they are unsure of their whole
cryptographic stack. This is not to say that post-init /dev/urandom is insecure - a widely perpetuated bit of crypto folklore that is patently false. The argument here is rather that, since the API provides no mechanism to knowing if the entropy pool is initialized or not, it is
not possible for a consumer to know that what they receive from it is full entropy.
Conclusion
Entropy is a friction point that must always be dealt with. However, with cryptography, it is always best to fail closed rather than operate insecurely, as insecure operation can lead to a false sense of comfort. This means that runtime fallbacks to just “keep it alive” are often not
the correct mechanism to implement in a cryptographic library. Will users be annoyed? Absolutely. But, the point is that for every annoyed user that comes to complain about this, that is a potentially vulnerable system (or fleet of systems, in the case of embedded firmware) that is
not operating insecurely. As such, in modern libraries, we must prioritize making use of what the kernel developers have done for us. Use the getentropy() or getrandom() interfaces. If you must preserve legacy compatibility, use /dev/random. Don’t leave your crypto to chance.