mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
769823e57c
PR-URL: https://github.com/nodejs/node/pull/48890 Refs: https://github.com/orgs/nodejs/discussions/44975 Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Paolo Insogna <paolo@cowtech.it>
488 lines
13 KiB
C++
488 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:
|
|
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()) {
|
|
static_assert(std::is_same<uid_t, uint32_t>::value);
|
|
return 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()) {
|
|
static_assert(std::is_same<gid_t, uint32_t>::value);
|
|
return 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) {
|
|
SetMethod(context, target, "safeGetenv", SafeGetenv);
|
|
|
|
#ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS
|
|
Environment* env = Environment::GetCurrent(context);
|
|
Isolate* isolate = env->isolate();
|
|
|
|
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_BINDING_CONTEXT_AWARE_INTERNAL(credentials, node::credentials::Initialize)
|
|
NODE_BINDING_EXTERNAL_REFERENCE(credentials,
|
|
node::credentials::RegisterExternalReferences)
|