针对 CVE-2026-15718 的概念验证漏洞利用链,该漏洞是 Mozilla SpiderMonkey 的 WebAssembly baseline 编译器("JavaScript: WebAssembly" 组件)中一处缺失 sync() 的错误编译。单个零次循环的 array.fill 即可产生 fakeobj 原语;该利用链可升级为对宿主进程的任意读/写以及任意本机代码执行(posix_spawnp("/bin/sh", ...))。
4441102e62e8("Bug 2045443",一行)BaseCompiler::emitArrayFill()(js/src/wasm/WasmBaselineCompile.cpp)在发出 array.fill 填充循环时没有先调用 :
sync() if (elementType.isRefRepr()) {
freePtr(RegPtr(PreBarrierReg));
}
+ sync();
+
// Perform the fill loop using `numElements` as the loop variable ...
sync() 将 baseline 编译器的值栈(stk_)从寄存器刷新到机器栈。填充循环每轮迭代的不精确事后屏障(emitPostBarrierEdgeImprecise)会在内部调用 sync() —— 但仅当循环实际运行时才会如此。循环的退出标签(done)是两条路径的汇合点:
stk_ 已同步 —— 每个操作数都位于机器栈槽中;numElements == 0):stk_ 未被触碰 —— 操作数仍只存在于寄存器中。汇合后编译器的静态模型始终反映已同步路径,因此在 array.fill 之后发出的代码会从栈槽读取操作数,而零次循环执行时从未写入这些栈槽。以这种方式读回的 reftype 操作数是无效(过期)指针(CWE-763)。
该不同步(desync)在两个帧结构相同的 wasm 函数之间是双向的:
n = 1,循环运行):循环内的 sync() 将值写入槽位 —— 一个真实的 ref(addrOf)或受控的 i64(fakeobj 设置);n = 0,零次循环):槽位从未被写入;孪生函数将过期的槽位读回 —— 作为 i64(泄漏)或作为 (ref $t)(fakeobj)。| 文件 | 阶段 | 结果 |
|---|---|---|
poc-min.js(+ gen_trigger.py) | 不同步演示 | 控制路径返回正确值;零次循环路径返回从未写入槽位中读出的垃圾指针 |
chain.js(+ gen_chain.py、chain.tpl.js、build.sh) | 完整利用链 | addrOf + fakeobj → 伪造 WasmArrayObject → 任意读/写 → XUL 基址泄漏 → 运行时 Mach-O 导入表遍历 → 伪造 funcref → posix_spawnp("/bin/sh", ["-c", "touch /tmp/CVE-2026-15718-PWNED"]) |
chain.js 中的阶段细节:
plantSpray/plantCmd/plantFun/plantExt + leak —— 获取 wasm 数组、funcref 和 JS 对象(externref)的 addrOf。sprayV + fakeRd/fakeWr —— fakeobj;伪造 WasmArrayObject(numElements_ @+16、data_ @+24、内联元素 @+40),伪造窗口保存在带外(malloc 分配)数组中,以便 GC 在扫描过程中无法移动它。0xfeedfacf)→ XUL 基址。LC_SEGMENT_64/LC_SYMTAB/LC_DYSYMTAB + 间接符号表)遍历 XUL 的 Mach-O 加载命令,定位 _posix_spawnp 的 __stubs 条目。无硬编码偏移;该桩在首次调用时执行懒绑定。ref.func 复制 typeDef @+0x40,调用目标 @+0x38 = 桩地址),并通过 call_ref 以 posix_spawnp(pid, "/bin/sh", 0, 0, [sh, -c, cmd], 0) 调用它。使用由 152.0.6 之前的代码树构建的 xpcshell(或 js shell / 浏览器)进行测试(此处的参考构建为 Firefox 149.0a1,2026-01-14):
# minimal desync demo
xpcshell poc/poc-min.js
# full chain (regenerates chain.js from the template + generator)
(cd poc && sh build.sh)
rm -f /tmp/CVE-2026-15718-PWNED
xpcshell poc/chain.js # creates /tmp/CVE-2026-15718-PWNED
ls -l /tmp/CVE-2026-15718-PWNED
测试框架设置 javascript.options.wasm_optimizingjit=false,以便在较长的 Mach-O 扫描期间固定 baseline 层级(该缺陷仅存在于 baseline 编译器;在浏览器中触发器的运行远低于层级升级阈值,因此那里不需要此设置)。tools/xul_slots.py 是一个离线验证器,用于列出 XUL 的命名桩/GOT 槽位(用于交叉检查运行时遍历器)。
偏移量适用于 macOS arm64、此确切代码树/构建(WasmArrayObject { +16 numElements、+24 data_、+40 inline }、JSFunction 原生 @ +0x20、WasmFuncRef { +0x38/+0x48 调用目标、+0x40 typeDef })。所有这些都由利用链的自检在运行时重新推导;仅假定结构布局。无 PAC 顾虑(XUL 是 arm64、非 arm64e 二进制);懒绑定桩在首次调用时解析目标,因此不需要 dyld 共享缓存偏移量。
此 PoC 在未沙箱化的 shell 进程中运行。在真实的 Firefox 攻击中,利用链会落在内容进程内,而 macOS 沙箱会拒绝其中的 posix_spawnp —— 实现完全攻陷需要单独的 OS 沙箱逃逸漏洞(参见例如 CVE-2026-2796-and-CVE-2026-2768-escape-the-wasm-box 中的第二阶段分析)。
javascript.options.wasm=false 可阻断触发向量。仅用于安全研究、教育和防御性测试。该漏洞已在当前 Firefox/Thunderbird 版本中修复。请勿针对你不拥有或未经明确授权测试的系统使用。