Closed Bug 2020906 (CVE-2026-4698) Opened 6 months ago Closed 6 months ago

ZDI-CAN-29301: Mozilla Firefox IonMonkey Switch Statement Optimization Type Confusion Remote Code Execution Vulnerability

Categories

(Core :: JavaScript Engine: JIT, defect, P1)

defect

Tracking

()

RESOLVED FIXED
150 Branch
Tracking Status
firefox-esr115 149+ fixed
firefox-esr140 149+ fixed
firefox148 --- wontfix
firefox149 + fixed
firefox150 + fixed

People

(Reporter: freddy, Assigned: jandem)

References

(Blocks 1 open bug, Regression)

Details

(4 keywords, Whiteboard: [credit: maxpl0it working with Trend Micro Zero Day Initiative][disclosure July 1st 2026][adv-main149+][adv-ESR140.9+][adv-ESR115.34+])

Attachments

(7 files)

Analysis

A type confusion vulnerability exists in the IonMonkey JIT compiler's MIR optimization pipeline. It is a variant of CVE-2024-3854 (https://bugzilla.mozilla.org/show_bug.cgi?id=1884552). Similar to the patched vulnerability in IsDiamondPattern, the IsTrianglePattern function uses "numSuccessors() == 1", as it expects the graph block to end with the MGoto MIR node. However this is not always the case. An empty switch statement can be used to satisfy this expression, which results in a type confusion from MTableSwitch to MGoto. The IsTrianglePattern function checks whether a JIT graph block will only go to a single location using the expression numSuccessors() == 1. The IsDiamondPattern function checks lastIns()->isGoto() instead. An empty switch statement (switch(0){}), satisfies this expression, even though it is an MTableSwitch JIT node and not an MGoto JIT node, and returns "true".
This results in MaybeFoldTriangleConditionBlock being called, which calls either UpdateGotoSuccessor or UpdateTestSuccessors on the block containing the MTableSwitch. Both functions execute MOZ_ASSERT(ins->isGoto());. This assertion is triggered in a debug build, but is stripped out in release.
Both of these Update functions will call ins->toGoto()->target()->removePredecessor(block) which calls into MBasicBlock::getPredecessorIndex and then MBasicBlock::getPredecessor. This getPredecessor function will index out of bounds in predecessors_[i].

Note: add "--fast-warmup --no-threads" flags if PoC does not trigger a crash.

JS ASAN (Linux):

