process: move POSIX credential accessors into node_credentials.cc

Expose the POSIX credential accessors through
`internalBinding('credentials')` instead of setting them on the
process or bootstrapper object from C++ directly. Also moves
`SafeGetEnv` from `internalBinding('util')` to
`internalBinding('credentials')` since it's closely related to
the credentials.

In the JS land, instead of wrapping the bindings then writing
to the process object directly in main_thread_only.js, return
the wrapped functions back to bootstrap/node.js where they get
written to the process object conditionally for clarity.

Refs: https://github.com/nodejs/node/issues/24961

PR-URL: https://github.com/nodejs/node/pull/25066
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Joyee Cheung 2018-12-16 03:13:12 +08:00
parent 74a1dfb56e
commit 321e296371
No known key found for this signature in database
GPG Key ID: 92B78A53C8303B8D
17 changed files with 497 additions and 473 deletions

View File

@ -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();
}

View File

@ -34,7 +34,7 @@ const {
internalModuleReadJSON,
internalModuleStat
} = internalBinding('fs');
const { safeGetenv } = internalBinding('util');
const { safeGetenv } = internalBinding('credentials');
const {
makeRequireFunction,
requireDepth,

View File

@ -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) {
function wrapPosixCredentialSetters(credentials) {
const {
initgroups: _initgroups,
setgroups: _setgroups,
setegid: _setegid,
seteuid: _seteuid,
setgid: _setgid,
setuid: _setuid
} = credentials;
process.initgroups = function initgroups(user, extraGroup) {
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
};

View File

@ -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';

View File

@ -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',

View File

@ -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<String> should_abort_on_uncaught_toggle =
FIXED_ONE_BYTE_STRING(env->isolate(), "_shouldAbortOnUncaughtToggle");
CHECK(bootstrapper->Set(env->context(),

View File

@ -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(

View File

@ -100,12 +100,7 @@ typedef int mode_t;
#else
#include <pthread.h>
#include <sys/resource.h> // getrlimit, setrlimit
#include <unistd.h> // setuid, getuid
#endif
#if defined(__POSIX__) && !defined(__ANDROID__) && !defined(__CloudABI__)
#include <pwd.h> // getpwnam()
#include <grp.h> // getgrnam()
#include <unistd.h> // 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<std::string>* 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<std::string> 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<std::string>* 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

View File

@ -30,6 +30,7 @@
V(cares_wrap) \
V(config) \
V(contextify) \
V(credentials) \
V(domain) \
V(fs) \
V(fs_event_wrap) \

395
src/node_credentials.cc Normal file
View File

@ -0,0 +1,395 @@
#include "node_internals.h"
#ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS
#include <grp.h> // getgrnam()
#include <pwd.h> // getpwnam()
#endif // NODE_IMPLEMENTS_POSIX_CREDENTIALS
#if !defined(_MSC_VER)
#include <unistd.h> // 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<Value>& args) {
CHECK(args[0]->IsString());
Isolate* isolate = args.GetIsolate();
Utf8Value strenvtag(isolate, args[0]);
std::string text;
if (!SafeGetenv(*strenvtag, &text)) return;
Local<Value> result =
ToV8Value(isolate->GetCurrentContext(), text).ToLocalChecked();
args.GetReturnValue().Set(result);
}
#ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS
static const uid_t uid_not_found = static_cast<uid_t>(-1);
static const gid_t gid_not_found = static_cast<gid_t>(-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> value) {
if (value->IsUint32()) {
return static_cast<uid_t>(value.As<Uint32>()->Value());
} else {
Utf8Value name(isolate, value);
return uid_by_name(*name);
}
}
static gid_t gid_by_name(Isolate* isolate, Local<Value> value) {
if (value->IsUint32()) {
return static_cast<gid_t>(value.As<Uint32>()->Value());
} else {
Utf8Value name(isolate, value);
return gid_by_name(*name);
}
}
static void GetUid(const FunctionCallbackInfo<Value>& args) {
// uid_t is an uint32_t on all supported platforms.
args.GetReturnValue().Set(static_cast<uint32_t>(getuid()));
}
static void GetGid(const FunctionCallbackInfo<Value>& args) {
// gid_t is an uint32_t on all supported platforms.
args.GetReturnValue().Set(static_cast<uint32_t>(getgid()));
}
static void GetEUid(const FunctionCallbackInfo<Value>& args) {
// uid_t is an uint32_t on all supported platforms.
args.GetReturnValue().Set(static_cast<uint32_t>(geteuid()));
}
static void GetEGid(const FunctionCallbackInfo<Value>& args) {
// gid_t is an uint32_t on all supported platforms.
args.GetReturnValue().Set(static_cast<uint32_t>(getegid()));
}
static void SetGid(const FunctionCallbackInfo<Value>& 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<Value>& 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<Value>& 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<Value>& 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<Value>& 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<Array> 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<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK_EQ(args.Length(), 1);
CHECK(args[0]->IsArray());
Local<Array> groups_list = args[0].As<Array>();
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<uint32_t>(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<Value>& 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<Uint32>()->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<Object> target,
Local<Value> unused,
Local<Context> 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)

View File

@ -126,7 +126,6 @@ void RegisterSignalHandler(int signal,
bool reset_handler = false);
#endif
bool SafeGetenv(const char* key, std::string* text);
v8::Local<v8::Object> CreateEnvVarProxy(v8::Local<v8::Context> context,
v8::Isolate* isolate,
v8::Local<v8::Value> data);
@ -734,19 +733,13 @@ void ProcessTitleSetter(v8::Local<v8::Name> property,
const v8::PropertyCallbackInfo<void>& info);
#if defined(__POSIX__) && !defined(__ANDROID__) && !defined(__CloudABI__)
void SetGid(const v8::FunctionCallbackInfo<v8::Value>& args);
void SetEGid(const v8::FunctionCallbackInfo<v8::Value>& args);
void SetUid(const v8::FunctionCallbackInfo<v8::Value>& args);
void SetEUid(const v8::FunctionCallbackInfo<v8::Value>& args);
void SetGroups(const v8::FunctionCallbackInfo<v8::Value>& args);
void InitGroups(const v8::FunctionCallbackInfo<v8::Value>& args);
void GetUid(const v8::FunctionCallbackInfo<v8::Value>& args);
void GetGid(const v8::FunctionCallbackInfo<v8::Value>& args);
void GetEUid(const v8::FunctionCallbackInfo<v8::Value>& args);
void GetEGid(const v8::FunctionCallbackInfo<v8::Value>& args);
void GetGroups(const v8::FunctionCallbackInfo<v8::Value>& 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<v8::Object> target);
} // namespace node

View File

@ -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<Elf_auxv_t*>(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;
}
}

View File

@ -24,12 +24,6 @@ typedef int mode_t;
#include <pthread.h>
#include <sys/resource.h> // getrlimit, setrlimit
#include <termios.h> // tcgetattr, tcsetattr
#include <unistd.h> // setuid, getuid
#endif
#if defined(__POSIX__) && !defined(__ANDROID__) && !defined(__CloudABI__)
#include <pwd.h> // getpwnam()
#include <grp.h> // 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<Value>& args) {
args.GetReturnValue().Set(uptime / 1000);
}
#if defined(__POSIX__) && !defined(__ANDROID__) && !defined(__CloudABI__)
static const uid_t uid_not_found = static_cast<uid_t>(-1);
static const gid_t gid_not_found = static_cast<gid_t>(-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> value) {
if (value->IsUint32()) {
return static_cast<uid_t>(value.As<Uint32>()->Value());
} else {
Utf8Value name(isolate, value);
return uid_by_name(*name);
}
}
static gid_t gid_by_name(Isolate* isolate, Local<Value> value) {
if (value->IsUint32()) {
return static_cast<gid_t>(value.As<Uint32>()->Value());
} else {
Utf8Value name(isolate, value);
return gid_by_name(*name);
}
}
void GetUid(const FunctionCallbackInfo<Value>& args) {
// uid_t is an uint32_t on all supported platforms.
args.GetReturnValue().Set(static_cast<uint32_t>(getuid()));
}
void GetGid(const FunctionCallbackInfo<Value>& args) {
// gid_t is an uint32_t on all supported platforms.
args.GetReturnValue().Set(static_cast<uint32_t>(getgid()));
}
void GetEUid(const FunctionCallbackInfo<Value>& args) {
// uid_t is an uint32_t on all supported platforms.
args.GetReturnValue().Set(static_cast<uint32_t>(geteuid()));
}
void GetEGid(const FunctionCallbackInfo<Value>& args) {
// gid_t is an uint32_t on all supported platforms.
args.GetReturnValue().Set(static_cast<uint32_t>(getegid()));
}
void SetGid(const FunctionCallbackInfo<Value>& 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<Value>& 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<Value>& 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<Value>& 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<Value>& 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<Array> 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<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK_EQ(args.Length(), 1);
CHECK(args[0]->IsArray());
Local<Array> groups_list = args[0].As<Array>();
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<uint32_t>(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<Value>& 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<Uint32>()->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<Name> property,
const PropertyCallbackInfo<Value>& info) {
char buffer[512];

View File

@ -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<Value>& args) {
args.GetReturnValue().Set(ret);
}
void SafeGetenv(const FunctionCallbackInfo<Value>& 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<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Isolate* isolate = env->isolate();
@ -232,8 +220,6 @@ void Initialize(Local<Object> target,
env->SetMethodNoSideEffect(target, "watchdogHasPendingSigint",
WatchdogHasPendingSigint);
env->SetMethod(target, "safeGetenv", SafeGetenv);
env->SetMethod(target, "enqueueMicrotask", EnqueueMicrotask);
Local<Object> constants = Object::New(env->isolate());

View File

@ -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')

View File

@ -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]
);
}

View File

@ -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);