From 4fb3d728f461ad640483d76c92e2a8cdbc0c8ce8 Mon Sep 17 00:00:00 2001 From: vedchaudhari Date: Mon, 26 Oct 2026 09:52:08 +0530 Subject: [PATCH] buffer: stop clamping indexOf byteOffset to int32 bidirectionalIndexOf() clamped byteOffset to the int32 range, so on buffers larger than 2 GiB an offset above 2 ** 31 - 1 started the search at 2 ** 31 - 1 instead. indexOf() and includes() could then match before the requested offset, and lastIndexOf() missed matches. Offsets below -(2 ** 31) were clamped the same way. The binding already takes an int64 offset, so clamp to the safe integer range instead. This still keeps Infinity and other values that int64 can't represent from reaching the binding. Fixes: https://github.com/nodejs/node/issues/67008 Refs: https://github.com/nodejs/node/pull/66325 Assisted-by: claude:opus-5.5 Signed-off-by: vedchaudhari --- lib/buffer.js | 10 +++++----- test/pummel/test-buffer-indexof-large.js | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/lib/buffer.js b/lib/buffer.js index d9c64f27cf3e..142fa804b1bd 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -1108,7 +1108,7 @@ Buffer.prototype.compare = function compare(target, // Arguments: // - buffer - a Buffer to search // - val - a string, Buffer, or number -// - byteOffset - an index into `buffer`; will be clamped to an int32 +// - byteOffset - an index into `buffer`; will be clamped to a safe integer // - end - absolute exclusive end of the search range // - encoding - an optional encoding, relevant if val is a string // - dir - true for indexOf, false for lastIndexOf @@ -1118,10 +1118,10 @@ function bidirectionalIndexOf(buffer, val, byteOffset, end, encoding, dir) { if (typeof byteOffset === 'string') { encoding = byteOffset; byteOffset = undefined; - } else if (byteOffset > 0x7fffffff) { - byteOffset = 0x7fffffff; - } else if (byteOffset < -0x80000000) { - byteOffset = -0x80000000; + } else if (byteOffset > NumberMAX_SAFE_INTEGER) { + byteOffset = NumberMAX_SAFE_INTEGER; + } else if (byteOffset < NumberMIN_SAFE_INTEGER) { + byteOffset = NumberMIN_SAFE_INTEGER; } // Coerce to Number. Values like null and [] become 0. byteOffset = +byteOffset; diff --git a/test/pummel/test-buffer-indexof-large.js b/test/pummel/test-buffer-indexof-large.js index 0f0999705417..e47b4fe49bff 100644 --- a/test/pummel/test-buffer-indexof-large.js +++ b/test/pummel/test-buffer-indexof-large.js @@ -25,3 +25,22 @@ assert.strictEqual(buffer.indexOf('\n', -1), match); assert.strictEqual(buffer.lastIndexOf(0x0a, -1), match); assert.strictEqual(buffer.lastIndexOf(Buffer.from([0x0a]), -1), match); assert.strictEqual(buffer.lastIndexOf('\n', -1), match); + +// Offsets outside the int32 range must not be clamped. +// Clear the ranges searched below, as allocUnsafe() does not zero memory. +const before = 2 ** 31 + 10; +const offset = 2 ** 31 + 50; +buffer.fill(0, 0, 100); +buffer.fill(0, 2 ** 31, match); +buffer[60] = 0x0a; +buffer[before] = 0x0a; + +for (const value of [0x0a, Buffer.from([0x0a]), '\n']) { + assert.strictEqual(buffer.indexOf(value, offset), match); + assert.strictEqual(buffer.lastIndexOf(value, offset), before); + assert.strictEqual(buffer.includes(value, match + 1), false); + assert.strictEqual(buffer.indexOf(value, Infinity), -1); + // buffer.length is 2 ** 31 + 101, so -offset resolves to index 51. + assert.strictEqual(buffer.indexOf(value, -offset), 60); + assert.strictEqual(buffer.lastIndexOf(value, -offset), -1); +}