Skip to content
FreeBSDDeep Dive Published Updated 7 min readViews unavailable

Understanding FreeBSD's Random Number Subsystem (random(4)) and Entropy Harvesting

How FreeBSD seeds Fortuna, harvests entropy, serves random bytes, and exposes early-boot failures without making unsafe assumptions.

A cryptographic random-number generator does not need output that merely looks irregular. It needs output that remains computationally unpredictable to an adversary who knows the design, observes the machine, and may even have learned an earlier generator state. On FreeBSD, the random(4) subsystem is the kernel service behind /dev/random, /dev/urandom, getrandom(2), and the entropy used by higher-level interfaces such as arc4random(3). Understanding it means separating three different jobs: collecting uncertain events, deciding when enough uncertainty exists to seed safely, and expanding that seed into a stream of cryptographic output.

/dev/random and /dev/urandom have the same semantics

FreeBSD deliberately makes /dev/random and /dev/urandom identical. Both are backed by the same cryptographically secure pseudorandom number generator; GRND_RANDOM therefore does nothing on FreeBSD. The historical idea that one device eventually “runs out of entropy” while the other becomes weak does not describe a modern CSPRNG. Once securely seeded, the generator can produce far more output than the small amount of true entropy used to initialize it without becoming predictable.

The important boundary is not the device name but the initial seeded state. getrandom(2) blocks by default until the kernel random device is seeded. With GRND_NONBLOCK, it returns EAGAIN instead. Requests of at most 256 bytes are guaranteed to return the requested amount once seeding has completed. Applications should normally use arc4random(3), getentropy(3), or getrandom(2) rather than inventing a pool, repeatedly opening a device, or treating short reads incorrectly.

There is one current FreeBSD detail that deserves unusual attention. The random(4) manual documents kern.random.initial_seeding.bypass_before_seeding, which defaults to enabled for availability on systems that cannot provide early entropy. Before initial seeding, that compatibility behavior can let some in-kernel consumers bypass the strong generator; the manual explicitly calls the setting unsafe. It also exposes read-only counters indicating whether read_random(9) or arc4random(9) bypass occurred. Consequently, “FreeBSD always blocks before seeding” is too broad. Userland getrandom(2) has clear blocking semantics, while a forensic review must inspect the kernel’s early-boot diagnostics as well.

sysctl kern.random.initial_seeding
sysctl kern.random.initial_seeding.read_random_bypassed_before_seeding
sysctl kern.random.initial_seeding.arc4random_bypassed_before_seeding

A non-zero bypass counter is evidence to investigate, especially on an appliance, diskless node, or cloned virtual-machine image. It is not repaired by switching applications from /dev/urandom to /dev/random, because those paths share the same generator.

Fortuna separates harvesting from generation

FreeBSD has used Fortuna as its default random algorithm since FreeBSD 11; the older Yarrow implementation was removed in FreeBSD 12. Fortuna hashes harvested events into multiple accumulator pools and periodically reseeds a generator from selected pools. Pool zero participates most often, while higher-numbered pools contribute progressively less often. That schedule prevents a bad or attacker-influenced high-rate source from deciding every reseed and gives slower pools time to accumulate independent uncertainty.

This design also provides recovery after state compromise. If an attacker learns the generator’s state at one instant, output at that instant is compromised. Fresh, unobserved entropy continues entering the accumulators, however, and a later reseed that incorporates enough of it makes subsequent output unpredictable again. This is a more useful security property than pretending that compromise cannot happen.

Entropy estimation is inherently conservative. Timing variation is not equivalent to one full random bit per event, and a hardware instruction returning 256 bits does not automatically mean it contributed 256 trustworthy entropy bits. The kernel’s harvesting framework accepts samples from registered sources, assigns them source classes, and feeds the configured classes into Fortuna. The generator, not the raw event, is what applications consume.

Harvest sources are selectable and observable

Depending on platform and kernel configuration, FreeBSD may harvest from interrupts, device activity, virtualized hardware, and dedicated random sources. Administrators can inspect the enabled mask in symbolic form instead of decoding an integer:

