Repository: denoland/deno
Pull request: fix(node/crypto): randomBytes returns buffer with dedicated ArrayBuffer

- [x] Understand the issue: `crypto.randomBytes()` returns buffer backed by shared pool, causing `.buffer` to return the same underlying ArrayBuffer
- [x] Investigate root cause: `Buffer.allocUnsafe()` uses a shared memory pool for small buffers (< 16KB)
- [x] Identify the fix: Use `Buffer.allocUnsafeSlow()` instead of `Buffer.allocUnsafe()` to create a dedicated buffer
- [x] Implement the fix in `/ext/node/polyfills/internal/crypto/_randomBytes.ts`
- [x] Verify the fix works manually
- [x] Add spec test in `tests/specs/node/crypto_random_bytes_buffer/`
- [x] Add unit test in `tests/unit_node/internal/_randomBytes_test.ts`
- [x] Run code review and address feedback
- [x] Run CodeQL security check
- [x] Fix issue URL reference to correct issue #32047

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>

> 
> ----
> 
> *This section details on the original issue you should resolve*
> 
> <issue_title>crypto.randomBytes different behaviour as nodejs</issue_title>
> <issue_description>when running in the deno repl:
> 
> ```
> > const crypto = await import("node:crypto")
> > new BigUint64Array(crypto.randomBytes(8).buffer)[0]
> 2573n
> > new BigUint64Array(crypto.randomBytes(8).buffer)[0]
> 2573n
> > new BigUint64Array(crypto.randomBytes(8).buffer)[0]
> 2573n
> ```
> 
> when running in the nodejs repl:
> 
> ```
> > const crypto = require("crypto")
> > new BigUint64Array(crypto.randomBytes(8).buffer)[0]
> 6766794548803353966n
> > new BigUint64Array(crypto.randomBytes(8).buffer)[0]
> 16305374909087544027n
> > new BigUint64Array(crypto.randomBytes(8).buffer)[0]
> 13790729495892961224n
> ```
> 
> I would expect that the function call returns a different number just like nodejs. 
> 
> Or is there another (better) way to generate bigint in Deno?
> 
> 
> ```
> > deno --version
> deno 2.6.8 (stable, release, x86_64-unknown-linux-gnu)
> v8 14.5.201.2-rusty
> typescript 5.9.2
> ```
> 
> 
> 
> Version: Deno 2.6.8
> </issue_description>
> 
> ## Comments on the Issue (you are @copilot in this section)
> 
> <comments>
> </comments>
> 


</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes denoland/deno#32047

<!-- START COPILOT CODING AGENT TIPS -->
---

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs.

Changed files:
filename: ext/node/polyfills/internal/crypto/_randomBytes.ts
patch: @@ -24,7 +24,7 @@ function generateRandomBytes(size: number) {
     );
   }
 
-  const bytes = Buffer.allocUnsafe(size);
+  const bytes = Buffer.allocUnsafeSlow(size);
 
   //Work around for getRandomValues max generation
   if (size > MAX_RANDOM_VALUES) {

filename: tests/specs/node/crypto_random_bytes_buffer/__test__.jsonc
patch: @@ -0,0 +1,8 @@
+{
+  "tests": {
+    "buffer_has_unique_arraybuffer": {
+      "args": "run --no-lock main.ts",
+      "output": "main.out"
+    }
+  }
+}

filename: tests/specs/node/crypto_random_bytes_buffer/main.out
patch: @@ -0,0 +1 @@
+ok

filename: tests/specs/node/crypto_random_bytes_buffer/main.ts
patch: @@ -0,0 +1,29 @@
+// Regression test for https://github.com/denoland/deno/issues/32047
+// crypto.randomBytes should return a buffer with its own ArrayBuffer,
+// not a shared pool buffer.
+
+import { randomBytes } from "node:crypto";
+
+// Test that the underlying ArrayBuffer has the expected size
+const buf8 = randomBytes(8);
+if (buf8.buffer.byteLength !== 8) {
+  throw new Error(
+    `Expected buffer.byteLength to be 8, got ${buf8.buffer.byteLength}`,
+  );
+}
+
+// Test that multiple calls return buffers with different underlying data
+// This was broken when using shared pool allocation - all values were identical
+const val1 = new BigUint64Array(randomBytes(8).buffer)[0];
+const val2 = new BigUint64Array(randomBytes(8).buffer)[0];
+const val3 = new BigUint64Array(randomBytes(8).buffer)[0];
+
+// While extremely unlikely to be identical by chance, this specifically
+// tests the fix for the bug where all values were the same due to shared buffer pool
+if (val1 === val2 && val2 === val3) {
+  throw new Error(
+    `All random values were identical: ${val1} (shared buffer pool bug)`,
+  );
+}
+
+console.log("ok");

filename: tests/unit_node/internal/_randomBytes_test.ts
patch: @@ -86,6 +86,31 @@ Deno.test("[std/node/crypto] randomBytes callback isn't called twice if error is
   });
 });
 
+// https://github.com/denoland/deno/issues/32047
+// randomBytes should return a buffer with its own ArrayBuffer, not a shared pool
+Deno.test("randomBytes buffer has correct byteLength and unique values", function () {
+  // Test that the underlying ArrayBuffer has the expected size
+  const buf8 = randomBytes(8);
+  assertEquals(
+    buf8.buffer.byteLength,
+    8,
+    "buffer.byteLength should match requested size",
+  );
+
+  // Test that multiple calls return buffers with different underlying data
+  // This was broken when using shared pool allocation
+  const val1 = new BigUint64Array(randomBytes(8).buffer)[0];
+  const val2 = new BigUint64Array(randomBytes(8).buffer)[0];
+  const val3 = new BigUint64Array(randomBytes(8).buffer)[0];
+
+  // While extremely unlikely to be identical by chance, this tests the fix
+  // for the bug where all values were the same due to shared pool
+  assert(
+    !(val1 === val2 && val2 === val3),
+    "random values should not all be identical (was caused by shared buffer pool)",
+  );
+});
+
 // https://github.com/denoland/deno/issues/21632
 Deno.test("pseudoRandomBytes works", function () {
   assertEquals(pseudoRandomBytes(0).length, 0, "len: " + 0);