Skip to content

Commit a18e130

Browse files
committed
lib: mask mode_t type of arguments with 0o777
- Introduce the `validateAndMaskMode` validator that validates `mode_t` arguments and mask them with 0o777 if they are 32-bit unsigned integer or octal string to be more consistent with POSIX APIs. - Use the validator in fs APIs and process.umask for consistency. - Add tests for 32-bit unsigned modes larger than 0o777. PR-URL: #20636 Fixes: #20498 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Anatoli Papirovski <[email protected]>
1 parent 0d9500d commit a18e130

13 files changed

+323
-141
lines changed

lib/fs.js

Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ const internalUtil = require('internal/util');
5252
const {
5353
copyObject,
5454
getOptions,
55-
modeNum,
5655
nullCheck,
5756
preprocessSymlinkDestination,
5857
Stats,
@@ -71,6 +70,7 @@ const {
7170
} = require('internal/constants');
7271
const {
7372
isUint32,
73+
validateAndMaskMode,
7474
validateInt32,
7575
validateUint32
7676
} = require('internal/validators');
@@ -531,32 +531,36 @@ fs.closeSync = function(fd) {
531531
handleErrorFromBinding(ctx);
532532
};
533533

534-
fs.open = function(path, flags, mode, callback_) {
535-
var callback = makeCallback(arguments[arguments.length - 1]);
536-
mode = modeNum(mode, 0o666);
537-
534+
fs.open = function(path, flags, mode, callback) {
538535
path = getPathFromURL(path);
539536
validatePath(path);
540-
validateUint32(mode, 'mode');
537+
const flagsNumber = stringToFlags(flags);
538+
if (arguments.length < 4) {
539+
callback = makeCallback(mode);
540+
mode = 0o666;
541+
} else {
542+
mode = validateAndMaskMode(mode, 'mode', 0o666);
543+
callback = makeCallback(callback);
544+
}
541545

542546
const req = new FSReqWrap();
543547
req.oncomplete = callback;
544548

545549
binding.open(pathModule.toNamespacedPath(path),
546-
stringToFlags(flags),
550+
flagsNumber,
547551
mode,
548552
req);
549553
};
550554

551555
fs.openSync = function(path, flags, mode) {
552-
mode = modeNum(mode, 0o666);
553556
path = getPathFromURL(path);
554557
validatePath(path);
555-
validateUint32(mode, 'mode');
558+
const flagsNumber = stringToFlags(flags);
559+
mode = validateAndMaskMode(mode, 'mode', 0o666);
556560

557561
const ctx = { path };
558562
const result = binding.open(pathModule.toNamespacedPath(path),
559-
stringToFlags(flags), mode,
563+
flagsNumber, mode,
560564
undefined, ctx);
561565
handleErrorFromBinding(ctx);
562566
return result;
@@ -834,12 +838,16 @@ fs.fsyncSync = function(fd) {
834838
};
835839

836840
fs.mkdir = function(path, mode, callback) {
837-
if (typeof mode === 'function') callback = mode;
838-
callback = makeCallback(callback);
839841
path = getPathFromURL(path);
840842
validatePath(path);
841-
mode = modeNum(mode, 0o777);
842-
validateUint32(mode, 'mode');
843+
844+
if (arguments.length < 3) {
845+
callback = makeCallback(mode);
846+
mode = 0o777;
847+
} else {
848+
callback = makeCallback(callback);
849+
mode = validateAndMaskMode(mode, 'mode', 0o777);
850+
}
843851

844852
const req = new FSReqWrap();
845853
req.oncomplete = callback;
@@ -849,8 +857,7 @@ fs.mkdir = function(path, mode, callback) {
849857
fs.mkdirSync = function(path, mode) {
850858
path = getPathFromURL(path);
851859
validatePath(path);
852-
mode = modeNum(mode, 0o777);
853-
validateUint32(mode, 'mode');
860+
mode = validateAndMaskMode(mode, 'mode', 0o777);
854861
const ctx = { path };
855862
binding.mkdir(pathModule.toNamespacedPath(path), mode, undefined, ctx);
856863
handleErrorFromBinding(ctx);
@@ -1032,25 +1039,18 @@ fs.unlinkSync = function(path) {
10321039
};
10331040

10341041
fs.fchmod = function(fd, mode, callback) {
1035-
mode = modeNum(mode);
10361042
validateUint32(fd, 'fd');
1037-
validateUint32(mode, 'mode');
1038-
// Values for mode < 0 are already checked via the validateUint32 function
1039-
if (mode > 0o777)
1040-
throw new ERR_OUT_OF_RANGE('mode', undefined, mode);
1043+
mode = validateAndMaskMode(mode, 'mode');
1044+
callback = makeCallback(callback);
10411045

10421046
const req = new FSReqWrap();
1043-
req.oncomplete = makeCallback(callback);
1047+
req.oncomplete = callback;
10441048
binding.fchmod(fd, mode, req);
10451049
};
10461050

10471051
fs.fchmodSync = function(fd, mode) {
1048-
mode = modeNum(mode);
10491052
validateUint32(fd, 'fd');
1050-
validateUint32(mode, 'mode');
1051-
// Values for mode < 0 are already checked via the validateUint32 function
1052-
if (mode > 0o777)
1053-
throw new ERR_OUT_OF_RANGE('mode', undefined, mode);
1053+
mode = validateAndMaskMode(mode, 'mode');
10541054
const ctx = {};
10551055
binding.fchmod(fd, mode, undefined, ctx);
10561056
handleErrorFromBinding(ctx);
@@ -1091,11 +1091,10 @@ if (constants.O_SYMLINK !== undefined) {
10911091

10921092

10931093
fs.chmod = function(path, mode, callback) {
1094-
callback = makeCallback(callback);
10951094
path = getPathFromURL(path);
10961095
validatePath(path);
1097-
mode = modeNum(mode);
1098-
validateUint32(mode, 'mode');
1096+
mode = validateAndMaskMode(mode, 'mode');
1097+
callback = makeCallback(callback);
10991098

11001099
const req = new FSReqWrap();
11011100
req.oncomplete = callback;
@@ -1105,8 +1104,8 @@ fs.chmod = function(path, mode, callback) {
11051104
fs.chmodSync = function(path, mode) {
11061105
path = getPathFromURL(path);
11071106
validatePath(path);
1108-
mode = modeNum(mode);
1109-
validateUint32(mode, 'mode');
1107+
mode = validateAndMaskMode(mode, 'mode');
1108+
11101109
const ctx = { path };
11111110
binding.chmod(pathModule.toNamespacedPath(path), mode, undefined, ctx);
11121111
handleErrorFromBinding(ctx);

lib/internal/fs/promises.js

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,14 @@ const { Buffer, kMaxLength } = require('buffer');
1212
const {
1313
ERR_FS_FILE_TOO_LARGE,
1414
ERR_INVALID_ARG_TYPE,
15-
ERR_METHOD_NOT_IMPLEMENTED,
16-
ERR_OUT_OF_RANGE
15+
ERR_METHOD_NOT_IMPLEMENTED
1716
} = require('internal/errors').codes;
1817
const { getPathFromURL } = require('internal/url');
1918
const { isUint8Array } = require('internal/util/types');
2019
const {
2120
copyObject,
2221
getOptions,
2322
getStatsFromBinding,
24-
modeNum,
2523
nullCheck,
2624
preprocessSymlinkDestination,
2725
stringToFlags,
@@ -34,6 +32,7 @@ const {
3432
} = require('internal/fs/utils');
3533
const {
3634
isUint32,
35+
validateAndMaskMode,
3736
validateInt32,
3837
validateUint32
3938
} = require('internal/validators');
@@ -191,10 +190,9 @@ async function copyFile(src, dest, flags) {
191190
// Note that unlike fs.open() which uses numeric file descriptors,
192191
// fsPromises.open() uses the fs.FileHandle class.
193192
async function open(path, flags, mode) {
194-
mode = modeNum(mode, 0o666);
195193
path = getPathFromURL(path);
196194
validatePath(path);
197-
validateUint32(mode, 'mode');
195+
mode = validateAndMaskMode(mode, 'mode', 0o666);
198196
return new FileHandle(
199197
await binding.openFileHandle(pathModule.toNamespacedPath(path),
200198
stringToFlags(flags),
@@ -287,10 +285,9 @@ async function fsync(handle) {
287285
}
288286

289287
async function mkdir(path, mode) {
290-
mode = modeNum(mode, 0o777);
291288
path = getPathFromURL(path);
292289
validatePath(path);
293-
validateUint32(mode, 'mode');
290+
mode = validateAndMaskMode(mode, 'mode', 0o777);
294291
return binding.mkdir(pathModule.toNamespacedPath(path), mode, kUsePromises);
295292
}
296293

@@ -361,19 +358,15 @@ async function unlink(path) {
361358
}
362359

363360
async function fchmod(handle, mode) {
364-
mode = modeNum(mode);
365361
validateFileHandle(handle);
366-
validateUint32(mode, 'mode');
367-
if (mode > 0o777)
368-
throw new ERR_OUT_OF_RANGE('mode', undefined, mode);
362+
mode = validateAndMaskMode(mode, 'mode');
369363
return binding.fchmod(handle.fd, mode, kUsePromises);
370364
}
371365

372366
async function chmod(path, mode) {
373367
path = getPathFromURL(path);
374368
validatePath(path);
375-
mode = modeNum(mode);
376-
validateUint32(mode, 'mode');
369+
mode = validateAndMaskMode(mode, 'mode');
377370
return binding.chmod(pathModule.toNamespacedPath(path), mode, kUsePromises);
378371
}
379372

lib/internal/fs/utils.js

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -70,21 +70,6 @@ function getOptions(options, defaultOptions) {
7070
return options;
7171
}
7272

73-
function modeNum(m, def) {
74-
if (typeof m === 'number')
75-
return m;
76-
if (typeof m === 'string') {
77-
const parsed = parseInt(m, 8);
78-
if (Number.isNaN(parsed))
79-
return m;
80-
return parsed;
81-
}
82-
// TODO(BridgeAR): Only return `def` in case `m == null`
83-
if (def !== undefined)
84-
return def;
85-
return m;
86-
}
87-
8873
// Check if the path contains null types if it is a string nor Uint8Array,
8974
// otherwise return silently.
9075
function nullCheck(path, propName, throwError = true) {
@@ -391,7 +376,6 @@ module.exports = {
391376
assertEncoding,
392377
copyObject,
393378
getOptions,
394-
modeNum,
395379
nullCheck,
396380
preprocessSymlinkDestination,
397381
realpathCacheKey: Symbol('realpathCacheKey'),

lib/internal/process/methods.js

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
const {
44
ERR_INVALID_ARG_TYPE,
5-
ERR_INVALID_ARG_VALUE,
65
ERR_UNKNOWN_CREDENTIAL
76
} = require('internal/errors').codes;
87
const {
8+
validateAndMaskMode,
99
validateUint32
1010
} = require('internal/validators');
1111

@@ -30,29 +30,13 @@ function setupProcessMethods() {
3030
return _chdir(directory);
3131
}
3232

33-
const octalReg = /^[0-7]+$/;
3433
function umask(mask) {
35-
if (typeof mask === 'undefined') {
34+
if (mask === undefined) {
35+
// Get the mask
3636
return _umask(mask);
3737
}
38-
39-
if (typeof mask === 'number') {
40-
validateUint32(mask, 'mask');
41-
return _umask(mask);
42-
}
43-
44-
if (typeof mask === 'string') {
45-
if (!octalReg.test(mask)) {
46-
throw new ERR_INVALID_ARG_VALUE('mask', mask,
47-
'must be an octal string');
48-
}
49-
const octal = Number.parseInt(mask, 8);
50-
validateUint32(octal, 'mask');
51-
return _umask(octal);
52-
}
53-
54-
throw new ERR_INVALID_ARG_TYPE('mask', ['number', 'string', 'undefined'],
55-
mask);
38+
mask = validateAndMaskMode(mask, 'mask');
39+
return _umask(mask);
5640
}
5741
}
5842

lib/internal/validators.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const {
44
ERR_INVALID_ARG_TYPE,
5+
ERR_INVALID_ARG_VALUE,
56
ERR_OUT_OF_RANGE
67
} = require('internal/errors').codes;
78

@@ -13,6 +14,40 @@ function isUint32(value) {
1314
return value === (value >>> 0);
1415
}
1516

17+
const octalReg = /^[0-7]+$/;
18+
const modeDesc = 'must be a 32-bit unsigned integer or an octal string';
19+
// Validator for mode_t (the S_* constants). Valid numbers or octal strings
20+
// will be masked with 0o777 to be consistent with the behavior in POSIX APIs.
21+
function validateAndMaskMode(value, name, def) {
22+
if (isUint32(value)) {
23+
return value & 0o777;
24+
}
25+
26+
if (typeof value === 'number') {
27+
if (!Number.isInteger(value)) {
28+
throw new ERR_OUT_OF_RANGE(name, 'an integer', value);
29+
} else {
30+
// 2 ** 32 === 4294967296
31+
throw new ERR_OUT_OF_RANGE(name, '>= 0 && < 4294967296', value);
32+
}
33+
}
34+
35+
if (typeof value === 'string') {
36+
if (!octalReg.test(value)) {
37+
throw new ERR_INVALID_ARG_VALUE(name, value, modeDesc);
38+
}
39+
const parsed = parseInt(value, 8);
40+
return parsed & 0o777;
41+
}
42+
43+
// TODO(BridgeAR): Only return `def` in case `value == null`
44+
if (def !== undefined) {
45+
return def;
46+
}
47+
48+
throw new ERR_INVALID_ARG_VALUE(name, value, modeDesc);
49+
}
50+
1651
function validateInt32(value, name) {
1752
if (!isInt32(value)) {
1853
let err;
@@ -53,6 +88,7 @@ function validateUint32(value, name, positive) {
5388
module.exports = {
5489
isInt32,
5590
isUint32,
91+
validateAndMaskMode,
5692
validateInt32,
5793
validateUint32
5894
};

0 commit comments

Comments
 (0)