mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 18:25:13 +08:00
When an asynchronous randomInt() call finds the cache empty, it queues itself and starts randomFill() on the shared cache buffer. A synchronous call made before that job completes refills the same buffer with randomFillSync() and returns values from offset 0. The completion callback then reset the offset to 0, so the queued call and later calls returned the bytes that synchronous calls had already used. The threadpool job and randomFillSync() could also write the buffer at the same time. Fill a separate buffer asynchronously and copy it into the cache once the job is done. Fixes: https://github.com/nodejs/node/issues/66595 Refs: https://github.com/nodejs/node/pull/35110 Signed-off-by: Jiawen Geng <technicalcute@gmail.com> Assisted-by: claude:opus-5.5 PR-URL: https://github.com/nodejs/node/pull/66597 Reviewed-By: Filip Skokan <panva.ip@gmail.com>
44 lines
1.5 KiB
JavaScript
44 lines
1.5 KiB
JavaScript
// Flags: --expose-internals
|
|
'use strict';
|
|
const common = require('../common');
|
|
if (!common.hasCrypto)
|
|
common.skip('missing crypto');
|
|
|
|
// Synchronous randomInt() calls made while an asynchronous cache refill is
|
|
// pending must not cause the same random bytes to be returned twice.
|
|
|
|
const assert = require('assert');
|
|
const { randomInt } = require('crypto');
|
|
const { internalBinding } = require('internal/test/binding');
|
|
const { RandomBytesJob } = internalBinding('crypto');
|
|
|
|
// With this range, every 6-byte draw except 0xffffffffffff is returned
|
|
// unchanged, so a repeated value means repeated cache bytes.
|
|
const max = 2 ** 48 - 1;
|
|
const values = [];
|
|
|
|
// The refill job calls ondone on the main thread after the threadpool has
|
|
// written its buffer. Make the synchronous calls at that point, before the
|
|
// refill callback in randomInt() runs.
|
|
const { run } = RandomBytesJob.prototype;
|
|
RandomBytesJob.prototype.run = common.mustCall(function() {
|
|
RandomBytesJob.prototype.run = run;
|
|
const { ondone } = this;
|
|
this.ondone = common.mustCall((...args) => {
|
|
for (let i = 0; i < 3; i++)
|
|
values.push(randomInt(max));
|
|
ondone(...args);
|
|
});
|
|
run.call(this);
|
|
});
|
|
|
|
// This is the first randomInt() call in the process, so the cache is empty.
|
|
// The call is queued and an asynchronous refill starts.
|
|
randomInt(max, common.mustSucceed((n) => {
|
|
values.push(n);
|
|
for (let i = 0; i < 3; i++)
|
|
values.push(randomInt(max));
|
|
assert.strictEqual(new Set(values).size, values.length,
|
|
`duplicate values: ${values}`);
|
|
}));
|