本目录包含一个诚实的复现脚手架,用于复现 mozilla::image::RasterImage::OnSurfaceDiscarded 中的跨线程释放后使用(use-after-free)漏洞。它旨在仅使用合成图像数据,针对本地下载的易受攻击 Firefox 构建运行。它绝不会接触任何在线或第三方系统。
匹配的 PoC 仓库:https://github.com/defineid/Revenant
分析文章:本作品集中的 writeup-rasterimage-uaf.html。
以下内容是在原始研究期间通过反汇编易受攻击的构建(libxul.so,Firefox 140.9.0esr)确认的;提交的截图 assets/writeup-uaf-disasm.png 即为该分析。函数名来自该确切构建的官方 Mozilla Breakpad 符号文件;随附的 libxul.so 已被剥离符号。它们并未在组装此 PoC 包的环境中重新反汇编(参见验证状态)。
| 事实 | 证据 | 来源 |
|---|---|---|
易受攻击的函数是 RasterImage::OnSurfaceDiscarded(SurfaceKey const&) | FUNC 3eba200 16c 0 mozilla::image::RasterImage::OnSurfaceDiscarded(...) | Mozilla 符号服务器 .sym |
在 this+0xC0 处提前读取一个字节(分析文章:mIsBeingDestroyed) | 3eba20a: cmp BYTE PTR [rdi+0xc0],0x0,其中 rdi = this | objdump -d libxul.so |
在 this+0x40 处对 RasterImage 引用计数执行 AddRef | 3eba25a: lock inc QWORD PTR [rbx+0x40](发出两次),rbx = this | objdump -d libxul.so |
分配一个 0x20 字节的可运行对象并捕获 this | 3eba264: mov edi,0x20 → call moz_xmalloc@plt;3eba280: mov QWORD PTR [rax+0x10],rbx | objdump -d libxul.so |
该可运行对象被派发,随后 this 被 Release | 3eba298: call QWORD PTR [rcx+0x28](虚派发);3eba29b: lock dec QWORD PTR [rbx+0x40] | objdump -d libxul.so |
引用计数归零后运行 ~RasterImage 然后 free | je 3eba345 → call 3ebbe90 <~RasterImage> → call free@plt | objdump + .sym |
被派发的可运行对象捕获 OnSurfaceDiscarded::$_0 lambda | FUNC 5279d90 ... RunnableFunction<...OnSurfaceDiscarded(...)::$_0>::Run() | Mozilla 符号服务器 .sym |
| 字段 | 值 |
|---|---|
| CVE | CVE-2026-74943 |
| 组件 | Core / Graphics: ImageLib |
| 类别 | Use-After-Free (CWE-416) |
| 严重性 | sec-high(Web 可达的内存安全) |
| 修复于 | Firefox 154 · ESR 140.14 |
| Bug | bugzilla.mozilla.org/show_bug.cgi?id=2057308(安全受限) |
SurfaceCacheImpl::Remove 通过一个原始、非拥有的 ImageKey 指针,在解码器 / ImageIO 线程上、持有表面缓存互斥锁的情况下调用 OnSurfaceDiscarded,通知所属图像某个已解码表面被丢弃。在易受攻击的构建中,该处理函数:
this+0xC0 处的单字节标志(cmp BYTE PTR [rdi+0xc0],0x0)。分析文章将其识别为 mIsBeingDestroyed,一项早期的缓解措施。this+0x40 处的 RasterImage 引用计数(lock inc QWORD PTR [rbx+0x40])来获取 this 的强引用。这是一个普通的 add-ref,而非原子的 add-ref-if-nonzero。moz_xmalloc(0x20) 分配一个捕获 this 的 RunnableFunction(mov QWORD PTR [rax+0x10],rbx),将其派发到全局事件目标(call QWORD PTR [rcx+0x28]),然后释放本地引用(lock dec QWORD PTR [rbx+0x40])。由于读取标志和递增计数是针对无锁引用计数的两个独立操作,它们可能跨越主线程丢弃最后一个引用的时刻。解码器线程随后从零开始 AddRef,复活一个析构函数已经提交的对象。当析构函数完成并 free 掉 RasterImage 后,排队的可运行对象稍后在主线程上运行并读取已释放的存储。
修复方案不再直接引用该图像:OnSurfaceDiscarded 捕获对单独进行引用计数的 ProgressTracker 的强引用,并在主线程上通过该 tracker 的弱反向引用恢复图像,使用原子的 add-ref-if-live 将其提升;mIsBeingDestroyed 标志被移除。
内容进程中的一个普通网页会驱动 surface 缓存压力(大量可丢弃的解码 surface),同时释放拥有图像请求的帧。无需点击、权限、扩展或首选项更改。竞态发生在解码线程的丢弃通知与主线程对同一图像的最终释放之间。
https://ftp.mozilla.org/pub/firefox/releases/140.9.0esr/ 下载匹配的平台构建。ASAN 构建(Nightly asan 或本地 --enable-address-sanitizer 构建)可使 use-after-free 表现为清晰的报告,而非偶发的静默崩溃。<canvas> 生成自己的图像;它不从网络加载任何内容。在存在漏洞的构建中打开 trigger.html,最好从 file:// 路径或你控制的本地静态服务器打开:
/path/to/firefox --new-instance --profile ./poc-profile ./trigger.html
默认情况下,驱动程序运行一个紧凑的微任务循环,以实现最大的解码/释放重叠。在 URL 后追加 ?tick=N 会将其切换为让出的 setTimeout(N) 循环,该循环同样高强度运转,但会让事件循环得以喘息,这正是自动化驱动程序能够在页面运行时对其进行步进和截图的原因。已提交的 trigger-running.png 正是以这种方式捕获的:通过 Marionette 驱动本地 140.11.0esr 构建,并带上 ?tick=4:它展示了测试装置实时运行(周期和拆除次数不断攀升)。该构建已打补丁,因此不会发生崩溃;该图片仅证明驱动程序能够运行。在无头模式下,屏幕外的图像不会被绘制,因此那里的“images decoded”计数器保持为 0;有头模式下的易受攻击构建会解码它们,竞态正是在那里被触发。
让页面持续运行。它会反复解码许多不同的大图像(以填满表面缓存并强制驱逐),同时拆除拥有这些图像的 DOM 子树(以驱动所属图像的最终释放)。这最大化了解码器线程上“表面被丢弃”与主线程上“最后一次释放”的重叠。竞态是概率性的;让它持续运行。
heap-use-after-free,其 READ 帧位于 RasterImage::OnSurfaceDiscardedInternal / 被派发的 runnable 内部,而 freed 帧位于 RasterImage::Release 拆除过程中。在没有 ASAN 的情况下,它可能表现为间歇性的内容进程崩溃。ProgressTracker 弱反向引用进行,并在销毁已经提交时丢弃该通知。libxul.so(140.9.0esr)进行了反汇编,上表中每一个二进制层面的事实都与官方 Mozilla 符号文件逐字节确认;已提交的 assets/writeup-uaf-disasm.png 就是那项工作。来自该报告的三个结构性论断(早期读取 this+0xC0 标志;在 this+0x40 处 lock inc AddRef;moz_xmalloc(0x20) 的 runnable 捕获 this、派发、然后 Release)全部成立。这针对的是本地下载的构建,使用合成的、自行生成的数据,用于一份关于已修补 CVE 的报告。它不攻击任何在线或第三方服务,并且除触发该缺陷外不包含任何利用原语。