GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: node_credentials.cc Lines: 204 235 86.8 %
Date: 2022-09-29 04:23:51 Branches: 99 174 56.9 %

Line Branch Exec Source
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
35
69453
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

69453
  static const bool value = getauxval(AT_SECURE);
42
69453
  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.
52
69453
bool HasOnly(int capability) {
53
  DCHECK(cap_valid(capability));
54
55
  struct __user_cap_data_struct cap_data[2];
56
69453
  struct __user_cap_header_struct cap_header_data = {
57
    _LINUX_CAPABILITY_VERSION_3,
58
69453
    getpid()};
59
60
61
69453
  if (syscall(SYS_capget, &cap_header_data, &cap_data) != 0) {
62
    return false;
63
  }
64
69453
  if (capability < 32) {
65
69453
    return cap_data[0].permitted ==
66
69453
        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.
77
69453
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

208359
  if ((!HasOnly(CAP_NET_BIND_SERVICE) && linux_at_secure()) ||
84

208359
      getuid() != geteuid() || getgid() != getegid())
85
#else
86
  if (linux_at_secure() || getuid() != geteuid() || getgid() != getegid())
87
#endif
88
    goto fail;
89
#endif
90
91
69453
  if (env_vars != nullptr) {
92
    DCHECK_NOT_NULL(isolate);
93
19214
    HandleScope handle_scope(isolate);
94
19214
    TryCatch ignore_errors(isolate);
95
19214
    MaybeLocal<String> maybe_value = env_vars->Get(
96
38428
        isolate, String::NewFromUtf8(isolate, key).ToLocalChecked());
97
    Local<String> value;
98
19214
    if (!maybe_value.ToLocal(&value)) goto fail;
99
6403
    String::Utf8Value utf8_value(isolate, value);
100
6403
    if (*utf8_value == nullptr) goto fail;
101
6403
    *text = std::string(*utf8_value, utf8_value.length());
102
6403
    return true;
103
  }
104
105
  {
106
50239
    Mutex::ScopedLock lock(per_process::env_var_mutex);
107
108
50239
    size_t init_sz = 256;
109
50239
    MaybeStackBuffer<char, 256> val;
110
50239
    int ret = uv_os_getenv(key, *val, &init_sz);
111
112
50239
    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
50239
    if (ret >= 0) {  // Env key value fetch success.
120
5546
      *text = *val;
121
5546
      return true;
122
    }
123
  }
124
125
57504
fail:
126
57504
  text->clear();
127
57504
  return false;
128
}
129
130
12910
static void SafeGetenv(const FunctionCallbackInfo<Value>& args) {
131
25820
  CHECK(args[0]->IsString());
132
12910
  Environment* env = Environment::GetCurrent(args);
133
12910
  Isolate* isolate = env->isolate();
134
12910
  Utf8Value strenvtag(isolate, args[0]);
135
12910
  std::string text;
136
12910
  if (!SafeGetenv(*strenvtag, &text, env->env_vars(), isolate)) return;
137
  Local<Value> result =
138
12690
      ToV8Value(isolate->GetCurrentContext(), text).ToLocalChecked();
139
12690
  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
147
4
static uid_t uid_by_name(const char* name) {
148
  struct passwd pwd;
149
  struct passwd* pp;
150
  char buf[8192];
151
152
4
  errno = 0;
153
4
  pp = nullptr;
154
155

4
  if (getpwnam_r(name, &pwd, buf, sizeof(buf), &pp) == 0 && pp != nullptr)
156
2
    return pp->pw_uid;
157
158
2
  return uid_not_found;
159
}
160
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
180
4
static gid_t gid_by_name(const char* name) {
181
  struct group pwd;
182
  struct group* pp;
183
  char buf[8192];
184
185
4
  errno = 0;
186
4
  pp = nullptr;
187
188

4
  if (getgrnam_r(name, &pwd, buf, sizeof(buf), &pp) == 0 && pp != nullptr)
189
    return pp->gr_gid;
190
191
4
  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
216
6
static uid_t uid_by_name(Isolate* isolate, Local<Value> value) {
217
6
  if (value->IsUint32()) {
218
2
    return static_cast<uid_t>(value.As<Uint32>()->Value());
219
  } else {
220
8
    Utf8Value name(isolate, value);
221
4
    return uid_by_name(*name);
222
  }
223
}
224
225
7
static gid_t gid_by_name(Isolate* isolate, Local<Value> value) {
226
7
  if (value->IsUint32()) {
227
3
    return static_cast<gid_t>(value.As<Uint32>()->Value());
228
  } else {
229
8
    Utf8Value name(isolate, value);
230
4
    return gid_by_name(*name);
231
  }
232
}
233
234
253
static void GetUid(const FunctionCallbackInfo<Value>& args) {
235
253
  Environment* env = Environment::GetCurrent(args);
236
253
  CHECK(env->has_run_bootstrapping_code());
237
  // uid_t is an uint32_t on all supported platforms.
238
253
  args.GetReturnValue().Set(static_cast<uint32_t>(getuid()));
239
253
}
240
241
116
static void GetGid(const FunctionCallbackInfo<Value>& args) {
242
116
  Environment* env = Environment::GetCurrent(args);
243
116
  CHECK(env->has_run_bootstrapping_code());
244
  // gid_t is an uint32_t on all supported platforms.
245
116
  args.GetReturnValue().Set(static_cast<uint32_t>(getgid()));
246
116
}
247
248
2
static void GetEUid(const FunctionCallbackInfo<Value>& args) {
249
2
  Environment* env = Environment::GetCurrent(args);
250
2
  CHECK(env->has_run_bootstrapping_code());
251
  // uid_t is an uint32_t on all supported platforms.
252
2
  args.GetReturnValue().Set(static_cast<uint32_t>(geteuid()));
253
2
}
254
255
2
static void GetEGid(const FunctionCallbackInfo<Value>& args) {
256
2
  Environment* env = Environment::GetCurrent(args);
257
2
  CHECK(env->has_run_bootstrapping_code());
258
  // gid_t is an uint32_t on all supported platforms.
259
2
  args.GetReturnValue().Set(static_cast<uint32_t>(getegid()));
260
2
}
261
262
2
static void SetGid(const FunctionCallbackInfo<Value>& args) {
263
2
  Environment* env = Environment::GetCurrent(args);
264
2
  CHECK(env->owns_process_state());
265
266
2
  CHECK_EQ(args.Length(), 1);
267

4
  CHECK(args[0]->IsUint32() || args[0]->IsString());
268
269
2
  gid_t gid = gid_by_name(env->isolate(), args[0]);
270
271
2
  if (gid == gid_not_found) {
272
    // Tells JS to throw ERR_INVALID_CREDENTIAL
273
2
    args.GetReturnValue().Set(1);
274
1
  } else if (setgid(gid)) {
275
1
    env->ThrowErrnoException(errno, "setgid");
276
  } else {
277
    args.GetReturnValue().Set(0);
278
  }
279
2
}
280
281
2
static void SetEGid(const FunctionCallbackInfo<Value>& args) {
282
2
  Environment* env = Environment::GetCurrent(args);
283
2
  CHECK(env->owns_process_state());
284
285
2
  CHECK_EQ(args.Length(), 1);
286

4
  CHECK(args[0]->IsUint32() || args[0]->IsString());
287
288
2
  gid_t gid = gid_by_name(env->isolate(), args[0]);
289
290
2
  if (gid == gid_not_found) {
291
    // Tells JS to throw ERR_INVALID_CREDENTIAL
292
2
    args.GetReturnValue().Set(1);
293
1
  } else if (setegid(gid)) {
294
1
    env->ThrowErrnoException(errno, "setegid");
295
  } else {
296
    args.GetReturnValue().Set(0);
297
  }
298
2
}
299
300
3
static void SetUid(const FunctionCallbackInfo<Value>& args) {
301
3
  Environment* env = Environment::GetCurrent(args);
302
3
  CHECK(env->owns_process_state());
303
304
3
  CHECK_EQ(args.Length(), 1);
305

7
  CHECK(args[0]->IsUint32() || args[0]->IsString());
306
307
3
  uid_t uid = uid_by_name(env->isolate(), args[0]);
308
309
3
  if (uid == uid_not_found) {
310
    // Tells JS to throw ERR_INVALID_CREDENTIAL
311
2
    args.GetReturnValue().Set(1);
312
2
  } else if (setuid(uid)) {
313
2
    env->ThrowErrnoException(errno, "setuid");
314
  } else {
315
    args.GetReturnValue().Set(0);
316
  }
317
3
}
318
319
3
static void SetEUid(const FunctionCallbackInfo<Value>& args) {
320
3
  Environment* env = Environment::GetCurrent(args);
321
3
  CHECK(env->owns_process_state());
322
323
3
  CHECK_EQ(args.Length(), 1);
324

7
  CHECK(args[0]->IsUint32() || args[0]->IsString());
325
326
3
  uid_t uid = uid_by_name(env->isolate(), args[0]);
327
328
3
  if (uid == uid_not_found) {
329
    // Tells JS to throw ERR_INVALID_CREDENTIAL
330
2
    args.GetReturnValue().Set(1);
331
2
  } else if (seteuid(uid)) {
332
2
    env->ThrowErrnoException(errno, "seteuid");
333
  } else {
334
    args.GetReturnValue().Set(0);
335
  }
336
3
}
337
338
2
static void GetGroups(const FunctionCallbackInfo<Value>& args) {
339
2
  Environment* env = Environment::GetCurrent(args);
340
2
  CHECK(env->has_run_bootstrapping_code());
341
342
2
  int ngroups = getgroups(0, nullptr);
343
2
  if (ngroups == -1) return env->ThrowErrnoException(errno, "getgroups");
344
345
2
  std::vector<gid_t> groups(ngroups);
346
347
2
  ngroups = getgroups(ngroups, groups.data());
348
2
  if (ngroups == -1)
349
    return env->ThrowErrnoException(errno, "getgroups");
350
351
2
  groups.resize(ngroups);
352
2
  gid_t egid = getegid();
353
2
  if (std::find(groups.begin(), groups.end(), egid) == groups.end())
354
    groups.push_back(egid);
355
2
  MaybeLocal<Value> array = ToV8Value(env->context(), groups);
356
2
  if (!array.IsEmpty())
357
4
    args.GetReturnValue().Set(array.ToLocalChecked());
358
}
359
360
1
static void SetGroups(const FunctionCallbackInfo<Value>& args) {
361
1
  Environment* env = Environment::GetCurrent(args);
362
363
1
  CHECK_EQ(args.Length(), 1);
364
1
  CHECK(args[0]->IsArray());
365
366
2
  Local<Array> groups_list = args[0].As<Array>();
367
1
  size_t size = groups_list->Length();
368
1
  MaybeStackBuffer<gid_t, 64> groups(size);
369
370
2
  for (size_t i = 0; i < size; i++) {
371
2
    gid_t gid = gid_by_name(
372
2
        env->isolate(), groups_list->Get(env->context(), i).ToLocalChecked());
373
374
2
    if (gid == gid_not_found) {
375
      // Tells JS to throw ERR_INVALID_CREDENTIAL
376
1
      args.GetReturnValue().Set(static_cast<uint32_t>(i + 1));
377
1
      return;
378
    }
379
380
1
    groups[i] = gid;
381
  }
382
383
  int rc = setgroups(size, *groups);
384
385
  if (rc == -1) return env->ThrowErrnoException(errno, "setgroups");
386
387
  args.GetReturnValue().Set(0);
388
}
389
390
1
static void InitGroups(const FunctionCallbackInfo<Value>& args) {
391
1
  Environment* env = Environment::GetCurrent(args);
392
393
1
  CHECK_EQ(args.Length(), 2);
394

3
  CHECK(args[0]->IsUint32() || args[0]->IsString());
395

3
  CHECK(args[1]->IsUint32() || args[1]->IsString());
396
397
1
  Utf8Value arg0(env->isolate(), args[0]);
398
  gid_t extra_group;
399
  bool must_free;
400
  char* user;
401
402
1
  if (args[0]->IsUint32()) {
403
    user = name_by_uid(args[0].As<Uint32>()->Value());
404
    must_free = true;
405
  } else {
406
1
    user = *arg0;
407
1
    must_free = false;
408
  }
409
410
1
  if (user == nullptr) {
411
    // Tells JS to throw ERR_INVALID_CREDENTIAL
412
    return args.GetReturnValue().Set(1);
413
  }
414
415
1
  extra_group = gid_by_name(env->isolate(), args[1]);
416
417
1
  if (extra_group == gid_not_found) {
418
1
    if (must_free) free(user);
419
    // Tells JS to throw ERR_INVALID_CREDENTIAL
420
2
    return args.GetReturnValue().Set(2);
421
  }
422
423
  int rc = initgroups(user, extra_group);
424
425
  if (must_free) free(user);
426
427
  if (rc) return env->ThrowErrnoException(errno, "initgroups");
428
429
  args.GetReturnValue().Set(0);
430
}
431
432
#endif  // NODE_IMPLEMENTS_POSIX_CREDENTIALS
433
434
5530
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
435
5530
  registry->Register(SafeGetenv);
436
437
#ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS
438
5530
  registry->Register(GetUid);
439
5530
  registry->Register(GetEUid);
440
5530
  registry->Register(GetGid);
441
5530
  registry->Register(GetEGid);
442
5530
  registry->Register(GetGroups);
443
444
5530
  registry->Register(InitGroups);
445
5530
  registry->Register(SetEGid);
446
5530
  registry->Register(SetEUid);
447
5530
  registry->Register(SetGid);
448
5530
  registry->Register(SetUid);
449
5530
  registry->Register(SetGroups);
450
#endif  // NODE_IMPLEMENTS_POSIX_CREDENTIALS
451
5530
}
452
453
781
static void Initialize(Local<Object> target,
454
                       Local<Value> unused,
455
                       Local<Context> context,
456
                       void* priv) {
457
781
  Environment* env = Environment::GetCurrent(context);
458
781
  Isolate* isolate = env->isolate();
459
460
781
  SetMethod(context, target, "safeGetenv", SafeGetenv);
461
462
#ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS
463
2343
  READONLY_TRUE_PROPERTY(target, "implementsPosixCredentials");
464
781
  SetMethodNoSideEffect(context, target, "getuid", GetUid);
465
781
  SetMethodNoSideEffect(context, target, "geteuid", GetEUid);
466
781
  SetMethodNoSideEffect(context, target, "getgid", GetGid);
467
781
  SetMethodNoSideEffect(context, target, "getegid", GetEGid);
468
781
  SetMethodNoSideEffect(context, target, "getgroups", GetGroups);
469
470
781
  if (env->owns_process_state()) {
471
57
    SetMethod(context, target, "initgroups", InitGroups);
472
57
    SetMethod(context, target, "setegid", SetEGid);
473
57
    SetMethod(context, target, "seteuid", SetEUid);
474
57
    SetMethod(context, target, "setgid", SetGid);
475
57
    SetMethod(context, target, "setuid", SetUid);
476
57
    SetMethod(context, target, "setgroups", SetGroups);
477
  }
478
#endif  // NODE_IMPLEMENTS_POSIX_CREDENTIALS
479
781
}
480
481
}  // namespace credentials
482
}  // namespace node
483
484
5600
NODE_MODULE_CONTEXT_AWARE_INTERNAL(credentials, node::credentials::Initialize)
485
5530
NODE_MODULE_EXTERNAL_REFERENCE(credentials,
486
                               node::credentials::RegisterExternalReferences)