Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 20 additions & 12 deletions src/node_buffer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -587,9 +587,9 @@ void StringSlice(const FunctionCallbackInfo<Value>& args) {

void CopyImpl(Local<Value> source_obj,
Local<Value> target_obj,
const uint32_t target_start,
const uint32_t source_start,
const uint32_t to_copy) {
const size_t target_start,
const size_t source_start,
const size_t to_copy) {
ArrayBufferViewContents<char> source(source_obj);
SPREAD_BUFFER_ARG(target_obj, target);

Expand All @@ -598,24 +598,32 @@ void CopyImpl(Local<Value> source_obj,

// Assume caller has properly validated args.
void SlowCopy(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Local<Value> source_obj = args[0];
Local<Value> target_obj = args[1];
const uint32_t target_start = args[2].As<Uint32>()->Value();
const uint32_t source_start = args[3].As<Uint32>()->Value();
const uint32_t to_copy = args[4].As<Uint32>()->Value();
int64_t target_start, source_start, to_copy;
if (!args[2]->IntegerValue(env->context()).To(&target_start) ||
!args[3]->IntegerValue(env->context()).To(&source_start) ||
!args[4]->IntegerValue(env->context()).To(&to_copy)) {
return;
}

CopyImpl(source_obj, target_obj, target_start, source_start, to_copy);
CopyImpl(source_obj,
target_obj,
static_cast<size_t>(target_start),
static_cast<size_t>(source_start),
static_cast<size_t>(to_copy));

args.GetReturnValue().Set(to_copy);
args.GetReturnValue().Set(static_cast<double>(to_copy));
}

// Assume caller has properly validated args.
uint32_t FastCopy(Local<Value> receiver,
uint64_t FastCopy(Local<Value> receiver,
Local<Value> source_obj,
Local<Value> target_obj,
uint32_t target_start,
uint32_t source_start,
uint32_t to_copy,
uint64_t target_start,
uint64_t source_start,
uint64_t to_copy,
// NOLINTNEXTLINE(runtime/references)
FastApiCallbackOptions& options) {
HandleScope scope(options.isolate);
Expand Down
44 changes: 44 additions & 0 deletions test/parallel/test-buffer-copy-large.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use strict';

// Regression test for https://github.com/nodejs/node/issues/55422
// Buffer.copy and Buffer.concat silently produced incorrect results when
// indices involved were >= 2^32 due to 32-bit integer overflow in SlowCopy.

const common = require('../common');
const assert = require('assert');

// This test exercises the native SlowCopy path in node_buffer.cc.
// SlowCopy is invoked by _copyActual when the fast TypedArrayPrototypeSet
// path cannot be used (i.e. when sourceStart !== 0 or nb !== sourceLen).

// Cannot test on 32-bit machines since buffers that large cannot exist there.
common.skipIf32Bits();

const THRESHOLD = 2 ** 32; // 4 GiB

// Allocate a large target buffer (just over 4 GiB) to test that a targetStart
// value >= 2^32 is not silently truncated to its lower 32 bits.
let target;
try {
target = Buffer.alloc(THRESHOLD + 10, 0);
} catch (e) {
if (e.code === 'ERR_MEMORY_ALLOCATION_FAILED' ||
/Array buffer allocation failed/.test(e.message)) {
common.skip('insufficient memory for large buffer allocation');
}
throw e;
}

const source = Buffer.alloc(10, 111);

// Copy only the first 5 bytes of source (nb=5, sourceLen=10 → nb !== sourceLen)
// so _copyActual falls through to the native _copy (SlowCopy) instead of
// using TypedArrayPrototypeSet. The targetStart is THRESHOLD (2^32), which
// previously overflowed to 0 when cast to uint32_t.
source.copy(target, THRESHOLD, 0, 5);

// The 5 copied bytes must appear at position THRESHOLD, not at position 0.
assert.strictEqual(target[0], 0, 'position 0 must not have been overwritten');

Check failure on line 41 in test/parallel/test-buffer-copy-large.js

View workflow job for this annotation

GitHub Actions / lint-js-and-md

Do not use a literal for the third argument of assert.strictEqual()
assert.strictEqual(target[THRESHOLD], 111, 'byte at THRESHOLD must be 111');

Check failure on line 42 in test/parallel/test-buffer-copy-large.js

View workflow job for this annotation

GitHub Actions / lint-js-and-md

Do not use a literal for the third argument of assert.strictEqual()
assert.strictEqual(target[THRESHOLD + 4], 111, 'byte at THRESHOLD+4 must be 111');

Check failure on line 43 in test/parallel/test-buffer-copy-large.js

View workflow job for this annotation

GitHub Actions / lint-js-and-md

Do not use a literal for the third argument of assert.strictEqual()
assert.strictEqual(target[THRESHOLD + 5], 0, 'byte at THRESHOLD+5 must be 0 (not copied)');

Check failure on line 44 in test/parallel/test-buffer-copy-large.js

View workflow job for this annotation

GitHub Actions / lint-js-and-md

Do not use a literal for the third argument of assert.strictEqual()
Loading