Closed Bug 2057143 Opened 3 months ago Closed 3 months ago

CompressionStream/DecompressionStream: MOZ_CRASH via reentrancy in ProcessTypedArraysFixed (missed variant of Bug 2044843)

Categories

(Core :: DOM: Streams, defect, P3)

defect

Tracking

()

RESOLVED FIXED
155 Branch
Tracking Status
firefox-esr140 --- unaffected
firefox-esr153 --- fixed
firefox153 --- wontfix
firefox154 --- wontfix
firefox155 --- fixed

People

(Reporter: med.mbarek.isi, Assigned: sfink)

References

Details

(Keywords: crash, csectype-dos, reporter-external)

Crash Data

Attachments

(2 files, 2 external files)

Type: Denial of service (content process crash)
Attack vector: Web content (no user interaction beyond page visit)
Platform: All
ASAN confirmation: Yes (consistent crash, 4/4 on fresh Nightly 155.0a1)

Root Cause

CompressionStreamAlgorithms::TransformCallbackImpl (dom/compression/BaseAlgorithms.cpp:40-44) calls CompressAndEnqueue() inside a ProcessTypedArraysFixed lambda. CompressAndEnqueue calls aController.Enqueue() (BaseAlgorithms.cpp:89), which is MOZ_CAN_RUN_SCRIPT. The lambda is annotated MOZ_CAN_RUN_SCRIPT_BOUNDARY, silencing the static analyzer but not preventing reentrancy.

The same pattern exists in DecompressionStreamAlgorithms::TransformCallbackImpl at BaseAlgorithms.cpp:124-128.

Reentrancy mechanism

When Enqueue() resolves a pending read request, the resolution value {value: Uint8Array, done: false} is an Object. ResolvePromiseInternal (Promise.cpp:1522) performs GetProperty("then") on the resolution value, walking up the prototype chain to Object.prototype. An attacker-defined getter on Object.prototype.then fires synchronously inside the ProcessTypedArraysFixed lambda while the buffer's length is pinned.

This is the same bug class fixed in Bug 2044843 for VideoFrame.CopyTo and ImageDecoder.Initialize. The fix there moved MaybeResolve outside the ProcessTypedArraysFixed lambda. The compression/decompression stream code was not updated.

Crash path

  1. cs1.writable.getWriter().write(data) -> TransformCallbackImpl -> ProcessTypedArraysFixed(data) -> pins buffer -> lambda -> CompressAndEnqueue -> Enqueue()
  2. Enqueue -> ReadableStreamFulfillReadRequest -> ChunkSteps -> MaybeResolve({value: Uint8Array, done: false}) -> ResolvePromiseInternal -> GetProperty("then") -> getter fires
  3. Getter calls cs2.writable.getWriter().write(data) -> TransformCallbackImpl -> ProcessTypedArraysFixed(data) on the SAME buffer
  4. Inner ProcessFixedData -> EnsureNonInlineArrayBufferOrView -> buffer is inline (<=96 bytes) AND already pinned -> returns false
  5. MOZ_CRASH("failed to make ArrayBuffer data be stored separately") at TypedArray.h:700 -- unconditional in ALL builds including release

Impact

Any web page can crash the content process by writing a small (<=96 byte) buffer to a CompressionStream while an Object.prototype.then getter triggers a nested write. No user interaction is required beyond visiting the page. The MOZ_CRASH fires in release builds, not just debug/ASAN builds.

Suggested Fix

Move CompressAndEnqueue/DecompressAndEnqueue calls outside the ProcessTypedArraysFixed lambda, matching the fix applied to VideoFrame.CopyTo in Bug 2044843.

