Skip to content

[v24.x backport] buffer: fix Blob.stream() leaking source buffer - #64664

Closed
gabrielbryk wants to merge 241 commits into
nodejs:v24.x-stagingfrom
gabrielbryk:backport-63577-to-v24.x
Closed

[v24.x backport] buffer: fix Blob.stream() leaking source buffer#64664
gabrielbryk wants to merge 241 commits into
nodejs:v24.x-stagingfrom
gabrielbryk:backport-63577-to-v24.x

Conversation

@gabrielbryk

Copy link
Copy Markdown

Backport of #63577 to v24.x. It doesn't cherry-pick cleanly on its own: the
JS fix clears the reader's wakeup handle with reader.setWakeup(undefined),
but on v24.x the C++ Blob::Reader::SetWakeup still asserts a function
argument. The guard that makes it accept undefined only landed as part of
the QUIC commit cf91d18 (#62876), which isn't on this line, so a plain
cherry-pick builds but aborts the first time a stream reaches a terminal/idle
path:

Assertion failed: args[0]->IsFunction()
node::Blob::Reader::SetWakeup at src/node_blob.cc

The second commit extracts only that four-line node_blob.cc guard from
#62876; the rest of that commit (the NotifyPull(fin) / pull_pending_
coalescing) is QUIC-specific and not needed here. It could instead be done as
a prerequisite backport of #62876, or folded into the cherry-pick, if a
reviewer prefers.

parallel/test-blob*, including the new test-blob-stream-gc.js from #63577,
passes on a local build; the #63574 reproducer holds arrayBuffers flat where
24.16.0 leaks the source buffer on every stream.

panva and others added 30 commits June 25, 2026 15:30
Replace the O(n) case-insensitive algorithm-name scan
with an O(1) SafeMap lookup. The map is pre-built at
module init alongside kSupportedAlgorithms.

Hoist the opts object literal used in normalizeAlgorithm
to module level to avoid allocating identical
{ prefix, context } objects on every call.

Pre-compute ObjectKeys() for simpleAlgorithmDictionaries
entries at module init to avoid allocating a new keys
array on every normalizeAlgorithm call.

Signed-off-by: Filip Skokan <[email protected]>
PR-URL: nodejs#62756
Reviewed-By: Yagiz Nizipli <[email protected]>
Replace object spread in nested WebIDL conversion
options with stable-shape ordinary objects. This keeps
hot dictionary and sequence conversion paths from
allocating null-prototype spread results.

Apply the same pattern to Web Crypto converter wrappers
that override allowResizable or enable [EnforceRange].

Signed-off-by: Filip Skokan <[email protected]>
PR-URL: nodejs#62756
Reviewed-By: Yagiz Nizipli <[email protected]>
Signed-off-by: Matteo Collina <[email protected]>
PR-URL: nodejs#62562
Reviewed-By: James M Snell <[email protected]>
Signed-off-by: Nad Alaba <[email protected]>
PR-URL: nodejs#63117
Reviewed-By: Antoine du Hamel <[email protected]>
Reviewed-By: Luigi Pinca <[email protected]>
Signed-off-by: avivkeller <[email protected]>
PR-URL: nodejs#63493
Reviewed-By: Antoine du Hamel <[email protected]>
Reviewed-By: René <[email protected]>
Rename WebCrypto operation parameters and local variables to match the
spec terminology.

Use operation, algorithm, and key names consistently between the docs
and implementation.

Signed-off-by: Filip Skokan <[email protected]>
PR-URL: nodejs#63518
Reviewed-By: René <[email protected]>
Reviewed-By: Antoine du Hamel <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Luigi Pinca <[email protected]>
Drive the regression test from the WebCrypto algorithm registry so all
supported algorithms and operations must add explicit coverage
regardless of whether they are native-job backed or js-based.

