GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: node_credentials.cc Lines: 201 232 86.6 %
Date: 2022-07-21 04:16:21 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
67803
bool HasOnly(int capability) {
43
  DCHECK(cap_valid(capability));
44
45
  struct __user_cap_data_struct cap_data[2];
46
67803
  struct __user_cap_header_struct cap_header_data = {
47
    _LINUX_CAPABILITY_VERSION_3,
48
67803
    getpid()};
49
50
51
67803
  if (syscall(SYS_capget, &cap_header_data, &cap_data) != 0) {
52
    return false;
53
  }
54
67803
  if (capability < 32) {
55
67803
    return cap_data[0].permitted ==
56
67803
        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
67803
bool SafeGetenv(const char* key,
68
                std::string* text,
69
                std::shared_ptr<KVStore> env_vars,
70
                v8::Isolate* isolate) {
71
#if !defined(__CloudABI__) && !defined(_WIN32)
72
#if defined(__linux__)
73

203409
  if ((!HasOnly(CAP_NET_BIND_SERVICE) && per_process::linux_at_secure) ||
74

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

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

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

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

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

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

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

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

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