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