Files
node/src/node_credentials.cc
T
Anna Henningsen b697160256 src: add detailed embedder process initialization API
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>
2022-08-05 19:46:08 +00:00

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)