zdi@ubuntu:~/Desktop$ '/home/zdi/Desktop/js-m-c-20260223210545-asan-opt/dist/bin/js' '/home/zdi/Desktop/poc.js' 
=================================================================
==5707==ERROR: AddressSanitizer: use-after-poison on address 0x745600844ce8 at pc 0x6361d72d03fe bp 0x71857ddfd790 sp 0x71857ddfd788
READ of size 8 at 0x745600844ce8 thread T2
    #0 0x6361d72d03fd in getPredecessor /builds/worker/checkouts/gecko/js/src/jit/MIRGraph.h:410:58
    #1 0x6361d72d03fd in js::jit::MBasicBlock::getPredecessorIndex(js::jit::MBasicBlock*) const /builds/worker/checkouts/gecko/js/src/jit/MIRGraph.cpp:1264:9
    #2 0x6361d72cb8f8 in js::jit::MBasicBlock::removePredecessor(js::jit::MBasicBlock*) /builds/worker/checkouts/gecko/js/src/jit/MIRGraph.cpp:1329:22
    #3 0x6361d70f2dd9 in UpdateTestSuccessors(js::jit::TempAllocator&, js::jit::MBasicBlock*, js::jit::MDefinition*, js::jit::MBasicBlock*, js::jit::MBasicBlock*, js::jit::MBasicBlock*) /builds/worker/checkouts/gecko/js/src/jit/IonAnalysis.cpp:734:28
    #4 0x6361d70cc89c in MaybeFoldTriangleConditionBlock /builds/worker/checkouts/gecko/js/src/jit/IonAnalysis.cpp
    #5 0x6361d70cc89c in MaybeFoldConditionBlock /builds/worker/checkouts/gecko/js/src/jit/IonAnalysis.cpp:1116:12
    #6 0x6361d70cc89c in js::jit::FoldTests(js::jit::MIRGraph&) /builds/worker/checkouts/gecko/js/src/jit/IonAnalysis.cpp:1313:10
    #7 0x6361d70c7fec in js::jit::OptimizeMIR(js::jit::MIRGenerator*) /builds/worker/checkouts/gecko/js/src/jit/Ion.cpp:1014:10
    #8 0x6361d70e1375 in js::jit::CompileBackEnd(js::jit::MIRGenerator*, js::jit::WarpSnapshot*) /builds/worker/checkouts/gecko/js/src/jit/Ion.cpp:1695:8
    #9 0x6361d7126091 in runTask /builds/worker/checkouts/gecko/js/src/jit/IonCompileTask.cpp:52:24
    #10 0x6361d7126091 in js::jit::IonCompileTask::runHelperThreadTask(js::AutoLockHelperThreadState&) /builds/worker/checkouts/gecko/js/src/jit/IonCompileTask.cpp:30:5
    #11 0x6361d7794ab2 in js::GlobalHelperThreadState::runTaskLocked(JS::HelperThreadTask*, js::AutoLockHelperThreadState&) /builds/worker/checkouts/gecko/js/src/vm/HelperThreads.cpp:667:11
    #12 0x6361d77a2cca in runOneTask /builds/worker/checkouts/gecko/js/src/vm/HelperThreads.cpp:623:3
    #13 0x6361d77a2cca in js::HelperThread::threadLoop(js::InternalThreadPool*) /builds/worker/checkouts/gecko/js/src/vm/InternalThreadPool.cpp:324:25
    #14 0x6361d77a2986 in js::HelperThread::ThreadMain(js::InternalThreadPool*, js::HelperThread*) /builds/worker/checkouts/gecko/js/src/vm/InternalThreadPool.cpp:251:11
    #15 0x6361d77ac2fb in callMain<0UL, 1UL> /builds/worker/checkouts/gecko/js/src/threading/Thread.h:226:5
    #16 0x6361d77ac2fb in js::detail::ThreadTrampoline<void (&)(js::InternalThreadPool*, js::HelperThread*), js::InternalThreadPool*&, js::HelperThread*>::Start(void*) /builds/worker/checkouts/gecko/js/src/threading/Thread.h:215:11
    #17 0x6361d5b7a086 in asan_thread_start(void*) /builds/worker/fetches/llvm-project/compiler-rt/lib/asan/asan_interceptors.cpp:239:28
    #18 0x75860149caa3 in start_thread nptl/pthread_create.c:447:8
    #19 0x758601529c6b in clone3 misc/../sysdeps/unix/sysv/linux/x86_64/clone3.S:78

0x745600844ce8 is located 2280 bytes inside of 32768-byte region [0x745600844400,0x74560084c400)
allocated by thread T0 here:
/home/zdi/Desktop/js-m-c-20260223210545-asan-opt/dist/bin/llvm-symbolizer: error: '[anon:js-executable-memory]': No such file or directory
    #0 0x6361d5b7de94 in malloc /builds/worker/fetches/llvm-project/compiler-rt/lib/asan/asan_malloc_linux.cpp:67:3
    #1 0x6361d6275589 in js_arena_malloc /builds/worker/workspace/obj-build/dist/include/js/Utility.h:386:10
    #2 0x6361d6275589 in js::detail::BumpChunk::newWithCapacity(unsigned long, unsigned long) /builds/worker/checkouts/gecko/js/src/ds/LifoAlloc.cpp:26:15
    #3 0x6361d6276520 in newChunkWithCapacity /builds/worker/checkouts/gecko/js/src/ds/LifoAlloc.cpp:189:7
    #4 0x6361d6276520 in js::LifoAlloc::getOrCreateChunk(unsigned long) /builds/worker/checkouts/gecko/js/src/ds/LifoAlloc.cpp:221:30
    #5 0x6361d6276acf in js::LifoAlloc::allocImplColdPath(unsigned long) /builds/worker/checkouts/gecko/js/src/ds/LifoAlloc.cpp:231:30
    #6 0x6361d70e2e98 in allocImpl /builds/worker/checkouts/gecko/js/src/ds/LifoAlloc.h:802:12
    #7 0x6361d70e2e98 in alloc /builds/worker/checkouts/gecko/js/src/ds/LifoAlloc.h:859:12
    #8 0x6361d70e2e98 in new_<js::jit::TempAllocator, js::LifoAlloc *> /builds/worker/checkouts/gecko/js/src/ds/LifoAlloc.h:1076:3
    #9 0x6361d70e2e98 in IonCompile /builds/worker/checkouts/gecko/js/src/jit/Ion.cpp:1783:32
    #10 0x6361d70e2e98 in js::jit::Compile(JSContext*, JS::Handle<JSScript*>, js::jit::BaselineFrame*, unsigned char*) /builds/worker/checkouts/gecko/js/src/jit/Ion.cpp:2020:24
    #11 0x6361d70e43e1 in BaselineCanEnterAtEntry /builds/worker/checkouts/gecko/js/src/jit/Ion.cpp:2137:25
    #12 0x6361d70e43e1 in IonCompileScriptForBaseline(JSContext*, js::jit::BaselineFrame*, unsigned char*) /builds/worker/checkouts/gecko/js/src/jit/Ion.cpp:2246:12
    #13 0x71857de1307a  ([anon:js-executable-memory]+0x407a)
    #14 0x71857de2f6aa  ([anon:js-executable-memory]+0x6aa)
    #15 0x71857de2f147  ([anon:js-executable-memory]+0x147)
    #16 0x71857de2fbdf  ([anon:js-executable-memory]+0xbdf)
    #17 0x71857de0f3dc  ([anon:js-executable-memory]+0x3dc)
    #18 0x6361d6d431a4 in EnterBaseline /builds/worker/checkouts/gecko/js/src/jit/BaselineJIT.cpp:147:5
    #19 0x6361d6d431a4 in js::jit::EnterBaselineInterpreterAtBranch(JSContext*, js::InterpreterFrame*, unsigned char*) /builds/worker/checkouts/gecko/js/src/jit/BaselineJIT.cpp:202:26
    #20 0x6361d73a306a in js::Interpret(JSContext*, js::RunState&) /builds/worker/checkouts/gecko/js/src/vm/Interpreter.cpp:2032:17
    #21 0x6361d737e1d6 in MaybeEnterInterpreterTrampoline /builds/worker/checkouts/gecko/js/src/vm/Interpreter.cpp:384:10
    #22 0x6361d737e1d6 in js::RunScript(JSContext*, js::RunState&) /builds/worker/checkouts/gecko/js/src/vm/Interpreter.cpp:460:13
    #23 0x6361d738339c in js::ExecuteKernel(JSContext*, JS::Handle<JSScript*>, JS::Handle<JSObject*>, js::AbstractFramePtr, JS::MutableHandle<JS::Value>) /builds/worker/checkouts/gecko/js/src/vm/Interpreter.cpp:850:10
    #24 0x6361d7707a51 in JS_ExecuteScript(JSContext*, JS::Handle<JSScript*>) /builds/worker/checkouts/gecko/js/src/vm/CompilationAndEvaluation.cpp:572:10
    #25 0x6361d5c6e14f in RunFile(JSContext*, char const*, _IO_FILE*, CompileUtf8, bool, bool) /builds/worker/checkouts/gecko/js/src/shell/js.cpp:1388:10
    #26 0x6361d5c6d7ce in Process(JSContext*, char const*, bool, FileKind) /builds/worker/checkouts/gecko/js/src/shell/js.cpp
    #27 0x6361d5beba5f in ProcessArgs /builds/worker/checkouts/gecko/js/src/shell/js.cpp:12094:10
    #28 0x6361d5beba5f in Shell(JSContext*, js::cli::OptionParser*) /builds/worker/checkouts/gecko/js/src/shell/js.cpp:12347:12
    #29 0x6361d5bd54b4 in main /builds/worker/checkouts/gecko/js/src/shell/js.cpp:12753:12
    #30 0x75860142a1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #31 0x75860142a28a in __libc_start_main csu/../csu/libc-start.c:360:3
    #32 0x6361d5adcaf8 in _start (/home/zdi/Desktop/js-m-c-20260223210545-asan-opt/dist/bin/js+0x1f5faf8) (BuildId: 30fc0b0e79286c9c20fa79dfbd72c5371bee1dec)

