Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit f8aff90

Browse filesBrowse files
ShogunPandaaduh95
authored andcommitted
process: add execve
PR-URL: #56496 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com> Reviewed-By: Bryan English <bryan@bryanenglish.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 7f5e316 commit f8aff90
Copy full SHA for f8aff90
Expand file treeCollapse file tree

14 files changed

+470
-2
lines changed
Open diff view settings
Collapse file

‎doc/api/diagnostics_channel.md‎

Copy file name to clipboardExpand all lines: doc/api/diagnostics_channel.md
+9Lines changed: 9 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -1319,6 +1319,14 @@ added: v16.18.0
13191319

13201320
Emitted when a new process is created.
13211321

1322+
`execve`
1323+
1324+
* `execPath` {string}
1325+
* `args` {string\[]}
1326+
* `env` {string\[]}
1327+
1328+
Emitted when [`process.execve()`][] is invoked.
1329+
13221330
#### Worker Thread
13231331

13241332
<!-- YAML
@@ -1348,5 +1356,6 @@ Emitted when a new thread is created.
13481356
[`end` event]: #endevent
13491357
[`error` event]: #errorevent
13501358
[`net.Server.listen()`]: net.md#serverlisten
1359+
[`process.execve()`]: process.md#processexecvefile-args-env
13511360
[`start` event]: #startevent
13521361
[context loss]: async_context.md#troubleshooting-context-loss
Collapse file

‎doc/api/process.md‎

