Commit b9bb1bd36c4 for nodejs

commit b9bb1bd36c4fac33d986326edb8f1943168d205d
Author: Christian Aurich Zanettini Martins <christian.aurichzm@gmail.com>
Date:   Wed Sep 30 10:30:12 2026 -0300

    ffi: reject unsafe integers as length or offset

    GetValidatedSize() checks the value against
    static_cast<double>(SIZE_MAX), which rounds up to 2^64 on 64-bit
    platforms. A length or offset of 2 ** 64 gets through, and the cast to
    size_t after it is undefined behavior. With GCC on x64 it gives 0, so
    ffi.setUint8(ptr, 2 ** 64, 42) writes to ptr instead of throwing.

    Anything above Number.MAX_SAFE_INTEGER may already have been rounded
    by the time it gets here, so reject those values too. The export*()
    helpers already cap their length there, and so does setInt64() for
    number values. SIZE_MAX is still the limit on 32-bit platforms.

    When buffer.constants.MAX_LENGTH is Number.MAX_SAFE_INTEGER, as on
    64-bit builds without the V8 sandbox, toBuffer() and toArrayBuffer()
    now throw ERR_OUT_OF_RANGE for MAX_LENGTH + 1 instead of
    ERR_BUFFER_TOO_LARGE.

    Signed-off-by: Christian Aurich Zanettini Martins <christian.aurichzm@gmail.com>
    PR-URL: https://github.com/nodejs/node/pull/66216
    Reviewed-By: René <contact.9a5d6388@renegade334.me.uk>

diff --git a/src/ffi/data.cc b/src/ffi/data.cc
index c42d51600c9..cd2a2481ef6 100644
--- a/src/ffi/data.cc
+++ b/src/ffi/data.cc
@@ -50,7 +50,10 @@ Maybe<size_t> GetValidatedSize(Environment* env,
     return Nothing<size_t>();
   }

-  if (length > static_cast<double>(std::numeric_limits<size_t>::max())) {
+  // Values beyond Number.MAX_SAFE_INTEGER may already have been rounded
+  // by the caller. On 32-bit platforms SIZE_MAX is the tighter bound.
+  if (length > kMaxSafeJsInteger ||
+      length > static_cast<double>(std::numeric_limits<size_t>::max())) {
     THROW_ERR_OUT_OF_RANGE(env, "The %s is too large", label);
     return Nothing<size_t>();
   }
diff --git a/test/ffi/test-ffi-memory.js b/test/ffi/test-ffi-memory.js
index 38ea177b6cb..be9160337dd 100644
--- a/test/ffi/test-ffi-memory.js
+++ b/test/ffi/test-ffi-memory.js
@@ -342,8 +342,13 @@ test('ffi validates memory access arguments', () => {

     assert.throws(() => ffi.toBuffer(maxPointer, 8), /pointer and length exceed the platform address range/);
     assert.throws(() => ffi.toArrayBuffer(maxPointer, 8), /pointer and length exceed the platform address range/);
-    assert.throws(() => ffi.toBuffer(1n, bufferConstants.MAX_LENGTH + 1), { code: 'ERR_BUFFER_TOO_LARGE' });
-    assert.throws(() => ffi.toArrayBuffer(1n, bufferConstants.MAX_LENGTH + 1), { code: 'ERR_BUFFER_TOO_LARGE' });
+
+    // If MAX_LENGTH is Number.MAX_SAFE_INTEGER, MAX_LENGTH + 1 is an unsafe
+    // integer and is rejected before the buffer length is checked.
+    if (bufferConstants.MAX_LENGTH < Number.MAX_SAFE_INTEGER) {
+      assert.throws(() => ffi.toBuffer(1n, bufferConstants.MAX_LENGTH + 1), { code: 'ERR_BUFFER_TOO_LARGE' });
+      assert.throws(() => ffi.toArrayBuffer(1n, bufferConstants.MAX_LENGTH + 1), { code: 'ERR_BUFFER_TOO_LARGE' });
+    }

     if (process.arch === 'ia32' || process.arch === 'arm') {
       assert.throws(() => ffi.toBuffer(2n ** 32n, 0), /platform pointer range/);
@@ -351,6 +356,21 @@ test('ffi validates memory access arguments', () => {
   }));
 });

+test('ffi rejects unsafe integers as an offset or length', () => {
+  withAllocations(common.mustCall((alloc) => {
+    const ptr = alloc(8);
+    const range = { code: 'ERR_OUT_OF_RANGE' };
+
+    // On 64-bit platforms SIZE_MAX rounds up to 2 ** 64 as a double.
+    for (const value of [Number.MAX_SAFE_INTEGER + 1, 2 ** 64]) {
+      assert.throws(() => ffi.getUint8(ptr, value), range);
+      assert.throws(() => ffi.setUint8(ptr, value, 42), range);
+      assert.throws(() => ffi.toBuffer(ptr, value), range);
+      assert.throws(() => ffi.toArrayBuffer(ptr, value), range);
+    }
+  }));
+});
+
 test('ffi memory helpers reject missing required arguments', () => {
   const widths = ['Int8', 'Uint8', 'Int16', 'Uint16', 'Int32', 'Uint32',
                   'Int64', 'Uint64', 'Float32', 'Float64'];