lib: unmask mode_t values with 0o777

This commit allows permission bits higher than 0o777 to go through
the API (e.g. `S_ISVTX`=`0o1000`, `S_ISGID`=`0o2000`,
`S_ISUID`=`0o4000`).

Also documents that these bits are not exposed through `fs.constants`
and their behaviors are platform-specific, so the users need to
use them on their own risk.

PR-URL: https://github.com/nodejs/node/pull/20975
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Joyee Cheung 2018-05-26 18:51:19 +08:00 committed by Anna Henningsen
parent 38c938aa90
commit cf72301545
No known key found for this signature in database
GPG Key ID: 9C63F3A6CD2AD8F9
10 changed files with 45 additions and 29 deletions

View File

@ -1089,6 +1089,12 @@ For example, the octal value `0o765` means:
* The group may read and write the file. * The group may read and write the file.
* Others may read and execute the file. * Others may read and execute the file.
Note: When using raw numbers where file modes are expected,
any value larger than `0o777` may result in platform-specific
behaviors that are not supported to work consistently.
Therefore constants like `S_ISVTX`, `S_ISGID` or `S_ISUID` are
not exposed in `fs.constants`.
Caveats: on Windows only the write permission can be changed, and the Caveats: on Windows only the write permission can be changed, and the
distinction among the permissions of group, owner or others is not distinction among the permissions of group, owner or others is not
implemented. implemented.

View File

