diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index 35565769d21706..33d79689d0993f 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -23,8 +23,7 @@ const { _setupPromises, _chdir, _cpuUsage, _hrtime, _hrtimeBigInt, _memoryUsage, _rawDebug, - _umask, _initgroups, _setegid, _seteuid, - _setgid, _setuid, _setgroups, + _umask, _shouldAbortOnUncaughtToggle } = bootstrappers; const { internalBinding, NativeModule } = loaderExports; @@ -72,13 +71,28 @@ function startup() { NativeModule.require('internal/process/warning').setup(); NativeModule.require('internal/process/next_tick').setup(_setupNextTick, _setupPromises); + const credentials = internalBinding('credentials'); + if (credentials.implementsPosixCredentials) { + process.getuid = credentials.getuid; + process.geteuid = credentials.geteuid; + process.getgid = credentials.getgid; + process.getegid = credentials.getegid; + process.getgroups = credentials.getgroups; + + if (isMainThread) { + const wrapped = mainThreadSetup.wrapPosixCredentialSetters(credentials); + process.initgroups = wrapped.initgroups; + process.setgroups = wrapped.setgroups; + process.setegid = wrapped.setegid; + process.seteuid = wrapped.seteuid; + process.setgid = wrapped.setgid; + process.setuid = wrapped.setuid; + } + } if (isMainThread) { mainThreadSetup.setupStdio(); - mainThreadSetup.setupProcessMethods( - _chdir, _umask, _initgroups, _setegid, _seteuid, - _setgid, _setuid, _setgroups - ); + mainThreadSetup.setupProcessMethods(_chdir, _umask); } else { workerThreadSetup.setupStdio(); } diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index b70171cc5da8c1..bf6a9d4029c2a5 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -34,7 +34,7 @@ const { internalModuleReadJSON, internalModuleStat } = internalBinding('fs'); -const { safeGetenv } = internalBinding('util'); +const { safeGetenv } = internalBinding('credentials'); const { makeRequireFunction, requireDepth, diff --git a/lib/internal/process/main_thread_only.js b/lib/internal/process/main_thread_only.js index b8b7a454dc2f9f..5f53f3408406b9 100644 --- a/lib/internal/process/main_thread_only.js +++ b/lib/internal/process/main_thread_only.js @@ -29,13 +29,7 @@ function setupStdio() { // Non-POSIX platforms like Windows don't have certain methods. // Workers also lack these methods since they change process-global state. -function setupProcessMethods(_chdir, _umask, _initgroups, _setegid, - _seteuid, _setgid, _setuid, _setgroups) { - if (_setgid !== undefined) { - setupPosixMethods(_initgroups, _setegid, _seteuid, - _setgid, _setuid, _setgroups); - } - +function setupProcessMethods(_chdir, _umask) { process.chdir = function chdir(directory) { validateString(directory, 'directory'); return _chdir(directory); @@ -51,10 +45,17 @@ function setupProcessMethods(_chdir, _umask, _initgroups, _setegid, }; } -function setupPosixMethods(_initgroups, _setegid, _seteuid, - _setgid, _setuid, _setgroups) { - - process.initgroups = function initgroups(user, extraGroup) { +function wrapPosixCredentialSetters(credentials) { + const { + initgroups: _initgroups, + setgroups: _setgroups, + setegid: _setegid, + seteuid: _seteuid, + setgid: _setgid, + setuid: _setuid + } = credentials; + + 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. @@ -64,25 +65,9 @@ function setupPosixMethods(_initgroups, _setegid, _seteuid, } else if (result === 2) { throw new ERR_UNKNOWN_CREDENTIAL('Group', extraGroup); } - }; - - process.setegid = function setegid(id) { - return execId(id, 'Group', _setegid); - }; - - process.seteuid = function seteuid(id) { - return execId(id, 'User', _seteuid); - }; - - process.setgid = function setgid(id) { - return execId(id, 'Group', _setgid); - }; - - process.setuid = function setuid(id) { - return execId(id, 'User', _setuid); - }; + } - process.setgroups = function setgroups(groups) { + function setgroups(groups) { if (!Array.isArray(groups)) { throw new ERR_INVALID_ARG_TYPE('groups', 'Array', groups); } @@ -95,15 +80,17 @@ function setupPosixMethods(_initgroups, _setegid, _seteuid, 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 wrapIdSetter(type, method) { + return function(id) { + 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) { @@ -113,6 +100,15 @@ function setupPosixMethods(_initgroups, _setegid, _seteuid, throw new ERR_INVALID_ARG_TYPE(name, ['number', 'string'], id); } } + + return { + initgroups, + setgroups, + setegid: wrapIdSetter('Group', _setegid), + seteuid: wrapIdSetter('User', _seteuid), + setgid: wrapIdSetter('Group', _setgid), + setuid: wrapIdSetter('User', _setuid) + }; } // Worker threads don't receive signals. @@ -181,5 +177,6 @@ module.exports = { setupStdio, setupProcessMethods, setupSignalHandlers, - setupChildProcessIpcChannel + setupChildProcessIpcChannel, + wrapPosixCredentialSetters }; diff --git a/lib/os.js b/lib/os.js index 2c806908eeac98..28e1d7797f65dc 100644 --- a/lib/os.js +++ b/lib/os.js @@ -21,7 +21,7 @@ 'use strict'; -const { safeGetenv } = internalBinding('util'); +const { safeGetenv } = internalBinding('credentials'); const constants = internalBinding('constants').os; const { deprecate } = require('internal/util'); const isWindows = process.platform === 'win32'; diff --git a/node.gyp b/node.gyp index db8443d813e789..b337de0a422be6 100644 --- a/node.gyp +++ b/node.gyp @@ -350,6 +350,7 @@ 'src/node_config.cc', 'src/node_constants.cc', 'src/node_contextify.cc', + 'src/node_credentials.cc', 'src/node_domain.cc', 'src/node_encoding.cc', 'src/node_env_var.cc', diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc index 2c8212177b41fa..0a1cfed2109e5c 100644 --- a/src/bootstrapper.cc +++ b/src/bootstrapper.cc @@ -147,17 +147,6 @@ void SetupBootstrapObject(Environment* env, BOOTSTRAP_METHOD(_rawDebug, RawDebug); BOOTSTRAP_METHOD(_umask, Umask); -#if defined(__POSIX__) && !defined(__ANDROID__) && !defined(__CloudABI__) - if (env->is_main_thread()) { - BOOTSTRAP_METHOD(_initgroups, InitGroups); - BOOTSTRAP_METHOD(_setegid, SetEGid); - BOOTSTRAP_METHOD(_seteuid, SetEUid); - BOOTSTRAP_METHOD(_setgid, SetGid); - BOOTSTRAP_METHOD(_setuid, SetUid); - BOOTSTRAP_METHOD(_setgroups, SetGroups); - } -#endif // __POSIX__ && !defined(__ANDROID__) && !defined(__CloudABI__) - Local should_abort_on_uncaught_toggle = FIXED_ONE_BYTE_STRING(env->isolate(), "_shouldAbortOnUncaughtToggle"); CHECK(bootstrapper->Set(env->context(), diff --git a/src/env.cc b/src/env.cc index ed6717d684421b..1e65dfdac88ae2 100644 --- a/src/env.cc +++ b/src/env.cc @@ -225,7 +225,7 @@ Environment::Environment(IsolateData* isolate_data, should_abort_on_uncaught_toggle_[0] = 1; std::string debug_cats; - SafeGetenv("NODE_DEBUG_NATIVE", &debug_cats); + credentials::SafeGetenv("NODE_DEBUG_NATIVE", &debug_cats); set_debug_categories(debug_cats, true); isolate()->GetHeapProfiler()->AddBuildEmbedderGraphCallback( diff --git a/src/node.cc b/src/node.cc index d830b846c90f6e..58722f23b31253 100644 --- a/src/node.cc +++ b/src/node.cc @@ -100,12 +100,7 @@ typedef int mode_t; #else #include #include // getrlimit, setrlimit -#include // setuid, getuid -#endif - -#if defined(__POSIX__) && !defined(__ANDROID__) && !defined(__CloudABI__) -#include // getpwnam() -#include // getgrnam() +#include // STDIN_FILENO, STDERR_FILENO #endif namespace node { @@ -153,8 +148,6 @@ unsigned int reverted = 0; bool v8_initialized = false; -bool linux_at_secure = false; - // process-relative uptime base, initialized at start-up double prog_start_time; @@ -501,27 +494,6 @@ const char* signo_string(int signo) { } } -// Look up environment variable unless running as setuid root. -bool SafeGetenv(const char* key, std::string* text) { -#if !defined(__CloudABI__) && !defined(_WIN32) - if (linux_at_secure || getuid() != geteuid() || getgid() != getegid()) - goto fail; -#endif - - { - Mutex::ScopedLock lock(environ_mutex); - if (const char* value = getenv(key)) { - *text = value; - return true; - } - } - -fail: - text->clear(); - return false; -} - - void* ArrayBufferAllocator::Allocate(size_t size) { if (zero_fill_field_ || per_process_opts->zero_fill_all_buffers) return UncheckedCalloc(size); @@ -1157,14 +1129,6 @@ void SetupProcessObject(Environment* env, env->SetMethod(process, "dlopen", binding::DLOpen); env->SetMethod(process, "reallyExit", Exit); env->SetMethodNoSideEffect(process, "uptime", Uptime); - -#if defined(__POSIX__) && !defined(__ANDROID__) && !defined(__CloudABI__) - env->SetMethodNoSideEffect(process, "getuid", GetUid); - env->SetMethodNoSideEffect(process, "geteuid", GetEUid); - env->SetMethodNoSideEffect(process, "getgid", GetGid); - env->SetMethodNoSideEffect(process, "getegid", GetEGid); - env->SetMethodNoSideEffect(process, "getgroups", GetGroups); -#endif // __POSIX__ && !defined(__ANDROID__) && !defined(__CloudABI__) } @@ -1625,37 +1589,40 @@ void Init(std::vector* argv, { std::string text; default_env_options->pending_deprecation = - SafeGetenv("NODE_PENDING_DEPRECATION", &text) && text[0] == '1'; + credentials::SafeGetenv("NODE_PENDING_DEPRECATION", &text) && + text[0] == '1'; } // Allow for environment set preserving symlinks. { std::string text; default_env_options->preserve_symlinks = - SafeGetenv("NODE_PRESERVE_SYMLINKS", &text) && text[0] == '1'; + credentials::SafeGetenv("NODE_PRESERVE_SYMLINKS", &text) && + text[0] == '1'; } { std::string text; default_env_options->preserve_symlinks_main = - SafeGetenv("NODE_PRESERVE_SYMLINKS_MAIN", &text) && text[0] == '1'; + credentials::SafeGetenv("NODE_PRESERVE_SYMLINKS_MAIN", &text) && + text[0] == '1'; } if (default_env_options->redirect_warnings.empty()) { - SafeGetenv("NODE_REDIRECT_WARNINGS", - &default_env_options->redirect_warnings); + credentials::SafeGetenv("NODE_REDIRECT_WARNINGS", + &default_env_options->redirect_warnings); } #if HAVE_OPENSSL std::string* openssl_config = &per_process_opts->openssl_config; if (openssl_config->empty()) { - SafeGetenv("OPENSSL_CONF", openssl_config); + credentials::SafeGetenv("OPENSSL_CONF", openssl_config); } #endif #if !defined(NODE_WITHOUT_NODE_OPTIONS) std::string node_options; - if (SafeGetenv("NODE_OPTIONS", &node_options)) { + if (credentials::SafeGetenv("NODE_OPTIONS", &node_options)) { std::vector env_argv; // [0] is expected to be the program name, fill it in from the real argv. env_argv.push_back(argv->at(0)); @@ -1687,7 +1654,7 @@ void Init(std::vector* argv, #if defined(NODE_HAVE_I18N_SUPPORT) // If the parameter isn't given, use the env variable. if (per_process_opts->icu_data_dir.empty()) - SafeGetenv("NODE_ICU_DATA", &per_process_opts->icu_data_dir); + credentials::SafeGetenv("NODE_ICU_DATA", &per_process_opts->icu_data_dir); // Initialize ICU. // If icu_data_dir is empty here, it will load the 'minimal' data. if (!i18n::InitializeICUDirectory(per_process_opts->icu_data_dir)) { @@ -2095,7 +2062,7 @@ int Start(int argc, char** argv) { #if HAVE_OPENSSL { std::string extra_ca_certs; - if (SafeGetenv("NODE_EXTRA_CA_CERTS", &extra_ca_certs)) + if (credentials::SafeGetenv("NODE_EXTRA_CA_CERTS", &extra_ca_certs)) crypto::UseExtraCaCerts(extra_ca_certs); } #ifdef NODE_FIPS_MODE diff --git a/src/node_binding.cc b/src/node_binding.cc index 4a47c744c2d7fb..1e78f7c17f7db3 100644 --- a/src/node_binding.cc +++ b/src/node_binding.cc @@ -30,6 +30,7 @@ V(cares_wrap) \ V(config) \ V(contextify) \ + V(credentials) \ V(domain) \ V(fs) \ V(fs_event_wrap) \ diff --git a/src/node_credentials.cc b/src/node_credentials.cc new file mode 100644 index 00000000000000..0f202dfb753bf7 --- /dev/null +++ b/src/node_credentials.cc @@ -0,0 +1,395 @@ +#include "node_internals.h" + +#ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS +#include // getgrnam() +#include // getpwnam() +#endif // NODE_IMPLEMENTS_POSIX_CREDENTIALS + +#if !defined(_MSC_VER) +#include // setuid, getuid +#endif + +namespace node { + +using v8::Array; +using v8::Context; +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::Integer; +using v8::Isolate; +using v8::Local; +using v8::Object; +using v8::String; +using v8::Uint32; +using v8::Value; + +namespace per_process { +bool linux_at_secure = false; +} // namespace per_process + +namespace credentials { + +// Look up environment variable unless running as setuid root. +bool SafeGetenv(const char* key, std::string* text) { +#if !defined(__CloudABI__) && !defined(_WIN32) + if (per_process::linux_at_secure || getuid() != geteuid() || + getgid() != getegid()) + goto fail; +#endif + + { + Mutex::ScopedLock lock(environ_mutex); + if (const char* value = getenv(key)) { + *text = value; + return true; + } + } + +fail: + text->clear(); + return false; +} + +static void SafeGetenv(const FunctionCallbackInfo& args) { + CHECK(args[0]->IsString()); + Isolate* isolate = args.GetIsolate(); + Utf8Value strenvtag(isolate, args[0]); + std::string text; + if (!SafeGetenv(*strenvtag, &text)) return; + Local result = + ToV8Value(isolate->GetCurrentContext(), text).ToLocalChecked(); + args.GetReturnValue().Set(result); +} + +#ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS + +static const uid_t uid_not_found = static_cast(-1); +static const gid_t gid_not_found = static_cast(-1); + +static uid_t uid_by_name(const char* name) { + struct passwd pwd; + struct passwd* pp; + char buf[8192]; + + errno = 0; + pp = nullptr; + + if (getpwnam_r(name, &pwd, buf, sizeof(buf), &pp) == 0 && pp != nullptr) + return pp->pw_uid; + + return uid_not_found; +} + +static char* name_by_uid(uid_t uid) { + struct passwd pwd; + struct passwd* pp; + char buf[8192]; + int rc; + + errno = 0; + pp = nullptr; + + if ((rc = getpwuid_r(uid, &pwd, buf, sizeof(buf), &pp)) == 0 && + pp != nullptr) { + return strdup(pp->pw_name); + } + + if (rc == 0) errno = ENOENT; + + return nullptr; +} + +static gid_t gid_by_name(const char* name) { + struct group pwd; + struct group* pp; + char buf[8192]; + + errno = 0; + pp = nullptr; + + if (getgrnam_r(name, &pwd, buf, sizeof(buf), &pp) == 0 && pp != nullptr) + return pp->gr_gid; + + return gid_not_found; +} + +#if 0 // For future use. +static const char* name_by_gid(gid_t gid) { + struct group pwd; + struct group* pp; + char buf[8192]; + int rc; + + errno = 0; + pp = nullptr; + + if ((rc = getgrgid_r(gid, &pwd, buf, sizeof(buf), &pp)) == 0 && + pp != nullptr) { + return strdup(pp->gr_name); + } + + if (rc == 0) + errno = ENOENT; + + return nullptr; +} +#endif + +static uid_t uid_by_name(Isolate* isolate, Local value) { + if (value->IsUint32()) { + return static_cast(value.As()->Value()); + } else { + Utf8Value name(isolate, value); + return uid_by_name(*name); + } +} + +static gid_t gid_by_name(Isolate* isolate, Local value) { + if (value->IsUint32()) { + return static_cast(value.As()->Value()); + } else { + Utf8Value name(isolate, value); + return gid_by_name(*name); + } +} + +static void GetUid(const FunctionCallbackInfo& args) { + // uid_t is an uint32_t on all supported platforms. + args.GetReturnValue().Set(static_cast(getuid())); +} + +static void GetGid(const FunctionCallbackInfo& args) { + // gid_t is an uint32_t on all supported platforms. + args.GetReturnValue().Set(static_cast(getgid())); +} + +static void GetEUid(const FunctionCallbackInfo& args) { + // uid_t is an uint32_t on all supported platforms. + args.GetReturnValue().Set(static_cast(geteuid())); +} + +static void GetEGid(const FunctionCallbackInfo& args) { + // gid_t is an uint32_t on all supported platforms. + args.GetReturnValue().Set(static_cast(getegid())); +} + +static void SetGid(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CHECK(env->is_main_thread()); + + 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) { + // Tells JS to throw ERR_INVALID_CREDENTIAL + args.GetReturnValue().Set(1); + } else if (setgid(gid)) { + env->ThrowErrnoException(errno, "setgid"); + } else { + args.GetReturnValue().Set(0); + } +} + +static void SetEGid(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CHECK(env->is_main_thread()); + + 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) { + // Tells JS to throw ERR_INVALID_CREDENTIAL + args.GetReturnValue().Set(1); + } else if (setegid(gid)) { + env->ThrowErrnoException(errno, "setegid"); + } else { + args.GetReturnValue().Set(0); + } +} + +static void SetUid(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CHECK(env->is_main_thread()); + + 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) { + // Tells JS to throw ERR_INVALID_CREDENTIAL + args.GetReturnValue().Set(1); + } else if (setuid(uid)) { + env->ThrowErrnoException(errno, "setuid"); + } else { + args.GetReturnValue().Set(0); + } +} + +static void SetEUid(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CHECK(env->is_main_thread()); + + 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) { + // Tells JS to throw ERR_INVALID_CREDENTIAL + args.GetReturnValue().Set(1); + } else if (seteuid(uid)) { + env->ThrowErrnoException(errno, "seteuid"); + } else { + args.GetReturnValue().Set(0); + } +} + +static void GetGroups(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + int ngroups = getgroups(0, nullptr); + + if (ngroups == -1) return env->ThrowErrnoException(errno, "getgroups"); + + gid_t* groups = new gid_t[ngroups]; + + ngroups = getgroups(ngroups, groups); + + if (ngroups == -1) { + delete[] groups; + return env->ThrowErrnoException(errno, "getgroups"); + } + + Local groups_list = Array::New(env->isolate(), ngroups); + bool seen_egid = false; + gid_t egid = getegid(); + + for (int i = 0; i < ngroups; i++) { + groups_list->Set(env->context(), i, Integer::New(env->isolate(), groups[i])) + .FromJust(); + if (groups[i] == egid) seen_egid = true; + } + + delete[] groups; + + if (seen_egid == false) + groups_list + ->Set(env->context(), ngroups, Integer::New(env->isolate(), egid)) + .FromJust(); + + args.GetReturnValue().Set(groups_list); +} + +static void SetGroups(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + CHECK_EQ(args.Length(), 1); + CHECK(args[0]->IsArray()); + + Local groups_list = args[0].As(); + size_t size = groups_list->Length(); + gid_t* groups = new gid_t[size]; + + for (size_t i = 0; i < size; i++) { + gid_t gid = gid_by_name( + env->isolate(), groups_list->Get(env->context(), i).ToLocalChecked()); + + if (gid == gid_not_found) { + delete[] groups; + // Tells JS to throw ERR_INVALID_CREDENTIAL + args.GetReturnValue().Set(static_cast(i + 1)); + return; + } + + groups[i] = gid; + } + + int rc = setgroups(size, groups); + delete[] groups; + + if (rc == -1) return env->ThrowErrnoException(errno, "setgroups"); + + args.GetReturnValue().Set(0); +} + +static void InitGroups(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + CHECK_EQ(args.Length(), 2); + CHECK(args[0]->IsUint32() || args[0]->IsString()); + CHECK(args[1]->IsUint32() || args[1]->IsString()); + + Utf8Value arg0(env->isolate(), args[0]); + gid_t extra_group; + bool must_free; + char* user; + + if (args[0]->IsUint32()) { + user = name_by_uid(args[0].As()->Value()); + must_free = true; + } else { + user = *arg0; + must_free = false; + } + + if (user == nullptr) { + // Tells JS to throw ERR_INVALID_CREDENTIAL + return args.GetReturnValue().Set(1); + } + + extra_group = gid_by_name(env->isolate(), args[1]); + + if (extra_group == gid_not_found) { + if (must_free) free(user); + // Tells JS to throw ERR_INVALID_CREDENTIAL + return args.GetReturnValue().Set(2); + } + + int rc = initgroups(user, extra_group); + + if (must_free) free(user); + + if (rc) return env->ThrowErrnoException(errno, "initgroups"); + + args.GetReturnValue().Set(0); +} + +#endif // NODE_IMPLEMENTS_POSIX_CREDENTIALS + +static void Initialize(Local target, + Local unused, + Local context, + void* priv) { + Environment* env = Environment::GetCurrent(context); + Isolate* isolate = env->isolate(); + + env->SetMethod(target, "safeGetenv", SafeGetenv); + +#ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS + READONLY_TRUE_PROPERTY(target, "implementsPosixCredentials"); + env->SetMethodNoSideEffect(target, "getuid", GetUid); + env->SetMethodNoSideEffect(target, "geteuid", GetEUid); + env->SetMethodNoSideEffect(target, "getgid", GetGid); + env->SetMethodNoSideEffect(target, "getegid", GetEGid); + env->SetMethodNoSideEffect(target, "getgroups", GetGroups); + + if (env->is_main_thread()) { + env->SetMethod(target, "initgroups", InitGroups); + env->SetMethod(target, "setegid", SetEGid); + env->SetMethod(target, "seteuid", SetEUid); + env->SetMethod(target, "setgid", SetGid); + env->SetMethod(target, "setuid", SetUid); + env->SetMethod(target, "setgroups", SetGroups); + } +#endif // NODE_IMPLEMENTS_POSIX_CREDENTIALS +} + +} // namespace credentials +} // namespace node + +NODE_MODULE_CONTEXT_AWARE_INTERNAL(credentials, node::credentials::Initialize) diff --git a/src/node_internals.h b/src/node_internals.h index d9a72a10a440ea..2c6aad5b967c3d 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -126,7 +126,6 @@ void RegisterSignalHandler(int signal, bool reset_handler = false); #endif -bool SafeGetenv(const char* key, std::string* text); v8::Local CreateEnvVarProxy(v8::Local context, v8::Isolate* isolate, v8::Local data); @@ -734,19 +733,13 @@ void ProcessTitleSetter(v8::Local property, const v8::PropertyCallbackInfo& info); #if defined(__POSIX__) && !defined(__ANDROID__) && !defined(__CloudABI__) -void SetGid(const v8::FunctionCallbackInfo& args); -void SetEGid(const v8::FunctionCallbackInfo& args); -void SetUid(const v8::FunctionCallbackInfo& args); -void SetEUid(const v8::FunctionCallbackInfo& args); -void SetGroups(const v8::FunctionCallbackInfo& args); -void InitGroups(const v8::FunctionCallbackInfo& args); -void GetUid(const v8::FunctionCallbackInfo& args); -void GetGid(const v8::FunctionCallbackInfo& args); -void GetEUid(const v8::FunctionCallbackInfo& args); -void GetEGid(const v8::FunctionCallbackInfo& args); -void GetGroups(const v8::FunctionCallbackInfo& args); +#define NODE_IMPLEMENTS_POSIX_CREDENTIALS 1 #endif // __POSIX__ && !defined(__ANDROID__) && !defined(__CloudABI__) +namespace credentials { +bool SafeGetenv(const char* key, std::string* text); +} // namespace credentials + void DefineZlibConstants(v8::Local target); } // namespace node diff --git a/src/node_main.cc b/src/node_main.cc index bea1af7bdde5e3..7107aea8c1a455 100644 --- a/src/node_main.cc +++ b/src/node_main.cc @@ -88,7 +88,9 @@ extern char** environ; #endif namespace node { - extern bool linux_at_secure; +namespace per_process { +extern bool linux_at_secure; +} // namespace per_process } // namespace node int main(int argc, char* argv[]) { @@ -112,7 +114,7 @@ int main(int argc, char* argv[]) { Elf_auxv_t* auxv = reinterpret_cast(envp); for (; auxv->a_type != AT_NULL; auxv++) { if (auxv->a_type == AT_SECURE) { - node::linux_at_secure = auxv->a_un.a_val; + node::per_process::linux_at_secure = auxv->a_un.a_val; break; } } diff --git a/src/node_process.cc b/src/node_process.cc index ccd8ce2fd92b83..b9376953e241be 100644 --- a/src/node_process.cc +++ b/src/node_process.cc @@ -24,12 +24,6 @@ typedef int mode_t; #include #include // getrlimit, setrlimit #include // tcgetattr, tcsetattr -#include // setuid, getuid -#endif - -#if defined(__POSIX__) && !defined(__ANDROID__) && !defined(__CloudABI__) -#include // getpwnam() -#include // getgrnam() #endif namespace node { @@ -42,7 +36,6 @@ using v8::Float64Array; using v8::Function; using v8::FunctionCallbackInfo; using v8::HeapStatistics; -using v8::Integer; using v8::Isolate; using v8::Local; using v8::Name; @@ -254,331 +247,6 @@ void Uptime(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(uptime / 1000); } - -#if defined(__POSIX__) && !defined(__ANDROID__) && !defined(__CloudABI__) - -static const uid_t uid_not_found = static_cast(-1); -static const gid_t gid_not_found = static_cast(-1); - - -static uid_t uid_by_name(const char* name) { - struct passwd pwd; - struct passwd* pp; - char buf[8192]; - - errno = 0; - pp = nullptr; - - if (getpwnam_r(name, &pwd, buf, sizeof(buf), &pp) == 0 && pp != nullptr) - return pp->pw_uid; - - return uid_not_found; -} - - -static char* name_by_uid(uid_t uid) { - struct passwd pwd; - struct passwd* pp; - char buf[8192]; - int rc; - - errno = 0; - pp = nullptr; - - if ((rc = getpwuid_r(uid, &pwd, buf, sizeof(buf), &pp)) == 0 && - pp != nullptr) { - return strdup(pp->pw_name); - } - - if (rc == 0) - errno = ENOENT; - - return nullptr; -} - - -static gid_t gid_by_name(const char* name) { - struct group pwd; - struct group* pp; - char buf[8192]; - - errno = 0; - pp = nullptr; - - if (getgrnam_r(name, &pwd, buf, sizeof(buf), &pp) == 0 && pp != nullptr) - return pp->gr_gid; - - return gid_not_found; -} - - -#if 0 // For future use. -static const char* name_by_gid(gid_t gid) { - struct group pwd; - struct group* pp; - char buf[8192]; - int rc; - - errno = 0; - pp = nullptr; - - if ((rc = getgrgid_r(gid, &pwd, buf, sizeof(buf), &pp)) == 0 && - pp != nullptr) { - return strdup(pp->gr_name); - } - - if (rc == 0) - errno = ENOENT; - - return nullptr; -} -#endif - - -static uid_t uid_by_name(Isolate* isolate, Local value) { - if (value->IsUint32()) { - return static_cast(value.As()->Value()); - } else { - Utf8Value name(isolate, value); - return uid_by_name(*name); - } -} - - -static gid_t gid_by_name(Isolate* isolate, Local value) { - if (value->IsUint32()) { - return static_cast(value.As()->Value()); - } else { - Utf8Value name(isolate, value); - return gid_by_name(*name); - } -} - -void GetUid(const FunctionCallbackInfo& args) { - // uid_t is an uint32_t on all supported platforms. - args.GetReturnValue().Set(static_cast(getuid())); -} - - -void GetGid(const FunctionCallbackInfo& args) { - // gid_t is an uint32_t on all supported platforms. - args.GetReturnValue().Set(static_cast(getgid())); -} - - -void GetEUid(const FunctionCallbackInfo& args) { - // uid_t is an uint32_t on all supported platforms. - args.GetReturnValue().Set(static_cast(geteuid())); -} - - -void GetEGid(const FunctionCallbackInfo& args) { - // gid_t is an uint32_t on all supported platforms. - args.GetReturnValue().Set(static_cast(getegid())); -} - - -void SetGid(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - CHECK(env->is_main_thread()); - - 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) { - // Tells JS to throw ERR_INVALID_CREDENTIAL - args.GetReturnValue().Set(1); - } else if (setgid(gid)) { - env->ThrowErrnoException(errno, "setgid"); - } else { - args.GetReturnValue().Set(0); - } -} - - -void SetEGid(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - CHECK(env->is_main_thread()); - - 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) { - // Tells JS to throw ERR_INVALID_CREDENTIAL - args.GetReturnValue().Set(1); - } else if (setegid(gid)) { - env->ThrowErrnoException(errno, "setegid"); - } else { - args.GetReturnValue().Set(0); - } -} - - -void SetUid(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - CHECK(env->is_main_thread()); - - 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) { - // Tells JS to throw ERR_INVALID_CREDENTIAL - args.GetReturnValue().Set(1); - } else if (setuid(uid)) { - env->ThrowErrnoException(errno, "setuid"); - } else { - args.GetReturnValue().Set(0); - } -} - - -void SetEUid(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - CHECK(env->is_main_thread()); - - 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) { - // Tells JS to throw ERR_INVALID_CREDENTIAL - args.GetReturnValue().Set(1); - } else if (seteuid(uid)) { - env->ThrowErrnoException(errno, "seteuid"); - } else { - args.GetReturnValue().Set(0); - } -} - - -void GetGroups(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - int ngroups = getgroups(0, nullptr); - - if (ngroups == -1) - return env->ThrowErrnoException(errno, "getgroups"); - - gid_t* groups = new gid_t[ngroups]; - - ngroups = getgroups(ngroups, groups); - - if (ngroups == -1) { - delete[] groups; - return env->ThrowErrnoException(errno, "getgroups"); - } - - Local groups_list = Array::New(env->isolate(), ngroups); - bool seen_egid = false; - gid_t egid = getegid(); - - for (int i = 0; i < ngroups; i++) { - groups_list->Set(env->context(), - i, Integer::New(env->isolate(), groups[i])).FromJust(); - if (groups[i] == egid) - seen_egid = true; - } - - delete[] groups; - - if (seen_egid == false) - groups_list->Set(env->context(), - ngroups, - Integer::New(env->isolate(), egid)).FromJust(); - - args.GetReturnValue().Set(groups_list); -} - - -void SetGroups(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - CHECK_EQ(args.Length(), 1); - CHECK(args[0]->IsArray()); - - Local groups_list = args[0].As(); - size_t size = groups_list->Length(); - gid_t* groups = new gid_t[size]; - - for (size_t i = 0; i < size; i++) { - gid_t gid = gid_by_name(env->isolate(), - groups_list->Get(env->context(), - i).ToLocalChecked()); - - if (gid == gid_not_found) { - delete[] groups; - // Tells JS to throw ERR_INVALID_CREDENTIAL - args.GetReturnValue().Set(static_cast(i + 1)); - return; - } - - groups[i] = gid; - } - - int rc = setgroups(size, groups); - delete[] groups; - - if (rc == -1) - return env->ThrowErrnoException(errno, "setgroups"); - - args.GetReturnValue().Set(0); -} - - -void InitGroups(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - CHECK_EQ(args.Length(), 2); - CHECK(args[0]->IsUint32() || args[0]->IsString()); - CHECK(args[1]->IsUint32() || args[1]->IsString()); - - Utf8Value arg0(env->isolate(), args[0]); - gid_t extra_group; - bool must_free; - char* user; - - if (args[0]->IsUint32()) { - user = name_by_uid(args[0].As()->Value()); - must_free = true; - } else { - user = *arg0; - must_free = false; - } - - if (user == nullptr) { - // Tells JS to throw ERR_INVALID_CREDENTIAL - return args.GetReturnValue().Set(1); - } - - extra_group = gid_by_name(env->isolate(), args[1]); - - if (extra_group == gid_not_found) { - if (must_free) - free(user); - // Tells JS to throw ERR_INVALID_CREDENTIAL - return args.GetReturnValue().Set(2); - } - - int rc = initgroups(user, extra_group); - - if (must_free) - free(user); - - if (rc) - return env->ThrowErrnoException(errno, "initgroups"); - - args.GetReturnValue().Set(0); -} - -#endif // __POSIX__ && !defined(__ANDROID__) && !defined(__CloudABI__) - void ProcessTitleGetter(Local property, const PropertyCallbackInfo& info) { char buffer[512]; diff --git a/src/node_util.cc b/src/node_util.cc index f6ebee895fe3bf..f7412d92bcd8e7 100644 --- a/src/node_util.cc +++ b/src/node_util.cc @@ -15,7 +15,6 @@ using v8::Integer; using v8::Isolate; using v8::KeyCollectionMode; using v8::Local; -using v8::NewStringType; using v8::Object; using v8::ONLY_CONFIGURABLE; using v8::ONLY_ENUMERABLE; @@ -172,17 +171,6 @@ void WatchdogHasPendingSigint(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(ret); } -void SafeGetenv(const FunctionCallbackInfo& args) { - CHECK(args[0]->IsString()); - Utf8Value strenvtag(args.GetIsolate(), args[0]); - std::string text; - if (!node::SafeGetenv(*strenvtag, &text)) return; - args.GetReturnValue() - .Set(String::NewFromUtf8( - args.GetIsolate(), text.c_str(), - NewStringType::kNormal).ToLocalChecked()); -} - void EnqueueMicrotask(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Isolate* isolate = env->isolate(); @@ -232,8 +220,6 @@ void Initialize(Local target, env->SetMethodNoSideEffect(target, "watchdogHasPendingSigint", WatchdogHasPendingSigint); - env->SetMethod(target, "safeGetenv", SafeGetenv); - env->SetMethod(target, "enqueueMicrotask", EnqueueMicrotask); Local constants = Object::New(env->isolate()); diff --git a/test/parallel/test-bootstrap-modules.js b/test/parallel/test-bootstrap-modules.js index 8f1592084cd31f..5c1693ca89c5db 100644 --- a/test/parallel/test-bootstrap-modules.js +++ b/test/parallel/test-bootstrap-modules.js @@ -9,7 +9,7 @@ const common = require('../common'); const assert = require('assert'); const isMainThread = common.isMainThread; -const kMaxModuleCount = isMainThread ? 59 : 82; +const kMaxModuleCount = isMainThread ? 60 : 82; assert(list.length <= kMaxModuleCount, `Total length: ${list.length}\n` + list.join('\n') diff --git a/test/parallel/test-safe-get-env.js b/test/parallel/test-safe-get-env.js new file mode 100644 index 00000000000000..0bee9971dbb3eb --- /dev/null +++ b/test/parallel/test-safe-get-env.js @@ -0,0 +1,19 @@ +'use strict'; +// Flags: --expose_internals + +require('../common'); +const assert = require('assert'); +const { internalBinding } = require('internal/test/binding'); +const { safeGetenv } = internalBinding('credentials'); + +// FIXME(joyeecheung): this test is not entirely useful. To properly +// test this we could create a mismatch between the effective/real +// group/user id of a Node.js process and see if the environment variables +// are no longer available - but that might be tricky to set up reliably. + +for (const oneEnv in process.env) { + assert.strictEqual( + safeGetenv(oneEnv), + process.env[oneEnv] + ); +} diff --git a/test/parallel/test-util-internal.js b/test/parallel/test-util-internal.js index 4b78cefc6efe60..f16ccfdbdc534f 100644 --- a/test/parallel/test-util-internal.js +++ b/test/parallel/test-util-internal.js @@ -9,17 +9,9 @@ const { internalBinding } = require('internal/test/binding'); const { getHiddenValue, setHiddenValue, - arrow_message_private_symbol: kArrowMessagePrivateSymbolIndex, - safeGetenv + arrow_message_private_symbol: kArrowMessagePrivateSymbolIndex } = internalBinding('util'); -for (const oneEnv in process.env) { - assert.strictEqual( - safeGetenv(oneEnv), - process.env[oneEnv] - ); -} - assert.strictEqual( getHiddenValue({}, kArrowMessagePrivateSymbolIndex), undefined);