
既然这是Project Zero的BigSleep发现的第一个V8漏洞,Buff这么多,那就很难不来一窥究竟了。

背景
8月19日的 Google Chrome 更新修复了一个由 Google Big Sleep 发现的漏洞。
Chrome Releases: Stable Channel Update for Desktop
[436181695] High CVE-2025-9132: Out of bounds write in V8. Reported by Google Big Sleep on 2025-08-04
通过分析补丁,我们成功实现了 CVE-2025-9132 的利用。以下所有分析和利用都基于 v8 13.9.205.19,commit 505ec917b67c535519bebec58c62a34f145dd49f,即 v8 13.9 分支中漏洞修复前的 commit。
CVE-2025-9132 的补丁和补丁中附带的 PoC 如下。
1 | diff --git a/src/parsing/parser.cc b/src/parsing/parser.cc |
1 | // Copyright 2025 the V8 project authors. All rights reserved. |
使用 debug 版本的 d8 运行 PoC 会在 BytecodeArrayWriter::BindJumpTableEntry 中触发 DCHECK [1]。
1 | void BytecodeArrayWriter::BindJumpTableEntry(BytecodeJumpTable* jump_table, |
分析
补丁和 PoC 都显示漏洞与 await using 语法有关。await using 是 JavaScript 中的新特性。使用 await using 声明的变量离开其作用域时,它的 [asyncDispose] 会被异步调用。
The await using declaration declares block-scoped local variables that are asynchronously disposed.
1 | async function foo() { |
在 v8 中,如果 async function 中有 await 关键字,那么函数的开头会是一个 SwitchOnGeneratorState 字节码,每个 await 会产生一对 SuspendGenerator/ResumeGenerator 字节码。SuspendGenerator 会将函数的当前状态保存到 JSGeneratorObject 对象中,然后退出。当 await 完成,函数会重新从开头的 SwitchOnGeneratorState 处开始执行,SwitchOnGeneratorState 会根据 JSGeneratorObject 从对应的 ResumeGenerator 处恢复执行,ResumeGenerator 会从 JSGeneratorObject 导入函数状态。
SwitchOnGeneratorState 有一个 JumpTable,用于选择从哪一个 ResumeGenerator 执行。v8 先从源码产生 AST,再从 AST 生成字节码。为了确定 JumpTable 的大小,v8 在 parse 源码时会记录 await、await using、yield 等关键字的个数。例如 ParserBase<Impl>::ParseVariableDeclarations 第一次在一个作用域中遇到 await using 时会调用 AddSuspend 来增加计数。
1 |
|
生成字节码时,BytecodeGenerator 会先 constant_pool 中预留 info()->literal()->suspend_count() 个位置(constant_pool 是一个数组),作为 JumpTable。JumpTable 会在生成字节码的过程中逐个被填充成实际的跳转偏移。
1 | void BytecodeGenerator::BuildGeneratorPrologue() { |
BytecodeGenerator 有自己的数据成员 suspend_count_,初始值为 0。 在根据 AST 生成字节码时,每当需要生成一个 SuspendGenerator ,就会以 suspend_count_ 字段作为 suspend_id,然后将 suspend_count_ 自增。suspend_id 被用作 BytecodeArrayWriter::BindJumpTableEntry 函数的 case_value 参数,作为索引填充 JumpTable。正常来说,suspend_id 的范围是 [0, info()->literal()->suspend_count() - 1]。
1 | // Suspends the generator to resume at the next suspend_id, with output stored |
CVE-2025-9132 的根本原因是 Parser::DesugarLexicalBindingsInForStatement 对 AST进行变换时引入了额外的 await using 变量。DesugarLexicalBindingsInForStatement 的注释解释了变换的方式。for (let/const x = i; cond; next) body 中的 let/const x = i 变成了 [1] [2] 两处 let/const x = ...。当这种变换应用到 PoC 中的代码时,源码中的一个 await using x = ... 变成了 AST 中的两处await using x = ... 。因此 BytecodeGenerator 在按照 AST 生成字节码并填充 JumpTable 时会出现 suspend_id >= info()->literal()->suspend_count() 的情况, 造成越界写。
1 | // ES6 13.7.4.8 specifies that on each loop iteration the let variables are |
利用

Xion大佬说得对,漏洞容易利用,“大觉”(真是一个信达雅的翻译:D)也确实很有趣。大家也可以动动手了。
参考
[1] https://chromereleases.googleblog.com/2025/08/stable-channel-update-for-desktop_19.html
[2] https://chromium-review.googlesource.com/c/v8/v8/+/6853483
[3] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/await_using