Copy file name to clipboardExpand all lines: doc/api/process.md
+28-2Lines changed: 28 additions & 2 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -2502,8 +2502,7 @@ if (process.getuid) {
25022502
}
25032503
```
25042504
2505-
This function is only available on POSIX platforms (i.e. not Windows or
2506-
Android).
2505+
This function not available on Windows.
25072506
25082507
## `process.hasUncaughtExceptionCaptureCallback()`
25092508
@@ -3327,6 +3326,33 @@ In custom builds from non-release versions of the source tree, only the
33273326
`name` property may be present. The additional properties should not be
33283327
relied upon to exist.
33293328
3329+
## `process.execve(file[, args[, env]])`
3330+
3331+
<!-- YAML
3332+
added: REPLACEME
3333+
-->
3334+
3335+
> Stability: 1 - Experimental
3336+
3337+
* `file` {string} The name or path of the executable file to run.
3338+
* `args` {string\[]} List of string arguments. No argument can contain a null-byte (`\u0000`).
3339+
* `env` {Object} Environment key-value pairs.
3340+
No key or value can contain a null-byte (`\u0000`).
3341+
**Default:** `process.env`.
3342+
3343+
Replaces the current process with a new process.
3344+
3345+
This is achieved by using the `execve` POSIX function and therefore no memory or other
3346+
resources from the current process are preserved, except for the standard input,
3347+
standard output and standard error file descriptor.
3348+
3349+
All other resources are discarded by the system when the processes are swapped, without triggering
3350+
any exit or close events and without running any cleanup handler.
3351+
3352+
This function will never return, unless an error occurred.
3353+
3354+
This function is only available on POSIX platforms (i.e. not Windows or Android).
3355+
33303356
## `process.report`
33313357
33323358
<!-- YAML
Collapse file

‎lib/internal/bootstrap/node.js‎

Copy file name to clipboardExpand all lines: lib/internal/bootstrap/node.js
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ const rawMethods = internalBinding('process_methods');
177177
process.availableMemory = rawMethods.availableMemory;
178178
process.kill = wrapped.kill;
179179
process.exit = wrapped.exit;
180+
process.execve = wrapped.execve;
180181
process.ref = perThreadSetup.ref;
181182
process.unref = perThreadSetup.unref;
182183

Collapse file

‎lib/internal/process/per_thread.js‎

Copy file name to clipboardExpand all lines: lib/internal/process/per_thread.js
+60Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const {
1616
FunctionPrototypeCall,
1717
NumberMAX_SAFE_INTEGER,
1818
ObjectDefineProperty,
19+
ObjectEntries,
1920
ObjectFreeze,
2021
ReflectApply,
2122
RegExpPrototypeExec,
@@ -24,6 +25,7 @@ const {
2425
SetPrototypeEntries,
2526
SetPrototypeValues,
2627
StringPrototypeEndsWith,
28+
StringPrototypeIncludes,
2729
StringPrototypeReplace,
2830
StringPrototypeSlice,
2931
Symbol,
@@ -34,20 +36,27 @@ const {
3436
const {
3537
ErrnoException,
3638
codes: {
39+
ERR_FEATURE_UNAVAILABLE_ON_PLATFORM,
3740
ERR_INVALID_ARG_TYPE,
3841
ERR_INVALID_ARG_VALUE,
3942
ERR_OPERATION_FAILED,
4043
ERR_OUT_OF_RANGE,
4144
ERR_UNKNOWN_SIGNAL,
45+
ERR_WORKER_UNSUPPORTED_OPERATION,
4246
},
4347
} = require('internal/errors');
48+
const { emitExperimentalWarning } = require('internal/util');
4449
const format = require('internal/util/inspect').format;
4550
const {
4651
validateArray,
4752
validateNumber,
4853
validateObject,
54+
validateString,
4955
} = require('internal/validators');
5056

57+
const dc = require('diagnostics_channel');
58+
const execveDiagnosticChannel = dc.channel('process.execve');
59+
5160
const constants = internalBinding('constants').os.signals;
5261

5362
let getValidatedPath; // We need to lazy load it because of the circular dependency.
@@ -103,6 +112,7 @@ function wrapProcessMethods(binding) {
103112
rss,
104113
resourceUsage: _resourceUsage,
105114
loadEnvFile: _loadEnvFile,
115+
execve: _execve,
106116
} = binding;
107117

108118
function _rawDebug(...args) {
@@ -269,6 +279,55 @@ function wrapProcessMethods(binding) {
269279
return true;
270280
}
271281

282+
function execve(execPath, args, env) {
283+
emitExperimentalWarning('process.execve');
284+
285+
const { isMainThread } = require('internal/worker');
286+
287+
if (!isMainThread) {
288+
throw new ERR_WORKER_UNSUPPORTED_OPERATION('Calling process.execve');
289+
} else if (process.platform === 'win32') {
290+
throw new ERR_FEATURE_UNAVAILABLE_ON_PLATFORM('process.execve');
291+
}
292+
293+
validateString(execPath, 'execPath');
294+
validateArray(args, 'args');
295+
296+
for (let i = 0; i < args.length; i++) {
297+
const arg = args[i];
298+
if (typeof arg !== 'string' || StringPrototypeIncludes(arg, '\u0000')) {
299+
throw new ERR_INVALID_ARG_VALUE(`args[${i}]`, arg, 'must be a string without null bytes');
300+
}
301+
}
302+
303+
const envArray = [];
304+
if (env !== undefined) {
305+
validateObject(env, 'env');
306+
307+
for (const { 0: key, 1: value } of ObjectEntries(env)) {
308+
if (
309+
typeof key !== 'string' ||
310+
typeof value !== 'string' ||
311+
StringPrototypeIncludes(key, '\u0000') ||
312+
StringPrototypeIncludes(value, '\u0000')
313+
) {
314+
throw new ERR_INVALID_ARG_VALUE(
315+
'env', env, 'must be an object with string keys and values without null bytes',
316+
);
317+
} else {
318+
ArrayPrototypePush(envArray, `${key}=${value}`);
319+
}
320+
}
321+
}
322+
323+
if (execveDiagnosticChannel.hasSubscribers) {
324+
execveDiagnosticChannel.publish({ execPath, args, env: envArray });
325+
}
326+
327+
// Perform the system call
328+
_execve(execPath, args, envArray);
329+
}
330+
272331
const resourceValues = new Float64Array(16);
273332
function resourceUsage() {
274333
_resourceUsage(resourceValues);
@@ -314,6 +373,7 @@ function wrapProcessMethods(binding) {
314373
memoryUsage,
315374
kill,
316375
exit,
376+
execve,
317377
loadEnvFile,
318378
};
319379
}
Collapse file

‎src/node_errors.h‎

Copy file name to clipboardExpand all lines: src/node_errors.h
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
#include <sstream>
1414

1515
namespace node {
16+
// This forward declaration is required to have the method
17+
// available in error messages.
18+
namespace errors {
19+
const char* errno_string(int errorno);
20+
}
1621

1722
enum ErrorHandlingMode { CONTEXTIFY_ERROR, FATAL_ERROR, MODULE_ERROR };
1823
void AppendExceptionLine(Environment* env,
Collapse file

‎src/node_process_methods.cc‎

Copy file name to clipboardExpand all lines: src/node_process_methods.cc
+99Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,14 @@
2727
#if defined(_MSC_VER)
2828
#include <direct.h>
2929
#include <io.h>
30+
#include <process.h>
3031
#define umask _umask
3132
typedef int mode_t;
3233
#else
3334
#include <pthread.h>
3435
#include <sys/resource.h> // getrlimit, setrlimit
3536
#include <termios.h> // tcgetattr, tcsetattr
37+
#include <unistd.h>
3638
#endif
3739

3840
namespace node {
@@ -495,6 +497,95 @@ static void ReallyExit(const FunctionCallbackInfo<Value>& args) {
495497
env->Exit(code);
496498
}
497499

500+
#ifdef __POSIX__
501+
inline int persist_standard_stream(int fd) {
502+
int flags = fcntl(fd, F_GETFD, 0);
503+
504+
if (flags < 0) {
505+
return flags;
506+
}
507+
508+
flags &= ~FD_CLOEXEC;
509+
return fcntl(fd, F_SETFD, flags);
510+
}
511+
512+
static void Execve(const FunctionCallbackInfo<Value>& args) {
513+
Environment* env = Environment::GetCurrent(args);
514+
Isolate* isolate = env->isolate();
515+
Local<Context> context = env->context();
516+
517+
THROW_IF_INSUFFICIENT_PERMISSIONS(
518+
env, permission::PermissionScope::kChildProcess, "");
519+
520+
CHECK(args[0]->IsString());
521+
CHECK(args[1]->IsArray());
522+
CHECK(args[2]->IsArray());
523+
524+
Local<Array> argv_array = args[1].As<Array>();
525+
Local<Array> envp_array = args[2].As<Array>();
526+
527+
// Copy arguments and environment
528+
Utf8Value executable(isolate, args[0]);
529+
std::vector<std::string> argv_strings(argv_array->Length());
530+
std::vector<std::string> envp_strings(envp_array->Length());
531+
std::vector<char*> argv(argv_array->Length() + 1);
532+
std::vector<char*> envp(envp_array->Length() + 1);
533+
534+
for (unsigned int i = 0; i < argv_array->Length(); i++) {
535+
Local<Value> str;
536+
if (!argv_array->Get(context, i).ToLocal(&str)) {
537+
THROW_ERR_INVALID_ARG_VALUE(env, "Failed to deserialize argument.");
538+
return;
539+
}
540+
541+
argv_strings[i] = Utf8Value(isolate, str).ToString();
542+
argv[i] = argv_strings[i].data();
543+
}
544+
argv[argv_array->Length()] = nullptr;
545+
546+
for (unsigned int i = 0; i < envp_array->Length(); i++) {
547+
Local<Value> str;
548+
if (!envp_array->Get(context, i).ToLocal(&str)) {
549+
THROW_ERR_INVALID_ARG_VALUE(
550+
env, "Failed to deserialize environment variable.");
551+
return;
552+
}
553+
554+
envp_strings[i] = Utf8Value(isolate, str).ToString();
555+
envp[i] = envp_strings[i].data();
556+
}
557+
558+
envp[envp_array->Length()] = nullptr;
559+
560+
// Set stdin, stdout and stderr to be non-close-on-exec
561+
// so that the new process will inherit it.
562+
if (persist_standard_stream(0) < 0 || persist_standard_stream(1) < 0 ||
563+
persist_standard_stream(2) < 0) {
564+
env->ThrowErrnoException(errno, "fcntl");
565+
return;
566+
}
567+
568+
// Perform the execve operation.
569+
RunAtExit(env);
570+
execve(*executable, argv.data(), envp.data());
571+
572+
// If it returns, it means that the execve operation failed.
573+
// In that case we abort the process.
574+
auto error_message = std::string("process.execve failed with error code ") +
575+
errors::errno_string(errno);
576+
577+
// Abort the process
578+
Local<v8::Value> exception =
579+
ErrnoException(isolate, errno, "execve", *executable);
580+
Local<v8::Message> message = v8::Exception::CreateMessage(isolate, exception);
581+
582+
std::string info = FormatErrorMessage(
583+
isolate, context, error_message.c_str(), message, true);
584+
FPrintF(stderr, "%s\n", info);
585+
ABORT();
586+
}
587+
#endif
588+
498589
static void LoadEnvFile(const v8::FunctionCallbackInfo<v8::Value>& args) {
499590
Environment* env = Environment::GetCurrent(args);
500591
std::string path = ".env";
@@ -687,6 +778,10 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data,
687778
SetMethodNoSideEffect(isolate, target, "cwd", Cwd);
688779
SetMethod(isolate, target, "dlopen", binding::DLOpen);
689780
SetMethod(isolate, target, "reallyExit", ReallyExit);
781+
782+
#ifdef __POSIX__
783+
SetMethod(isolate, target, "execve", Execve);
784+
#endif
690785
SetMethodNoSideEffect(isolate, target, "uptime", Uptime);
691786
SetMethod(isolate, target, "patchProcessObject", PatchProcessObject);
692787

@@ -730,6 +825,10 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
730825
registry->Register(Cwd);
731826
registry->Register(binding::DLOpen);
732827
registry->Register(ReallyExit);
828+
829+
#ifdef __POSIX__
830+
registry->Register(Execve);
831+
#endif
733832
registry->Register(Uptime);
734833
registry->Register(PatchProcessObject);
735834

Collapse file
+26Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
'use strict';
2+
3+
const { skip, isWindows } = require('../common');
4+
const { ok } = require('assert');
5+
const { spawnSync } = require('child_process');
6+
const { isMainThread } = require('worker_threads');
7+
8+
if (!isMainThread) {
9+
skip('process.execve is not available in Workers');
10+
} else if (isWindows) {
11+
skip('process.execve is not available in Windows');
12+
}
13+
14+
if (process.argv[2] === 'child') {
15+
process.execve(
16+
process.execPath + '_non_existing',
17+
[__filename, 'replaced'],
18+
{ ...process.env, EXECVE_A: 'FIRST', EXECVE_B: 'SECOND', CWD: process.cwd() }
19+
);
20+
} else {
21+
const child = spawnSync(`${process.execPath}`, [`${__filename}`, 'child']);
22+
const stderr = child.stderr.toString();
23+
24+
ok(stderr.includes('process.execve failed with error code ENOENT'), stderr);
25+
ok(stderr.includes('execve (node:internal/process/per_thread'), stderr);
26+
}
Collapse file
+18Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
'use strict';
2+
3+
const { mustNotCall, skip, isWindows } = require('../common');
4+
const { strictEqual } = require('assert');
5+
const { isMainThread } = require('worker_threads');
6+
7+
if (!isMainThread) {
8+
skip('process.execve is not available in Workers');
9+
} else if (isWindows) {
10+
skip('process.execve is not available in Windows');
11+
}
12+
13+
if (process.argv[2] === 'replaced') {
14+
strictEqual(process.argv[2], 'replaced');
15+
} else {
16+
process.on('exit', mustNotCall());
17+
process.execve(process.execPath, [process.execPath, __filename, 'replaced'], process.env);
18+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.