Coverage Report

Created: 2026-01-21 08:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/node/src/process_wrap.cc
Line
Count
Source
1
// Copyright Joyent, Inc. and other Node contributors.
2
//
3
// Permission is hereby granted, free of charge, to any person obtaining a
4
// copy of this software and associated documentation files (the
5
// "Software"), to deal in the Software without restriction, including
6
// without limitation the rights to use, copy, modify, merge, publish,
7
// distribute, sublicense, and/or sell copies of the Software, and to permit
8
// persons to whom the Software is furnished to do so, subject to the
9
// following conditions:
10
//
11
// The above copyright notice and this permission notice shall be included
12
// in all copies or substantial portions of the Software.
13
//
14
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20
// USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22
#include "env-inl.h"
23
#include "node_errors.h"
24
#include "node_external_reference.h"
25
#include "permission/permission.h"
26
#include "stream_base-inl.h"
27
#include "stream_wrap.h"
28
#include "util-inl.h"
29
30
#include <climits>
31
#include <cstdlib>
32
#include <cstring>
33
34
namespace node {
35
36
using v8::Array;
37
using v8::Context;
38
using v8::FunctionCallbackInfo;
39
using v8::FunctionTemplate;
40
using v8::HandleScope;
41
using v8::Int32;
42
using v8::Integer;
43
using v8::Isolate;
44
using v8::Just;
45
using v8::JustVoid;
46
using v8::Local;
47
using v8::Maybe;
48
using v8::Nothing;
49
using v8::Number;
50
using v8::Object;
51
using v8::String;
52
using v8::Value;
53
54
namespace {
55
56
class ProcessWrap : public HandleWrap {
57
 public:
58
  static void Initialize(Local<Object> target,
59
                         Local<Value> unused,
60
                         Local<Context> context,
61
0
                         void* priv) {
62
0
    Environment* env = Environment::GetCurrent(context);
63
0
    Isolate* isolate = env->isolate();
64
0
    Local<FunctionTemplate> constructor = NewFunctionTemplate(isolate, New);
65
0
    constructor->InstanceTemplate()->SetInternalFieldCount(
66
0
        ProcessWrap::kInternalFieldCount);
67
68
0
    constructor->Inherit(HandleWrap::GetConstructorTemplate(env));
69
70
0
    SetProtoMethod(isolate, constructor, "spawn", Spawn);
71
0
    SetProtoMethod(isolate, constructor, "kill", Kill);
72
73
0
    SetConstructorFunction(context, target, "Process", constructor);
74
0
  }
75
76
0
  static void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
77
0
    registry->Register(New);
78
0
    registry->Register(Spawn);
79
0
    registry->Register(Kill);
80
0
  }
81
82
  SET_NO_MEMORY_INFO()
83
  SET_MEMORY_INFO_NAME(ProcessWrap)
84
  SET_SELF_SIZE(ProcessWrap)
85
86
 private:
87
0
  static void New(const FunctionCallbackInfo<Value>& args) {
88
    // This constructor should not be exposed to public javascript.
89
    // Therefore we assert that we are not trying to call this as a
90
    // normal function.
91
0
    CHECK(args.IsConstructCall());
92
0
    Environment* env = Environment::GetCurrent(args);
93
0
    new ProcessWrap(env, args.This());
94
0
  }
95
96
  ProcessWrap(Environment* env, Local<Object> object)
97
0
      : HandleWrap(env,
98
0
                   object,
99
0
                   reinterpret_cast<uv_handle_t*>(&process_),
100
0
                   AsyncWrap::PROVIDER_PROCESSWRAP) {
101
0
    MarkAsUninitialized();
102
0
  }
103
104
  static Maybe<uv_stream_t*> StreamForWrap(Environment* env,
105
0
                                           Local<Object> stdio) {
106
0
    Local<String> handle_key = env->handle_string();
107
    // This property has always been set by JS land if we are in this code path.
108
0
    Local<Value> val;
109
0
    if (!stdio->Get(env->context(), handle_key).ToLocal(&val)) {
110
0
      return Nothing<uv_stream_t*>();
111
0
    }
112
0
    Local<Object> handle = val.As<Object>();
113
114
0
    uv_stream_t* stream = LibuvStreamWrap::From(env, handle)->stream();
115
0
    CHECK_NOT_NULL(stream);
116
0
    return Just(stream);
117
0
  }
118
119
  static Maybe<void> ParseStdioOptions(
120
      Environment* env,
121
      Local<Object> js_options,
122
0
      std::vector<uv_stdio_container_t>* options_stdio) {
123
0
    Local<Context> context = env->context();
124
0
    Local<String> stdio_key = env->stdio_string();
125
0
    Local<Value> stdios_val;
126
0
    if (!js_options->Get(context, stdio_key).ToLocal(&stdios_val)) {
127
0
      return Nothing<void>();
128
0
    }
129
0
    if (!stdios_val->IsArray()) {
130
0
      THROW_ERR_INVALID_ARG_TYPE(env, "options.stdio must be an array");
131
0
      return Nothing<void>();
132
0
    }
133
0
    Local<Array> stdios = stdios_val.As<Array>();
134
135
0
    uint32_t len = stdios->Length();
136
0
    options_stdio->resize(len);
137
138
0
    for (uint32_t i = 0; i < len; i++) {
139
0
      Local<Value> val;
140
0
      if (!stdios->Get(context, i).ToLocal(&val)) {
141
0
        return Nothing<void>();
142
0
      }
143
0
      Local<Object> stdio = val.As<Object>();
144
0
      Local<Value> type;
145
0
      if (!stdio->Get(context, env->type_string()).ToLocal(&type)) {
146
0
        return Nothing<void>();
147
0
      }
148
149
0
      if (type->StrictEquals(env->ignore_string())) {
150
0
        (*options_stdio)[i].flags = UV_IGNORE;
151
0
      } else if (type->StrictEquals(env->pipe_string())) {
152
0
        (*options_stdio)[i].flags = static_cast<uv_stdio_flags>(
153
0
            UV_CREATE_PIPE | UV_READABLE_PIPE | UV_WRITABLE_PIPE);
154
0
        if (!StreamForWrap(env, stdio).To(&(*options_stdio)[i].data.stream)) {
155
0
          return Nothing<void>();
156
0
        }
157
0
      } else if (type->StrictEquals(env->overlapped_string())) {
158
0
        (*options_stdio)[i].flags =
159
0
            static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_READABLE_PIPE |
160
0
                                        UV_WRITABLE_PIPE | UV_OVERLAPPED_PIPE);
161
0
        if (!StreamForWrap(env, stdio).To(&(*options_stdio)[i].data.stream)) {
162
0
          return Nothing<void>();
163
0
        }
164
0
      } else if (type->StrictEquals(env->wrap_string())) {
165
0
        (*options_stdio)[i].flags = UV_INHERIT_STREAM;
166
0
        if (!StreamForWrap(env, stdio).To(&(*options_stdio)[i].data.stream)) {
167
0
          return Nothing<void>();
168
0
        }
169
0
      } else {
170
0
        Local<String> fd_key = env->fd_string();
171
0
        Local<Value> fd_value;
172
0
        if (!stdio->Get(context, fd_key).ToLocal(&fd_value)) {
173
0
          return Nothing<void>();
174
0
        }
175
0
        CHECK(fd_value->IsNumber());
176
0
        int fd = FromV8Value<int>(fd_value);
177
0
        (*options_stdio)[i].flags = UV_INHERIT_FD;
178
0
        (*options_stdio)[i].data.fd = fd;
179
0
      }
180
0
    }
181
0
    return JustVoid();
182
0
  }
183
184
0
  static void Spawn(const FunctionCallbackInfo<Value>& args) {
185
0
    Environment* env = Environment::GetCurrent(args);
186
0
    Local<Context> context = env->context();
187
0
    ProcessWrap* wrap;
188
0
    ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());
189
0
    int err = 0;
190
191
0
    if (!args[0]->IsObject()) {
192
0
      return THROW_ERR_INVALID_ARG_TYPE(env, "options must be an object");
193
0
    }
194
195
0
    Local<Object> js_options = args[0].As<Object>();
196
197
0
    uv_process_options_t options;
198
0
    memset(&options, 0, sizeof(uv_process_options_t));
199
200
0
    options.exit_cb = OnExit;
201
202
    // options.file
203
0
    Local<Value> file_v;
204
0
    if (!js_options->Get(context, env->file_string()).ToLocal(&file_v)) {
205
0
      return;
206
0
    }
207
0
    CHECK(file_v->IsString());
208
0
    node::Utf8Value file(env->isolate(), file_v);
209
0
    options.file = *file;
210
211
0
    THROW_IF_INSUFFICIENT_PERMISSIONS(
212
0
        env, permission::PermissionScope::kChildProcess, file.ToStringView());
213
214
    // options.uid
215
0
    Local<Value> uid_v;
216
0
    if (!js_options->Get(context, env->uid_string()).ToLocal(&uid_v)) {
217
0
      return;
218
0
    }
219
0
    if (!uid_v->IsUndefined() && !uid_v->IsNull()) {
220
0
      CHECK(uid_v->IsInt32());
221
0
      const int32_t uid = uid_v.As<Int32>()->Value();
222
0
      options.flags |= UV_PROCESS_SETUID;
223
0
      options.uid = static_cast<uv_uid_t>(uid);
224
0
    }
225
226
    // options.gid
227
0
    Local<Value> gid_v;
228
0
    if (!js_options->Get(context, env->gid_string()).ToLocal(&gid_v)) {
229
0
      return;
230
0
    }
231
0
    if (!gid_v->IsUndefined() && !gid_v->IsNull()) {
232
0
      CHECK(gid_v->IsInt32());
233
0
      const int32_t gid = gid_v.As<Int32>()->Value();
234
0
      options.flags |= UV_PROCESS_SETGID;
235
0
      options.gid = static_cast<uv_gid_t>(gid);
236
0
    }
237
238
    // Undocumented feature of Win32 CreateProcess API allows spawning
239
    // batch files directly but is potentially insecure because arguments
240
    // are not escaped (and sometimes cannot be unambiguously escaped),
241
    // hence why they are rejected here.
242
#ifdef _WIN32
243
    if (IsWindowsBatchFile(options.file))
244
      err = UV_EINVAL;
245
#endif
246
247
    // options.args
248
0
    Local<Value> argv_v;
249
0
    if (!js_options->Get(context, env->args_string()).ToLocal(&argv_v)) {
250
0
      return;
251
0
    }
252
0
    std::vector<char*> options_args;
253
0
    std::vector<std::string> args_vals;
254
0
    if (argv_v->IsArray()) {
255
0
      Local<Array> js_argv = argv_v.As<Array>();
256
0
      int argc = js_argv->Length();
257
0
      CHECK_LT(argc, INT_MAX);  // Check for overflow.
258
0
      args_vals.reserve(argc);
259
0
      for (int i = 0; i < argc; i++) {
260
0
        Local<Value> val;
261
0
        if (!js_argv->Get(context, i).ToLocal(&val)) {
262
0
          return;
263
0
        }
264
0
        node::Utf8Value arg(env->isolate(), val);
265
0
        args_vals.emplace_back(arg.ToString());
266
0
      }
267
0
      options_args.resize(args_vals.size() + 1);
268
0
      for (size_t i = 0; i < args_vals.size(); i++) {
269
0
        options_args[i] = const_cast<char*>(args_vals[i].c_str());
270
0
        CHECK_NOT_NULL(options_args[i]);
271
0
      }
272
0
      options_args.back() = nullptr;
273
0
      options.args = options_args.data();
274
0
    }
275
276
    // options.cwd
277
0
    Local<Value> cwd_v;
278
0
    if (!js_options->Get(context, env->cwd_string()).ToLocal(&cwd_v)) {
279
0
      return;
280
0
    }
281
0
    node::Utf8Value cwd(env->isolate(),
282
0
                        cwd_v->IsString() ? cwd_v : Local<Value>());
283
0
    if (cwd.length() > 0) {
284
0
      options.cwd = *cwd;
285
0
    }
286
287
    // options.env
288
0
    Local<Value> env_v;
289
0
    if (!js_options->Get(context, env->env_pairs_string()).ToLocal(&env_v)) {
290
0
      return;
291
0
    }
292
0
    std::vector<char*> options_env;
293
0
    std::vector<std::string> env_vals;
294
0
    if (env_v->IsArray()) {
295
0
      Local<Array> env_opt = env_v.As<Array>();
296
0
      int envc = env_opt->Length();
297
0
      CHECK_LT(envc, INT_MAX);            // Check for overflow.
298
0
      env_vals.reserve(envc);
299
0
      for (int i = 0; i < envc; i++) {
300
0
        Local<Value> val;
301
0
        if (!env_opt->Get(context, i).ToLocal(&val)) {
302
0
          return;
303
0
        }
304
0
        node::Utf8Value pair(env->isolate(), val);
305
0
        env_vals.emplace_back(pair.ToString());
306
0
      }
307
0
      options_env.resize(env_vals.size() + 1);
308
0
      for (size_t i = 0; i < env_vals.size(); i++) {
309
0
        options_env[i] = const_cast<char*>(env_vals[i].c_str());
310
0
        CHECK_NOT_NULL(options_env[i]);
311
0
      }
312
0
      options_env.back() = nullptr;
313
0
      options.env = options_env.data();
314
0
    }
315
316
    // options.stdio
317
0
    std::vector<uv_stdio_container_t> options_stdio;
318
0
    if (ParseStdioOptions(env, js_options, &options_stdio).IsNothing()) {
319
0
      return;
320
0
    }
321
0
    options.stdio = options_stdio.data();
322
0
    options.stdio_count = options_stdio.size();
323
324
    // options.windowsHide
325
0
    Local<Value> hide_v;
326
0
    if (!js_options->Get(context, env->windows_hide_string())
327
0
             .ToLocal(&hide_v)) {
328
0
      return;
329
0
    }
330
331
0
    if (hide_v->IsTrue()) {
332
0
      options.flags |= UV_PROCESS_WINDOWS_HIDE;
333
0
    }
334
335
0
    if (env->hide_console_windows()) {
336
0
      options.flags |= UV_PROCESS_WINDOWS_HIDE_CONSOLE;
337
0
    }
338
339
    // options.windows_verbatim_arguments
340
0
    Local<Value> wva_v;
341
0
    if (!js_options->Get(context, env->windows_verbatim_arguments_string())
342
0
             .ToLocal(&wva_v)) {
343
0
      return;
344
0
    }
345
346
0
    if (wva_v->IsTrue()) {
347
0
      options.flags |= UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS;
348
0
    }
349
350
    // options.detached
351
0
    Local<Value> detached_v;
352
0
    if (!js_options->Get(context, env->detached_string())
353
0
             .ToLocal(&detached_v)) {
354
0
      return;
355
0
    }
356
357
0
    if (detached_v->IsTrue()) {
358
0
      options.flags |= UV_PROCESS_DETACHED;
359
0
    }
360
361
0
    if (err == 0) {
362
0
      err = uv_spawn(env->event_loop(), &wrap->process_, &options);
363
0
      wrap->MarkAsInitialized();
364
0
    }
365
366
0
    if (err == 0) {
367
0
      CHECK_EQ(wrap->process_.data, wrap);
368
0
      if (wrap->object()
369
0
              ->Set(context,
370
0
                    env->pid_string(),
371
0
                    Integer::New(env->isolate(), wrap->process_.pid))
372
0
              .IsNothing()) {
373
0
        return;
374
0
      }
375
0
    }
376
377
0
    args.GetReturnValue().Set(err);
378
0
  }