The most targeted fix: compress inside the lambda (Compress() doesn't run script) and move only the Enqueue loop outside:

JS::RootedVector<JSObject*> array(cx);
ProcessTypedArraysFixed(
    bufferSource,
    [&](const Span<uint8_t>& aData) {
      Compress(cx, aData, &array, Flush::No, aRv);
    });
if (!aRv.Failed()) {
  for (const auto& view : array) {
    JS::Rooted<JS::Value> value(cx, JS::ObjectValue(*view));
    aController.Enqueue(cx, value, aRv);
    if (aRv.Failed()) break;
  }
}

ASAN Output (fresh Nightly 155.0a1, July 22 2026)

Key frames (full trace and PoC attached):

#0  MOZ_CrashSequence -- Assertions.h:261
#1  ArrayBufferObject::ensureNonInline -- ArrayBufferObject.cpp:3119
#4  ProcessFixedData -- TypedArray.h:664  <- INNER (nested) call
#10 CompressionStreamAlgorithms::TransformCallbackImpl -- BaseAlgorithms.cpp:40
#34 CallGetter -- NativeObject.cpp:2178  <- Object.prototype.then getter
#41 ResolvePromiseInternal -- Promise.cpp:1522  <- thenable check
#48 Read_ReadRequest::ChunkSteps
#50 ReadableStreamDefaultControllerEnqueue -- ReadableStreamDefaultController.cpp:266
#51 TransformStreamDefaultController::Enqueue
#52 CompressAndEnqueue -- BaseAlgorithms.cpp:89  <- Enqueue INSIDE lambda
#53 lambda -- BaseAlgorithms.cpp:42
#55 ProcessFixedData -- TypedArray.h:673  <- OUTER call
#61 CompressionStreamAlgorithms::TransformCallbackImpl -- BaseAlgorithms.cpp:40

Relationship to Bug 2044843

Bug 2044843 fixed this pattern in VideoFrame.CopyTo and ImageDecoder.Initialize. The dom/compression/BaseAlgorithms.cpp call sites were not updated. The July 20 2026 follow-up fix (changeset 863391 by Steve Fink) modified ensureNonInline to allow re-pinning of out-of-line buffers, but inline buffers (<=96 bytes) still hit MOZ_CRASH.

Tested on: Mozilla Firefox 155.0a1 (ASAN Nightly, July 22 2026, Taskcluster build)

Group: core-security → dom-core-security
Status: UNCONFIRMED → NEW
Ever confirmed: true

The rating assigned by our bug rating tool (currently under development) is:

Rating: N/A

Explanation: Not a security vulnerability. The reentrancy is real (CompressionStream/DecompressionStream call Enqueue() inside the ProcessTypedArraysFixed lambda, and an Object.prototype.then getter can re-enter), but the reporter's central claim of a release-build MOZ_CRASH is inaccurate. The outer ProcessFixedData calls EnsureNonInlineArrayBufferOrView first, which MOVES a small inline (<=96B) buffer OUT-OF-LINE (ArrayBufferObject.cpp:3138+) before pinning. So when the nested write runs, the buffer is already out-of-line, and ensureNonInline takes the out-of-line branch (lines 3108-3124) which returns true in Release/late-Beta (no crash, memory-safe: the pin keeps out-of-line data stable; LengthPinner's mWasPinned prevents premature unpin). Only in Nightly/DevEdition/debug does it hit the MOZ_DIAGNOSTIC_ASSERT at line 3119 - a deliberate, safe diagnostic crash to flag unexpected reentrancy. The reporter's own ASAN trace shows the crash at ensureNonInline:3119 (the diagnostic assert), not TypedArray.h:669's MOZ_CRASH, confirming this. No memory corruption and no Release DoS. This is at most a code-quality cleanup (move Enqueue out of the lambda per Bug 2044843), not an exploitable/security issue.

Steve, do you agree that this isn't a sec issue? I'll unhide it if so. Thanks.

Flags: needinfo?(sphink)
See Also: → 2044843
Group: dom-core-security
Keywords: crash, csectype-dos
Flags: needinfo?(sphink)
Crash Signature: [@ js::ArrayBufferObject::ensureNonInline ]
Severity: -- → S3
Priority: -- → P3

(In reply to Andrew McCreight [:mccr8] from comment #4)

Steve, do you agree that this isn't a sec issue? I'll unhide it if so. Thanks.

Sorry for the delay. Yes, I agree. In fact, this is sort of a false positive of the ProcessFixed mechanism. What's going on is basically

ProcessTypedArraysFixed(source, CompressAndEnqueue);

CompressAndEnqueue(inputData) {
  obj = compress(inputData);
  // data is no longer used.
  enqueue(obj); // can run script
}

So even if the script moves the inputData and re-enters, the old location would never be used by anything.

Assignee: nobody → sphink
Status: NEW → ASSIGNED
Pushed by sfink@mozilla.com: https://github.com/mozilla-firefox/firefox/commit/9223e4e4fd2d https://hg.mozilla.org/integration/autoland/rev/bd759fa70abe Avoid ProcessTypedArraysFixed re-entrancy by splitting Enqueue out of {Compress,Decompress}AndEnqueue and calling it afterwards. r=saschanaz
Status: ASSIGNED → RESOLVED
Closed: 3 months ago
Resolution: --- → FIXED
Target Milestone: --- → 155 Branch

The patch landed in nightly and beta is affected, along with ESR.
:sfink, is this bug important enough to require an uplift?

For more information, please visit BugBot documentation.

Flags: needinfo?(sphink)
Flags: needinfo?(sphink)
QA Whiteboard: [qa-triage-done-c156/b155]

Did you want to nominate this for ESR153 uplift? Go ahead and do so if yes.

Flags: needinfo?(sphink)

I'll do it because this blocks another uplift I want to do.

Attachment #9656928 - Flags: approval-mozilla-esr153?

firefox-esr153 Uplift Approval Request

  • User impact if declined/Reason for urgency: potential content process crash on some webpage
  • Code covered by automated testing?: yes
  • Fix verified in Nightly?: yes
  • Needs manual QE testing?: no
  • Steps to reproduce for manual QE testing: N/A
  • Risk associated with taking this patch: low
  • Explanation of risk level: Tested in multiple stable releases
  • String changes made/needed?: N/A
  • Is Android affected?: yes
Attachment #9656928 - Flags: approval-mozilla-esr153? → approval-mozilla-esr153+
Flags: needinfo?(sphink)
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: