镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

crypto.randomInt() can return the same value to a sync and an async caller #66595

Description

@gengjiawen

Version

v27.0.0-nightly20261007aa1bf2cc37 (main at 6d5e309), also v24.21.0

Platform

Linux x64

Subsystem

crypto

What steps will reproduce the bug?

const crypto = require('node:crypto');

const max = 2 ** 48 - 1;
const syncValues = [];

// The first randomInt() call finds the cache empty, so it is queued
// while the cache is refilled asynchronously.
crypto.randomInt(max, (err, n) => {
  console.log('async value:', n);
  console.log('sync values:', syncValues);
});

// Let the refill job finish before the synchronous calls below.
const start = Date.now();
while (Date.now() - start < 50);

for (let i = 0; i < 3; i++) syncValues.push(crypto.randomInt(max));

How often does it reproduce? Is there a required condition?

Every time with the snippet above. In general it needs an asynchronous randomInt() call that finds the cache empty (the first call in the process, or after the 1024 cached values are used up) followed by synchronous randomInt() calls before the refill callback runs.

What is the expected behavior? Why is that the expected behavior?

The async value is independent of the sync values. Every randomInt() call should get its own CSPRNG bytes.

What do you see instead?

async value: 59323658883457
sync values: [ 59323658883457, 78518031191563, 206053154540152 ]

The async callback receives the same value as the first sync call. Calls made after the callback also repeat the second and third sync values.

Additional information

randomInt() keeps a 6 KiB cache. An async call that finds it empty queues itself and runs randomFill() on that cache. A sync call made before the job's callback runs sees the cache as still empty, refills the same buffer with randomFillSync(), and reads from offset 0. The callback then sets the offset back to 0 and replays the queued call, which reads those bytes again. The threadpool job and randomFillSync() can also write the buffer at the same time.

The 50 ms wait only makes the threadpool job finish before the sync refill. Without it the result depends on timing. This has been there since the cache was added in #35110.

I have a fix and will open a PR.

Activity

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