From 7cf971d8413434f86f148d4bbbe80ebb8eb4145e Mon Sep 17 00:00:00 2001 From: lazerg Date: Sun, 27 Sep 2026 13:34:12 +0500 Subject: [PATCH] fs: fix readFileUtf8 overflow on short pipe reads Assisted-by: Claude Signed-off-by: lazerg --- src/node_file.cc | 4 +- .../test-fs-readfilesync-pipe-short-reads.js | 46 +++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 test/parallel/test-fs-readfilesync-pipe-short-reads.js diff --git a/src/node_file.cc b/src/node_file.cc index b624e9da41bd..5ef2921d3a7b 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -3508,7 +3508,7 @@ static void ReadFileUtf8(const FunctionCallbackInfo& args) { } // Switch to the heap buffer. uv_fs_req_cleanup(&req); - big_cap = kMinChunk; + big_cap = std::max(kMinChunk, result.size() + sizeof(buffer)); big = UncheckedMalloc(big_cap); if (big == nullptr) { FS_SYNC_TRACE_END(read); @@ -3520,7 +3520,7 @@ static void ReadFileUtf8(const FunctionCallbackInfo& args) { } else { big_len += static_cast(r); } - if (big_len == big_cap) { + if (big_len >= big_cap) { // +1 leaves room for the read() that reports EOF. size_t new_cap = big_cap + std::min(kMaxChunk, std::max(kMinChunk, big_cap)); diff --git a/test/parallel/test-fs-readfilesync-pipe-short-reads.js b/test/parallel/test-fs-readfilesync-pipe-short-reads.js new file mode 100644 index 000000000000..ea4e264dbf9b --- /dev/null +++ b/test/parallel/test-fs-readfilesync-pipe-short-reads.js @@ -0,0 +1,46 @@ +'use strict'; +const common = require('../common'); + +// readFileSync(fd, 'utf8') on a pipe must not overflow its heap buffer when +// more than 64 KiB arrive in short reads before the first full 8 KiB read. + +if (common.isWindows) + common.skip('short-read timing on Windows pipes is not reliable'); + +const assert = require('assert'); +const fs = require('fs'); +const { spawn } = require('child_process'); + +const kChunks = 24; +const kChunkSize = 4000; +const kTailSize = 400000; + +if (process.argv[2] === 'child') { + process.stderr.write('r'); + process.stdout.write(String(fs.readFileSync(0, 'utf8').length)); + return; +} + +const child = spawn(process.execPath, [__filename, 'child']); + +// Pace the short writes so each arrives as its own read, then send one large +// write to force the switch to the heap buffer. +child.stderr.once('data', common.mustCall(() => { + let i = 0; + const timer = setInterval(() => { + child.stdin.write('x'.repeat(kChunkSize)); + if (++i === kChunks) { + clearInterval(timer); + child.stdin.end('y'.repeat(kTailSize)); + } + }, common.platformTimeout(15)); +})); + +let stdout = ''; +child.stdout.setEncoding('utf8'); +child.stdout.on('data', (d) => { stdout += d; }); +child.on('close', common.mustCall((code, signal) => { + assert.strictEqual(signal, null); + assert.strictEqual(code, 0); + assert.strictEqual(stdout, String(kChunks * kChunkSize + kTailSize)); +}));