1 #include "env-inl.h"
2 #include "node_external_reference.h"
3 #include "node_internals.h"
4 #include "util-inl.h"
5
6 #ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS
7 #include <grp.h> // getgrnam()
8 #include <pwd.h> // getpwnam()
9 #endif // NODE_IMPLEMENTS_POSIX_CREDENTIALS
10
11 #if !defined(_MSC_VER)
12 #include <unistd.h> // setuid, getuid
13 #endif
14 #ifdef __linux__
15 #include <linux/capability.h>
16 #include <sys/auxv.h>
17 #include <sys/syscall.h>
18 #endif // __linux__
19
20 namespace node {
21
22 using v8::Array;
23 using v8::Context;
24 using v8::FunctionCallbackInfo;
25 using v8::HandleScope;
26 using v8::Isolate;
27 using v8::Local;
28 using v8::MaybeLocal;
29 using v8::Object;
30 using v8::String;
31 using v8::TryCatch;
32 using v8::Uint32;
33 using v8::Value;
34
linux_at_secure()35 bool linux_at_secure() {
36 // This could reasonably be a static variable, but this way
37 // we can guarantee that this function is always usable
38 // and returns the correct value, e.g. even in static
39 // initialization code in other files.
40 #ifdef __linux__
41 static const bool value = getauxval(AT_SECURE);
42 return value;
43 #else
44 return false;
45 #endif
46 }
47
48 namespace credentials {
49
50 #if defined(__linux__)
51 // Returns true if the current process only has the passed-in capability.
HasOnly(int capability)52 bool HasOnly(int capability) {
53 DCHECK(cap_valid(capability));
54
55 struct __user_cap_data_struct cap_data[_LINUX_CAPABILITY_U32S_3];
56 struct __user_cap_header_struct cap_header_data = {
57 _LINUX_CAPABILITY_VERSION_3,
58 getpid()};
59
60
61 if (syscall(SYS_capget, &cap_header_data, &cap_data) != 0) {
62 return false;
63 }
64
65 static_assert(arraysize(cap_data) == 2);
66 return cap_data[CAP_TO_INDEX(capability)].permitted ==
67 static_cast<unsigned int>(CAP_TO_MASK(capability)) &&
68 cap_data[1 - CAP_TO_INDEX(capability)].permitted == 0;
69 }
70 #endif
71
72 // Look up the environment variable and allow the lookup if the current
73 // process only has the capability CAP_NET_BIND_SERVICE set. If the current
74 // process does not have any capabilities set and the process is running as
75 // setuid root then lookup will not be allowed.
SafeGetenv(const char * key,std::string * text,std::shared_ptr<KVStore> env_vars,v8::Isolate * isolate)76 bool SafeGetenv(const char* key,
77 std::string* text,
78 std::shared_ptr<KVStore> env_vars,
79 v8::Isolate* isolate) {
80 #if !defined(__CloudABI__) && !defined(_WIN32)
81 #if defined(__linux__)
82 if ((!HasOnly(CAP_NET_BIND_SERVICE) && linux_at_secure()) ||
83 getuid() != geteuid() || getgid() != getegid())
84 #else
85 if (linux_at_secure() || getuid() != geteuid() || getgid() != getegid())
86 #endif
87 goto fail;
88 #endif
89
90 if (env_vars != nullptr) {
91 DCHECK_NOT_NULL(isolate);
92 HandleScope handle_scope(isolate);
93 TryCatch ignore_errors(isolate);
94 MaybeLocal<String> maybe_value = env_vars->Get(
95 isolate, String::NewFromUtf8(isolate, key).ToLocalChecked());
96 Local<String> value;
97 if (!maybe_value.ToLocal(&value)) goto fail;
98 String::Utf8Value utf8_value(isolate, value);
99 if (*utf8_value == nullptr) goto fail;
100 *text = std::string(*utf8_value, utf8_value.length());
101 return true;
102 }
103
104 {
105 Mutex::ScopedLock lock(per_process::env_var_mutex);
106
107 size_t init_sz = 256;
108 MaybeStackBuffer<char, 256> val;
109 int ret = uv_os_getenv(key, *val, &init_sz);
110
111 if (ret == UV_ENOBUFS) {
112 // Buffer is not large enough, reallocate to the updated init_sz
113 // and fetch env value again.
114 val.AllocateSufficientStorage(init_sz);
115 ret = uv_os_getenv(key, *val, &init_sz);
116 }
117
118 if (ret >= 0) { // Env key value fetch success.
119 *text = *val;
120 return true;
121 }
122 }
123
124 fail:
125 text->clear();
126 return false;
127 }
128
SafeGetenv(const FunctionCallbackInfo<Value> & args)129 static void SafeGetenv(const FunctionCallbackInfo<Value>& args) {
130 CHECK(args[0]->IsString());
131 Environment* env = Environment::GetCurrent(args);
132 Isolate* isolate = env->isolate();
133 Utf8Value strenvtag(isolate, args[0]);
134 std::string text;
135 if (!SafeGetenv(*strenvtag, &text, env->env_vars(), isolate)) return;
136 Local<Value> result =
137 ToV8Value(isolate->GetCurrentContext(), text).ToLocalChecked();
138 args.GetReturnValue().Set(result);
139 }
140
141 #ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS
142
143 static const uid_t uid_not_found = static_cast<uid_t>(-1);
144 static const gid_t gid_not_found = static_cast<gid_t>(-1);
145
uid_by_name(const char * name)146 static uid_t uid_by_name(const char* name) {
147 struct passwd pwd;
148 struct passwd* pp;
149 char buf[8192];
150
151 errno = 0;
152 pp = nullptr;
153
154 if (getpwnam_r(name, &pwd, buf, sizeof(buf), &pp) == 0 && pp != nullptr)
155 return pp->pw_uid;
156
157 return uid_not_found;
158 }
159
name_by_uid(uid_t uid)160 static char* name_by_uid(uid_t uid) {
161 struct passwd pwd;
162 struct passwd* pp;
163 char buf[8192];
164 int rc;
165
166 errno = 0;
167 pp = nullptr;
168
169 if ((rc = getpwuid_r(uid, &pwd, buf, sizeof(buf), &pp)) == 0 &&
170 pp != nullptr) {
171 return strdup(pp->pw_name);
172 }
173
174 if (rc == 0) errno = ENOENT;
175
176 return nullptr;
177 }
178
gid_by_name(const char * name)179 static gid_t gid_by_name(const char* name) {
180 struct group pwd;
181 struct group* pp;
182 char buf[8192];
183
184 errno = 0;
185 pp = nullptr;
186
187 if (getgrnam_r(name, &pwd, buf, sizeof(buf), &pp) == 0 && pp != nullptr)
188 return pp->gr_gid;
189
190 return gid_not_found;
191 }
192
193 #if 0 // For future use.
194 static const char* name_by_gid(gid_t gid) {
195 struct group pwd;
196 struct group* pp;
197 char buf[8192];
198 int rc;
199
200 errno = 0;
201 pp = nullptr;
202
203 if ((rc = getgrgid_r(gid, &pwd, buf, sizeof(buf), &pp)) == 0 &&
204 pp != nullptr) {
205 return strdup(pp->gr_name);
206 }
207
208 if (rc == 0)
209 errno = ENOENT;
210
211 return nullptr;
212 }
213 #endif
214
uid_by_name(Isolate * isolate,Local<Value> value)215 static uid_t uid_by_name(Isolate* isolate, Local<Value> value) {
216 if (value->IsUint32()) {
217 static_assert(std::is_same<uid_t, uint32_t>::value);
218 return value.As<Uint32>()->Value();
219 } else {
220 Utf8Value name(isolate, value);
221 return uid_by_name(*name);
222 }
223 }
224
gid_by_name(Isolate * isolate,Local<Value> value)225 static gid_t gid_by_name(Isolate* isolate, Local<Value> value) {
226 if (value->IsUint32()) {
227 static_assert(std::is_same<gid_t, uint32_t>::value);
228 return value.As<Uint32>()->Value();
229 } else {
230 Utf8Value name(isolate, value);
231 return gid_by_name(*name);
232 }
233 }
234
GetUid(const FunctionCallbackInfo<Value> & args)235 static void GetUid(const FunctionCallbackInfo<Value>& args) {
236 Environment* env = Environment::GetCurrent(args);
237 CHECK(env->has_run_bootstrapping_code());
238 // uid_t is an uint32_t on all supported platforms.
239 args.GetReturnValue().Set(static_cast<uint32_t>(getuid()));
240 }
241
GetGid(const FunctionCallbackInfo<Value> & args)242 static void GetGid(const FunctionCallbackInfo<Value>& args) {
243 Environment* env = Environment::GetCurrent(args);
244 CHECK(env->has_run_bootstrapping_code());
245 // gid_t is an uint32_t on all supported platforms.
246 args.GetReturnValue().Set(static_cast<uint32_t>(getgid()));
247 }
248
GetEUid(const FunctionCallbackInfo<Value> & args)249 static void GetEUid(const FunctionCallbackInfo<Value>& args) {
250 Environment* env = Environment::GetCurrent(args);
251 CHECK(env->has_run_bootstrapping_code());
252 // uid_t is an uint32_t on all supported platforms.
253 args.GetReturnValue().Set(static_cast<uint32_t>(geteuid()));
254 }
255
GetEGid(const FunctionCallbackInfo<Value> & args)256 static void GetEGid(const FunctionCallbackInfo<Value>& args) {
257 Environment* env = Environment::GetCurrent(args);
258 CHECK(env->has_run_bootstrapping_code());
259 // gid_t is an uint32_t on all supported platforms.
260 args.GetReturnValue().Set(static_cast<uint32_t>(getegid()));
261 }
262
SetGid(const FunctionCallbackInfo<Value> & args)263 static void SetGid(const FunctionCallbackInfo<Value>& args) {
264 Environment* env = Environment::GetCurrent(args);
265 CHECK(env->owns_process_state());
266
267 CHECK_EQ(args.Length(), 1);
268 CHECK(args[0]->IsUint32() || args[0]->IsString());
269
270 gid_t gid = gid_by_name(env->isolate(), args[0]);
271
272 if (gid == gid_not_found) {
273 // Tells JS to throw ERR_INVALID_CREDENTIAL
274 args.GetReturnValue().Set(1);
275 } else if (setgid(gid)) {
276 env->ThrowErrnoException(errno, "setgid");
277 } else {
278 args.GetReturnValue().Set(0);
279 }
280 }
281
SetEGid(const FunctionCallbackInfo<Value> & args)282 static void SetEGid(const FunctionCallbackInfo<Value>& args) {
283 Environment* env = Environment::GetCurrent(args);
284 CHECK(env->owns_process_state());
285
286 CHECK_EQ(args.Length(), 1);
287 CHECK(args[0]->IsUint32() || args[0]->IsString());
288
289 gid_t gid = gid_by_name(env->isolate(), args[0]);
290
291 if (gid == gid_not_found) {
292 // Tells JS to throw ERR_INVALID_CREDENTIAL
293 args.GetReturnValue().Set(1);
294 } else if (setegid(gid)) {
295 env->ThrowErrnoException(errno, "setegid");
296 } else {
297 args.GetReturnValue().Set(0);
298 }
299 }
300
SetUid(const FunctionCallbackInfo<Value> & args)301 static void SetUid(const FunctionCallbackInfo<Value>& args) {
302 Environment* env = Environment::GetCurrent(args);
303 CHECK(env->owns_process_state());
304
305 CHECK_EQ(args.Length(), 1);
306 CHECK(args[0]->IsUint32() || args[0]->IsString());
307
308 uid_t uid = uid_by_name(env->isolate(), args[0]);
309
310 if (uid == uid_not_found) {
311 // Tells JS to throw ERR_INVALID_CREDENTIAL
312 args.GetReturnValue().Set(1);
313 } else if (setuid(uid)) {
314 env->ThrowErrnoException(errno, "setuid");
315 } else {
316 args.GetReturnValue().Set(0);
317 }
318 }
319
SetEUid(const FunctionCallbackInfo<Value> & args)320 static void SetEUid(const FunctionCallbackInfo<Value>& args) {
321 Environment* env = Environment::GetCurrent(args);
322 CHECK(env->owns_process_state());
323
324 CHECK_EQ(args.Length(), 1);
325 CHECK(args[0]->IsUint32() || args[0]->IsString());
326
327 uid_t uid = uid_by_name(env->isolate(), args[0]);
328
329 if (uid == uid_not_found) {
330 // Tells JS to throw ERR_INVALID_CREDENTIAL
331 args.GetReturnValue().Set(1);
332 } else if (seteuid(uid)) {
333 env->ThrowErrnoException(errno, "seteuid");
334 } else {
335 args.GetReturnValue().Set(0);
336 }
337 }
338
GetGroups(const FunctionCallbackInfo<Value> & args)339 static void GetGroups(const FunctionCallbackInfo<Value>& args) {
340 Environment* env = Environment::GetCurrent(args);
341 CHECK(env->has_run_bootstrapping_code());
342
343 int ngroups = getgroups(0, nullptr);
344 if (ngroups == -1) return env->ThrowErrnoException(errno, "getgroups");
345
346 std::vector<gid_t> groups(ngroups);
347
348 ngroups = getgroups(ngroups, groups.data());
349 if (ngroups == -1)
350 return env->ThrowErrnoException(errno, "getgroups");
351
352 groups.resize(ngroups);
353 gid_t egid = getegid();
354 if (std::find(groups.begin(), groups.end(), egid) == groups.end())
355 groups.push_back(egid);
356 MaybeLocal<Value> array = ToV8Value(env->context(), groups);
357 if (!array.IsEmpty())
358 args.GetReturnValue().Set(array.ToLocalChecked());
359 }
360
SetGroups(const FunctionCallbackInfo<Value> & args)361 static void SetGroups(const FunctionCallbackInfo<Value>& args) {
362 Environment* env = Environment::GetCurrent(args);
363
364 CHECK_EQ(args.Length(), 1);
365 CHECK(args[0]->IsArray());
366
367 Local<Array> groups_list = args[0].As<Array>();
368 size_t size = groups_list->Length();
369 MaybeStackBuffer<gid_t, 64> groups(size);
370
371 for (size_t i = 0; i < size; i++) {
372 gid_t gid = gid_by_name(
373 env->isolate(), groups_list->Get(env->context(), i).ToLocalChecked());
374
375 if (gid == gid_not_found) {
376 // Tells JS to throw ERR_INVALID_CREDENTIAL
377 args.GetReturnValue().Set(static_cast<uint32_t>(i + 1));
378 return;
379 }
380
381 groups[i] = gid;
382 }
383
384 int rc = setgroups(size, *groups);
385
386 if (rc == -1) return env->ThrowErrnoException(errno, "setgroups");
387
388 args.GetReturnValue().Set(0);
389 }
390
InitGroups(const FunctionCallbackInfo<Value> & args)391 static void InitGroups(const FunctionCallbackInfo<Value>& args) {
392 Environment* env = Environment::GetCurrent(args);
393
394 CHECK_EQ(args.Length(), 2);
395 CHECK(args[0]->IsUint32() || args[0]->IsString());
396 CHECK(args[1]->IsUint32() || args[1]->IsString());
397
398 Utf8Value arg0(env->isolate(), args[0]);
399 gid_t extra_group;
400 bool must_free;
401 char* user;
402
403 if (args[0]->IsUint32()) {
404 user = name_by_uid(args[0].As<Uint32>()->Value());
405 must_free = true;
406 } else {
407 user = *arg0;
408 must_free = false;
409 }
410
411 if (user == nullptr) {
412 // Tells JS to throw ERR_INVALID_CREDENTIAL
413 return args.GetReturnValue().Set(1);
414 }
415
416 extra_group = gid_by_name(env->isolate(), args[1]);
417
418 if (extra_group == gid_not_found) {
419 if (must_free) free(user);
420 // Tells JS to throw ERR_INVALID_CREDENTIAL
421 return args.GetReturnValue().Set(2);
422 }
423
424 int rc = initgroups(user, extra_group);
425
426 if (must_free) free(user);
427
428 if (rc) return env->ThrowErrnoException(errno, "initgroups");
429
430 args.GetReturnValue().Set(0);
431 }
432
433 #endif // NODE_IMPLEMENTS_POSIX_CREDENTIALS
434
RegisterExternalReferences(ExternalReferenceRegistry * registry)435 void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
436 registry->Register(SafeGetenv);
437
438 #ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS
439 registry->Register(GetUid);
440 registry->Register(GetEUid);
441 registry->Register(GetGid);
442 registry->Register(GetEGid);
443 registry->Register(GetGroups);
444
445 registry->Register(InitGroups);
446 registry->Register(SetEGid);
447 registry->Register(SetEUid);
448 registry->Register(SetGid);
449 registry->Register(SetUid);
450 registry->Register(SetGroups);
451 #endif // NODE_IMPLEMENTS_POSIX_CREDENTIALS
452 }
453
Initialize(Local<Object> target,Local<Value> unused,Local<Context> context,void * priv)454 static void Initialize(Local<Object> target,
455 Local<Value> unused,
456 Local<Context> context,
457 void* priv) {
458 SetMethod(context, target, "safeGetenv", SafeGetenv);
459
460 #ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS
461 Environment* env = Environment::GetCurrent(context);
462 Isolate* isolate = env->isolate();
463
464 READONLY_TRUE_PROPERTY(target, "implementsPosixCredentials");
465 SetMethodNoSideEffect(context, target, "getuid", GetUid);
466 SetMethodNoSideEffect(context, target, "geteuid", GetEUid);
467 SetMethodNoSideEffect(context, target, "getgid", GetGid);
468 SetMethodNoSideEffect(context, target, "getegid", GetEGid);
469 SetMethodNoSideEffect(context, target, "getgroups", GetGroups);
470
471 if (env->owns_process_state()) {
472 SetMethod(context, target, "initgroups", InitGroups);
473 SetMethod(context, target, "setegid", SetEGid);
474 SetMethod(context, target, "seteuid", SetEUid);
475 SetMethod(context, target, "setgid", SetGid);
476 SetMethod(context, target, "setuid", SetUid);
477 SetMethod(context, target, "setgroups", SetGroups);
478 }
479 #endif // NODE_IMPLEMENTS_POSIX_CREDENTIALS
480 }
481
482 } // namespace credentials
483 } // namespace node
484
485 NODE_BINDING_CONTEXT_AWARE_INTERNAL(credentials, node::credentials::Initialize)
486 NODE_BINDING_EXTERNAL_REFERENCE(credentials,
487 node::credentials::RegisterExternalReferences)
488