这是 Mozilla 将严重级别为 Critical 的问题标记为 High 的案例之一。如果确实如此,也许有人应该去让 Firefox 更新对原始漏洞发现者的奖励。
简而言之:你可以对一个受控指针执行越界(OOB)读原语,然后从该指针开始上下移动。因此,如果你将受控指针指向一个数组,那么恭喜你,你可以做很多有趣的事情。
该 PoC 仅包含 SEGV 部分,其余部分则是一次有趣的冒险——指向数组内部并跳转到你选择的 WASM。
针对 Firefox/SpiderMonkey 154.0.1 的确定性发布版构建 SEGV 概念验证。已在 155 中修复(AtomMarking 重构)。Mozilla 严重级别:HIGH。
/path/to/release/js poc.js
已在 154.0.1 上复现:普通优化 shell 上 3/3(SIGSEGV,退出码 139),release+gczeal shell 上 5/5,每次运行故障完全相同:
SEGV on address 0x000000000014 (READ)
JS::shadow::Zone::gcState() <- zone == nullptr, +0x14 offset
154 将按 zone 的原子 标记 位图与 引用 跟踪混为一谈。一个仅被 弱 持有的 Symbol——此处是作为弱 Value 字段被捕获在 Baseline CacheIR IC 桩内——并且被一个 未回收 的 zone 引用,当原子 zone 在 zone 范围的 GC 中被回收时,它永远不会被重新标记。结果是产生了一条指向已清除/已移动单元的陈旧边。
发布版构建的致命一击是第二个静态 bug:MightBeForwarded<JS::Symbol> 被硬编码为 false,因此 IsForwarded 不进行检查 就返回 false(gc/Marking-inl.h:92-97),所有依赖它的指针修复路径都会静默跳过该边。当原子 zone 被 压缩 时,强边会被正确更新,但这条弱 CacheIR 边会永久悬空在受害 Symbol 的旧地址上。(调试构建会将其捕获为 Assertion failure: !t->isForwarded(), gc/Marking-inl.h:94。)
PoC 不仅仅留下一个指向可重用堆的陈旧指针——它让底层页面被 munmap,因此下一次解引用就会立即触发故障。
Zone::availableChunks/fullChunks),因此原子 zone 的块只包含原子 zone 的 arena。一个块的命运可以纯粹通过原子/Symbol 的生命周期来控制。c.eval(b) 使顶层调用 IC 将 Symbol 捕获到弱桩字段中;b = undefined 丢弃最后一个强根;喷洒被丢弃;两次 zone 范围的 GC(gc("zone")、schedulezone(c) + schedulezone("") + gc("zone")——空字符串调度 原子 zone)清除已死的喷洒,将受害对象压缩到磁铁 arena 中,跳过弱边修复,并释放受害对象的 arena。其块现在完全为空。gcparam("minEmptyChunkCount", 0) 使每个空块都可过期;在 sleep(0.5) 期间,BackgroundDecommitTask 运行 expireEmptyChunkPool -> FreeChunkPool -> UnmapPages——对受害块进行真正的 munmap。(Arena 级别的去提交仅为 ——可读的零——整块释放是唯一的 munmap 路径,这就是步骤 1 中块隔离至关重要的原因。)这不仅仅是 DoS。已演示的提权(PoC 和日志位于 GC-NDAY/ 和 CHAIN/ 研究目录中):
segv9):在 munmap 之后,一次 minorgc() 半空间翻转使 nursery 占用已释放的 VA;一次原始密集双精度喷洒将攻击者 qword 写入陈旧 arena 头的 zone 字段。随后清除过程解引用 attacker_ptr + 0x14——在 0x424242424256 处确定性 3/3。js::Zone*;收集器 未触发故障 地消费了伪造的 arena 元数据(一个 1 位扰动的控制植入会崩溃),证明了 ASLR 破解 -> UAF 植入的组合。madvise(MADV_DONTNEED)gc() 清除 JIT 数据:sweepJitDataOnMainThread -> ICEntry::traceWeak -> TraceWeakCacheIRStub 读取陈旧的 Symbol 指针,然后 SweepingTracer::onEdge(gc/Marking.cpp:3195)对陈旧地址执行块算术以加载 arena 头的 zone 字段。该页面已消失/被清零,因此 zone == nullptr,而 zone->isGCSweeping() 读取 nullptr + 0x14(shadow::Zone::gcState)-> SIGSEGV。在故障点,rcx 持有陈旧的 Symbol 指针,rdx 持有被 munmap 的块基址。