Skip to content

Commit 4161985

Browse files
committed
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: #62516 PR-URL: TBD Reviewed-By: TBD
1 parent 53bcd11 commit 4161985

File tree

3 files changed

+98
-3
lines changed

3 files changed

+98
-3
lines changed

lib/internal/fs/streams.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const {
2323
kEmptyObject,
2424
} = require('internal/util');
2525
const {
26+
parseFileMode,
2627
validateBoolean,
2728
validateFunction,
2829
validateInteger,
@@ -181,7 +182,7 @@ function ReadStream(path, options) {
181182
// Path will be ignored when fd is specified, so it can be falsy
182183
this.path = toPathIfFileURL(path);
183184
this.flags = options.flags === undefined ? 'r' : options.flags;
184-
this.mode = options.mode === undefined ? 0o666 : options.mode;
185+
this.mode = parseFileMode(options.mode, 'options.mode', 0o666);
185186

186187
validatePath(this.path);
187188
} else {
@@ -333,7 +334,7 @@ function WriteStream(path, options) {
333334
// Path will be ignored when fd is specified, so it can be falsy
334335
this.path = toPathIfFileURL(path);
335336
this.flags = options.flags === undefined ? 'w' : options.flags;
336-
this.mode = options.mode === undefined ? 0o666 : options.mode;
337+
this.mode = parseFileMode(options.mode, 'options.mode', 0o666);
337338

338339
validatePath(this.path);
339340
} else {

lib/internal/validators.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ function parseFileMode(value, name, def) {
7878
value = NumberParseInt(value, 8);
7979
}
8080

81-
validateUint32(value, name);
81+
validateInt32(value, name);
8282
return value;
8383
}
8484

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Copyright Joyent, Inc. and other Node contributors.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a
4+
// copy of this software and associated documentation files (the
5+
// "Software"), to deal in the Software without restriction, including
6+
// without limitation the rights to use, copy, modify, merge, publish,
7+
// distribute, sublicense, and/or sell copies of the Software, and to permit
8+
// persons to whom the Software is furnished to do so, subject to the
9+
// following conditions:
10+
//
11+
// The above copyright notice and this permission notice shall be included
12+
// in all copies or substantial portions of the Software.
13+
//
14+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15+
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17+
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18+
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20+
// USE OR OTHER DEALINGS IN THE SOFTWARE.
21+
22+
'use strict';
23+
24+
const common = require('../common');
25+
const assert = require('assert');
26+
const fs = require('fs');
27+
28+
// Regression test for https://github.com/nodejs/node/issues/62516
29+
// Passing a value that exceeds Int32 max (but is within UInt32 range) to
30+
// createWriteStream/createReadStream mode option should throw RangeError,
31+
// not crash with assertion failure.
32+
33+
const INT32_MAX = 2147483647;
34+
const VALUE_EXCEEDS_INT32 = 2176057344; // Within UInt32, exceeds Int32
35+
36+
let testsCompleted = 0;
37+
const expectedTests = 4;
38+
39+
function checkCompletion() {
40+
testsCompleted++;
41+
if (testsCompleted === expectedTests) {
42+
console.log('ok');
43+
}
44+
}
45+
46+
// Test createWriteStream with mode value exceeding Int32 max
47+
{
48+
const stream = fs.createWriteStream('/tmp/test-write.txt', {
49+
mode: VALUE_EXCEEDS_INT32,
50+
});
51+
stream.on('error', common.mustCall((err) => {
52+
assert.strictEqual(err.code, 'ERR_OUT_OF_RANGE');
53+
assert.strictEqual(err.name, 'RangeError');
54+
assert(/The value of "mode" is out of range/.test(err.message));
55+
checkCompletion();
56+
}));
57+
}
58+
59+
// Test createReadStream with mode value exceeding Int32 max
60+
{
61+
const stream = fs.createReadStream('/tmp/test-read.txt', {
62+
mode: VALUE_EXCEEDS_INT32,
63+
});
64+
stream.on('error', common.mustCall((err) => {
65+
assert.strictEqual(err.code, 'ERR_OUT_OF_RANGE');
66+
assert.strictEqual(err.name, 'RangeError');
67+
assert(/The value of "mode" is out of range/.test(err.message));
68+
checkCompletion();
69+
}));
70+
}
71+
72+
// Test that valid mode values still work
73+
{
74+
const writeStream = fs.createWriteStream('/tmp/test-valid.txt', {
75+
mode: 0o666,
76+
});
77+
writeStream.on('ready', () => {
78+
writeStream.destroy();
79+
checkCompletion();
80+
});
81+
writeStream.write('test');
82+
}
83+
84+
// Test boundary value (Int32 max should be accepted)
85+
{
86+
const writeStream = fs.createWriteStream('/tmp/test-boundary.txt', {
87+
mode: INT32_MAX,
88+
});
89+
writeStream.on('ready', () => {
90+
writeStream.destroy();
91+
checkCompletion();
92+
});
93+
writeStream.write('test');
94+
}

0 commit comments

Comments
 (0)