sysctl kern.random.harvest.mask_symbolic
sysctl kern.random.harvest.mask_bin
sysctl kern.random.random_sources

The exact names reported are hardware- and release-dependent; documentation and live sysctl output are more reliable than copying a list from another host. kern.random.harvest.mask controls which possible harvest classes are accepted. Changing it blindly can remove useful diversity, so a normal audit records the current value and the reason for any local override rather than “hardening” every system to one fashionable mask.

FreeBSD 15.1 also documents the loader tunable kern.random.nist_healthtest_enabled. When enabled, entropy sources undergo the repetition-count and adaptive-proportion tests described by NIST SP 800-90B; a failing source is disabled and subsequent samples from it are discarded. These are online health checks for catastrophic source failures, not a proof that an input is genuinely unpredictable and not a substitute for multiple independent sources.

Hardware RNGs deserve the same caution. A CPU or platform source can greatly improve a headless system’s startup, but treating one opaque source as infallible creates a single point of cryptographic failure. FreeBSD’s harvesting model lets hardware input contribute to the accumulator instead of requiring applications to consume that instruction directly. The correct audit question is therefore “which sources are active and did seeding succeed?”, not “does this CPU advertise one RNG instruction?”

Persistent entropy solves only part of early boot

FreeBSD’s save-entropy(8) periodically saves bytes from /dev/random for use at the next startup. By default it runs from cron(8) approximately every eleven minutes, writes rotating files under /var/db/entropy, saves 4096 bytes per file, and retains eight files. The entropy_dir, entropy_save_sz, and entropy_save_num settings in rc.conf(5) control that behavior; setting entropy_dir="NO" disables it. The utility does nothing inside a jail because the host kernel owns the random subsystem.

sysrc entropy_dir
ls -ld /var/db/entropy
ls -l /var/db/entropy
service cron status

Those files are sensitive seed material and should remain root-controlled. Their existence is not proof that they are unique. Golden images, VM templates, and read-only appliance images must not ship reusable entropy files as though they belonged to each clone. Provisioning should remove template-generated state and arrange for every instance to obtain unique early entropy from its hypervisor, hardware, or a securely provisioned seed. Copying the same saved file to a thousand machines defeats the exact purpose of persistence.

Persistent state also does not excuse poor boot design. A diskless or embedded system may have no writable media, and a first boot has no prior local state. The random(4) manual assigns the system architect responsibility for deciding whether blocking is acceptable or for supplying a credible early source. Developers can set kern.random.block_seeded_status=1 to test whether boot unexpectedly depends on randomness before seeding.

A practical forensic checklist

Start by identifying the running kernel and reading its matching manual page; random-subsystem defaults can change between releases. Then preserve evidence rather than immediately changing tunables:

freebsd-version -kru
sysctl -a | grep '^kern.random\.'
dmesg | grep -i random
sysctl kern.random.harvest.mask_symbolic

Confirm that the bypass counters are zero, that the expected hardware source appears, that entropy files are root-owned and rotate, and that a cloned guest does not inherit a template’s cache. Exercise the application-facing API with getrandom(2) semantics rather than using statistical tests on a few kilobytes: randomness test suites cannot prove cryptographic unpredictability, while a failed initialization path or repeated VM seed is a concrete defect.

Do not lower kern.random.fortuna.minpoolsize merely to make a warning disappear. The manual notes that smaller values seed faster but less securely and gives 64–256 bytes as a practical range. Similarly, disabling bypass can expose a boot dependency that previously went unnoticed; that may be the desired security posture, but it should first be tested on the exact hardware so the machine does not deadlock before it can obtain entropy.

FreeBSD’s design is strongest when all layers are respected: diverse harvesters supply uncertain events, Fortuna turns accumulated uncertainty into a recoverable CSPRNG state, saved entropy accelerates later boots, and explicit diagnostics reveal when early consumers did not receive strong output. None of those layers alone is a magic source of randomness, and operational verification is what keeps a sound design from being undermined by cloning, boot order, or an unexamined default.

Related:

Sources:

Comments