@ -77,7 +77,7 @@ const {
} = require('internal/constants'); } = require('internal/constants');
const { const {
isUint32, isUint32,
validateAndMaskMode, validateMode,
validateInteger, validateInteger,
validateInt32, validateInt32,
validateUint32 validateUint32
@ -416,7 +416,7 @@ function open(path, flags, mode, callback) {
callback = makeCallback(mode); callback = makeCallback(mode);
mode = 0o666; mode = 0o666;
} else { } else {
mode = validateAndMaskMode(mode, 'mode', 0o666); mode = validateMode(mode, 'mode', 0o666);
callback = makeCallback(callback); callback = makeCallback(callback);
} }
@ -434,7 +434,7 @@ function openSync(path, flags, mode) {
path = getPathFromURL(path); path = getPathFromURL(path);
validatePath(path); validatePath(path);
const flagsNumber = stringToFlags(flags); const flagsNumber = stringToFlags(flags);
mode = validateAndMaskMode(mode, 'mode', 0o666); mode = validateMode(mode, 'mode', 0o666);
const ctx = { path }; const ctx = { path };
const result = binding.open(pathModule.toNamespacedPath(path), const result = binding.open(pathModule.toNamespacedPath(path),
@ -721,7 +721,7 @@ function mkdir(path, mode, callback) {
mode = 0o777; mode = 0o777;
} else { } else {
callback = makeCallback(callback); callback = makeCallback(callback);
mode = validateAndMaskMode(mode, 'mode', 0o777); mode = validateMode(mode, 'mode', 0o777);
} }
const req = new FSReqWrap(); const req = new FSReqWrap();
@ -732,7 +732,7 @@ function mkdir(path, mode, callback) {
function mkdirSync(path, mode) { function mkdirSync(path, mode) {
path = getPathFromURL(path); path = getPathFromURL(path);
validatePath(path); validatePath(path);
mode = validateAndMaskMode(mode, 'mode', 0o777); mode = validateMode(mode, 'mode', 0o777);
const ctx = { path }; const ctx = { path };
binding.mkdir(pathModule.toNamespacedPath(path), mode, undefined, ctx); binding.mkdir(pathModule.toNamespacedPath(path), mode, undefined, ctx);
handleErrorFromBinding(ctx); handleErrorFromBinding(ctx);
@ -915,7 +915,7 @@ function unlinkSync(path) {
function fchmod(fd, mode, callback) { function fchmod(fd, mode, callback) {
validateInt32(fd, 'fd', 0); validateInt32(fd, 'fd', 0);
mode = validateAndMaskMode(mode, 'mode'); mode = validateMode(mode, 'mode');
callback = makeCallback(callback); callback = makeCallback(callback);
const req = new FSReqWrap(); const req = new FSReqWrap();
@ -925,7 +925,7 @@ function fchmod(fd, mode, callback) {
function fchmodSync(fd, mode) { function fchmodSync(fd, mode) {
validateInt32(fd, 'fd', 0); validateInt32(fd, 'fd', 0);
mode = validateAndMaskMode(mode, 'mode'); mode = validateMode(mode, 'mode');
const ctx = {}; const ctx = {};
binding.fchmod(fd, mode, undefined, ctx); binding.fchmod(fd, mode, undefined, ctx);
handleErrorFromBinding(ctx); handleErrorFromBinding(ctx);
@ -966,7 +966,7 @@ function lchmodSync(path, mode) {
function chmod(path, mode, callback) { function chmod(path, mode, callback) {
path = getPathFromURL(path); path = getPathFromURL(path);
validatePath(path); validatePath(path);
mode = validateAndMaskMode(mode, 'mode'); mode = validateMode(mode, 'mode');
callback = makeCallback(callback); callback = makeCallback(callback);
const req = new FSReqWrap(); const req = new FSReqWrap();
@ -977,7 +977,7 @@ function chmod(path, mode, callback) {
function chmodSync(path, mode) { function chmodSync(path, mode) {
path = getPathFromURL(path); path = getPathFromURL(path);
validatePath(path); validatePath(path);
mode = validateAndMaskMode(mode, 'mode'); mode = validateMode(mode, 'mode');
const ctx = { path }; const ctx = { path };
binding.chmod(pathModule.toNamespacedPath(path), mode, undefined, ctx); binding.chmod(pathModule.toNamespacedPath(path), mode, undefined, ctx);

View File

@ -32,7 +32,7 @@ const {
} = require('internal/fs/utils'); } = require('internal/fs/utils');
const { const {
isUint32, isUint32,
validateAndMaskMode, validateMode,
validateInteger, validateInteger,
validateUint32 validateUint32
} = require('internal/validators'); } = require('internal/validators');
@ -192,7 +192,7 @@ async function copyFile(src, dest, flags) {
async function open(path, flags, mode) { async function open(path, flags, mode) {
path = getPathFromURL(path); path = getPathFromURL(path);
validatePath(path); validatePath(path);
mode = validateAndMaskMode(mode, 'mode', 0o666); mode = validateMode(mode, 'mode', 0o666);
return new FileHandle( return new FileHandle(
await binding.openFileHandle(pathModule.toNamespacedPath(path), await binding.openFileHandle(pathModule.toNamespacedPath(path),
stringToFlags(flags), stringToFlags(flags),
@ -287,7 +287,7 @@ async function fsync(handle) {
async function mkdir(path, mode) { async function mkdir(path, mode) {
path = getPathFromURL(path); path = getPathFromURL(path);
validatePath(path); validatePath(path);
mode = validateAndMaskMode(mode, 'mode', 0o777); mode = validateMode(mode, 'mode', 0o777);
return binding.mkdir(pathModule.toNamespacedPath(path), mode, kUsePromises); return binding.mkdir(pathModule.toNamespacedPath(path), mode, kUsePromises);
} }
@ -359,14 +359,14 @@ async function unlink(path) {
async function fchmod(handle, mode) { async function fchmod(handle, mode) {
validateFileHandle(handle); validateFileHandle(handle);
mode = validateAndMaskMode(mode, 'mode'); mode = validateMode(mode, 'mode');
return binding.fchmod(handle.fd, mode, kUsePromises); return binding.fchmod(handle.fd, mode, kUsePromises);
} }
async function chmod(path, mode) { async function chmod(path, mode) {
path = getPathFromURL(path); path = getPathFromURL(path);
validatePath(path); validatePath(path);
mode = validateAndMaskMode(mode, 'mode'); mode = validateMode(mode, 'mode');
return binding.chmod(pathModule.toNamespacedPath(path), mode, kUsePromises); return binding.chmod(pathModule.toNamespacedPath(path), mode, kUsePromises);
} }

View File

@ -5,7 +5,7 @@ const {
ERR_UNKNOWN_CREDENTIAL ERR_UNKNOWN_CREDENTIAL
} = require('internal/errors').codes; } = require('internal/errors').codes;
const { const {
validateAndMaskMode, validateMode,
validateUint32 validateUint32
} = require('internal/validators'); } = require('internal/validators');
@ -30,7 +30,7 @@ function setupProcessMethods(_chdir, _cpuUsage, _hrtime, _memoryUsage,
// Get the mask // Get the mask
return _umask(mask); return _umask(mask);
} }
mask = validateAndMaskMode(mask, 'mask'); mask = validateMode(mask, 'mask');
return _umask(mask); return _umask(mask);
}; };
} }

View File

@ -16,11 +16,22 @@ function isUint32(value) {
const octalReg = /^[0-7]+$/; const octalReg = /^[0-7]+$/;
const modeDesc = 'must be a 32-bit unsigned integer or an octal string'; const modeDesc = 'must be a 32-bit unsigned integer or an octal string';
// Validator for mode_t (the S_* constants). Valid numbers or octal strings
// will be masked with 0o777 to be consistent with the behavior in POSIX APIs. /**
function validateAndMaskMode(value, name, def) { * Validate values that will be converted into mode_t (the S_* constants).
* Only valid numbers and octal strings are allowed. They could be converted
* to 32-bit unsigned integers or non-negative signed integers in the C++
* land, but any value higher than 0o777 will result in platform-specific
* behaviors.
*
* @param {*} value Values to be validated
* @param {string} name Name of the argument
* @param {number} def If specified, will be returned for invalid values
* @returns {number}
*/
function validateMode(value, name, def) {
if (isUint32(value)) { if (isUint32(value)) {
return value & 0o777; return value;
} }
if (typeof value === 'number') { if (typeof value === 'number') {
@ -37,7 +48,7 @@ function validateAndMaskMode(value, name, def) {
throw new ERR_INVALID_ARG_VALUE(name, value, modeDesc); throw new ERR_INVALID_ARG_VALUE(name, value, modeDesc);
} }
const parsed = parseInt(value, 8); const parsed = parseInt(value, 8);
return parsed & 0o777; return parsed;
} }
// TODO(BridgeAR): Only return `def` in case `value == null` // TODO(BridgeAR): Only return `def` in case `value == null`
@ -106,7 +117,7 @@ function validateUint32(value, name, positive) {
module.exports = { module.exports = {
isInt32, isInt32,
isUint32, isUint32,
validateAndMaskMode, validateMode,
validateInteger, validateInteger,
validateInt32, validateInt32,
validateUint32 validateUint32

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
// This tests that mode > 0o777 will be masked off with 0o777 in fs APIs. // This tests that the lower bits of mode > 0o777 still works in fs APIs.
const common = require('../common'); const common = require('../common');
const assert = require('assert'); const assert = require('assert');

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
// This tests that mode > 0o777 will be masked off with 0o777 in fs.mkdir(). // This tests that the lower bits of mode > 0o777 still works in fs.mkdir().
const common = require('../common'); const common = require('../common');
const assert = require('assert'); const assert = require('assert');

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
// This tests that mode > 0o777 will be masked off with 0o777 in fs.open(). // This tests that the lower bits of mode > 0o777 still works in fs.open().
const common = require('../common'); const common = require('../common');
const assert = require('assert'); const assert = require('assert');

View File

@ -107,9 +107,8 @@ function verifyStatObject(stat) {
await chmod(dest, 0o666); await chmod(dest, 0o666);
await handle.chmod(0o666); await handle.chmod(0o666);
// Mode larger than 0o777 should be masked off. await chmod(dest, (0o10777));
await chmod(dest, (0o777 + 1)); await handle.chmod(0o10777);
await handle.chmod(0o777 + 1);
await utimes(dest, new Date(), new Date()); await utimes(dest, new Date(), new Date());

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
// This tests that mask > 0o777 will be masked off with 0o777 in // This tests that the lower bits of mode > 0o777 still works in
// process.umask() // process.umask()
const common = require('../common'); const common = require('../common');