xref: /third_party/node/src/node_credentials.cc (revision 1cb0ef41)
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
20namespace node {
21
22using v8::Array;
23using v8::Context;
24using v8::FunctionCallbackInfo;
25using v8::HandleScope;
26using v8::Isolate;
27using v8::Local;
28using v8::MaybeLocal;
29using v8::Object;
30using v8::String;
31using v8::TryCatch;
32using v8::Uint32;
33using v8::Value;
34
35bool 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
48namespace credentials {
49
50#if defined(__linux__)
51// Returns true if the current process only has the passed-in capability.
52bool 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.
76bool 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
124fail:
125  text->clear();
126  return false;
127}
128
129static 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
143static const uid_t uid_not_found = static_cast<uid_t>(-1);
144static const gid_t gid_not_found = static_cast<gid_t>(-1);
145
146static 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
160static 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
179static 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.
194static 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
215static 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
225static 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
235static 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
242static 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
249static 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
256static 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
263static 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
282static 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
301static 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
320static 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
339static 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
361static 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
391static 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
435void 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
454static 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
485NODE_BINDING_CONTEXT_AWARE_INTERNAL(credentials, node::credentials::Initialize)
486NODE_BINDING_EXTERNAL_REFERENCE(credentials,
487                                node::credentials::RegisterExternalReferences)
488