diff --git a/doc/api/errors.md b/doc/api/errors.md index ff317f676a8881..84db161af8d4e0 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -1607,6 +1607,11 @@ A string that contained unescaped characters was received. An unhandled error occurred (for instance, when an `'error'` event is emitted by an [`EventEmitter`][] but an `'error'` handler is not registered). + +### ERR_UNKNOWN_CREDENTIAL + +A Unix group or user identifier that does not exist was passed. + ### ERR_UNKNOWN_ENCODING diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index f9bed03d269fb2..5993cc71f7d404 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -41,6 +41,7 @@ NativeModule.require('internal/process/warning').setup(); NativeModule.require('internal/process/next_tick').setup(); NativeModule.require('internal/process/stdio').setup(); + NativeModule.require('internal/process/methods').setup(); const perf = process.binding('performance'); const { diff --git a/lib/internal/errors.js b/lib/internal/errors.js index e7eb3bd133813a..882e40af88ae75 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -1017,6 +1017,7 @@ E('ERR_UNHANDLED_ERROR', if (err === undefined) return msg; return `${msg} (${err})`; }, Error); +E('ERR_UNKNOWN_CREDENTIAL', '%s identifier does not exist: %s', Error); E('ERR_UNKNOWN_ENCODING', 'Unknown encoding: %s', TypeError); // This should probably be a `TypeError`. diff --git a/lib/internal/process/methods.js b/lib/internal/process/methods.js new file mode 100644 index 00000000000000..503fd317f60395 --- /dev/null +++ b/lib/internal/process/methods.js @@ -0,0 +1,137 @@ +'use strict'; + +const { + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, + ERR_UNKNOWN_CREDENTIAL +} = require('internal/errors').codes; +const { + validateUint32 +} = require('internal/validators'); + +function setupProcessMethods() { + // Non-POSIX platforms like Windows don't have certain methods. + if (process.setgid !== undefined) { + setupPosixMethods(); + } + + const { + chdir: _chdir, + umask: _umask, + } = process; + + process.chdir = chdir; + process.umask = umask; + + function chdir(directory) { + if (typeof directory !== 'string') { + throw new ERR_INVALID_ARG_TYPE('directory', 'string', directory); + } + return _chdir(directory); + } + + const octalReg = /^[0-7]+$/; + function umask(mask) { + if (typeof mask === 'undefined') { + return _umask(mask); + } + + if (typeof mask === 'number') { + validateUint32(mask, 'mask'); + return _umask(mask); + } + + if (typeof mask === 'string') { + if (!octalReg.test(mask)) { + throw new ERR_INVALID_ARG_VALUE('mask', mask, + 'must be an octal string'); + } + const octal = Number.parseInt(mask, 8); + validateUint32(octal, 'mask'); + return _umask(octal); + } + + throw new ERR_INVALID_ARG_TYPE('mask', ['number', 'string', 'undefined'], + mask); + } +} + +function setupPosixMethods() { + const { + initgroups: _initgroups, + setegid: _setegid, + seteuid: _seteuid, + setgid: _setgid, + setuid: _setuid, + setgroups: _setgroups + } = process; + + process.initgroups = initgroups; + process.setegid = setegid; + process.seteuid = seteuid; + process.setgid = setgid; + process.setuid = setuid; + process.setgroups = setgroups; + + function initgroups(user, extraGroup) { + validateId(user, 'user'); + validateId(extraGroup, 'extraGroup'); + // Result is 0 on success, 1 if user is unknown, 2 if group is unknown. + const result = _initgroups(user, extraGroup); + if (result === 1) { + throw new ERR_UNKNOWN_CREDENTIAL('User', user); + } else if (result === 2) { + throw new ERR_UNKNOWN_CREDENTIAL('Group', extraGroup); + } + } + + function setegid(id) { + return execId(id, 'Group', _setegid); + } + + function seteuid(id) { + return execId(id, 'User', _seteuid); + } + + function setgid(id) { + return execId(id, 'Group', _setgid); + } + + function setuid(id) { + return execId(id, 'User', _setuid); + } + + function setgroups(groups) { + if (!Array.isArray(groups)) { + throw new ERR_INVALID_ARG_TYPE('groups', 'Array', groups); + } + for (var i = 0; i < groups.length; i++) { + validateId(groups[i], `groups[${i}]`); + } + // Result is 0 on success. A positive integer indicates that the + // corresponding group was not found. + const result = _setgroups(groups); + if (result > 0) { + throw new ERR_UNKNOWN_CREDENTIAL('Group', groups[result - 1]); + } + } + + function execId(id, type, method) { + validateId(id, 'id'); + // Result is 0 on success, 1 if credential is unknown. + const result = method(id); + if (result === 1) { + throw new ERR_UNKNOWN_CREDENTIAL(type, id); + } + } + + function validateId(id, name) { + if (typeof id === 'number') { + validateUint32(id, name); + } else if (typeof id !== 'string') { + throw new ERR_INVALID_ARG_TYPE(name, ['number', 'string'], id); + } + } +} + +exports.setup = setupProcessMethods; diff --git a/node.gyp b/node.gyp index 2c7eb994d247cc..ba65eafee9a49d 100644 --- a/node.gyp +++ b/node.gyp @@ -118,6 +118,7 @@ 'lib/internal/net.js', 'lib/internal/os.js', 'lib/internal/process/esm_loader.js', + 'lib/internal/process/methods.js', 'lib/internal/process/next_tick.js', 'lib/internal/process/promises.js', 'lib/internal/process/stdio.js', diff --git a/src/node.cc b/src/node.cc index 032963bff41a69..07dc4f13fdc07c 100644 --- a/src/node.cc +++ b/src/node.cc @@ -164,6 +164,7 @@ using v8::ScriptOrigin; using v8::SealHandleScope; using v8::String; using v8::TryCatch; +using v8::Uint32; using v8::Uint32Array; using v8::Undefined; using v8::V8; @@ -1580,10 +1581,8 @@ static void Abort(const FunctionCallbackInfo& args) { static void Chdir(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - if (args.Length() != 1 || !args[0]->IsString()) { - return env->ThrowTypeError("Bad argument."); - } - + CHECK_EQ(args.Length(), 1); + CHECK(args[0]->IsString()); node::Utf8Value path(args.GetIsolate(), args[0]); int err = uv_chdir(*path); if (err) { @@ -1616,32 +1615,16 @@ static void Cwd(const FunctionCallbackInfo& args) { static void Umask(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); uint32_t old; - if (args.Length() < 1 || args[0]->IsUndefined()) { + CHECK_EQ(args.Length(), 1); + CHECK(args[0]->IsUndefined() || args[0]->IsUint32()); + + if (args[0]->IsUndefined()) { old = umask(0); umask(static_cast(old)); - } else if (!args[0]->IsInt32() && !args[0]->IsString()) { - return env->ThrowTypeError("argument must be an integer or octal string."); } else { - int oct; - if (args[0]->IsInt32()) { - oct = args[0]->Uint32Value(); - } else { - oct = 0; - node::Utf8Value str(env->isolate(), args[0]); - - // Parse the octal string. - for (size_t i = 0; i < str.length(); i++) { - char c = (*str)[i]; - if (c > '7' || c < '0') { - return env->ThrowTypeError("invalid octal string"); - } - oct *= 8; - oct += c - '0'; - } - } + int oct = args[0].As()->Value(); old = umask(static_cast(oct)); } @@ -1779,18 +1762,18 @@ static void GetEGid(const FunctionCallbackInfo& args) { static void SetGid(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - if (!args[0]->IsUint32() && !args[0]->IsString()) { - return env->ThrowTypeError("setgid argument must be a number or a string"); - } + CHECK_EQ(args.Length(), 1); + CHECK(args[0]->IsUint32() || args[0]->IsString()); gid_t gid = gid_by_name(env->isolate(), args[0]); if (gid == gid_not_found) { - return env->ThrowError("setgid group id does not exist"); - } - - if (setgid(gid)) { - return env->ThrowErrnoException(errno, "setgid"); + // Tells JS to throw ERR_INVALID_CREDENTIAL + args.GetReturnValue().Set(1); + } else if (setgid(gid)) { + env->ThrowErrnoException(errno, "setgid"); + } else { + args.GetReturnValue().Set(0); } } @@ -1798,18 +1781,18 @@ static void SetGid(const FunctionCallbackInfo& args) { static void SetEGid(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - if (!args[0]->IsUint32() && !args[0]->IsString()) { - return env->ThrowTypeError("setegid argument must be a number or string"); - } + CHECK_EQ(args.Length(), 1); + CHECK(args[0]->IsUint32() || args[0]->IsString()); gid_t gid = gid_by_name(env->isolate(), args[0]); if (gid == gid_not_found) { - return env->ThrowError("setegid group id does not exist"); - } - - if (setegid(gid)) { - return env->ThrowErrnoException(errno, "setegid"); + // Tells JS to throw ERR_INVALID_CREDENTIAL + args.GetReturnValue().Set(1); + } else if (setegid(gid)) { + env->ThrowErrnoException(errno, "setegid"); + } else { + args.GetReturnValue().Set(0); } } @@ -1817,18 +1800,18 @@ static void SetEGid(const FunctionCallbackInfo& args) { static void SetUid(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - if (!args[0]->IsUint32() && !args[0]->IsString()) { - return env->ThrowTypeError("setuid argument must be a number or a string"); - } + CHECK_EQ(args.Length(), 1); + CHECK(args[0]->IsUint32() || args[0]->IsString()); uid_t uid = uid_by_name(env->isolate(), args[0]); if (uid == uid_not_found) { - return env->ThrowError("setuid user id does not exist"); - } - - if (setuid(uid)) { - return env->ThrowErrnoException(errno, "setuid"); + // Tells JS to throw ERR_INVALID_CREDENTIAL + args.GetReturnValue().Set(1); + } else if (setuid(uid)) { + env->ThrowErrnoException(errno, "setuid"); + } else { + args.GetReturnValue().Set(0); } } @@ -1836,18 +1819,18 @@ static void SetUid(const FunctionCallbackInfo& args) { static void SetEUid(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - if (!args[0]->IsUint32() && !args[0]->IsString()) { - return env->ThrowTypeError("seteuid argument must be a number or string"); - } + CHECK_EQ(args.Length(), 1); + CHECK(args[0]->IsUint32() || args[0]->IsString()); uid_t uid = uid_by_name(env->isolate(), args[0]); if (uid == uid_not_found) { - return env->ThrowError("seteuid user id does not exist"); - } - - if (seteuid(uid)) { - return env->ThrowErrnoException(errno, "seteuid"); + // Tells JS to throw ERR_INVALID_CREDENTIAL + args.GetReturnValue().Set(1); + } else if (seteuid(uid)) { + env->ThrowErrnoException(errno, "seteuid"); + } else { + args.GetReturnValue().Set(0); } } @@ -1893,9 +1876,8 @@ static void GetGroups(const FunctionCallbackInfo& args) { static void SetGroups(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - if (!args[0]->IsArray()) { - return env->ThrowTypeError("argument 1 must be an array"); - } + CHECK_EQ(args.Length(), 1); + CHECK(args[0]->IsArray()); Local groups_list = args[0].As(); size_t size = groups_list->Length(); @@ -1906,7 +1888,9 @@ static void SetGroups(const FunctionCallbackInfo& args) { if (gid == gid_not_found) { delete[] groups; - return env->ThrowError("group name not found"); + // Tells JS to throw ERR_INVALID_CREDENTIAL + args.GetReturnValue().Set(static_cast(i + 1)); + return; } groups[i] = gid; @@ -1918,19 +1902,17 @@ static void SetGroups(const FunctionCallbackInfo& args) { if (rc == -1) { return env->ThrowErrnoException(errno, "setgroups"); } + + args.GetReturnValue().Set(0); } static void InitGroups(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - if (!args[0]->IsUint32() && !args[0]->IsString()) { - return env->ThrowTypeError("argument 1 must be a number or a string"); - } - - if (!args[1]->IsUint32() && !args[1]->IsString()) { - return env->ThrowTypeError("argument 2 must be a number or a string"); - } + CHECK_EQ(args.Length(), 2); + CHECK(args[0]->IsUint32() || args[0]->IsString()); + CHECK(args[1]->IsUint32() || args[1]->IsString()); node::Utf8Value arg0(env->isolate(), args[0]); gid_t extra_group; @@ -1946,7 +1928,8 @@ static void InitGroups(const FunctionCallbackInfo& args) { } if (user == nullptr) { - return env->ThrowError("initgroups user not found"); + // Tells JS to throw ERR_INVALID_CREDENTIAL + return args.GetReturnValue().Set(1); } extra_group = gid_by_name(env->isolate(), args[1]); @@ -1954,7 +1937,8 @@ static void InitGroups(const FunctionCallbackInfo& args) { if (extra_group == gid_not_found) { if (must_free) free(user); - return env->ThrowError("initgroups extra group not found"); + // Tells JS to throw ERR_INVALID_CREDENTIAL + return args.GetReturnValue().Set(2); } int rc = initgroups(user, extra_group); @@ -1966,6 +1950,8 @@ static void InitGroups(const FunctionCallbackInfo& args) { if (rc) { return env->ThrowErrnoException(errno, "initgroups"); } + + args.GetReturnValue().Set(0); } #endif // __POSIX__ && !defined(__ANDROID__) && !defined(__CloudABI__) diff --git a/test/parallel/test-process-chdir.js b/test/parallel/test-process-chdir.js index c0a245ffd3483b..998147dd43dfc8 100644 --- a/test/parallel/test-process-chdir.js +++ b/test/parallel/test-process-chdir.js @@ -1,6 +1,6 @@ 'use strict'; -require('../common'); +const common = require('../common'); const assert = require('assert'); const fs = require('fs'); const path = require('path'); @@ -33,10 +33,9 @@ process.chdir('..'); assert.strictEqual(process.cwd().normalize(), path.resolve(tmpdir.path).normalize()); -const errMessage = /^TypeError: Bad argument\.$/; -assert.throws(function() { process.chdir({}); }, - errMessage, 'Bad argument.'); -assert.throws(function() { process.chdir(); }, - errMessage, 'Bad argument.'); -assert.throws(function() { process.chdir('x', 'y'); }, - errMessage, 'Bad argument.'); +const err = { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "directory" argument must be of type string/ +}; +common.expectsError(function() { process.chdir({}); }, err); +common.expectsError(function() { process.chdir(); }, err); diff --git a/test/parallel/test-process-euid-egid.js b/test/parallel/test-process-euid-egid.js index adae58abebdfb7..84fbd03e327b05 100644 --- a/test/parallel/test-process-euid-egid.js +++ b/test/parallel/test-process-euid-egid.js @@ -13,11 +13,18 @@ if (common.isWindows) { assert.throws(() => { process.seteuid({}); -}, /^TypeError: seteuid argument must be a number or string$/); +}, { + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "id" argument must be one of type number or string. ' + + 'Received type object' +}); assert.throws(() => { - process.seteuid('fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf'); -}, /^Error: seteuid user id does not exist$/); + process.seteuid('fhqwhgadshgnsdhjsdbkhsdabkfabkveyb'); +}, { + code: 'ERR_UNKNOWN_CREDENTIAL', + message: 'User identifier does not exist: fhqwhgadshgnsdhjsdbkhsdabkfabkveyb' +}); // If we're not running as super user... if (process.getuid() !== 0) { @@ -27,11 +34,11 @@ if (process.getuid() !== 0) { assert.throws(() => { process.setegid('nobody'); - }, /^Error: (?:EPERM, .+|setegid group id does not exist)$/); + }, /(?:EPERM, .+|Group identifier does not exist: nobody)$/); assert.throws(() => { process.seteuid('nobody'); - }, /^Error: (?:EPERM, .+|seteuid user id does not exist)$/); + }, /^Error: (?:EPERM, .+|User identifier does not exist: nobody)$/); return; } @@ -41,7 +48,7 @@ const oldgid = process.getegid(); try { process.setegid('nobody'); } catch (err) { - if (err.message !== 'setegid group id does not exist') { + if (err.message !== 'Group identifier does not exist: nobody') { throw err; } else { process.setegid('nogroup'); diff --git a/test/parallel/test-process-uid-gid.js b/test/parallel/test-process-uid-gid.js index d044c45a32783d..24751943092761 100644 --- a/test/parallel/test-process-uid-gid.js +++ b/test/parallel/test-process-uid-gid.js @@ -35,11 +35,18 @@ if (common.isWindows) { assert.throws(() => { process.setuid({}); -}, /^TypeError: setuid argument must be a number or a string$/); +}, { + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "id" argument must be one of type ' + + 'number or string. Received type object' +}); assert.throws(() => { - process.setuid('fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf'); -}, /^Error: setuid user id does not exist$/); + process.setuid('fhqwhgadshgnsdhjsdbkhsdabkfabkveyb'); +}, { + code: 'ERR_UNKNOWN_CREDENTIAL', + message: 'User identifier does not exist: fhqwhgadshgnsdhjsdbkhsdabkfabkveyb' +}); // If we're not running as super user... if (process.getuid() !== 0) { @@ -49,12 +56,12 @@ if (process.getuid() !== 0) { assert.throws( () => { process.setgid('nobody'); }, - /^Error: (?:EPERM, .+|setgid group id does not exist)$/ + /(?:EPERM, .+|Group identifier does not exist: nobody)$/ ); assert.throws( () => { process.setuid('nobody'); }, - /^Error: (?:EPERM, .+|setuid user id does not exist)$/ + /(?:EPERM, .+|User identifier does not exist: nobody)$/ ); return; } diff --git a/test/parallel/test-umask.js b/test/parallel/test-process-umask.js similarity index 82% rename from test/parallel/test-umask.js rename to test/parallel/test-process-umask.js index 1ac32cb7018906..869619f191a78b 100644 --- a/test/parallel/test-umask.js +++ b/test/parallel/test-process-umask.js @@ -43,8 +43,17 @@ assert.strictEqual(old, process.umask()); assert.throws(() => { process.umask({}); -}, /argument must be an integer or octal string/); +}, { + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "mask" argument must be one of type number, string, or ' + + 'undefined. Received type object' +}); -assert.throws(() => { - process.umask('123x'); -}, /invalid octal string/); +['123x', 'abc', '999'].forEach((value) => { + assert.throws(() => { + process.umask(value); + }, { + code: 'ERR_INVALID_ARG_VALUE', + message: `The argument 'mask' must be an octal string. Received '${value}'` + }); +});