Skip to content

BYOB: Protect against shenanigans in size callback#6613

Merged
erikcorry merged 2 commits into
mainfrom
erikcorry/size-callback
Apr 20, 2026
Merged

BYOB: Protect against shenanigans in size callback#6613
erikcorry merged 2 commits into
mainfrom
erikcorry/size-callback

Conversation

@erikcorry

Copy link
Copy Markdown
Contributor

Also added a defensive change for the standard streams

@erikcorry erikcorry requested review from a team as code owners April 20, 2026 10:16
@ask-bonk

ask-bonk Bot commented Apr 20, 2026

Copy link
Copy Markdown
Contributor

I see that ReadableImpl::enqueue at line 1127 also has a JSG_REQUIRE(canCloseOrEnqueue(), ...). So without the new check in ReadableStreamDefaultController::enqueue, the code would reach impl.enqueue and then JSG_REQUIRE would throw a TypeError. This wouldn't be a crash, but the additional check at the outer level is cleaner and avoids the unexpected throw.

Now let me think about whether there are any issues with the PR:

  1. auto self = JSG_THIS in ReadableStreamDefaultController::enqueue (line 2286): Correct. Keeps this alive through the size callback. The kj::mv(self) on line 2304 passes it to impl.enqueue which needs it.

  2. auto readableControllerRef = kj::addRef(readableController) (line 4349): This uses kj::addRef which returns kj::Own<T>. This is a jsg::Object — using kj::Own directly rather than jsg::Ref is fine for a stack-local keep-alive guard. Both increment the ref count on Wrappable. However, jsg::Ref<T> is the conventional way to hold references to jsg::Object subclasses. Let me check if kj::Own<T> is actually equivalent here.

Looking at _jsgThis at line 1395-1397: Ref<T>(kj::addRef(*obj)) -- so jsg::Ref<T> wraps a kj::Own<T>. Using kj::addRef directly is equivalent from a lifetime perspective. The difference is that jsg::Ref<T> could participate in GC tracing via visitForGc, but since this is a local variable, that's not relevant. This is fine.

  1. !errored && impl.canCloseOrEnqueue() check (line 2303): Correct. The size callback can error the controller through a side-channel (calling transformController.error()) without throwing. In that case, errored is still false (the tryCatch didn't catch anything), but the impl state has transitioned to Errored. The added check prevents enqueueing into an errored controller.

  2. auto self = JSG_THIS in ReadableByteStreamController::enqueue (line 2564): This is a "defensive" change as mentioned in the PR title. It addresses C++ argument evaluation order (unspecified) where JSG_THIS as a function argument to impl.enqueue might not be evaluated before chunk.detach(js). By capturing it upfront, the order is guaranteed. This is a legitimate fix for a potential issue.

  3. Early return at line 4360-4362: Correct. After the size callback errors the controller, hasBackpressure() and the backpressure update logic shouldn't run.

  4. WPT expected failure removals: Two expected failures are removed matching the two cases now fixed. The remaining expected failures are for different scenarios (close, cancel, read inside size).

  5. Test coverage: Two regression tests added, one where size callback calls error() and one where it also throws. Both good.

The changes look solid. This is a well-targeted fix for a use-after-free vulnerability in the streams implementation, with good tests and proper WPT conformance improvements. I don't see any actionable issues.

LGTM

github run

@codspeed-hq

codspeed-hq Bot commented Apr 20, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 28.25%

❌ 1 regressed benchmark
✅ 71 untouched benchmarks
⏩ 129 skipped benchmarks1

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
simpleStringBody[Response] 19.3 µs 26.9 µs -28.25%

Comparing erikcorry/size-callback (d9f6586) with main (4e71c55)

Open in CodSpeed

Footnotes

  1. 129 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@erikcorry erikcorry merged commit f2f9b49 into main Apr 20, 2026
25 checks passed
@erikcorry erikcorry deleted the erikcorry/size-callback branch April 20, 2026 11:56
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.

2 participants