From b9638db386c1112b1cc54900612d17e451f9d5d0 Mon Sep 17 00:00:00 2001 From: Maruthan G Date: Thu, 2 Jul 2026 20:31:41 +0530 Subject: [PATCH] buffer: add regression test for copy beyond 2 GiB The 32-bit truncation in Buffer.prototype.copy for buffers larger than 2 GiB (refs: #55422) was fixed as part of routing copy through V8's CopyArrayBufferBytes API, but no regression test covers offsets and byte counts above 2**32. Add an opt-in test (NODE_TEST_LARGE_BUFFER=1) that exercises copies across the uint32 boundary. Refs: https://github.com/nodejs/node/issues/55422 Signed-off-by: Maruthan G --- test/parallel/test-buffer-copy-4gb.js | 76 +++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 test/parallel/test-buffer-copy-4gb.js diff --git a/test/parallel/test-buffer-copy-4gb.js b/test/parallel/test-buffer-copy-4gb.js new file mode 100644 index 00000000000000..89e5132e4219d2 --- /dev/null +++ b/test/parallel/test-buffer-copy-4gb.js @@ -0,0 +1,76 @@ +'use strict'; + +// This tests that Buffer.prototype.copy correctly handles copies whose +// source/target offsets or byte counts exceed 2**32. Refs: +// https://github.com/nodejs/node/issues/55422 +const common = require('../common'); + +// Cannot test on 32-bit machines because the test relies on creating +// buffers larger than 4 GiB. +common.skipIf32Bits(); + +// Allocating 4+ GiB buffers in CI environments is expensive and may also +// fail under sanitizer / memory-constrained builds. Gate the test behind +// an explicit opt-in env var to avoid timeouts/OOMs in normal CI runs. +if (!process.env.NODE_TEST_LARGE_BUFFER) { + common.skip('Skipping: requires NODE_TEST_LARGE_BUFFER=1 (allocates >4GiB)'); +} + +const assert = require('assert'); + +const threshold = 0xFFFFFFFF; // 2**32 - 1 +const overflow = threshold + 5; // 2**32 + 4 — exercises offsets > 2**32 + +let largeBuffer; +try { + // Allocate a buffer slightly larger than 2**32 so we can copy to and from + // offsets above the uint32 boundary. + largeBuffer = Buffer.alloc(overflow); +} catch (e) { + if (e.code === 'ERR_MEMORY_ALLOCATION_FAILED' || + /Array buffer allocation failed/.test(e.message)) { + common.skip('insufficient memory for >4GiB Buffer.alloc'); + } + throw e; +} + +// Sentinel byte at an index above 2**32. Before the fix, copy() truncates the +// length to 32 bits and the sentinel never gets written, so reading it back +// would yield 0. +const sentinelIndex = threshold + 2; +largeBuffer[sentinelIndex] = 0xAB; + +// Test 1: Buffer.prototype.copy with sourceEnd > 2**32 should copy the bytes +// past the 4 GiB boundary instead of silently truncating. +{ + const target = Buffer.alloc(overflow); + const copied = largeBuffer.copy(target, 0, 0, overflow); + // copy() must report copying the full byte range, and the byte beyond + // 2**32 must be copied rather than truncated. + assert.strictEqual(copied, overflow); + assert.strictEqual(target[sentinelIndex], 0xAB); +} + +// Test 2: Buffer.prototype.copy with targetStart > 2**32 should write at the +// large offset rather than wrapping back to a low address. +{ + const target = Buffer.alloc(overflow); + const src = Buffer.from([0xCD]); + const copied = src.copy(target, threshold + 1, 0, 1); + assert.strictEqual(copied, 1); + // targetStart > 2**32 must not wrap to a low offset. + assert.strictEqual(target[threshold + 1], 0xCD); + // The low offset that uint32 truncation would have written to must remain + // untouched. + assert.strictEqual(target[(threshold + 1) >>> 0], 0); +} + +// Test 3: Buffer.concat with a single >4 GiB buffer should preserve the +// trailing bytes, exercising the original repro from the issue. +{ + largeBuffer.fill(0x6F); // 'o' + const result = Buffer.concat([largeBuffer]); + assert.strictEqual(result.length, overflow); + // The final byte beyond 2**32 must survive concat. + assert.strictEqual(result[overflow - 1], 0x6F); +}