GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: node_credentials.cc Lines: 201 232 86.6 %
Date: 2021-10-03 04:15:53 Branches: 96 170 56.5 %

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/syscall.h>
17
#endif  // __linux__
18
19
namespace node {
20
21
using v8::Array;
22
using v8::Context;
23
using v8::FunctionCallbackInfo;
24
using v8::HandleScope;
25
using v8::Isolate;
26
using v8::Local;
27
using v8::MaybeLocal;
28
using v8::Object;
29
using v8::String;
30
using v8::TryCatch;
31
using v8::Uint32;
32
using v8::Value;
33
34
namespace per_process {
35
bool linux_at_secure = false;
36
}  // namespace per_process
37
38
namespace credentials {
39
40
#if defined(__linux__)
41
// Returns true if the current process only has the passed-in capability.
42
56000
bool HasOnly(int capability) {
43
  DCHECK(cap_valid(capability));
44
45
  struct __user_cap_data_struct cap_data[2];
46
56000
  struct __user_cap_header_struct cap_header_data = {
47
    _LINUX_CAPABILITY_VERSION_3,
48
56000
    getpid()};
49
50
51
56000
  if (syscall(SYS_capget, &cap_header_data, &cap_data) != 0) {
52
    return false;
53
  }
54
56000
  if (capability < 32) {
55
56000
    return cap_data[0].permitted ==
56
56000
        static_cast<unsigned int>(CAP_TO_MASK(capability));
57
  }
58
  return cap_data[1].permitted ==
59
      static_cast<unsigned int>(CAP_TO_MASK(capability));
60
}
61
#endif
62
63
// Look up the environment variable and allow the lookup if the current
64
// process only has the capability CAP_NET_BIND_SERVICE set. If the current
65
// process does not have any capabilities set and the process is running as
66
// setuid root then lookup will not be allowed.
67
56000
bool SafeGetenv(const char* key, std::string* text, Environment* env) {
68
#if !defined(__CloudABI__) && !defined(_WIN32)
69
#if defined(__linux__)
70

168000
  if ((!HasOnly(CAP_NET_BIND_SERVICE) && per_process::linux_at_secure) ||
71

168000
      getuid() != geteuid() || getgid() != getegid())
72
#else
73
  if (per_process::linux_at_secure || getuid() != geteuid() ||
74
      getgid() != getegid())
75
#endif
76
    goto fail;
77
#endif
78
79
56000
  if (env != nullptr) {
80
16699
    HandleScope handle_scope(env->isolate());
81
16699
    TryCatch ignore_errors(env->isolate());
82
33398
    MaybeLocal<String> maybe_value = env->env_vars()->Get(
83
        env->isolate(),
84
33398
        String::NewFromUtf8(env->isolate(), key).ToLocalChecked());
85
    Local<String> value;
86
16699
    if (!maybe_value.ToLocal(&value)) goto fail;
87
5576
    String::Utf8Value utf8_value(env->isolate(), value);
88
5576
    if (*utf8_value == nullptr) goto fail;
89
5576
    *text = std::string(*utf8_value, utf8_value.length());
90
5576
    return true;
91
  }
92
93
  {
94
39301
    Mutex::ScopedLock lock(per_process::env_var_mutex);
95
96
39301
    size_t init_sz = 256;
97
39301
    MaybeStackBuffer<char, 256> val;
98
39301
    int ret = uv_os_getenv(key, *val, &init_sz);
99
100
39301
    if (ret == UV_ENOBUFS) {
101
      // Buffer is not large enough, reallocate to the updated init_sz
102
      // and fetch env value again.
103
      val.AllocateSufficientStorage(init_sz);
104
      ret = uv_os_getenv(key, *val, &init_sz);
105
    }
106
107
39301
    if (ret >= 0) {  // Env key value fetch success.
108
4876
      *text = *val;
109
4876
      return true;
110
    }
111
  }
112
113
45548
fail:
114
45548
  text->clear();
115
45548
  return false;
116
}
117
118
11235
static void SafeGetenv(const FunctionCallbackInfo<Value>& args) {
119
22470
  CHECK(args[0]->IsString());
120
11235
  Environment* env = Environment::GetCurrent(args);
121
11235
  Isolate* isolate = env->isolate();
122
11235
  Utf8Value strenvtag(isolate, args[0]);
123
11235
  std::string text;
124
11235
  if (!SafeGetenv(*strenvtag, &text, env)) return;
125
  Local<Value> result =
126
11040
      ToV8Value(isolate->GetCurrentContext(), text).ToLocalChecked();
127
11040
  args.GetReturnValue().Set(result);
128
}
129
130
#ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS
131
132
static const uid_t uid_not_found = static_cast<uid_t>(-1);
133
static const gid_t gid_not_found = static_cast<gid_t>(-1);
134
135
4
static uid_t uid_by_name(const char* name) {
136
  struct passwd pwd;
137
  struct passwd* pp;
138
  char buf[8192];
139
140
4
  errno = 0;
141
4
  pp = nullptr;
142
143

4
  if (getpwnam_r(name, &pwd, buf, sizeof(buf), &pp) == 0 && pp != nullptr)
144
2
    return pp->pw_uid;
145
146
2
  return uid_not_found;
147
}
148
149
static char* name_by_uid(uid_t uid) {
150
  struct passwd pwd;
151
  struct passwd* pp;
152
  char buf[8192];
153
  int rc;
154
155
  errno = 0;
156
  pp = nullptr;
157
158
  if ((rc = getpwuid_r(uid, &pwd, buf, sizeof(buf), &pp)) == 0 &&
159
      pp != nullptr) {
160
    return strdup(pp->pw_name);
161
  }
162
163
  if (rc == 0) errno = ENOENT;
164
165
  return nullptr;
166
}
167
168
4
static gid_t gid_by_name(const char* name) {
169
  struct group pwd;
170
  struct group* pp;
171
  char buf[8192];
172
173
4
  errno = 0;
174
4
  pp = nullptr;
175
176

4
  if (getgrnam_r(name, &pwd, buf, sizeof(buf), &pp) == 0 && pp != nullptr)
177
    return pp->gr_gid;
178
179
4
  return gid_not_found;
180
}
181
182
#if 0  // For future use.
183
static const char* name_by_gid(gid_t gid) {
184
  struct group pwd;
185
  struct group* pp;
186
  char buf[8192];
187
  int rc;
188
189
  errno = 0;
190
  pp = nullptr;
191
192
  if ((rc = getgrgid_r(gid, &pwd, buf, sizeof(buf), &pp)) == 0 &&
193
      pp != nullptr) {
194
    return strdup(pp->gr_name);
195
  }
196
197
  if (rc == 0)
198
    errno = ENOENT;
199
200
  return nullptr;
201
}
202
#endif
203
204
6
static uid_t uid_by_name(Isolate* isolate, Local<Value> value) {
205
6
  if (value->IsUint32()) {
206
2
    return static_cast<uid_t>(value.As<Uint32>()->Value());
207
  } else {
208
8
    Utf8Value name(isolate, value);
209
4
    return uid_by_name(*name);
210
  }
211
}
212
213
7
static gid_t gid_by_name(Isolate* isolate, Local<Value> value) {
214
7
  if (value->IsUint32()) {
215
3
    return static_cast<gid_t>(value.As<Uint32>()->Value());
216
  } else {
217
8
    Utf8Value name(isolate, value);
218
4
    return gid_by_name(*name);
219
  }
220
}
221
222
243
static void GetUid(const FunctionCallbackInfo<Value>& args) {
223
243
  Environment* env = Environment::GetCurrent(args);
224
243
  CHECK(env->has_run_bootstrapping_code());
225
  // uid_t is an uint32_t on all supported platforms.
226
243
  args.GetReturnValue().Set(static_cast<uint32_t>(getuid()));
227
243
}
228
229
109
static void GetGid(const FunctionCallbackInfo<Value>& args) {
230
109
  Environment* env = Environment::GetCurrent(args);
231
109
  CHECK(env->has_run_bootstrapping_code());
232
  // gid_t is an uint32_t on all supported platforms.
233
109
  args.GetReturnValue().Set(static_cast<uint32_t>(getgid()));
234
109
}
235
236
2
static void GetEUid(const FunctionCallbackInfo<Value>& args) {
237
2
  Environment* env = Environment::GetCurrent(args);
238
2
  CHECK(env->has_run_bootstrapping_code());
239
  // uid_t is an uint32_t on all supported platforms.
240
2
  args.GetReturnValue().Set(static_cast<uint32_t>(geteuid()));
241
2
}
242
243
2
static void GetEGid(const FunctionCallbackInfo<Value>& args) {
244
2
  Environment* env = Environment::GetCurrent(args);
245
2
  CHECK(env->has_run_bootstrapping_code());
246
  // gid_t is an uint32_t on all supported platforms.
247
2
  args.GetReturnValue().Set(static_cast<uint32_t>(getegid()));
248
2
}
249
250
2
static void SetGid(const FunctionCallbackInfo<Value>& args) {
251
2
  Environment* env = Environment::GetCurrent(args);
252
2
  CHECK(env->owns_process_state());
253
254
2
  CHECK_EQ(args.Length(), 1);
255

4
  CHECK(args[0]->IsUint32() || args[0]->IsString());
256
257
2
  gid_t gid = gid_by_name(env->isolate(), args[0]);
258
259
2
  if (gid == gid_not_found) {
260
    // Tells JS to throw ERR_INVALID_CREDENTIAL
261
2
    args.GetReturnValue().Set(1);
262
1
  } else if (setgid(gid)) {
263
1
    env->ThrowErrnoException(errno, "setgid");
264
  } else {
265
    args.GetReturnValue().Set(0);
266
  }
267
2
}
268
269
2
static void SetEGid(const FunctionCallbackInfo<Value>& args) {
270
2
  Environment* env = Environment::GetCurrent(args);
271
2
  CHECK(env->owns_process_state());
272
273
2
  CHECK_EQ(args.Length(), 1);
274

4
  CHECK(args[0]->IsUint32() || args[0]->IsString());
275
276
2
  gid_t gid = gid_by_name(env->isolate(), args[0]);
277
278
2
  if (gid == gid_not_found) {
279
    // Tells JS to throw ERR_INVALID_CREDENTIAL
280
2
    args.GetReturnValue().Set(1);
281
1
  } else if (setegid(gid)) {
282
1
    env->ThrowErrnoException(errno, "setegid");
283
  } else {
284
    args.GetReturnValue().Set(0);
285
  }
286
2
}
287
288
3
static void SetUid(const FunctionCallbackInfo<Value>& args) {
289
3
  Environment* env = Environment::GetCurrent(args);
290
3
  CHECK(env->owns_process_state());
291
292
3
  CHECK_EQ(args.Length(), 1);
293

7
  CHECK(args[0]->IsUint32() || args[0]->IsString());
294
295
3
  uid_t uid = uid_by_name(env->isolate(), args[0]);
296
297
3
  if (uid == uid_not_found) {
298
    // Tells JS to throw ERR_INVALID_CREDENTIAL
299
2
    args.GetReturnValue().Set(1);
300
2
  } else if (setuid(uid)) {
301
2
    env->ThrowErrnoException(errno, "setuid");
302
  } else {
303
    args.GetReturnValue().Set(0);
304
  }
305
3
}
306
307
3
static void SetEUid(const FunctionCallbackInfo<Value>& args) {
308
3
  Environment* env = Environment::GetCurrent(args);
309
3
  CHECK(env->owns_process_state());
310
311
3
  CHECK_EQ(args.Length(), 1);
312

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

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

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