You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
ThreadRng and StdRng after a snapshot or checkpoint restore: a sentence for the docs? #1843
We measure how userspace random generators behave when a process or a machine is duplicated (fork variants, checkpoint and restore, VM snapshot restore), and would like to ask about one case the documentation does not cover.
What the documentation says.ThreadRng's "Forks and interrupts" section (src/rngs/thread.rs, the same text in 0.9.5 and on master at 0.10.3): "ThreadRng is not automatically reseeded on fork. It is recommended to explicitly call [ThreadRng::reseed] immediately after a fork". The "Security" section lists "Limitation: there is no automatic reseeding on process fork". StdRng's documentation gives seeding guidance and says nothing about fork. Neither mentions a snapshot or checkpoint restore.
What we measured (rand 0.9.5, rustc 1.99.1, static musl binary, Linux 7.2.11; blocks of 32 bytes compared at equal sequence numbers between two continuations of one state):
Fork family, two children of one parent, 32 blocks each: rand::rng() and a StdRng seeded once are 32 of 32 identical after fork(), glibc's _Fork(), a raw clone() and a pid-1 namespace clone. This is exactly what the documentation says, and we report it as documented.
Restores, one guest or process saved and restored twice from the same image: both are 33 of 33 identical under QEMU with and without a vmgenid device and under Firecracker with VMGenID and VMClock attached, and 14 of 14 under CRIU. OsRng and the kernel's getrandom beside them are distinct from the first block wherever the guest kernel's vmgenid driver is present.
A restore duplicates the state without any fork, so the caller who follows the fork advice still never calls reseed there. The harness and every log are available on request.
Questions.
Would a sentence beside the fork note that extends it to snapshot and checkpoint restores (a VM restored from a snapshot, a process restored by CRIU, a resume on platforms such as Lambda SnapStart or CRaC) be welcome? We are happy to send a PR.
StdRng carries no such note. Is the same caller duty intended there, and should it say so?
Linux 7.0 exposes a VMClock page with a vm_generation_counter that a process can map and compare cheaply (include/uapi/linux/vmclock-abi.h; Firecracker attaches the device to every guest). Would an opt-in reseed on a generation change for ThreadRng be in scope as a feature, or is the crate's position that reseeding policy stays with the caller?
The repeated blocks are the generators' own output; we have not looked at anything downstream.
Objectively, this is correct: neither fork nor checkpoint-and-restore will (automatically) result in reseeding of the in-process CSPRNG.
Up to version 0.8, ThreadRng did attempt to automatically reseed on fork, however it did not do so perfectly (see #1362) due to a conflict between priorities: we did not wish to significantly compromise on performance in order to test for a fork on every call into the thread-local CSPRNG. (Looking at it from another point of view: the whole purpose of using a thread-local CSPRNG is to avoid the overhead of locks and system calls.) I haven't tested usage of vm_generation_counter, but suspect testing this on every output would have a significant performance impact.
I see two possible ways to go about resolving this:
Add documentation recommending against usage of ThreadRng / rand::rng() for anything security-sensitive when checkpoint-and-restore is possible: SysRng is a good alternative.
Thank you, that answers our questions. We will record ThreadRng as not reseeding after a fork or a restore by design, and StdRng as never reseeding itself.
On the cost of the check, in case it helps with #1828: a consistent read of the VMClock page is two loads of seq_count around one load of vm_generation_counter, and the test is a compare. The vDSO getrandom does the same kind of test on every call. It need not run per output: ThreadRng already decides on a reseed each time it refills its buffer (ReseedingCore::generate, the block position against RESEED_BLOCK_THRESHOLD), and a counter test there runs once per refill. What it leaves is the rest of the buffer generated before the restore. Two limits hold for any placement: the kernel creates /dev/vmclock0 with mode 0600, owner root, so an unprivileged process cannot map it unless the system grants that; and the counter only says that a restore happened, so the reseed is as fresh as SysRng is at that moment, which after a restore depends on the guest kernel's vmgenid driver.
For the first option we can send a PR. A possible wording, for ThreadRng's fork section:
ThreadRng is not reseeded after a snapshot or checkpoint restore either: a virtual machine restored from a snapshot, a process restored by CRIU, or a function resumed by a platform such as Lambda SnapStart continues from the saved state, and two instances restored from one image produce the same output until the next reseed. Where such restores are possible and the output is security-sensitive, use [SysRng] instead, or call [ThreadRng::reseed] after the restore.
and for StdRng:
StdRng never reseeds itself. A copy of its state, made by fork or by a snapshot or checkpoint restore, produces the same output as the original.
Would that fit, or would you rather word it yourselves?
I would say that "StdRng never reseeds itself" directly follows from it being CSPRNG, the P letter meaning "pseudo" being the key part here. It applies to any other PRNG and I am not sure it's worth to explicitly document it everywhere.
Fair enough. StdRng is a seeded PRNG and its documentation already says how to seed it, so that sentence adds nothing there. ThreadRng is the case where a reader may expect otherwise, since it seeds itself from SysRng and reseeds every 64 kB of output. How do you plan to handle that case?
It need not run per output: ThreadRng already decides on a reseed each time it refills its buffer
But is this good enough? No; arguably it is worse than useless since it advertises support for reseeding after restore yet can still yield duplicate keys (until the buffer is exhausted). We had complaints about this exact issue with the reseed-after-fork functionality in 0.8 (again, see #1362).
The wording on ThreadRng is mostly adequate, but there may be one issue: ThreadRng::reseed only reseeds the generator on the current thread. With fork this shouldn't be an issue since there should only be one thread, but this may not be the case with a snapshot.
Activity
dhardy commentedon Oct 21, 2026
Objectively, this is correct: neither fork nor checkpoint-and-restore will (automatically) result in reseeding of the in-process CSPRNG.
Up to version 0.8,
ThreadRngdid attempt to automatically reseed on fork, however it did not do so perfectly (see #1362) due to a conflict between priorities: we did not wish to significantly compromise on performance in order to test for a fork on every call into the thread-local CSPRNG. (Looking at it from another point of view: the whole purpose of using a thread-local CSPRNG is to avoid the overhead of locks and system calls.) I haven't tested usage ofvm_generation_counter, but suspect testing this on every output would have a significant performance impact.I see two possible ways to go about resolving this:
ThreadRng/rand::rng()for anything security-sensitive when checkpoint-and-restore is possible:SysRngis a good alternative.ThreadRnginto two generators: weak (fast) and strong (secure); see The future ofThreadRng#1828.StdRngdoes not reseed itself, ever. Maybe the docs could be clearer on this.KurathSec commentedon Oct 21, 2026
Thank you, that answers our questions. We will record
ThreadRngas not reseeding after a fork or a restore by design, andStdRngas never reseeding itself.On the cost of the check, in case it helps with #1828: a consistent read of the VMClock page is two loads of
seq_countaround one load ofvm_generation_counter, and the test is a compare. The vDSOgetrandomdoes the same kind of test on every call. It need not run per output:ThreadRngalready decides on a reseed each time it refills its buffer (ReseedingCore::generate, the block position againstRESEED_BLOCK_THRESHOLD), and a counter test there runs once per refill. What it leaves is the rest of the buffer generated before the restore. Two limits hold for any placement: the kernel creates/dev/vmclock0with mode 0600, owner root, so an unprivileged process cannot map it unless the system grants that; and the counter only says that a restore happened, so the reseed is as fresh asSysRngis at that moment, which after a restore depends on the guest kernel's vmgenid driver.For the first option we can send a PR. A possible wording, for
ThreadRng's fork section:and for
StdRng:Would that fit, or would you rather word it yourselves?
newpavlov commentedon Oct 21, 2026
I would say that "
StdRngnever reseeds itself" directly follows from it being CSPRNG, the P letter meaning "pseudo" being the key part here. It applies to any other PRNG and I am not sure it's worth to explicitly document it everywhere.KurathSec commentedon Oct 21, 2026
Fair enough.
StdRngis a seeded PRNG and its documentation already says how to seed it, so that sentence adds nothing there.ThreadRngis the case where a reader may expect otherwise, since it seeds itself fromSysRngand reseeds every 64 kB of output. How do you plan to handle that case?newpavlov commentedon Oct 21, 2026
I think it's fine to extend our fork warnings towards VMs. I will leave the exact wording to @dhardy.
dhardy commentedon Oct 22, 2026
But is this good enough? No; arguably it is worse than useless since it advertises support for reseeding after restore yet can still yield duplicate keys (until the buffer is exhausted). We had complaints about this exact issue with the reseed-after-fork functionality in 0.8 (again, see #1362).
The wording on
ThreadRngis mostly adequate, but there may be one issue:ThreadRng::reseedonly reseeds the generator on the current thread. Withforkthis shouldn't be an issue since there should only be one thread, but this may not be the case with a snapshot.dhardy commentedon Nov 2, 2026
Documentation is updated: #1844.