You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Buffer.concat and Buffer.copy silently produce invalid results when the operation involves indices equal or greater than 2^32 #55422
changed the title [-]`Buffer.concat` silently produces invalid output when its output size is greater than 4GB[/-][+]`Buffer.concat` silently produces invalid output when its output size is greater than 4GiB[/+]on Oct 17, 2024
Anyone care to open a PR? I think this could be a simple case of just switching to .set(srcBuffer) (instead of using native methods) in the case where the total length exceeds e.g. 2 GB.
I'm not sure what exactly the binding refers to, but I found a candidate method in the C++ code (at node/src/node_buffer.cc) that treats all arguments as Uint32:
Measured on linux-x64 release binaries. big = Buffer.alloc(2**32 + 16, 111) with marker bytes 1..16 written past the 4 GiB boundary, then partial copy() calls that cannot take the TypedArrayPrototypeSet whole-buffer fast path in _copyActual:
D big.copy(dst, 0, 2**32 + 1, 2**32 + 11): v22/v24 fill dst with 6f6f6f… (source offset wrapped to 1); v26.4+ give 02030405060708090a0b.
E big.copy(dst, 0, 1, 2**32 + 16): v22/v24 return 4294967311 but only 15 bytes are written (dst.indexOf(0) === 15).
F2 small.copy(dst, 2**32 + 2, 1): v22/v24 write at offset 2 instead of 2**32 + 2.
G (the concat case above) passes everywhere now; SlowCopy on v22.x/v24.x still reads the offsets as Uint32.
So this is fixed on 26.x since v26.4.0 via #63828, but that PR is labeled dont-land-on-v22.x / dont-land-on-v24.x (needs the new V8 CopyArrayBufferBytes API), so both LTS lines still silently corrupt partial copies past 4 GiB.
Is a v22.x/v24.x-only change to SlowCopy/FastCopy (read offsets as doubles into size_t, no V8 change) acceptable, or should this be closed as fixed in 26.4.0? I can open that PR if wanted. Only checked linux-x64.
Activity
[-]`Buffer.concat` silently produces invalid output when its output size is greater than 4GB[/-][+]`Buffer.concat` silently produces invalid output when its output size is greater than 4GiB[/+]rotemdan commentedon Oct 17, 2024
My current workaround (tested to produce correct results with sizes greater than 4 GiB):
avivkeller commentedon Oct 17, 2024
The issue started in v22.7.0. I'll start bisecting. Maybe #54087?
avivkeller commentedon Oct 17, 2024
I've finished bisecting. This was indeed caused by #54087 cc @ronag.
ronag commentedon Oct 21, 2024
Anyone care to open a PR? I think this could be a simple case of just switching to
.set(srcBuffer)(instead of using native methods) in the case where the total length exceeds e.g. 2 GB.duncpro commentedon Oct 21, 2024
I reproduced this on macOS.
@ronag I'd like to try and tackle this one.
MrJithil commentedon Oct 21, 2024
good luck.
rotemdan commentedon Oct 21, 2024
This call to
_copyis possibly the reason:_copyis imported from some sort of internal binding.A thorough solution is to ensure this method correctly handles large array sizes, or fails.
Just working around it by falling back to
TypedArray.set, would leave the possibility of a future issue if some other code calls_copy.duncpro commentedon Oct 21, 2024
So the root cause of this problem is 32-bit integer overflow in
SlowCopyinnode_buffer.cchere.Apparently
Uint32Valueperforms a wrapping conversion. So that's why in the example below the target buffer only gets filled with 5 bytes.Simply replacing
Uint32ValuewithIntegerValuewill fix this barring edge cases I've yet to fully consider.rotemdan commentedon Oct 21, 2024
I'm not sure what exactly the binding refers to, but I found a candidate method in the C++ code (at
node/src/node_buffer.cc) that treats all arguments as Uint32:Regardless on whether it's the method used in the binding, using
Uint32Valueto extract the arguments doesn't seem right.This method follows, also taking in
uint32_ts:duncpro commentedon Oct 21, 2024
@rotemdan this is correct
10 remaining items
buffer: fix Buffer.copy for large offsets >= 2^32
buffer: optimize Buffer.prototype.copy
buffer: optimize Buffer.prototype.copy
buffer: speed up Buffer.prototype.copy via view-level copy
buffer: optimize Buffer.prototype.copy
LeonxLJX commentedon Sep 1, 2026
aron-intframe commentedon Sep 16, 2026
Measured on linux-x64 release binaries.
big = Buffer.alloc(2**32 + 16, 111)with marker bytes 1..16 written past the 4 GiB boundary, then partialcopy()calls that cannot take theTypedArrayPrototypeSetwhole-buffer fast path in_copyActual:big.copy(dst, 0, 2**32 + 1, 2**32 + 11): v22/v24 filldstwith6f6f6f…(source offset wrapped to 1); v26.4+ give02030405060708090a0b.big.copy(dst, 0, 1, 2**32 + 16): v22/v24 return4294967311but only 15 bytes are written (dst.indexOf(0) === 15).small.copy(dst, 2**32 + 2, 1): v22/v24 write at offset 2 instead of 2**32 + 2.concatcase above) passes everywhere now;SlowCopyon v22.x/v24.x still reads the offsets asUint32.So this is fixed on 26.x since v26.4.0 via #63828, but that PR is labeled
dont-land-on-v22.x/dont-land-on-v24.x(needs the new V8CopyArrayBufferBytesAPI), so both LTS lines still silently corrupt partial copies past 4 GiB.Is a v22.x/v24.x-only change to
SlowCopy/FastCopy(read offsets as doubles intosize_t, no V8 change) acceptable, or should this be closed as fixed in 26.4.0? I can open that PR if wanted. Only checked linux-x64.buffer: optimize Buffer.prototype.copy
buffer: optimize Buffer.prototype.copy