mirror of
https://github.com/nodejs/node.git
synced 2026-11-09 15:11:11 -05:00
So far, process initialization has been a bit all over the place in Node.js. `InitializeNodeWithArgs()` is our main public API for this, but inclusion of items in it vs. `InitializeOncePerProcess()` and `PlatformInit()` has been random at best. Likewise, some pieces of initialization have been guarded by `NODE_SHARED_MODE`, but also fairly randomly and without any meaningful connection to shared library usage. This leaves embedders in a position to cherry-pick some of the initialization code into their own code to make their application behave like typical Node.js applications to the degree to which they desire it. Electron takes an alternative route and makes direct use of `InitializeOncePerProcess()` already while it is a private API, with a `TODO` to add it to the public API in Node.js. This commit addresses that `TODO`, and `TODO`s around the `NODE_SHARED_MODE` usage. Specifically: - `InitializeOncePerProcess()` and `TearDownOncePerProcess()` are added to the public API. - The `flags` option of these functions are merged with the `flags` option for `InitializeNodeWithArgs()`, since they essentially share the same semantics. - The return value of the function is made an abstract class, rather than a struct, for easier API/ABI stability. - Initialization code from `main()` is brought into these functions (since that makes sense in general). - Add a `TODO` for turning `InitializeNodeWithArgs()` into a small wrapper around `InitializeOncePerProcess()` and eventually removing it (at least one major release cycle each, presumably). - Remove `NODE_SHARED_MODE` guards and replace them with runtime options. PR-URL: https://github.com/nodejs/node/pull/44121 Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Michael Dawson <midawson@redhat.com>
487 lines
13 KiB
C++
487 lines
13 KiB
C++
#include "env-inl.h"
|
|
#include "node_external_reference.h"
|
|
#include "node_internals.h"
|
|
#include "util-inl.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
|
|
#ifdef __linux__
|
|
#include <linux/capability.h>
|
|
#include <sys/auxv.h>
|
|
#include <sys/syscall.h>
|
|
#endif // __linux__
|
|
|
|
namespace node {
|
|
|
|
using v8::Array;
|
|
using v8::Context;
|
|
using v8::FunctionCallbackInfo;
|
|
using v8::HandleScope;
|
|
using v8::Isolate;
|
|
using v8::Local;
|
|
using v8::MaybeLocal;
|
|
using v8::Object;
|
|
using v8::String;
|
|
using v8::TryCatch;
|
|
using v8::Uint32;
|
|
using v8::Value;
|
|
|
|
bool linux_at_secure() {
|
|
// This could reasonably be a static variable, but this way
|
|
// we can guarantee that this function is always usable
|
|
// and returns the correct value, e.g. even in static
|
|
// initialization code in other files.
|
|
#ifdef __linux__
|
|
static const bool value = getauxval(AT_SECURE);
|
|
return value;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
namespace credentials {
|
|
|
|
#if defined(__linux__)
|
|
// Returns true if the current process only has the passed-in capability.
|
|
bool HasOnly(int capability) {
|
|
DCHECK(cap_valid(capability));
|
|
|
|
struct __user_cap_data_struct cap_data[2];
|
|
struct __user_cap_header_struct cap_header_data = {
|
|
_LINUX_CAPABILITY_VERSION_3,
|
|
getpid()};
|
|
|
|
|
|
if (syscall(SYS_capget, &cap_header_data, &cap_data) != 0) {
|
|
return false;
|
|
}
|
|
if (capability < 32) {
|
|
return cap_data[0].permitted ==
|
|
static_cast<unsigned int>(CAP_TO_MASK(capability));
|
|
}
|
|
return cap_data[1].permitted ==
|
|
static_cast<unsigned int>(CAP_TO_MASK(capability));
|
|
}
|
|
#endif
|
|
|
|
// Look up the environment variable and allow the lookup if the current
|
|
// process only has the capability CAP_NET_BIND_SERVICE set. If the current
|
|
// process does not have any capabilities set and the process is running as
|
|
// setuid root then lookup will not be allowed.
|
|
bool SafeGetenv(const char* key,
|
|
std::string* text,
|
|
std::shared_ptr<KVStore> env_vars,
|
|
v8::Isolate* isolate) {
|
|
#if !defined(__CloudABI__) && !defined(_WIN32)
|
|
#if defined(__linux__)
|
|
if ((!HasOnly(CAP_NET_BIND_SERVICE) && linux_at_secure()) ||
|
|
getuid() != geteuid() || getgid() != getegid())
|
|
#else
|
|
if (linux_at_secure() || getuid() != geteuid() || getgid() != getegid())
|
|
#endif
|
|
goto fail;
|
|
#endif
|
|
|
|
if (env_vars != nullptr) {
|
|
DCHECK_NOT_NULL(isolate);
|
|
HandleScope handle_scope(isolate);
|
|
TryCatch ignore_errors(isolate);
|
|
MaybeLocal<String> maybe_value = env_vars->Get(
|
|
isolate, String::NewFromUtf8(isolate, key).ToLocalChecked());
|
|
Local<String> value;
|
|
if (!maybe_value.ToLocal(&value)) goto fail;
|
|
String::Utf8Value utf8_value(isolate, value);
|
|
if (*utf8_value == nullptr) goto fail;
|
|
*text = std::string(*utf8_value, utf8_value.length());
|
|
return true;
|
|
}
|
|
|
|
{
|
|
Mutex::ScopedLock lock(per_process::env_var_mutex);
|
|
|
|
size_t init_sz = 256;
|
|
MaybeStackBuffer<char, 256> val;
|
|
int ret = uv_os_getenv(key, *val, &init_sz);
|
|
|
|
if (ret == UV_ENOBUFS) {
|
|
// Buffer is not large enough, reallocate to the updated init_sz
|
|
// and fetch env value again.
|
|
val.AllocateSufficientStorage(init_sz);
|
|
ret = uv_os_getenv(key, *val, &init_sz);
|
|
}
|
|
|
|
if (ret >= 0) { // Env key value fetch success.
|
|
*text = *val;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
fail:
|
|
text->clear();
|
|
return false;
|
|
}
|
|
|
|
static void SafeGetenv(const FunctionCallbackInfo<Value>& args) {
|
|
CHECK(args[0]->IsString());
|
|
Environment* env = Environment::GetCurrent(args);
|
|
Isolate* isolate = env->isolate();
|
|
Utf8Value strenvtag(isolate, args[0]);
|
|
std::string text;
|
|
if (!SafeGetenv(*strenvtag, &text, env->env_vars(), isolate)) 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) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
CHECK(env->has_run_bootstrapping_code());
|
|
// 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) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
CHECK(env->has_run_bootstrapping_code());
|
|
// 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) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
CHECK(env->has_run_bootstrapping_code());
|
|
// 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) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
CHECK(env->has_run_bootstrapping_code());
|
|
// 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->owns_process_state());
|
|
|
|
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->owns_process_state());
|
|
|
|
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->owns_process_state());
|
|
|
|
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->owns_process_state());
|
|
|
|
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);
|
|
CHECK(env->has_run_bootstrapping_code());
|
|
|
|
int ngroups = getgroups(0, nullptr);
|
|
if (ngroups == -1) return env->ThrowErrnoException(errno, "getgroups");
|
|
|
|
std::vector<gid_t> groups(ngroups);
|
|
|
|
ngroups = getgroups(ngroups, groups.data());
|
|
if (ngroups == -1)
|
|
return env->ThrowErrnoException(errno, "getgroups");
|
|
|
|
groups.resize(ngroups);
|
|
gid_t egid = getegid();
|
|
if (std::find(groups.begin(), groups.end(), egid) == groups.end())
|
|
groups.push_back(egid);
|
|
MaybeLocal<Value> array = ToV8Value(env->context(), groups);
|
|
if (!array.IsEmpty())
|
|
args.GetReturnValue().Set(array.ToLocalChecked());
|
|
}
|
|
|
|
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();
|
|
MaybeStackBuffer<gid_t, 64> groups(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) {
|
|
// 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);
|
|
|
|
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
|
|
|
|
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
|
|
registry->Register(SafeGetenv);
|
|
|
|
#ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS
|
|
registry->Register(GetUid);
|
|
registry->Register(GetEUid);
|
|
registry->Register(GetGid);
|
|
registry->Register(GetEGid);
|
|
registry->Register(GetGroups);
|
|
|
|
registry->Register(InitGroups);
|
|
registry->Register(SetEGid);
|
|
registry->Register(SetEUid);
|
|
registry->Register(SetGid);
|
|
registry->Register(SetUid);
|
|
registry->Register(SetGroups);
|
|
#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();
|
|
|
|
SetMethod(context, target, "safeGetenv", SafeGetenv);
|
|
|
|
#ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS
|
|
READONLY_TRUE_PROPERTY(target, "implementsPosixCredentials");
|
|
SetMethodNoSideEffect(context, target, "getuid", GetUid);
|
|
SetMethodNoSideEffect(context, target, "geteuid", GetEUid);
|
|
SetMethodNoSideEffect(context, target, "getgid", GetGid);
|
|
SetMethodNoSideEffect(context, target, "getegid", GetEGid);
|
|
SetMethodNoSideEffect(context, target, "getgroups", GetGroups);
|
|
|
|
if (env->owns_process_state()) {
|
|
SetMethod(context, target, "initgroups", InitGroups);
|
|
SetMethod(context, target, "setegid", SetEGid);
|
|
SetMethod(context, target, "seteuid", SetEUid);
|
|
SetMethod(context, target, "setgid", SetGid);
|
|
SetMethod(context, target, "setuid", SetUid);
|
|
SetMethod(context, target, "setgroups", SetGroups);
|
|
}
|
|
#endif // NODE_IMPLEMENTS_POSIX_CREDENTIALS
|
|
}
|
|
|
|
} // namespace credentials
|
|
} // namespace node
|
|
|
|
NODE_MODULE_CONTEXT_AWARE_INTERNAL(credentials, node::credentials::Initialize)
|
|
NODE_MODULE_EXTERNAL_REFERENCE(credentials,
|
|
node::credentials::RegisterExternalReferences)
|