1 #include "env-inl.h"
2 #include "node_internals.h"
3 #include "util-inl.h"
4
5 #ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS
6 #include <grp.h> // getgrnam()
7 #include <pwd.h> // getpwnam()
8 #endif // NODE_IMPLEMENTS_POSIX_CREDENTIALS
9
10 #if !defined(_MSC_VER)
11 #include <unistd.h> // setuid, getuid
12 #endif
13
14 namespace node {
15
16 using v8::Array;
17 using v8::Context;
18 using v8::FunctionCallbackInfo;
19 using v8::HandleScope;
20 using v8::Isolate;
21 using v8::Local;
22 using v8::MaybeLocal;
23 using v8::NewStringType;
24 using v8::Object;
25 using v8::String;
26 using v8::TryCatch;
27 using v8::Uint32;
28 using v8::Value;
29
30 namespace per_process {
31 bool linux_at_secure = false;
32 } // namespace per_process
33
34 namespace credentials {
35
36 // Look up environment variable unless running as setuid root.
SafeGetenv(const char * key,std::string * text,Environment * env)37 bool SafeGetenv(const char* key, std::string* text, Environment* env) {
38 #if !defined(__CloudABI__) && !defined(_WIN32)
39 if (per_process::linux_at_secure || getuid() != geteuid() ||
40 getgid() != getegid())
41 goto fail;
42 #endif
43
44 if (env != nullptr) {
45 HandleScope handle_scope(env->isolate());
46 TryCatch ignore_errors(env->isolate());
47 MaybeLocal<String> maybe_value = env->env_vars()->Get(
48 env->isolate(),
49 String::NewFromUtf8(env->isolate(), key, NewStringType::kNormal)
50 .ToLocalChecked());
51 Local<String> value;
52 if (!maybe_value.ToLocal(&value)) goto fail;
53 String::Utf8Value utf8_value(env->isolate(), value);
54 if (*utf8_value == nullptr) goto fail;
55 *text = std::string(*utf8_value, utf8_value.length());
56 return true;
57 }
58
59 {
60 Mutex::ScopedLock lock(per_process::env_var_mutex);
61
62 size_t init_sz = 256;
63 MaybeStackBuffer<char, 256> val;
64 int ret = uv_os_getenv(key, *val, &init_sz);
65
66 if (ret == UV_ENOBUFS) {
67 // Buffer is not large enough, reallocate to the updated init_sz
68 // and fetch env value again.
69 val.AllocateSufficientStorage(init_sz);
70 ret = uv_os_getenv(key, *val, &init_sz);
71 }
72
73 if (ret >= 0) { // Env key value fetch success.
74 *text = *val;
75 return true;
76 }
77 }
78
79 fail:
80 text->clear();
81 return false;
82 }
83
SafeGetenv(const FunctionCallbackInfo<Value> & args)84 static void SafeGetenv(const FunctionCallbackInfo<Value>& args) {
85 CHECK(args[0]->IsString());
86 Environment* env = Environment::GetCurrent(args);
87 Isolate* isolate = env->isolate();
88 Utf8Value strenvtag(isolate, args[0]);
89 std::string text;
90 if (!SafeGetenv(*strenvtag, &text, env)) return;
91 Local<Value> result =
92 ToV8Value(isolate->GetCurrentContext(), text).ToLocalChecked();
93 args.GetReturnValue().Set(result);
94 }
95
96 #ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS
97
98 static const uid_t uid_not_found = static_cast<uid_t>(-1);
99 static const gid_t gid_not_found = static_cast<gid_t>(-1);
100
uid_by_name(const char * name)101 static uid_t uid_by_name(const char* name) {
102 struct passwd pwd;
103 struct passwd* pp;
104 char buf[8192];
105
106 errno = 0;
107 pp = nullptr;
108
109 if (getpwnam_r(name, &pwd, buf, sizeof(buf), &pp) == 0 && pp != nullptr)
110 return pp->pw_uid;
111
112 return uid_not_found;
113 }
114
name_by_uid(uid_t uid)115 static char* name_by_uid(uid_t uid) {
116 struct passwd pwd;
117 struct passwd* pp;
118 char buf[8192];
119 int rc;
120
121 errno = 0;
122 pp = nullptr;
123
124 if ((rc = getpwuid_r(uid, &pwd, buf, sizeof(buf), &pp)) == 0 &&
125 pp != nullptr) {
126 return strdup(pp->pw_name);
127 }
128
129 if (rc == 0) errno = ENOENT;
130
131 return nullptr;
132 }
133
gid_by_name(const char * name)134 static gid_t gid_by_name(const char* name) {
135 struct group pwd;
136 struct group* pp;
137 char buf[8192];
138
139 errno = 0;
140 pp = nullptr;
141
142 if (getgrnam_r(name, &pwd, buf, sizeof(buf), &pp) == 0 && pp != nullptr)
143 return pp->gr_gid;
144
145 return gid_not_found;
146 }
147
148 #if 0 // For future use.
149 static const char* name_by_gid(gid_t gid) {
150 struct group pwd;
151 struct group* pp;
152 char buf[8192];
153 int rc;
154
155 errno = 0;
156 pp = nullptr;
157
158 if ((rc = getgrgid_r(gid, &pwd, buf, sizeof(buf), &pp)) == 0 &&
159 pp != nullptr) {
160 return strdup(pp->gr_name);
161 }
162
163 if (rc == 0)
164 errno = ENOENT;
165
166 return nullptr;
167 }
168 #endif
169
uid_by_name(Isolate * isolate,Local<Value> value)170 static uid_t uid_by_name(Isolate* isolate, Local<Value> value) {
171 if (value->IsUint32()) {
172 return static_cast<uid_t>(value.As<Uint32>()->Value());
173 } else {
174 Utf8Value name(isolate, value);
175 return uid_by_name(*name);
176 }
177 }
178
gid_by_name(Isolate * isolate,Local<Value> value)179 static gid_t gid_by_name(Isolate* isolate, Local<Value> value) {
180 if (value->IsUint32()) {
181 return static_cast<gid_t>(value.As<Uint32>()->Value());
182 } else {
183 Utf8Value name(isolate, value);
184 return gid_by_name(*name);
185 }
186 }
187
GetUid(const FunctionCallbackInfo<Value> & args)188 static void GetUid(const FunctionCallbackInfo<Value>& args) {
189 Environment* env = Environment::GetCurrent(args);
190 CHECK(env->has_run_bootstrapping_code());
191 // uid_t is an uint32_t on all supported platforms.
192 args.GetReturnValue().Set(static_cast<uint32_t>(getuid()));
193 }
194
GetGid(const FunctionCallbackInfo<Value> & args)195 static void GetGid(const FunctionCallbackInfo<Value>& args) {
196 Environment* env = Environment::GetCurrent(args);
197 CHECK(env->has_run_bootstrapping_code());
198 // gid_t is an uint32_t on all supported platforms.
199 args.GetReturnValue().Set(static_cast<uint32_t>(getgid()));
200 }
201
GetEUid(const FunctionCallbackInfo<Value> & args)202 static void GetEUid(const FunctionCallbackInfo<Value>& args) {
203 Environment* env = Environment::GetCurrent(args);
204 CHECK(env->has_run_bootstrapping_code());
205 // uid_t is an uint32_t on all supported platforms.
206 args.GetReturnValue().Set(static_cast<uint32_t>(geteuid()));
207 }
208
GetEGid(const FunctionCallbackInfo<Value> & args)209 static void GetEGid(const FunctionCallbackInfo<Value>& args) {
210 Environment* env = Environment::GetCurrent(args);
211 CHECK(env->has_run_bootstrapping_code());
212 // gid_t is an uint32_t on all supported platforms.
213 args.GetReturnValue().Set(static_cast<uint32_t>(getegid()));
214 }
215
SetGid(const FunctionCallbackInfo<Value> & args)216 static void SetGid(const FunctionCallbackInfo<Value>& args) {
217 Environment* env = Environment::GetCurrent(args);
218 CHECK(env->owns_process_state());
219
220 CHECK_EQ(args.Length(), 1);
221 CHECK(args[0]->IsUint32() || args[0]->IsString());
222
223 gid_t gid = gid_by_name(env->isolate(), args[0]);
224
225 if (gid == gid_not_found) {
226 // Tells JS to throw ERR_INVALID_CREDENTIAL
227 args.GetReturnValue().Set(1);
228 } else if (setgid(gid)) {
229 env->ThrowErrnoException(errno, "setgid");
230 } else {
231 args.GetReturnValue().Set(0);
232 }
233 }
234
SetEGid(const FunctionCallbackInfo<Value> & args)235 static void SetEGid(const FunctionCallbackInfo<Value>& args) {
236 Environment* env = Environment::GetCurrent(args);
237 CHECK(env->owns_process_state());
238
239 CHECK_EQ(args.Length(), 1);
240 CHECK(args[0]->IsUint32() || args[0]->IsString());
241
242 gid_t gid = gid_by_name(env->isolate(), args[0]);
243
244 if (gid == gid_not_found) {
245 // Tells JS to throw ERR_INVALID_CREDENTIAL
246 args.GetReturnValue().Set(1);
247 } else if (setegid(gid)) {
248 env->ThrowErrnoException(errno, "setegid");
249 } else {
250 args.GetReturnValue().Set(0);
251 }
252 }
253
SetUid(const FunctionCallbackInfo<Value> & args)254 static void SetUid(const FunctionCallbackInfo<Value>& args) {
255 Environment* env = Environment::GetCurrent(args);
256 CHECK(env->owns_process_state());
257
258 CHECK_EQ(args.Length(), 1);
259 CHECK(args[0]->IsUint32() || args[0]->IsString());
260
261 uid_t uid = uid_by_name(env->isolate(), args[0]);
262
263 if (uid == uid_not_found) {
264 // Tells JS to throw ERR_INVALID_CREDENTIAL
265 args.GetReturnValue().Set(1);
266 } else if (setuid(uid)) {
267 env->ThrowErrnoException(errno, "setuid");
268 } else {
269 args.GetReturnValue().Set(0);
270 }
271 }
272
SetEUid(const FunctionCallbackInfo<Value> & args)273 static void SetEUid(const FunctionCallbackInfo<Value>& args) {
274 Environment* env = Environment::GetCurrent(args);
275 CHECK(env->owns_process_state());
276
277 CHECK_EQ(args.Length(), 1);
278 CHECK(args[0]->IsUint32() || args[0]->IsString());
279
280 uid_t uid = uid_by_name(env->isolate(), args[0]);
281
282 if (uid == uid_not_found) {
283 // Tells JS to throw ERR_INVALID_CREDENTIAL
284 args.GetReturnValue().Set(1);
285 } else if (seteuid(uid)) {
286 env->ThrowErrnoException(errno, "seteuid");
287 } else {
288 args.GetReturnValue().Set(0);
289 }
290 }
291
GetGroups(const FunctionCallbackInfo<Value> & args)292 static void GetGroups(const FunctionCallbackInfo<Value>& args) {
293 Environment* env = Environment::GetCurrent(args);
294 CHECK(env->has_run_bootstrapping_code());
295
296 int ngroups = getgroups(0, nullptr);
297 if (ngroups == -1) return env->ThrowErrnoException(errno, "getgroups");
298
299 std::vector<gid_t> groups(ngroups);
300
301 ngroups = getgroups(ngroups, groups.data());
302 if (ngroups == -1)
303 return env->ThrowErrnoException(errno, "getgroups");
304
305 groups.resize(ngroups);
306 gid_t egid = getegid();
307 if (std::find(groups.begin(), groups.end(), egid) == groups.end())
308 groups.push_back(egid);
309 MaybeLocal<Value> array = ToV8Value(env->context(), groups);
310 if (!array.IsEmpty())
311 args.GetReturnValue().Set(array.ToLocalChecked());
312 }
313
SetGroups(const FunctionCallbackInfo<Value> & args)314 static void SetGroups(const FunctionCallbackInfo<Value>& args) {
315 Environment* env = Environment::GetCurrent(args);
316
317 CHECK_EQ(args.Length(), 1);
318 CHECK(args[0]->IsArray());
319
320 Local<Array> groups_list = args[0].As<Array>();
321 size_t size = groups_list->Length();
322 MaybeStackBuffer<gid_t, 64> groups(size);
323
324 for (size_t i = 0; i < size; i++) {
325 gid_t gid = gid_by_name(
326 env->isolate(), groups_list->Get(env->context(), i).ToLocalChecked());
327
328 if (gid == gid_not_found) {
329 // Tells JS to throw ERR_INVALID_CREDENTIAL
330 args.GetReturnValue().Set(static_cast<uint32_t>(i + 1));
331 return;
332 }
333
334 groups[i] = gid;
335 }
336
337 int rc = setgroups(size, *groups);
338
339 if (rc == -1) return env->ThrowErrnoException(errno, "setgroups");
340
341 args.GetReturnValue().Set(0);
342 }
343
InitGroups(const FunctionCallbackInfo<Value> & args)344 static void InitGroups(const FunctionCallbackInfo<Value>& args) {
345 Environment* env = Environment::GetCurrent(args);
346
347 CHECK_EQ(args.Length(), 2);
348 CHECK(args[0]->IsUint32() || args[0]->IsString());
349 CHECK(args[1]->IsUint32() || args[1]->IsString());
350
351 Utf8Value arg0(env->isolate(), args[0]);
352 gid_t extra_group;
353 bool must_free;
354 char* user;
355
356 if (args[0]->IsUint32()) {
357 user = name_by_uid(args[0].As<Uint32>()->Value());
358 must_free = true;
359 } else {
360 user = *arg0;
361 must_free = false;
362 }
363
364 if (user == nullptr) {
365 // Tells JS to throw ERR_INVALID_CREDENTIAL
366 return args.GetReturnValue().Set(1);
367 }
368
369 extra_group = gid_by_name(env->isolate(), args[1]);
370
371 if (extra_group == gid_not_found) {
372 if (must_free) free(user);
373 // Tells JS to throw ERR_INVALID_CREDENTIAL
374 return args.GetReturnValue().Set(2);
375 }
376
377 int rc = initgroups(user, extra_group);
378
379 if (must_free) free(user);
380
381 if (rc) return env->ThrowErrnoException(errno, "initgroups");
382
383 args.GetReturnValue().Set(0);
384 }
385
386 #endif // NODE_IMPLEMENTS_POSIX_CREDENTIALS
387
Initialize(Local<Object> target,Local<Value> unused,Local<Context> context,void * priv)388 static void Initialize(Local<Object> target,
389 Local<Value> unused,
390 Local<Context> context,
391 void* priv) {
392 Environment* env = Environment::GetCurrent(context);
393 Isolate* isolate = env->isolate();
394
395 env->SetMethod(target, "safeGetenv", SafeGetenv);
396
397 #ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS
398 READONLY_TRUE_PROPERTY(target, "implementsPosixCredentials");
399 env->SetMethodNoSideEffect(target, "getuid", GetUid);
400 env->SetMethodNoSideEffect(target, "geteuid", GetEUid);
401 env->SetMethodNoSideEffect(target, "getgid", GetGid);
402 env->SetMethodNoSideEffect(target, "getegid", GetEGid);
403 env->SetMethodNoSideEffect(target, "getgroups", GetGroups);
404
405 if (env->owns_process_state()) {
406 env->SetMethod(target, "initgroups", InitGroups);
407 env->SetMethod(target, "setegid", SetEGid);
408 env->SetMethod(target, "seteuid", SetEUid);
409 env->SetMethod(target, "setgid", SetGid);
410 env->SetMethod(target, "setuid", SetUid);
411 env->SetMethod(target, "setgroups", SetGroups);
412 }
413 #endif // NODE_IMPLEMENTS_POSIX_CREDENTIALS
414 }
415
416 } // namespace credentials
417 } // namespace node
418
419 NODE_MODULE_CONTEXT_AWARE_INTERNAL(credentials, node::credentials::Initialize)
420