Signed-off-by: Filip Skokan <[email protected]>
PR-URL: nodejs#63520
Reviewed-By: René <[email protected]>
Reviewed-By: Antoine du Hamel <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Refs: nodejs#62761
Signed-off-by: StefanStojanovic <[email protected]>
PR-URL: nodejs#63413
Reviewed-By: Vinícius Lourenço Claro Cardoso <[email protected]>
Signed-off-by: Mohamed Sayed <[email protected]>
PR-URL: nodejs#63529
Fixes: nodejs#63514
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Antoine du Hamel <[email protected]>
Signed-off-by: Filip Skokan <[email protected]>
PR-URL: nodejs#63556
Reviewed-By: Jordan Harband <[email protected]>
Reviewed-By: Chemi Atlow <[email protected]>
Signed-off-by: Filip Skokan <[email protected]>
PR-URL: nodejs#63556
Reviewed-By: Jordan Harband <[email protected]>
Reviewed-By: Chemi Atlow <[email protected]>
Signed-off-by: Filip Skokan <[email protected]>
PR-URL: nodejs#63556
Reviewed-By: Jordan Harband <[email protected]>
Reviewed-By: Chemi Atlow <[email protected]>
Signed-off-by: Filip Skokan <[email protected]>
PR-URL: nodejs#63556
Reviewed-By: Jordan Harband <[email protected]>
Reviewed-By: Chemi Atlow <[email protected]>
Signed-off-by: Mert Can Altin <[email protected]>
PR-URL: nodejs#63385
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: Matteo Collina <[email protected]>
Reviewed-By: Gürgün Dayıoğlu <[email protected]>
Fix build warnings by casting the number literals to the size_t
constants they are compared against.

Signed-off-by: Maya Lekova <[email protected]>
PR-URL: nodejs#63490
Reviewed-By: Joyee Cheung <[email protected]>
Reviewed-By: Filip Skokan <[email protected]>
Reviewed-By: René <[email protected]>
PR-URL: nodejs#63668
Reviewed-By: Antoine du Hamel <[email protected]>
Reviewed-By: Colin Ihrig <[email protected]>
Reviewed-By: Luigi Pinca <[email protected]>
PR-URL: nodejs#63669
Reviewed-By: Antoine du Hamel <[email protected]>
Reviewed-By: Colin Ihrig <[email protected]>
Reviewed-By: Luigi Pinca <[email protected]>
Signed-off-by: Mohamed Sayed <[email protected]>
PR-URL: nodejs#63601
Fixes: nodejs#63513
Reviewed-By: LiviaMedeiros <[email protected]>
Reviewed-By: Matthew Aitken <[email protected]>
Reviewed-By: Yagiz Nizipli <[email protected]>
Reviewed-By: Antoine du Hamel <[email protected]>
Signed-off-by: RafaelGSS <[email protected]>
PR-URL: nodejs#63293
Reviewed-By: Antoine du Hamel <[email protected]>
This function call can fail with `Z_VERSION_ERROR` if the compiled
library vs loaded library mismatched in version number or in
stream structure size.
In those cases, zlib doesn't initialize the `strm_.msg` field to
null. Therefore, when a `CompressionError` object is created via
`ErrorForMessage()`, it can read a stale or uninitialized `strm_.msg`
pointer that will cause a crash.