379
380
0
  static void Kill(const FunctionCallbackInfo<Value>& args) {
381
0
    Environment* env = Environment::GetCurrent(args);
382
0
    ProcessWrap* wrap;
383
0
    ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());
384
0
    int signal;
385
0
    if (!args[0]->Int32Value(env->context()).To(&signal)) {
386
0
      return;
387
0
    }
388
#ifdef _WIN32
389
    if (signal != SIGKILL && signal != SIGTERM && signal != SIGINT &&
390
        signal != SIGQUIT && signal != 0) {
391
      signal = SIGKILL;
392
    }
393
#endif
394
0
    int err = uv_process_kill(&wrap->process_, signal);
395
0
    args.GetReturnValue().Set(err);
396
0
  }
397
398
  static void OnExit(uv_process_t* handle,
399
                     int64_t exit_status,
400
0
                     int term_signal) {
401
0
    ProcessWrap* wrap = ContainerOf(&ProcessWrap::process_, handle);
402
0
    CHECK_EQ(&wrap->process_, handle);
403
404
0
    Environment* env = wrap->env();
405
0
    HandleScope handle_scope(env->isolate());
406
0
    Context::Scope context_scope(env->context());
407
408
0
    Local<Value> argv[] = {
409
0
      Number::New(env->isolate(), static_cast<double>(exit_status)),
410
0
      OneByteString(env->isolate(), signo_string(term_signal))
411
0
    };
412
413
0
    wrap->MakeCallback(env->onexit_string(), arraysize(argv), argv);
414
0
  }
415
416
  uv_process_t process_;
417
};
418
419
420
}  // anonymous namespace
421
}  // namespace node
422
423
NODE_BINDING_CONTEXT_AWARE_INTERNAL(process_wrap, node::ProcessWrap::Initialize)
424
NODE_BINDING_EXTERNAL_REFERENCE(process_wrap,
425
                                node::ProcessWrap::RegisterExternalReferences)