From 416198592c36d3b48861b323825ae5b695822e18 Mon Sep 17 00:00:00 2001 From: wei_ds Date: Wed, 1 Apr 2026 23:36:37 +0800 Subject: [PATCH] fs: validate mode as Int32 in ReadStream/WriteStream constructors Fixes a regression where passing a mode value that exceeds Int32 max (but is within UInt32 range) to createWriteStream or createReadStream would cause an assertion failure crash instead of throwing a catchable RangeError. The issue was introduced when mode validation was moved from JS to C++, but the ReadStream/WriteStream constructors were not updated to validate the mode option before passing it to the C++ layer. Changes: - Import and use parseFileMode in lib/internal/fs/streams.js for both ReadStream and WriteStream constructors - Change parseFileMode in lib/internal/validators.js to use validateInt32 instead of validateUint32 to match C++ expectations - Add regression test for issue #62516 Fixes: https://github.com/nodejs/node/issues/62516 PR-URL: TBD Reviewed-By: TBD --- lib/internal/fs/streams.js | 5 +- lib/internal/validators.js | 2 +- .../test-fs-stream-mode-validation.js | 94 +++++++++++++++++++ 3 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 test/parallel/test-fs-stream-mode-validation.js diff --git a/lib/internal/fs/streams.js b/lib/internal/fs/streams.js index c2e8abc4efdadd..af929825bbfa2b 100644 --- a/lib/internal/fs/streams.js +++ b/lib/internal/fs/streams.js @@ -23,6 +23,7 @@ const { kEmptyObject, } = require('internal/util'); const { + parseFileMode, validateBoolean, validateFunction, validateInteger, @@ -181,7 +182,7 @@ function ReadStream(path, options) { // Path will be ignored when fd is specified, so it can be falsy this.path = toPathIfFileURL(path); this.flags = options.flags === undefined ? 'r' : options.flags; - this.mode = options.mode === undefined ? 0o666 : options.mode; + this.mode = parseFileMode(options.mode, 'options.mode', 0o666); validatePath(this.path); } else { @@ -333,7 +334,7 @@ function WriteStream(path, options) { // Path will be ignored when fd is specified, so it can be falsy this.path = toPathIfFileURL(path); this.flags = options.flags === undefined ? 'w' : options.flags; - this.mode = options.mode === undefined ? 0o666 : options.mode; + this.mode = parseFileMode(options.mode, 'options.mode', 0o666); validatePath(this.path); } else { diff --git a/lib/internal/validators.js b/lib/internal/validators.js index 110b045a063460..340a240832a7f5 100644 --- a/lib/internal/validators.js +++ b/lib/internal/validators.js @@ -78,7 +78,7 @@ function parseFileMode(value, name, def) { value = NumberParseInt(value, 8); } - validateUint32(value, name); + validateInt32(value, name); return value; } diff --git a/test/parallel/test-fs-stream-mode-validation.js b/test/parallel/test-fs-stream-mode-validation.js new file mode 100644 index 00000000000000..79b414a5f2d63c --- /dev/null +++ b/test/parallel/test-fs-stream-mode-validation.js @@ -0,0 +1,94 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const fs = require('fs'); + +// Regression test for https://github.com/nodejs/node/issues/62516 +// Passing a value that exceeds Int32 max (but is within UInt32 range) to +// createWriteStream/createReadStream mode option should throw RangeError, +// not crash with assertion failure. + +const INT32_MAX = 2147483647; +const VALUE_EXCEEDS_INT32 = 2176057344; // Within UInt32, exceeds Int32 + +let testsCompleted = 0; +const expectedTests = 4; + +function checkCompletion() { + testsCompleted++; + if (testsCompleted === expectedTests) { + console.log('ok'); + } +} + +// Test createWriteStream with mode value exceeding Int32 max +{ + const stream = fs.createWriteStream('/tmp/test-write.txt', { + mode: VALUE_EXCEEDS_INT32, + }); + stream.on('error', common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_OUT_OF_RANGE'); + assert.strictEqual(err.name, 'RangeError'); + assert(/The value of "mode" is out of range/.test(err.message)); + checkCompletion(); + })); +} + +// Test createReadStream with mode value exceeding Int32 max +{ + const stream = fs.createReadStream('/tmp/test-read.txt', { + mode: VALUE_EXCEEDS_INT32, + }); + stream.on('error', common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_OUT_OF_RANGE'); + assert.strictEqual(err.name, 'RangeError'); + assert(/The value of "mode" is out of range/.test(err.message)); + checkCompletion(); + })); +} + +// Test that valid mode values still work +{ + const writeStream = fs.createWriteStream('/tmp/test-valid.txt', { + mode: 0o666, + }); + writeStream.on('ready', () => { + writeStream.destroy(); + checkCompletion(); + }); + writeStream.write('test'); +} + +// Test boundary value (Int32 max should be accepted) +{ + const writeStream = fs.createWriteStream('/tmp/test-boundary.txt', { + mode: INT32_MAX, + }); + writeStream.on('ready', () => { + writeStream.destroy(); + checkCompletion(); + }); + writeStream.write('test'); +}