Example ASAN report:
```
AddressSanitizer: SEGV on unknown address
    #0 __strlen_avx2
        string/../sysdeps/x86_64/multiarch/strlen-avx2.S:76
    nodejs#1 strlen (/work/node/out/Debug/node+0x1a42ab7)
    nodejs#2 v8::(anonymous namespace)::StringLength(char const*)
        /work/node/out/../deps/v8/src/api/api.cc:7581:16
    nodejs#3 v8::(anonymous namespace)::StringLength(unsigned char const*)
        /work/node/out/../deps/v8/src/api/api.cc:7587:10
    nodejs#4 v8::String::NewFromOneByte(v8::Isolate*,
        unsigned char const*, v8::NewStringType, int)
        /work/node/out/../deps/v8/src/api/api.cc:7677:3
    nodejs#5 node::OneByteString(v8::Isolate*,
        char const*, int, v8::NewStringType)
        /work/node/out/../src/util-inl.h:166:10
    nodejs#6 node::(anonymous namespace)::CompressionStream<
        node::(anonymous namespace)::ZlibContext>
        ::EmitError(node::(anonymous namespace)
        ::CompressionError const&)
        /work/node/out/../src/node_zlib.cc:565:7
    nodejs#7 node::(anonymous namespace)::CompressionStream<
        node::(anonymous namespace)::ZlibContext>
        ::CheckError()
        /work/node/out/../src/node_zlib.cc:519:5
    nodejs#8 node::(anonymous namespace)::CompressionStream<
        node::(anonymous namespace)::ZlibContext>
        ::AfterThreadPoolWork(int)
        /work/node/out/../src/node_zlib.cc:543:10
    nodejs#9 node::ThreadPoolWork::ScheduleWork()
        ::'lambda'(uv_work_s*, int)
        ::operator()(uv_work_s*, int) const
        /work/node/out/../src/threadpoolwork-inl.h:57:15
    nodejs#10 node::ThreadPoolWork::ScheduleWork()
        ::'lambda'(uv_work_s*, int)
        ::__invoke(uv_work_s*, int)
        /work/node/out/../src/threadpoolwork-inl.h:48:7
    nodejs#11 uv__work_done /work/libuv-1.51.0/src/threadpool.c:330:5
    nodejs#12 uv__async_io.part.0
        /work/libuv-1.51.0/src/unix/async.c:208:5
```

Signed-off-by: ndossche <[email protected]>
PR-URL: nodejs#63476
Reviewed-By: Anna Henningsen <[email protected]>
Signed-off-by: arbinger <[email protected]>
PR-URL: nodejs#63050
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Ethan Arrowood <[email protected]>
PR-URL: nodejs#63670
Reviewed-By: Marco Ippolito <[email protected]>
Reviewed-By: Chemi Atlow <[email protected]>
Reviewed-By: Darshan Sen <[email protected]>
Reviewed-By: Colin Ihrig <[email protected]>
Signed-off-by: Tobias Nießen <[email protected]>
PR-URL: nodejs#63644
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: Filip Skokan <[email protected]>
Update the setRawMode documentation to specify that raw mode disables
special processing of input characters only. Output processing, such as
newline translation on Unix terminals, is not affected.

Fixes: nodejs#63059
Signed-off-by: zeeshan56656 <[email protected]>
PR-URL: nodejs#63438
Reviewed-By: Anna Henningsen <[email protected]>
A recursively watched directory can be removed after a parent watcher
observes it but before the non-native recursive watcher scans it.

Ignore ENOENT from the directory scan so this deletion race does not
emit an unhandled watcher error.

Signed-off-by: Kamat, Trivikram <[email protected]>
Assisted-by: openai:gpt-5.5
PR-URL: nodejs#63686
Refs: https://github.com/nodejs/reliability/blob/main/reports/2026-06-01.md#jstest-failure
Reviewed-By: Moshe Atlow <[email protected]>
Reviewed-By: Luigi Pinca <[email protected]>
Use common.nodeProcessAborted() for the intentional abort path so
platform-specific abort signals such as SIGILL are accepted.

