Coverage Report

Created: 2025-09-05 10:05

/src/node/src/node_credentials.cc
Line
Count
Source (jump to first uncovered line)
1
#include "env-inl.h"
2
#include "node_errors.h"
3
#include "node_external_reference.h"
4
#include "node_internals.h"
5
#include "util-inl.h"
6
7
#ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS
8
#include <grp.h>  // getgrnam()
9
#include <pwd.h>  // getpwnam()
10
#endif            // NODE_IMPLEMENTS_POSIX_CREDENTIALS
11
12
#if !defined(_MSC_VER)
13
#include <unistd.h>  // setuid, getuid
14
#endif
15
#ifdef __linux__
16
#include <dlfcn.h>  // dlsym()
17
#include <linux/capability.h>
18
#include <sys/auxv.h>
19
#include <sys/syscall.h>
20
#endif  // __linux__
21
22
namespace node {
23
24
using v8::Array;
25
using v8::Context;
26
using v8::FunctionCallbackInfo;
27
using v8::Isolate;
28
using v8::Local;
29
using v8::MaybeLocal;
30
using v8::Object;
31
using v8::Uint32;
32
using v8::Value;
33
34
1.14M
bool linux_at_secure() {
35
  // This could reasonably be a static variable, but this way
36
  // we can guarantee that this function is always usable
37
  // and returns the correct value,  e.g. even in static
38
  // initialization code in other files.
39
1.14M
#ifdef __linux__
40
1.14M
  static const bool value = getauxval(AT_SECURE);
41
1.14M
  return value;
42
#else
43
  return false;
44
#endif
45
1.14M
}
46
47
namespace credentials {
48
49
#if defined(__linux__)
50
// Returns true if the current process only has the passed-in capability.
51
1.14M
static bool HasOnly(int capability) {
52
1.14M
  DCHECK(cap_valid(capability));
53
54
1.14M
  struct __user_cap_data_struct cap_data[_LINUX_CAPABILITY_U32S_3];
55
1.14M
  struct __user_cap_header_struct cap_header_data = {
56
1.14M
    _LINUX_CAPABILITY_VERSION_3,
57
1.14M
    getpid()};
58
59
60
1.14M
  if (syscall(SYS_capget, &cap_header_data, &cap_data) != 0) {
61
0
    return false;
62
0
  }
63
64
1.14M
  static_assert(arraysize(cap_data) == 2);
65
1.14M
  return cap_data[CAP_TO_INDEX(capability)].permitted ==
66
1.14M
             static_cast<unsigned int>(CAP_TO_MASK(capability)) &&
67
1.14M
         cap_data[1 - CAP_TO_INDEX(capability)].permitted == 0;
68
1.14M
}
69
#endif
70
71
// Look up the environment variable and allow the lookup if the current
72
// process only has the capability CAP_NET_BIND_SERVICE set. If the current
73
// process does not have any capabilities set and the process is running as
74
// setuid root then lookup will not be allowed.
75
bool SafeGetenv(const char* key,
76
                std::string* text,
77
1.14M
                std::shared_ptr<KVStore> env_vars) {
78
1.14M
#if !defined(__CloudABI__) && !defined(_WIN32)
79
1.14M
#if defined(__linux__)
80
1.14M
  if ((!HasOnly(CAP_NET_BIND_SERVICE) && linux_at_secure()) ||
81
1.14M
      getuid() != geteuid() || getgid() != getegid())
82
#else
83
  if (linux_at_secure() || getuid() != geteuid() || getgid() != getegid())
84
#endif
85
0
    return false;
86
1.14M
#endif
87
88
  // Fallback to system environment which reads the real environment variable
89
  // through uv_os_getenv.
90
1.14M
  if (env_vars == nullptr) {
91
761k
    env_vars = per_process::system_environment;
92
761k
  }
93
94
1.14M
  return env_vars->Get(key).To(text);
95
1.14M
}
96
97
254k
static void SafeGetenv(const FunctionCallbackInfo<Value>& args) {
98
254k
  CHECK(args[0]->IsString());
99
254k
  Environment* env = Environment::GetCurrent(args);
100
254k
  Isolate* isolate = env->isolate();
101
254k
  Utf8Value strenvtag(isolate, args[0]);
102
254k
  std::string text;
103
254k
  if (!SafeGetenv(*strenvtag, &text, env->env_vars())) return;
104
127k
  Local<Value> result =
105
127k
      ToV8Value(isolate->GetCurrentContext(), text).ToLocalChecked();
106
127k
  args.GetReturnValue().Set(result);
107
127k
}
108
109
#ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS
110
111
static const uid_t uid_not_found = static_cast<uid_t>(-1);
112
static const gid_t gid_not_found = static_cast<gid_t>(-1);
113
114
0
static uid_t uid_by_name(const char* name) {
115
0
  struct passwd pwd;
116
0
  struct passwd* pp;
117
0
  char buf[8192];
118
119
0
  errno = 0;
120
0
  pp = nullptr;
121
122
0
  if (getpwnam_r(name, &pwd, buf, sizeof(buf), &pp) == 0 && pp != nullptr)
123
0
    return pp->pw_uid;
124
125
0
  return uid_not_found;
126
0
}
127
128
0
static char* name_by_uid(uid_t uid) {
129
0
  struct passwd pwd;
130
0
  struct passwd* pp;
131
0
  char buf[8192];
132
0
  int rc;
133
134
0
  errno = 0;
135
0
  pp = nullptr;
136
137
0
  if ((rc = getpwuid_r(uid, &pwd, buf, sizeof(buf), &pp)) == 0 &&
138
0
      pp != nullptr) {
139
0
    return strdup(pp->pw_name);
140
0
  }
141
142
0
  if (rc == 0) errno = ENOENT;
143
144
0
  return nullptr;
145
0
}
146
147
0
static gid_t gid_by_name(const char* name) {
148
0
  struct group pwd;
149
0
  struct group* pp;
150
0
  char buf[8192];
151
152
0
  errno = 0;
153
0
  pp = nullptr;
154
155
0
  if (getgrnam_r(name, &pwd, buf, sizeof(buf), &pp) == 0 && pp != nullptr)
156
0
    return pp->gr_gid;
157
158
0
  return gid_not_found;
159
0
}
160
161
#if 0  // For future use.
162
static const char* name_by_gid(gid_t gid) {
163
  struct group pwd;
164
  struct group* pp;
165
  char buf[8192];
166
  int rc;
167
168
  errno = 0;
169
  pp = nullptr;
170
171
  if ((rc = getgrgid_r(gid, &pwd, buf, sizeof(buf), &pp)) == 0 &&
172
      pp != nullptr) {
173
    return strdup(pp->gr_name);
174
  }
175
176
  if (rc == 0)
177
    errno = ENOENT;
178
179
  return nullptr;
180
}
181
#endif
182
183
0
static uid_t uid_by_name(Isolate* isolate, Local<Value> value) {
184
0
  if (value->IsUint32()) {
185
0
    static_assert(std::is_same<uid_t, uint32_t>::value);
186
0
    return value.As<Uint32>()->Value();
187
0
  } else {
188
0
    Utf8Value name(isolate, value);
189
0
    return uid_by_name(*name);
190
0
  }
191
0
}
192
193
0
static gid_t gid_by_name(Isolate* isolate, Local<Value> value) {
194
0
  if (value->IsUint32()) {
195
0
    static_assert(std::is_same<gid_t, uint32_t>::value);
196
0
    return value.As<Uint32>()->Value();
197
0
  } else {
198
0
    Utf8Value name(isolate, value);
199
0
    return gid_by_name(*name);
200
0
  }
201
0
}
202
203
#ifdef __linux__
204
extern "C" {
205
int uv__node_patch_is_using_io_uring(void);
206
207
int uv__node_patch_is_using_io_uring(void) __attribute__((weak));
208
209
typedef int (*is_using_io_uring_fn)(void);
210
}
211
#endif  // __linux__
212
213
0
static bool UvMightBeUsingIoUring() {
214
0
#ifdef __linux__
215
  // Support for io_uring is only included in libuv 1.45.0 and later, and only
216
  // on Linux (and Android, but there it is always disabled). The patch that we
217
  // apply to libuv to work around the io_uring security issue adds a function
218
  // that tells us whether io_uring is being used. If that function is not
219
  // present, we assume that we are dynamically linking against an unpatched
220
  // version.
221
0
  static std::atomic<is_using_io_uring_fn> check =
222
0
      uv__node_patch_is_using_io_uring;
223
0
  if (check == nullptr) {
224
0
    check = reinterpret_cast<is_using_io_uring_fn>(
225
0
        dlsym(RTLD_DEFAULT, "uv__node_patch_is_using_io_uring"));
226
0
  }
227
0
  return uv_version() >= 0x012d00u && (check == nullptr || (*check)());
228
#else
229
  return false;
230
#endif
231
0
}
232
233
0
static bool ThrowIfUvMightBeUsingIoUring(Environment* env, const char* fn) {
234
0
  if (UvMightBeUsingIoUring()) {
235
0
    node::THROW_ERR_INVALID_STATE(
236
0
        env, "%s() disabled: io_uring may be enabled. See CVE-2024-22017.", fn);
237
0
    return true;
238
0
  }
239
0
  return false;
240
0
}
241
242
0
static void GetUid(const FunctionCallbackInfo<Value>& args) {
243
0
  Environment* env = Environment::GetCurrent(args);
244
0
  CHECK(env->has_run_bootstrapping_code());
245
  // uid_t is an uint32_t on all supported platforms.
246
0
  args.GetReturnValue().Set(static_cast<uint32_t>(getuid()));
247
0
}
248
249
0
static void GetGid(const FunctionCallbackInfo<Value>& args) {
250
0
  Environment* env = Environment::GetCurrent(args);
251
0
  CHECK(env->has_run_bootstrapping_code());
252
  // gid_t is an uint32_t on all supported platforms.
253
0
  args.GetReturnValue().Set(static_cast<uint32_t>(getgid()));
254
0
}
255
256
0
static void GetEUid(const FunctionCallbackInfo<Value>& args) {
257
0
  Environment* env = Environment::GetCurrent(args);
258
0
  CHECK(env->has_run_bootstrapping_code());
259
  // uid_t is an uint32_t on all supported platforms.
260
0
  args.GetReturnValue().Set(static_cast<uint32_t>(geteuid()));
261
0
}
262
263
0
static void GetEGid(const FunctionCallbackInfo<Value>& args) {
264
0
  Environment* env = Environment::GetCurrent(args);
265
0
  CHECK(env->has_run_bootstrapping_code());
266
  // gid_t is an uint32_t on all supported platforms.
267
0
  args.GetReturnValue().Set(static_cast<uint32_t>(getegid()));
268
0
}
269
270
0
static void SetGid(const FunctionCallbackInfo<Value>& args) {
271
0
  Environment* env = Environment::GetCurrent(args);
272
0
  CHECK(env->owns_process_state());
273
274
0
  CHECK_EQ(args.Length(), 1);
275
0
  CHECK(args[0]->IsUint32() || args[0]->IsString());
276
277
0
  if (ThrowIfUvMightBeUsingIoUring(env, "setgid")) return;
278
279
0
  gid_t gid = gid_by_name(env->isolate(), args[0]);
280
281
0
  if (gid == gid_not_found) {
282
    // Tells JS to throw ERR_INVALID_CREDENTIAL
283
0
    args.GetReturnValue().Set(1);
284
0
  } else if (setgid(gid)) {
285
0
    env->ThrowErrnoException(errno, "setgid");
286
0
  } else {
287
0
    args.GetReturnValue().Set(0);
288
0
  }
289
0
}
290
291
0
static void SetEGid(const FunctionCallbackInfo<Value>& args) {
292
0
  Environment* env = Environment::GetCurrent(args);
293
0
  CHECK(env->owns_process_state());
294
295
0
  CHECK_EQ(args.Length(), 1);
296
0
  CHECK(args[0]->IsUint32() || args[0]->IsString());
297
298
0
  if (ThrowIfUvMightBeUsingIoUring(env, "setegid")) return;
299
300
0
  gid_t gid = gid_by_name(env->isolate(), args[0]);
301
302
0
  if (gid == gid_not_found) {
303
    // Tells JS to throw ERR_INVALID_CREDENTIAL
304
0
    args.GetReturnValue().Set(1);
305
0
  } else if (setegid(gid)) {
306
0
    env->ThrowErrnoException(errno, "setegid");
307
0
  } else {
308
0
    args.GetReturnValue().Set(0);
309
0
  }
310
0
}
311
312
0
static void SetUid(const FunctionCallbackInfo<Value>& args) {
313
0
  Environment* env = Environment::GetCurrent(args);
314
0
  CHECK(env->owns_process_state());
315
316
0
  CHECK_EQ(args.Length(), 1);
317
0
  CHECK(args[0]->IsUint32() || args[0]->IsString());
318
319
0
  if (ThrowIfUvMightBeUsingIoUring(env, "setuid")) return;
320
321
0
  uid_t uid = uid_by_name(env->isolate(), args[0]);
322
323
0
  if (uid == uid_not_found) {
324
    // Tells JS to throw ERR_INVALID_CREDENTIAL
325
0
    args.GetReturnValue().Set(1);
326
0
  } else if (setuid(uid)) {
327
0
    env->ThrowErrnoException(errno, "setuid");
328
0
  } else {
329
0
    args.GetReturnValue().Set(0);
330
0
  }
331
0
}
332
333
0
static void SetEUid(const FunctionCallbackInfo<Value>& args) {
334
0
  Environment* env = Environment::GetCurrent(args);
335
0
  CHECK(env->owns_process_state());
336
337
0
  CHECK_EQ(args.Length(), 1);
338
0
  CHECK(args[0]->IsUint32() || args[0]->IsString());
339
340
0
  if (ThrowIfUvMightBeUsingIoUring(env, "seteuid")) return;
341
342
0
  uid_t uid = uid_by_name(env->isolate(), args[0]);
343
344
0
  if (uid == uid_not_found) {
345
    // Tells JS to throw ERR_INVALID_CREDENTIAL
346
0
    args.GetReturnValue().Set(1);
347
0
  } else if (seteuid(uid)) {
348
0
    env->ThrowErrnoException(errno, "seteuid");
349
0
  } else {
350
0
    args.GetReturnValue().Set(0);
351
0
  }
352
0
}
353
354
0
static void GetGroups(const FunctionCallbackInfo<Value>& args) {
355
0
  Environment* env = Environment::GetCurrent(args);
356
0
  CHECK(env->has_run_bootstrapping_code());
357
358
0
  int ngroups = getgroups(0, nullptr);
359
0
  if (ngroups == -1) return env->ThrowErrnoException(errno, "getgroups");
360
361
0
  std::vector<gid_t> groups(ngroups);
362
363
0
  ngroups = getgroups(ngroups, groups.data());
364
0
  if (ngroups == -1)
365
0
    return env->ThrowErrnoException(errno, "getgroups");
366
367
0
  groups.resize(ngroups);
368
0
  gid_t egid = getegid();
369
0
  if (std::find(groups.begin(), groups.end(), egid) == groups.end())
370
0
    groups.push_back(egid);
371
0
  MaybeLocal<Value> array = ToV8Value(env->context(), groups);
372
0
  if (!array.IsEmpty())
373
0
    args.GetReturnValue().Set(array.ToLocalChecked());
374
0
}
375
376
0
static void SetGroups(const FunctionCallbackInfo<Value>& args) {
377
0
  Environment* env = Environment::GetCurrent(args);
378
379
0
  CHECK_EQ(args.Length(), 1);
380
0
  CHECK(args[0]->IsArray());
381
382
0
  if (ThrowIfUvMightBeUsingIoUring(env, "setgroups")) return;
383
384
0
  Local<Array> groups_list = args[0].As<Array>();
385
0
  size_t size = groups_list->Length();
386
0
  MaybeStackBuffer<gid_t, 64> groups(size);
387
388
0
  for (size_t i = 0; i < size; i++) {
389
0
    gid_t gid = gid_by_name(
390
0
        env->isolate(), groups_list->Get(env->context(), i).ToLocalChecked());
391
392
0
    if (gid == gid_not_found) {
393
      // Tells JS to throw ERR_INVALID_CREDENTIAL
394
0
      args.GetReturnValue().Set(static_cast<uint32_t>(i + 1));
395
0
      return;
396
0
    }
397
398
0
    groups[i] = gid;
399
0
  }
400
401
0
  int rc = setgroups(size, *groups);
402
403
0
  if (rc == -1) return env->ThrowErrnoException(errno, "setgroups");
404
405
0
  args.GetReturnValue().Set(0);
406
0
}
407
408
0
static void InitGroups(const FunctionCallbackInfo<Value>& args) {
409
0
  Environment* env = Environment::GetCurrent(args);
410
411
0
  CHECK_EQ(args.Length(), 2);
412
0
  CHECK(args[0]->IsUint32() || args[0]->IsString());
413
0
  CHECK(args[1]->IsUint32() || args[1]->IsString());
414
415
0
  if (ThrowIfUvMightBeUsingIoUring(env, "initgroups")) return;
416
417
0
  Utf8Value arg0(env->isolate(), args[0]);
418
0
  gid_t extra_group;
419
0
  bool must_free;
420
0
  char* user;
421
422
0
  if (args[0]->IsUint32()) {
423
0
    user = name_by_uid(args[0].As<Uint32>()->Value());
424
0
    must_free = true;
425
0
  } else {
426
0
    user = *arg0;
427
0
    must_free = false;
428
0
  }
429
430
0
  if (user == nullptr) {
431
    // Tells JS to throw ERR_INVALID_CREDENTIAL
432
0
    return args.GetReturnValue().Set(1);
433
0
  }
434
435
0
  extra_group = gid_by_name(env->isolate(), args[1]);
436
437
0
  if (extra_group == gid_not_found) {
438
0
    if (must_free) free(user);
439
    // Tells JS to throw ERR_INVALID_CREDENTIAL
440
0
    return args.GetReturnValue().Set(2);
441
0
  }
442
443
0
  int rc = initgroups(user, extra_group);
444
445
0
  if (must_free) free(user);
446
447
0
  if (rc) return env->ThrowErrnoException(errno, "initgroups");
448
449
0
  args.GetReturnValue().Set(0);
450
0
}
451
452
#endif  // NODE_IMPLEMENTS_POSIX_CREDENTIALS
453
454
0
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
455
0
  registry->Register(SafeGetenv);
456
457
0
#ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS
458
0
  registry->Register(GetUid);
459
0
  registry->Register(GetEUid);
460
0
  registry->Register(GetGid);
461
0
  registry->Register(GetEGid);
462
0
  registry->Register(GetGroups);
463
464
0
  registry->Register(InitGroups);
465
0
  registry->Register(SetEGid);
466
0
  registry->Register(SetEUid);
467
0
  registry->Register(SetGid);
468
0
  registry->Register(SetUid);
469
0
  registry->Register(SetGroups);
470
0
#endif  // NODE_IMPLEMENTS_POSIX_CREDENTIALS
471
0
}
472
473
static void Initialize(Local<Object> target,
474
                       Local<Value> unused,
475
                       Local<Context> context,
476
127k
                       void* priv) {
477
127k
  SetMethod(context, target, "safeGetenv", SafeGetenv);
478
479
127k
#ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS
480
127k
  Environment* env = Environment::GetCurrent(context);
481
127k
  Isolate* isolate = env->isolate();
482
483
127k
  READONLY_TRUE_PROPERTY(target, "implementsPosixCredentials");
484
127k
  SetMethodNoSideEffect(context, target, "getuid", GetUid);
485
127k
  SetMethodNoSideEffect(context, target, "geteuid", GetEUid);
486
127k
  SetMethodNoSideEffect(context, target, "getgid", GetGid);
487
127k
  SetMethodNoSideEffect(context, target, "getegid", GetEGid);
488
127k
  SetMethodNoSideEffect(context, target, "getgroups", GetGroups);
489
490
127k
  if (env->owns_process_state()) {
491
127k
    SetMethod(context, target, "initgroups", InitGroups);
492
127k
    SetMethod(context, target, "setegid", SetEGid);
493
127k
    SetMethod(context, target, "seteuid", SetEUid);
494
127k
    SetMethod(context, target, "setgid", SetGid);
495
127k
    SetMethod(context, target, "setuid", SetUid);
496
127k
    SetMethod(context, target, "setgroups", SetGroups);
497
127k
  }
498
127k
#endif  // NODE_IMPLEMENTS_POSIX_CREDENTIALS
499
127k
}
500
501
}  // namespace credentials
502
}  // namespace node
503
504
NODE_BINDING_CONTEXT_AWARE_INTERNAL(credentials, node::credentials::Initialize)
505
NODE_BINDING_EXTERNAL_REFERENCE(credentials,
506
                                node::credentials::RegisterExternalReferences)