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
41 changes: 25 additions & 16 deletions src/node_buffer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -599,9 +599,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 @@ -612,27 +612,36 @@ void CopyImpl(Local<Value> source_obj,
void SlowCopy(const FunctionCallbackInfo<Value>& 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();
// Byte offsets and lengths can exceed uint32 for buffers larger than 4 GiB,
// so they are passed and returned as doubles (exact for integers < 2^53).
const size_t target_start =
static_cast<size_t>(args[2].As<Number>()->Value());
const size_t source_start =
static_cast<size_t>(args[3].As<Number>()->Value());
const size_t to_copy = static_cast<size_t>(args[4].As<Number>()->Value());

CopyImpl(source_obj, target_obj, target_start, source_start, 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,
Local<Value> source_obj,
Local<Value> target_obj,
uint32_t target_start,
uint32_t source_start,
uint32_t to_copy,
// NOLINTNEXTLINE(runtime/references)
FastApiCallbackOptions& options) {
double FastCopy(Local<Value> receiver,
Local<Value> source_obj,
Local<Value> target_obj,
double target_start,
double source_start,
double to_copy,
// NOLINTNEXTLINE(runtime/references)
FastApiCallbackOptions& options) {
TRACK_V8_FAST_API_CALL("buffer.copy");
HandleScope scope(options.isolate);

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));

return to_copy;
}
Expand Down
48 changes: 48 additions & 0 deletions test/pummel/test-buffer-large-size-copy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
'use strict';
const common = require('../common');

// Buffer.prototype.copy with offsets and a byte count > 2 ** 32. Regression
// test for the .copy() side of https://github.com/nodejs/node/issues/55422,
// where the byte count and the offsets were truncated to 32 bits.
common.skipIf32Bits();

const assert = require('node:assert');

// A little past the 2 ** 32 boundary, with headroom for the shift below.
const size = 2 ** 32 + 16;

let buf;
try {
buf = Buffer.alloc(size);
} catch (e) {
if (
e.code === 'ERR_MEMORY_ALLOCATION_FAILED' ||
/Array buffer allocation failed/.test(e.message)
) {
common.skip('insufficient memory for Buffer.alloc');
}

throw e;
}

// Place a marker near the end of the source range, at an index > 2 ** 32.
const marker = 0x42;
buf[size - 9] = marker;

// Shift the whole buffer right by 8 bytes within itself. `to_copy` is
// size - 8 (> 2 ** 32), so a truncated count would copy only 8 bytes. The
// source byte at size - 9 must land at size - 1.
const copied = buf.copy(buf, 8, 0, size - 8);

// The return value must not be truncated to 32 bits ...
assert.strictEqual(copied, size - 8);
// ... and the byte must actually have moved across the 2 ** 32 boundary.
assert.strictEqual(buf[size - 1], marker);

// `sourceStart` and `targetStart` past 2 ** 32 must not be truncated either:
// copy one byte from size - 2 to size - 1, both of which are > 2 ** 32.
const marker2 = 0x43;
buf[size - 2] = marker2;
buf[size - 1] = 0;
assert.strictEqual(buf.copy(buf, size - 1, size - 2, size - 1), 1);
assert.strictEqual(buf[size - 1], marker2);
Loading