c775e69845919cec3857e5ed079cf5daddedfa8c Add overflow guard to FetchDataLoaderAsString
The bug at a glance
Web-reachable renderer crash (DoS): reading a very large fetch() body as text appended into a StringBuilder without an overflow guard, so a 1+GB decoded body overflowed the builder length and crashed the renderer. Impact is a crash requiring ~1GB of data, with no corruption primitive shown; the fix rejects instead of crashing.
Any origin can serve or stream a large response and call Response.text()/Body text consumption to crash the victim renderer; a defender cares about the unauthenticated, easily reproducible DoS and the general class of unguarded StringBuilder growth.
Root cause
FetchDataLoaderAsString appended each decoded chunk (and the final Flush()) directly into builder_ with no length check. When the accumulated decoded string exceeds StringBuilder’s capacity (around/above 1GB), the length computation overflows and the renderer crashes. The patch adds an Append() helper that first calls builder_.DoesAppendCauseOverflow(length) and, on overflow, cancels the consumer and reports DidFetchDataLoadFailed() (rejecting the promise) instead of appending, mirroring the existing guard in FetchDataLoaderAsArrayBuffer.
Key code
Overflow pre-check before appending to the StringBuilder; failure rejects the load
[[nodiscard]] bool Append(const String& string) {
if (builder_.DoesAppendCauseOverflow(string.length())) {
return false;
}
builder_.Append(string);
return true;
}
Patch walkthrough
third_party/blink/renderer/core/fetch/fetch_data_loader.cc— Introduces a [[nodiscard]] Append() helper that checks StringBuilder::DoesAppendCauseOverflow before appending, and updates both the per-chunk Decode path and the final Flush path to, on overflow, end-read/cancel the consumer and call DidFetchDataLoadFailed() instead of crashing. This converts an over-large text body from a renderer crash into a clean promise rejection.third_party/blink/web_tests/wpt_internal/fetch/response-text-too-large-manual.html— Adds a manual test streaming >1GiB into Response.text() and asserting it rejects with TypeError rather than crashing, documenting the threshold and expected behavior.
Background
FetchDataLoaderAsString — Consumes a fetch body stream, decodes bytes to text, and accumulates them in a WTF::StringBuilder for Response.text()/Body.text().
StringBuilder::DoesAppendCauseOverflow — Checks whether adding N characters would exceed the builder’s representable length before performing the append.
BytesConsumer — Blink abstraction that supplies body bytes incrementally and supports Cancel/EndRead.
Vulnerability window
- Reachability — Script fetches a large/streamed response and calls .text() (or Response(stream).text()).
- Trigger — Decoded characters accumulate in builder_ until the total exceeds StringBuilder capacity (~1GB).
- Primitive — Unguarded length growth overflows/over-allocates and crashes the renderer (DoS); no controllable corruption shown (inferred).
- Escalation — Not established beyond denial of service (inferred).
Proof of concept
Conceptually, build a ReadableStream that enqueues more than 1GiB of bytes and call new Response(stream).text(); pre-patch this crashed the renderer, post-patch the promise rejects with a TypeError. See the added manual WPT for the structure.
Exploitation
- Deliver — Serve or stream a >1GB body to a page that reads it as text.
- Crash — Trigger the unguarded StringBuilder growth to crash the renderer process (DoS).
Detection & hunting
For defenders and detection engineers:
- grep/weggli for StringBuilder::Append on attacker-sized data without a preceding DoesAppendCauseOverflow/length check — Hunt accumulation loops that grow a StringBuilder from network/stream input without an overflow guard.
- renderer crash reports in FetchDataLoader / StringBuilder::Append — Correlate OOM/length-overflow crashes with large text() consumption.
Audit directions
- Other FetchDataLoader variants and body consumers in core/fetch — Confirm each accumulation path (string, blob, form-data) guards against size overflow like AsArrayBuffer does.
- StringBuilder accumulation of web-controlled data blink-wide — Audit large-input append loops for missing DoesAppendCauseOverflow checks.