Skip to content

ThreadRng and StdRng after a snapshot or checkpoint restore: a sentence for the docs? #1843

Closed

Description

@KurathSec

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.

  1. 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.
  2. StdRng carries no such note. Is the same caller duty intended there, and should it say so?
  3. 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.

Activity

  1. dhardy commented on Oct 21, 2026

    @dhardy
    Member

    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:

    1. Add documentation recommending against usage of ThreadRng / rand::rng() for anything security-sensitive when checkpoint-and-restore is possible: SysRng is a good alternative.
    2. Split ThreadRng into two generators: weak (fast) and strong (secure); see The future of ThreadRng #1828.

    2. StdRng carries no such note. Is the same caller duty intended there, and should it say so?

    StdRng does not reseed itself, ever. Maybe the docs could be clearer on this.

  2. KurathSec commented on Oct 21, 2026

    @KurathSec
    Author

    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?

  3. newpavlov commented on Oct 21, 2026

    @newpavlov
    Member

    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.

  4. KurathSec commented on Oct 21, 2026

    @KurathSec
    Author

    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?

  5. newpavlov commented on Oct 21, 2026

    @newpavlov
    Member

    I think it's fine to extend our fork warnings towards VMs. I will leave the exact wording to @dhardy.

  6. dhardy commented on Oct 22, 2026

    @dhardy
    Member

    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.

  7. dhardy commented on Nov 2, 2026

    @dhardy
    Member

    Documentation is updated: #1844.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions