ZDI-CAN-29301: Mozilla Firefox IonMonkey Switch Statement Optimization Type Confusion Remote Code Execution Vulnerability
Categories
(Core :: JavaScript Engine: JIT, defect, P1)
Tracking
()
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)
|
48.43 KB,
text/plain
|
Details | |
|
1.68 KB,
text/plain
|
Details | |
|
48 bytes,
text/x-phabricator-request
|
dveditz
:
sec-approval+
|
Details | Review |
|
48 bytes,
text/x-phabricator-request
|
Details | Review | |
|
48 bytes,
text/x-phabricator-request
|
phab-bot
:
approval-mozilla-beta+
|
Details | Review |
|
48 bytes,
text/x-phabricator-request
|
phab-bot
:
approval-mozilla-esr115+
|
Details | Review |
|
48 bytes,
text/x-phabricator-request
|
phab-bot
:
approval-mozilla-esr140+
|
Details | Review |
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$
| Reporter | ||
Comment 1•6 months ago
|
||
| Reporter | ||
Comment 2•6 months ago
|
||
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".
| Reporter | ||
Comment 3•6 months ago
|
||
| Reporter | ||
Comment 4•6 months ago
|
||
Text comprehension skills atrophying. I downloaded the test case from their Sharepoint and attached it here.
| Assignee | ||
Comment 5•6 months ago
|
||
Thanks for the report! Good find.
| Assignee | ||
Comment 6•6 months ago
|
||
| Assignee | ||
Comment 7•6 months ago
|
||
Updated•6 months ago
|
| Assignee | ||
Updated•6 months ago
|
Comment 8•6 months ago
|
||
Set release status flags based on info from the regressing bug 1767966
Updated•6 months ago
|
| Assignee | ||
Comment 9•6 months ago
|
||
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
Updated•6 months ago
|
Updated•6 months ago
|
Comment 10•6 months ago
|
||
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)
Comment 11•6 months ago
|
||
Comment 12•6 months ago
|
||
| Assignee | ||
Comment 13•6 months ago
|
||
Original Revision: https://phabricator.services.mozilla.com/D286003
Updated•6 months ago
|
Comment 14•6 months ago
|
||
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
Comment 15•6 months ago
|
||
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
| Assignee | ||
Comment 16•6 months ago
|
||
Original Revision: https://phabricator.services.mozilla.com/D286003
Comment 17•6 months ago
|
||
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
| Assignee | ||
Comment 18•6 months ago
|
||
Original Revision: https://phabricator.services.mozilla.com/D286003
Updated•6 months ago
|
Updated•6 months ago
|
Comment 19•6 months ago
|
||
| uplift | ||
Updated•6 months ago
|
Updated•6 months ago
|
Updated•6 months ago
|
Updated•6 months ago
|
Updated•6 months ago
|
Comment 20•6 months ago
|
||
| uplift | ||
Comment 21•6 months ago
|
||
| uplift | ||
Updated•6 months ago
|
Updated•6 months ago
|
Updated•6 months ago
|
Comment 22•4 months ago
|
||
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.
Comment 23•4 months ago
|
||
| Assignee | ||
Updated•4 months ago
|
Comment 24•4 months ago
|
||
Updated•1 month ago
|
Description
•