
CVE-2026-84118 के लिए प्रूफ-ऑफ-कॉन्सेप्ट, एक SpiderMonkey GC use-after-free जो आउट-ऑफ-बाउंड्स रीड/राइट और संभावित कोड निष्पादन की ओर ले जाता है। इसमें निर्धारक SEGV ट्रिगर और मनमाने रीड तथा ASLR पराजय तक बढ़ाने पर शोध शामिल है।
यह उन मामलों में से एक है जहां Mozilla ने एक Critical को High के रूप में लेबल किया। तो अगर ऐसा है तो किसी को Firefox को मूल बग खोजकर्ताओं का भुगतान अपडेट करने के लिए जाना चाहिए।
संक्षेप में: आप एक नियंत्रित पॉइंटर पर OOB R प्रिमिटिव कर सकते हैं और फिर उस पॉइंटर से ऊपर-नीचे जाना शुरू कर सकते हैं। इसलिए यदि आप नियंत्रित पॉइंटर को किसी array पर इंगित करते हैं तो बधाई हो, आप मज़ेदार चीज़ें कर सकते हैं।
POC में केवल SEGV भाग है, इसलिए बाकी एक array में इंगित करने और अपनी पसंद के WASM में कूदने का एक मज़ेदार साहसिक कार्य है।
Firefox/SpiderMonkey 154.0.1 के लिए निर्धारक release-build SEGV proof-of-concept। 155 में ठीक किया गया (AtomMarking रीवर्क)। Mozilla गंभीरता: HIGH।
/path/to/release/js poc.js
154.0.1 पर पुनरुत्पादित: एक सादे अनुकूलित शेल पर 3/3 (SIGSEGV, exit 139), release+gczeal शेल पर 5/5, हर बार समान फॉल्ट:
SEGV on address 0x000000000014 (READ)
JS::shadow::Zone::gcState() <- zone == nullptr, +0x14 offset
154 प्रति-zone atom mark बिटमैप्स को reference ट्रैकिंग के साथ मिलाता है। एक Symbol जो केवल weakly धारण किया गया है — यहाँ, एक Baseline CacheIR IC stub के अंदर एक weak Value फ़ील्ड के रूप में कैप्चर किया गया — और एक uncollected zone से संदर्भित है, उसे कभी re-mark नहीं किया जाता जब atoms zone को zone-scoped GC में collected किया जाता है। परिणाम एक swept/relocated cell के लिए एक stale edge है।
release-build kill shot एक दूसरा, स्थिर बग है: MightBeForwarded<JS::Symbol> हार्डकोडेड false है, इसलिए IsForwarded बिना जाँचे false लौटाता है (gc/Marking-inl.h:92-97) और उस पर गेटेड हर pointer-fixup पथ चुपचाप edge को छोड़ देता है। जब atoms zone को compacted किया जाता है, तो strong edges सही ढंग से अपडेट होते हैं, लेकिन यह weak CacheIR edge पीड़ित Symbol के पुराने पते पर स्थायी रूप से dangling रह जाता है। (Debug builds इसे Assertion failure: !t->isForwarded(), gc/Marking-inl.h:94 के रूप में पकड़ते हैं।)
PoC केवल reusable heap के लिए एक stale pointer नहीं छोड़ता — यह backing page को munmapped करवाता है, ताकि अगला ही dereference फॉल्ट करे।
Zone::availableChunks/fullChunks), इसलिए atoms-zone chunks में केवल atoms-zone arenas होते हैं। एक chunk का भाग्य पूरी तरह से atom/symbol lifetimes द्वारा नियंत्रित किया जा सकता है।c.eval(b) शीर्ष-स्तरीय call IC को Symbol को एक weak stub फ़ील्ड में कैप्चर करने देता है; b = undefined अंतिम strong root को गिरा देता है; spray गिरा दिया जाता है; दो zone-scoped GCs (gc("zone"), schedulezone(c) + schedulezone("") + gc("zone") — खाली स्ट्रिंग atoms zone को शेड्यूल करती है) मृत spray को sweep करते हैं, पीड़ित को magnet arena में compact करते हैं, weak-edge fixup को छोड़ देते हैं, और पीड़ित के arena को मुक्त कर देते हैं। इसका chunk अब पूरी तरह से खाली है।gcparam("minEmptyChunkCount", 0) हर खाली chunk को expirable बनाता है; sleep(0.5) के दौरान BackgroundDecommitTask -> -> चलाता है — पीड़ित chunk का एक वास्तविक munmap। (Arena-स्तरीय decommit केवल है — पठनीय शून्य — पूरे-chunk रिलीज़ ही एकमात्र munmap पथ है, यही कारण है कि चरण 1 में chunk अलगाव मायने रखता है।)यह केवल एक DoS नहीं है। प्रदर्शित escalations (GC-NDAY/ और CHAIN/ शोध निर्देशिकाओं में PoCs और logs):
segv9): munmap के बाद, एक minorgc() semispace फ्लिप nursery को मुक्त VA पर दावा करने देता है; एक raw dense-double spray stale arena header के zone फ़ील्ड पर एक attacker qword लिखता है। फिर sweep attacker_ptr + 0x14 को dereference करता है — 0x424242424256 पर निर्धारक 3/3।js::Zone* इस refill के माध्यम से लगाया गया था; collector ने forged arena metadata को बिना फॉल्ट किए उपभोग किया (एक 1-bit-perturbed नियंत्रण प्लांट क्रैश करता है), जो ASLR-defeat -> UAF-plant संरचना प्रदर्शित करता है।expireEmptyChunkPoolFreeChunkPoolUnmapPagesmadvise(MADV_DONTNEED)gc() JIT डेटा को sweep करता है: sweepJitDataOnMainThread -> ICEntry::traceWeak -> TraceWeakCacheIRStub stale Symbol pointer को पढ़ता है, फिर SweepingTracer::onEdge (gc/Marking.cpp:3195) stale पते पर chunk अंकगणित करता है ताकि arena header का zone फ़ील्ड लोड हो सके। पेज चला गया/शून्य हो गया है, इसलिए zone == nullptr, और zone->isGCSweeping() nullptr + 0x14 (shadow::Zone::gcState) पढ़ता है -> SIGSEGV। फॉल्ट पर, rcx stale Symbol pointer रखता है और rdx munmapped chunk base रखता है।