Thread T2 created by T0 here:
    #0 0x6361d5b62331 in pthread_create /builds/worker/fetches/llvm-project/compiler-rt/lib/asan/asan_interceptors.cpp:250:3
    #1 0x6361d735b66d in js::Thread::create(void* (*)(void*), void*) /builds/worker/checkouts/gecko/js/src/threading/posix/PosixThread.cpp:57:7
    #2 0x6361d77a27f9 in bool js::Thread::init<void (&)(js::InternalThreadPool*, js::HelperThread*), js::InternalThreadPool*&, js::HelperThread*>(void (&)(js::InternalThreadPool*, js::HelperThread*), js::InternalThreadPool*&, js::HelperThread*&&) /builds/worker/checkouts/gecko/js/src/threading/Thread.h:88:16
    #3 0x6361d778e902 in init /builds/worker/checkouts/gecko/js/src/vm/InternalThreadPool.cpp:241:17
    #4 0x6361d778e902 in js::InternalThreadPool::ensureThreadCount(unsigned long, js::AutoLockHelperThreadState&) /builds/worker/checkouts/gecko/js/src/vm/InternalThreadPool.cpp:147:29
    #5 0x6361d778e47f in js::InternalThreadPool::Initialize(unsigned long, js::AutoLockHelperThreadState&) /builds/worker/checkouts/gecko/js/src/vm/InternalThreadPool.cpp:121:18
    #6 0x6361d778d8c4 in js::GlobalHelperThreadState::ensureInitialized() /builds/worker/checkouts/gecko/js/src/vm/HelperThreads.cpp:175:10
    #7 0x6361d748f6ae in JSRuntime::init(JSContext*, unsigned int) /builds/worker/checkouts/gecko/js/src/vm/Runtime.cpp:184:32
    #8 0x6361d77ed8a1 in js::NewContext(unsigned int, JSRuntime*) /builds/worker/checkouts/gecko/js/src/vm/JSContext.cpp:195:17
    #9 0x6361d5bd522f in main /builds/worker/checkouts/gecko/js/src/shell/js.cpp:12660:25
    #10 0x75860142a1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #11 0x75860142a28a in __libc_start_main csu/../csu/libc-start.c:360:3
    #12 0x6361d5adcaf8 in _start (/home/zdi/Desktop/js-m-c-20260223210545-asan-opt/dist/bin/js+0x1f5faf8) (BuildId: 30fc0b0e79286c9c20fa79dfbd72c5371bee1dec)

SUMMARY: AddressSanitizer: use-after-poison /builds/worker/checkouts/gecko/js/src/jit/MIRGraph.h:410:58 in getPredecessor
Shadow bytes around the buggy address:
  0x745600844a00: 00 00 f7 f7 00 00 00 00 00 00 00 00 00 00 00 00
  0x745600844a80: 00 f7 f7 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x745600844b00: 00 00 00 00 00 00 00 f7 f7 00 f7 f7 00 f7 f7 00
  0x745600844b80: 00 00 00 00 00 00 00 f7 f7 00 00 f7 f7 00 00 00
  0x745600844c00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x745600844c80: 00 00 00 00 00 00 00 00 00 00 00 00 00[f7]f7 00
  0x745600844d00: 00 00 00 00 00 00 00 00 f7 f7 00 00 00 00 00 00
  0x745600844d80: 00 00 f7 f7 00 00 00 00 00 00 00 00 00 00 00 00
  0x745600844e00: 00 00 00 00 00 00 00 00 f7 f7 00 00 00 00 00 00
  0x745600844e80: 00 00 00 00 00 00 00 00 f7 f7 00 00 00 00 00 00
  0x745600844f00: 00 00 00 00 00 00 00 00 f7 f7 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==5707==ABORTING
zdi@ubuntu:~/Desktop$ 
Attached file ZDI-CAN-29301.txt

I have been reading the issue twice until I realized that there was no other test case except the prose text in the analysis section

An empty switch statement (switch(0){}), satisfies this expression, even though it is an MTableSwitch JIT node and not an MGoto JIT node, and returns "true".

Attached file testcase.js

Text comprehension skills atrophying. I downloaded the test case from their Sharepoint and attached it here.

Thanks for the report! Good find.

Assignee: nobody → jdemooij
Status: NEW → ASSIGNED
Attached file (secure)
Attached file (secure)
Blocks: sm-security
Severity: -- → S2
Priority: -- → P1
Keywords: regression
Regressed by: 1767966

Set release status flags based on info from the regressing bug 1767966

Comment on attachment 9549860 [details]
(secure)

Security Approval Request

  • How easily could an exploit be constructed based on the patch?: Not trivial but especially now with AI it's doable.
  • Do comments in the patch, the check-in comment, or tests included in the patch paint a bulls-eye on the security problem?: No
  • Which branches (beta, release, and/or ESR) are affected by this flaw, and do the release status flags reflect this affected/unaffected state correctly?: All
  • If not all supported branches, which bug introduced the flaw?: None
  • Do you have backports for the affected branches?: Yes
  • If not, how different, hard to create, and risky will they be?: Should apply or be easy to backport.
  • How likely is this patch to cause regressions; how much testing does it need?: Unlikely
  • Is the patch ready to land after security approval is given?: Yes
  • Is Android affected?: Yes
