Skip to content

crypto: chunk randomFillSync to exceed Web Crypto 64 KiB quota#6762

Open
younggglcy wants to merge 2 commits into
cloudflare:mainfrom
younggglcy:fix-6749-randombytes-quota
Open

crypto: chunk randomFillSync to exceed Web Crypto 64 KiB quota#6762
younggglcy wants to merge 2 commits into
cloudflare:mainfrom
younggglcy:fix-6749-randombytes-quota

Conversation

@younggglcy
Copy link
Copy Markdown

Fixes #6749.

Summary

crypto.randomBytes(n) (and randomFill/randomFillSync) under nodejs_compat throws QuotaExceededError: getRandomValues() only accepts buffers of size <= 64K but provided <N> bytes for any n > 65536. Node.js has no such limit.

Root cause

randomFillSync in src/node/internal/crypto_random.ts routes the entire requested span through a single call to crypto.getRandomValues(). The C++ implementation at src/workerd/api/crypto/crypto.c++ (Crypto::getRandomValues) correctly enforces the Web Crypto spec's 64 KiB per-call cap (buffer.size() <= 0x10000). Node's randomBytes/randomFill are a different API and do not share that limit.

Why Node has no 64 KiB cap (upstream evidence)

All references pinned to nodejs/node @ a159b57:

  1. The only size cap in the JS layer is kMaxPossibleLength = min(kMaxLength, 2**31 - 1) — about 2 GiB:
    lib/internal/crypto/random.js#L70
    randomBytes's only size check: #L100-L101
    randomFillSync (no further size cap; whole size passed to RandomBytesJob): #L120-L148

  2. The C++ binding forwards the entire buffer to ncrypto::CSPRNG:
    src/crypto/crypto_random.cc#L68-L74

  3. CSPRNG calls OpenSSL RAND_bytes_ex (OpenSSL ≥ 3) or chunks with RAND_bytes at INT_MAX (~2 GiB) for older OpenSSL — never at 65536:
    deps/ncrypto/ncrypto.cc#L559-L593

So in Node, the 64 KiB cap applies only to crypto.getRandomValues (which is a separate Web Crypto API surface), not to crypto.randomBytes/randomFill.

Fix

Chunk the fill in randomFillSync so each crypto.getRandomValues() call is ≤ 65 536 bytes. The cap and behavior of crypto.getRandomValues() itself are unchanged.

const view = buffer as Uint8Array<ArrayBuffer>;
for (let i = 0; i < view.length; i += kWebCryptoMaxRandomBytes) {
  crypto.getRandomValues(view.subarray(i, i + kWebCryptoMaxRandomBytes));
}
return view;

This was preferred over a new C++ cryptoImpl.randomBytes binding because it (a) requires no schema change, (b) keeps IoContext::getEntropySource() as the single entropy source (matters for workerd's predictable-test mode), and (c) is the same pattern used by Node's own browser polyfills (e.g. randombytes on npm).

Test plan

  • New test randomBytesAboveWebCryptoQuotaTest in src/workerd/api/node/tests/crypto_random-test.js covers:
    • randomBytes(65537) — the original repro from 🐛 crypto.randomBytes errors for values above 65536 #6749
    • randomBytes(200000) — multi-chunk path (3 chunks)
    • randomFillSync(buf, offset=100, size=65500) — chunk-spanning offset + bytes outside the range untouched
    • randomFill(buf, cb) — async path
  • just stream-test //src/workerd/api/node/tests:crypto_random-test@
  • CI on @all-compat-flags and @all-autogates variants

Node's crypto.randomBytes/randomFill have no per-call size limit other than
kMaxPossibleLength (~2 GiB), while crypto.getRandomValues mirrors Web Crypto
and is capped at 65536 bytes. The Node compatibility polyfill in
randomFillSync routed the whole buffer through crypto.getRandomValues in one
call, causing QuotaExceededError for any size > 64 KiB. Fill in 64 KiB
chunks so randomBytes/randomFill match Node behavior while leaving the
Web Crypto path untouched.

Fixes cloudflare#6749
@younggglcy younggglcy requested review from a team as code owners May 13, 2026 13:15
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 13, 2026

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@younggglcy
Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

github-actions Bot added a commit that referenced this pull request May 13, 2026
- Move kWebCryptoMaxRandomBytes constant above its sole consumer
  randomFillSync for readability (matches the RAND_MAX style at L165).
- Test additions:
  * randomBytes(65536) — exact 64 KiB boundary, off-by-one guard
  * randomFillSync(new ArrayBuffer(70000)) — exercises the
    isAnyArrayBuffer branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🐛 crypto.randomBytes errors for values above 65536

1 participant