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