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.
Version
v27.0.0-nightly20261007aa1bf2cc37 (main at 6d5e309), also v24.21.0
Platform
Linux x64
Subsystem
crypto
What steps will reproduce the bug?
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 synchronousrandomInt()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?
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 runsrandomFill()on that cache. A sync call made before the job's callback runs sees the cache as still empty, refills the same buffer withrandomFillSync(), 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 andrandomFillSync()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.