Signed-off-by: Kamat, Trivikram <[email protected]>
Assisted-by: openai:gpt-5.5
PR-URL: nodejs#63687
Refs: https://github.com/nodejs/reliability/blob/main/reports/2026-06-01.md#jstest-failure
Reviewed-By: Richard Lau <[email protected]>
Reviewed-By: Luigi Pinca <[email protected]>
Signed-off-by: Herrtian <[email protected]>
PR-URL: nodejs#63542
Fixes: nodejs#62774
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: Tobias Nießen <[email protected]>
Signed-off-by: Daijiro Wachi <[email protected]>
PR-URL: nodejs#63700
Reviewed-By: Stephen Belanger <[email protected]>
Reviewed-By: Tierney Cyren <[email protected]>
Reviewed-By: Luigi Pinca <[email protected]>
Reviewed-By: Rafael Gonzaga <[email protected]>
Signed-off-by: Antoine du Hamel <[email protected]>
PR-URL: nodejs#63707
Reviewed-By: Marco Ippolito <[email protected]>
Reviewed-By: Tobias Nießen <[email protected]>
Reviewed-By: Luigi Pinca <[email protected]>
mcollina and others added 20 commits July 22, 2026 15:20
Consolidate the spec's per-chunk predicate chains (CanCloseOrEnqueue,
IsLocked, HasDefaultReader, GetNumReadRequests, GetDesiredSize and the
writable-side equivalents) into single passes over the controller and
stream state, mirror "close queued or in flight" as a boolean flag
maintained at the few close-request transition sites, and materialize
the TransformStream [[backpressureChangePromise]] record lazily on
first observation so backpressure flips nobody is waiting on allocate
nothing.

Assisted-by: Claude Code
Signed-off-by: Matteo Collina <[email protected]>
PR-URL: nodejs#64252
Reviewed-By: Antoine du Hamel <[email protected]>
Reviewed-By: Yagiz Nizipli <[email protected]>
Reviewed-By: Filip Skokan <[email protected]>
Signed-off-by: Guy Bedford <[email protected]>
PR-URL: nodejs#63951
Reviewed-By: Ethan Arrowood <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Matteo Collina <[email protected]>
Signed-off-by: Filip Skokan <[email protected]>
PR-URL: nodejs#63104
Backport-PR-URL: nodejs#64629
Reviewed-By: Antoine du Hamel <[email protected]>
Signed-off-by: Filip Skokan <[email protected]>
PR-URL: nodejs#63134
Backport-PR-URL: nodejs#64629
Reviewed-By: Antoine du Hamel <[email protected]>
Signed-off-by: Filip Skokan <[email protected]>
PR-URL: nodejs#63556
Backport-PR-URL: nodejs#64629
Reviewed-By: Antoine du Hamel <[email protected]>
Move KeyObject.prototype.toCryptoKey() onto the base KeyObject
class and dispatch from the cached native key type. Both secret and
asymmetric conversions now pass a KeyObjectHandle through the Web
Crypto import paths.

Expose KeyObjectHandle.prototype.getKeyType() so asymmetric importers
can validate public/private usages without wrapping the handle back
into a KeyObject. Secret importers likewise consume KeyObjectHandle
directly.

Use the shared asymmetric conversion helper to derive public
CryptoKeys for SubtleCrypto.getPublicKey(), avoiding the temporary
PrivateKeyObject/createPublicKey round trip while keeping usage
validation in the import path.

Update getPublicKey and KeyObject.toCryptoKey tests to be driven from
the Web Crypto supported-algorithm registry so new algorithms require
either coverage or an explicit skip.

Signed-off-by: Filip Skokan <[email protected]>
PR-URL: nodejs#63622
Backport-PR-URL: nodejs#64629
Reviewed-By: Antoine du Hamel <[email protected]>
Introduce prepareSubtleMethod() and convertSubtleArgument() for the
common SubtleCrypto call prelude. Methods now reuse the same receiver
check, required-argument check, error prefix construction, argument
context selection, and WebIDL conversion path.

Introduce WebCrypto key usage helpers for common usage validation and
key pair usage splitting. Algorithm modules now define their allowed
public, private, and key generation usages once, then call the shared
helpers from generateKey() and importKey() paths.

This removes repeated function-invocation setup in webcrypto.js and
repeated key usage checks across the WebCrypto algorithm modules while
preserving the existing validation behavior.

Mark normalized-algorithm fallback branches as unreachable assertions.