Attachment #9549860 - Flags: sec-approval?
Flags: in-testsuite?
See Also: → CVE-2024-3854
Whiteboard: [credit: maxpl0it working with Trend Micro Zero Day Initiative][disclosure July 1st 2026] → [credit: maxpl0it working with Trend Micro Zero Day Initiative][disclosure July 1st 2026][reminder-test 2026-05-05]

Comment on attachment 9549860 [details]
(secure)

sec-approval+ to land now and request uplifts. Please wait until 2026-05-05 to land the tests (I set a bugbot reminder for you)

Attachment #9549860 - Flags: sec-approval? → sec-approval+
Group: javascript-core-security → core-security-release
Status: ASSIGNED → RESOLVED
Closed: 6 months ago
Resolution: --- → FIXED
Target Milestone: --- → 150 Branch
Attached file (secure)
Attachment #9550947 - Flags: approval-mozilla-beta?

firefox-beta Uplift Approval Request

  • User impact if declined: Security bugs.
  • Code covered by automated testing: yes
  • Fix verified in Nightly: yes
  • Needs manual QE test: no
  • Steps to reproduce for manual QE testing:
  • Risk associated with taking this patch: low
  • Explanation of risk level: The fix itself is very small and straight-forward. This patch also upgrades some assertions to release assertions and has a few other changes to help avoid similar bugs.
  • String changes made/needed: N/A
  • Is Android affected?: yes

firefox-esr115 Uplift Approval Request

  • User impact if declined: Security bugs.
  • Code covered by automated testing: yes
  • Fix verified in Nightly: yes
  • Needs manual QE test: no
  • Steps to reproduce for manual QE testing:
  • Risk associated with taking this patch: low
  • Explanation of risk level: The fix itself is very small and straight-forward. This patch also upgrades some assertions to release assertions and has a few other changes to help avoid similar bugs.
  • String changes made/needed: N/A
  • Is Android affected?: yes
Attachment #9550950 - Flags: approval-mozilla-esr115?
Attached file (secure)

firefox-esr140 Uplift Approval Request

  • User impact if declined: Security bugs.
  • Code covered by automated testing: yes
  • Fix verified in Nightly: yes
  • Needs manual QE test: no
  • Steps to reproduce for manual QE testing:
  • Risk associated with taking this patch: low
  • Explanation of risk level: The fix itself is very small and straight-forward. This patch also upgrades some assertions to release assertions and has a few other changes to help avoid similar bugs.
  • String changes made/needed: N/A
  • Is Android affected?: yes
Attachment #9550951 - Flags: approval-mozilla-esr140?
Attached file (secure)
Attachment #9550947 - Flags: approval-mozilla-beta? → approval-mozilla-beta+
QA Whiteboard: [sec] [uplift] [qa-triage-done-c150/b149]
Attachment #9550951 - Flags: approval-mozilla-esr140? → approval-mozilla-esr140+
Attachment #9550950 - Flags: approval-mozilla-esr115? → approval-mozilla-esr115+
Whiteboard: [credit: maxpl0it working with Trend Micro Zero Day Initiative][disclosure July 1st 2026][reminder-test 2026-05-05] → [credit: maxpl0it working with Trend Micro Zero Day Initiative][disclosure July 1st 2026][reminder-test 2026-05-05][adv-main149+][adv-ESR140.9+][adv-ESR115.34+]
Alias: CVE-2026-4698

2 months ago, dveditz placed a reminder on the bug using the whiteboard tag [reminder-test 2026-05-05] .

jandem, please refer to the original comment to better understand the reason for the reminder.

Flags: needinfo?(jdemooij)
Whiteboard: [credit: maxpl0it working with Trend Micro Zero Day Initiative][disclosure July 1st 2026][reminder-test 2026-05-05][adv-main149+][adv-ESR140.9+][adv-ESR115.34+] → [credit: maxpl0it working with Trend Micro Zero Day Initiative][disclosure July 1st 2026][adv-main149+][adv-ESR140.9+][adv-ESR115.34+]
Flags: needinfo?(jdemooij)
Group: core-security-release
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: