← WebKit — 2026-W38

c2c736166c9a  ReadableStream byte-source tee: BYOB close steps target the wrong branch

severity medium class LogicError confidence 0.42 signal score 6.17 Source/WebCore/Modules
Primitive: ReadableStream BYOB tee applied byob/other cancel flags to the wrong branch
Triage note: TeeBYOBReadRequest computed byobCanceled/otherCanceled by m_forBranch2 but then close()/respondWithNewView()/respond() operated on fixed branch1/branch2; the fix introduces byobBranch/otherBranch so the cancel check matches the branch acted on, avoiding operating on a canceled/closed controller, but no memory-safety primitive is evident.
Structural signals: smartptr_member_added (+4.87), surgical (+1.3)

Commit message

ReadableStream byte-source tee: BYOB close steps target the wrong branch
https://bugs.webkit.org/show_bug.cgi?id=319189
rdar://182035805

Reviewed by Youenn Fablet.

In byteStreamTee(), TeeBYOBReadRequest::runCloseSteps() correctly derived
byobCanceled/otherCanceled from m_forBranch2, but then performed the actual
close(), respondWithNewView(), and respond() calls against hardcoded branch1
and branch2 instead of the byobBranch/otherBranch pair implied by m_forBranch2
(as runChunkStepsInMicrotask() already does, and as the spec requires).

When a byte stream is teed and branch2 is drained through a BYOB reader while
branch1 has been canceled, the read-into request runs with m_forBranch2 == true.
The close steps then operated on branch1 (byobBranch should be branch2): they
skipped closing branch2 and, on end-of-stream, called
ReadableByteStreamControllerRespondWithNewView() on the canceled branch1, which
has no pending pull-into. That trips the RELEASE_ASSERT inside
respondWithNewView() and crashes the WebContent process:

      ReadableByteStreamController::respondWithNewView(...)
        TeeBYOBReadRequest::runCloseSteps(JSC::JSValue)
        ReadableByteStreamController::pullInto(...)
        ReadableStreamBYOBReader::read(...)
        pullWithBYOBReader(..., forBranch2=true)
        pull2Steps(...)

Route all four operations through byobBranch/otherBranch locals, matching the
chunk-steps path and the WHATWG "readable byte stream tee" close steps.

The new test (canceling branch1 while branch2 reads with a BYOB reader until
end of stream) crashes reliably without this change and passes with it.

* LayoutTests/imported/w3c/web-platform-tests/streams/readable-byte-streams/tee.any-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/streams/readable-byte-streams/tee.any.js:
(promise_test.async let):
(promise_test):
* LayoutTests/imported/w3c/web-platform-tests/streams/readable-byte-streams/tee.any.serviceworker-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/streams/readable-byte-streams/tee.any.sharedworker-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/streams/readable-byte-streams/tee.any.worker-expected.txt:
* Source/WebCore/Modules/streams/StreamTeeUtilities.cpp:

Canonical link: https://commits.webkit.org/317296@main

Before / after

Loading diff…