Signed-off-by: Filip Skokan <[email protected]>
PR-URL: nodejs#63975
Backport-PR-URL: nodejs#64629
Reviewed-By: Antoine du Hamel <[email protected]>
Add shared bit-length helpers for WebCrypto operations that accept bit
sequences whose length is not byte-aligned.

Use the helpers for cSHAKE output, ECDH-derived bits, HMAC/KMAC key
generation/import/derivation, and KMAC sign/verify output. Preserve the
requested bit length in CryptoKey algorithm metadata while storing and
exporting rounded-up byte material with unused low bits cleared.

Keep byte-multiple validation for algorithms whose specs require it.

Extend the lower-end of KMAC's key length support.

Enable cSHAKE customization and functionName parameters.

Signed-off-by: Filip Skokan <[email protected]>
PR-URL: nodejs#63988
Backport-PR-URL: nodejs#64629
Reviewed-By: Antoine du Hamel <[email protected]>
Return false for Ed25519 and Ed448 one-shot verification when the public
key or signature R component is a known low-order point. This keeps key
import behavior unchanged while making WebCrypto verification match WPT
expectations across OpenSSL variants.

Remove the stale WPT expected-failure entry and add focused regression
coverage for both curves.

Closes: nodejs#54572

Signed-off-by: Filip Skokan <[email protected]>
PR-URL: nodejs#64026
Backport-PR-URL: nodejs#64629
Fixes: nodejs#54572
Reviewed-By: Antoine du Hamel <[email protected]>
Signed-off-by: Filip Skokan <[email protected]>
PR-URL: nodejs#64033
Backport-PR-URL: nodejs#64629
Reviewed-By: Antoine du Hamel <[email protected]>
Original commit message:

    optimize ascii fast path in WriteUtf8V2

    Change-Id: If28168cb4395b953d0ec642ef4fc618ce963dbcd
    Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7124103
    Reviewed-by: Toon Verwaest <[email protected]>
    Commit-Queue: Erik Corry <[email protected]>
    Reviewed-by: Erik Corry <[email protected]>
    Cr-Commit-Position: refs/heads/main@{#103542}

Refs: v8/v8@64b36b4
PR-URL: nodejs#61712
Backport-PR-URL: nodejs#64630
Reviewed-By: Michaël Zasso <[email protected]>
Add a samplePerIteration option to monitorEventLoopDelay that records
event loop delay from libuv event loop iterations instead of the timer
interval sampler. The default remains interval-based; existing uses of
monitorEventLoopDelay() keep behaving the same unless the
samplePerIteration option is passed through.

Signed-off-by: Pablo Erhard <[email protected]>
PR-URL: nodejs#62935
Backport-PR-URL: nodejs#64480
Reviewed-By: Antoine du Hamel <[email protected]>
Signed-off-by: Chengzhong Wu <[email protected]>
PR-URL: nodejs#63626
Backport-PR-URL: nodejs#64632
Reviewed-By: Antoine du Hamel <[email protected]>
With the introduction of always static target `node_base`,
`node_aix_shared` is now redundant.

Signed-off-by: Chengzhong Wu <[email protected]>
PR-URL: nodejs#63747
Backport-PR-URL: nodejs#64632
Refs: nodejs#63626
Reviewed-By: Antoine du Hamel <[email protected]>
Original commit message:

    [api] Add V8::GetWasmMemoryReservationSizeInBytes()

    When the system does not have enough virtual memory for the wasm
    cage, installing the trap handler would cause any code allocating
    wasm memory to throw. Therefore it's useful for the embedder to
    know when the system doesn't have enough virtual address space
    to allocate enough wasm cages and in that case, skip the
    trap handler installation so that wasm code can at least work
    (even not at the maximal performance).

    Node.js previously has a command line option
    --disable-wasm-trap-handler to fully disable trap-based bound checks,
    this new API would allow it to adapt automatically while keeping the
    optimization in the happy path, since it's not always possible for
    end users to opt-into disabling trap-based bound checks (for example,
    when a VS Code Server is loaded in a remote server for debugging).

    Refs: nodejs#62132
    Refs: microsoft/vscode#251777
    Change-Id: I345c076af2b2b47700e5716b49c3133fdf8a0981
    Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7638233
    Reviewed-by: Jakob Kummerow <[email protected]>
    Reviewed-by: Michael Lippautz <[email protected]>
    Commit-Queue: Joyee Cheung <[email protected]>
    Reviewed-by: Clemens Backes <[email protected]>
    Cr-Commit-Position: refs/heads/main@{#105702}

Refs: v8/v8@bef0d9c
Co-authored-by: Joyee Cheung <[email protected]>
PR-URL: nodejs#62132
Backport-PR-URL: nodejs#64338
Refs: microsoft/vscode#251777
Refs: https://chromium-review.googlesource.com/c/v8/v8/+/7638233
Signed-off-by: Joyee Cheung <[email protected]>
Reviewed-By: Richard Lau <[email protected]>
PR-URL: nodejs#60798
Backport-PR-URL: nodejs#63659
Signed-off-by: Marco Ippolito <[email protected]>
Reviewed-By: Antoine du Hamel <[email protected]>
PR-URL: nodejs#60798
Backport-PR-URL: nodejs#63659
Signed-off-by: Marco Ippolito <[email protected]>
Reviewed-By: Antoine du Hamel <[email protected]>
PR-URL: nodejs#61610
Backport-PR-URL: nodejs#63659
Signed-off-by: Marco Ippolito <[email protected]>
Reviewed-By: Antoine du Hamel <[email protected]>
@aduh95
aduh95 requested a review from a team as a July 22, 2026 13:20
semimikoh and others added 2 commits July 22, 2026 13:25
Blob.prototype.stream() registered a wakeup callback on the
underlying source's start() and never released it. The strong
Reader::wakeup_ handle kept the reader -- and through it the
blob's DataQueue and backing store -- reachable as a GC root, so
the source buffer leaked on every stream() call. On Node 26+,
streaming a 1 MiB blob 300 times retained ~300 MiB in
process.memoryUsage().arrayBuffers while the V8 heap stayed small.

Register the wakeup lazily in pull() and clear it on every terminal
or idle path (EOS, error, cancel, backpressure), mirroring the
cleanup already done by the async iterator path. The strong handle
now only lives while a pull is in flight, so the reader and its
backing store become collectable once the stream finishes, errors,
is cancelled, or goes idle under backpressure.

Fixes: nodejs#63574

Signed-off-by: semimikoh <[email protected]>
PR-URL: nodejs#63577
Fixes: nodejs#63574
Reviewed-By: James M Snell <[email protected]>
(cherry picked from commit 243905a)
Backport prerequisite for the previous commit (backport of nodejs#63577).

The Blob.stream() leak fix clears the reader's wakeup handle by calling
reader.setWakeup(undefined) on every terminal/idle path. On main the C++
Blob::Reader::SetWakeup already tolerates an undefined argument
(resetting the strong wakeup_ handle), but that guard is not present on
v24.x -- it landed incidentally in the large QUIC implementation commit
cf91d18 (nodejs#62876), which is not itself being backported.

Extract only the minimal node_blob.cc SetWakeup hunk from that commit so
the JS fix works on v24.x. Without it, setWakeup(undefined) aborts with
`Assertion failed: args[0]->IsFunction()` at src/node_blob.cc. The
NotifyPull(fin)/pull_pending_ coalescing from the same upstream commit
is QUIC-specific, not required by the leak fix, and is intentionally
omitted.

Refs: nodejs#63577
Refs: nodejs#62876
Signed-off-by: Gabe Bryk <[email protected]>
@aduh95

aduh95 commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

It's already on the staging branch

@aduh95 aduh95 closed this Aug 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. v24.x Issues that can be reproduced on v24.x or PRs targeting the v24.x-staging branch.

Projects

None yet

Development

Successfully merging this pull request may close these issues.