node/lib/child_process.js
Liran Tal 47b877993f
child_process: fix incomplete prototype pollution hardening
Prior pull request (#48726) hardened against prototype pollution
vulnerabilities but effectively missed some use-cases which
opened a window for prototype pollution for some child_process
functions such as spawn(), spawnSync(), and execFileSync().

PR-URL: https://github.com/nodejs/node/pull/53781
Reviewed-By: Vinícius Lourenço Claro Cardoso <contact@viniciusl.com.br>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
2024-07-21 21:27:04 +00:00

1022 lines
27 KiB
JavaScript

// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const {
ArrayIsArray,
ArrayPrototypeFilter,
ArrayPrototypeIncludes,
ArrayPrototypeJoin,
ArrayPrototypeLastIndexOf,
ArrayPrototypePush,
ArrayPrototypePushApply,
ArrayPrototypeSlice,
ArrayPrototypeSort,
ArrayPrototypeSplice,
ArrayPrototypeUnshift,
NumberIsInteger,
ObjectAssign,
ObjectDefineProperty,
ObjectPrototypeHasOwnProperty,
RegExpPrototypeExec,
SafeSet,
StringPrototypeIncludes,
StringPrototypeSlice,
StringPrototypeToUpperCase,
SymbolDispose,
} = primordials;
const {
convertToValidSignal,
createDeferredPromise,
getSystemErrorName,
kEmptyObject,
promisify,
} = require('internal/util');
const { isArrayBufferView } = require('internal/util/types');
let debug = require('internal/util/debuglog').debuglog(
'child_process',
(fn) => {
debug = fn;
},
);
const { Buffer } = require('buffer');
const { Pipe, constants: PipeConstants } = internalBinding('pipe_wrap');
const {
AbortError,
codes: {
ERR_CHILD_PROCESS_IPC_REQUIRED,
ERR_CHILD_PROCESS_STDIO_MAXBUFFER,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_OUT_OF_RANGE,
},
genericNodeError,
} = require('internal/errors');
const { clearTimeout, setTimeout } = require('timers');
const { getValidatedPath } = require('internal/fs/utils');
const {
isInt32,
validateAbortSignal,
validateArray,
validateBoolean,
validateFunction,
validateObject,
validateString,
} = require('internal/validators');
const child_process = require('internal/child_process');
const {
getValidStdio,
setupChannel,
ChildProcess,
stdioStringToArray,
} = child_process;
const MAX_BUFFER = 1024 * 1024;
const isZOS = process.platform === 'os390';
let addAbortListener;
/**
* Spawns a new Node.js process + fork.
* @param {string|URL} modulePath
* @param {string[]} [args]
* @param {{
* cwd?: string | URL;
* detached?: boolean;
* env?: Record<string, string>;
* execPath?: string;
* execArgv?: string[];
* gid?: number;
* serialization?: string;
* signal?: AbortSignal;
* killSignal?: string | number;
* silent?: boolean;
* stdio?: Array | string;
* uid?: number;
* windowsVerbatimArguments?: boolean;
* timeout?: number;
* }} [options]
* @returns {ChildProcess}
*/
function fork(modulePath, args = [], options) {
modulePath = getValidatedPath(modulePath, 'modulePath');
// Get options and args arguments.
let execArgv;
if (args == null) {
args = [];
} else if (typeof args === 'object' && !ArrayIsArray(args)) {
options = args;
args = [];
} else {
validateArray(args, 'args');
}
if (options != null) {
validateObject(options, 'options');
}
options = { __proto__: null, ...options, shell: false };
options.execPath = options.execPath || process.execPath;
validateArgumentNullCheck(options.execPath, 'options.execPath');
// Prepare arguments for fork:
execArgv = options.execArgv || process.execArgv;
validateArgumentsNullCheck(execArgv, 'options.execArgv');
if (execArgv === process.execArgv && process._eval != null) {
const index = ArrayPrototypeLastIndexOf(execArgv, process._eval);
if (index > 0) {
// Remove the -e switch to avoid fork bombing ourselves.
execArgv = ArrayPrototypeSlice(execArgv);
ArrayPrototypeSplice(execArgv, index - 1, 2);
}
}
args = [...execArgv, modulePath, ...args];
if (typeof options.stdio === 'string') {
options.stdio = stdioStringToArray(options.stdio, 'ipc');
} else if (!ArrayIsArray(options.stdio)) {
// Use a separate fd=3 for the IPC channel. Inherit stdin, stdout,
// and stderr from the parent if silent isn't set.
options.stdio = stdioStringToArray(
options.silent ? 'pipe' : 'inherit',
'ipc');
} else if (!ArrayPrototypeIncludes(options.stdio, 'ipc')) {
throw new ERR_CHILD_PROCESS_IPC_REQUIRED('options.stdio');
}
return spawn(options.execPath, args, options);
}
function _forkChild(fd, serializationMode) {
// set process.send()
const p = new Pipe(PipeConstants.IPC);
p.open(fd);
p.unref();
const control = setupChannel(process, p, serializationMode);
process.on('newListener', function onNewListener(name) {
if (name === 'message' || name === 'disconnect') control.refCounted();
});
process.on('removeListener', function onRemoveListener(name) {
if (name === 'message' || name === 'disconnect') control.unrefCounted();
});
}
function normalizeExecArgs(command, options, callback) {
validateString(command, 'command');
validateArgumentNullCheck(command, 'command');
if (typeof options === 'function') {
callback = options;
options = undefined;
}
// Make a shallow copy so we don't clobber the user's options object.
options = { __proto__: null, ...options };
options.shell = typeof options.shell === 'string' ? options.shell : true;
return {
file: command,
options: options,
callback: callback,
};
}
/**
* Spawns a shell executing the given command.
* @param {string} command
* @param {{
* cmd?: string;
* env?: Record<string, string>;
* encoding?: string;
* shell?: string;
* signal?: AbortSignal;
* timeout?: number;
* maxBuffer?: number;
* killSignal?: string | number;
* uid?: number;
* gid?: number;
* windowsHide?: boolean;
* }} [options]
* @param {(
* error?: Error,
* stdout?: string | Buffer,
* stderr?: string | Buffer
* ) => any} [callback]
* @returns {ChildProcess}
*/
function exec(command, options, callback) {
const opts = normalizeExecArgs(command, options, callback);
return module.exports.execFile(opts.file,
opts.options,
opts.callback);
}
const customPromiseExecFunction = (orig) => {
return (...args) => {
const { promise, resolve, reject } = createDeferredPromise();
promise.child = orig(...args, (err, stdout, stderr) => {
if (err !== null) {
err.stdout = stdout;
err.stderr = stderr;
reject(err);
} else {
resolve({ stdout, stderr });
}
});
return promise;
};
};
ObjectDefineProperty(exec, promisify.custom, {
__proto__: null,
enumerable: false,
value: customPromiseExecFunction(exec),
});
function normalizeExecFileArgs(file, args, options, callback) {
if (ArrayIsArray(args)) {
args = ArrayPrototypeSlice(args);
} else if (args != null && typeof args === 'object') {
callback = options;
options = args;
args = null;
} else if (typeof args === 'function') {
callback = args;
options = null;
args = null;
}
if (args == null) {
args = [];
}
if (typeof options === 'function') {
callback = options;
} else if (options != null) {
validateObject(options, 'options');
}
if (options == null) {
options = kEmptyObject;
}
if (callback != null) {
validateFunction(callback, 'callback');
}
// Validate argv0, if present.
if (options.argv0 != null) {
validateString(options.argv0, 'options.argv0');
validateArgumentNullCheck(options.argv0, 'options.argv0');
}
return { file, args, options, callback };
}
/**
* Spawns the specified file as a shell.
* @param {string} file
* @param {string[]} [args]
* @param {{
* cwd?: string | URL;
* env?: Record<string, string>;
* encoding?: string;
* timeout?: number;
* maxBuffer?: number;
* killSignal?: string | number;
* uid?: number;
* gid?: number;
* windowsHide?: boolean;
* windowsVerbatimArguments?: boolean;
* shell?: boolean | string;
* signal?: AbortSignal;
* }} [options]
* @param {(
* error?: Error,
* stdout?: string | Buffer,
* stderr?: string | Buffer
* ) => any} [callback]
* @returns {ChildProcess}
*/
function execFile(file, args, options, callback) {
({ file, args, options, callback } = normalizeExecFileArgs(file, args, options, callback));
options = {
__proto__: null,
encoding: 'utf8',
timeout: 0,
maxBuffer: MAX_BUFFER,
killSignal: 'SIGTERM',
cwd: null,
env: null,
shell: false,
...options,
};
// Validate the timeout, if present.
validateTimeout(options.timeout);
// Validate maxBuffer, if present.
validateMaxBuffer(options.maxBuffer);
options.killSignal = sanitizeKillSignal(options.killSignal);
const child = spawn(file, args, {
cwd: options.cwd,
env: options.env,
gid: options.gid,
shell: options.shell,
signal: options.signal,
uid: options.uid,
windowsHide: !!options.windowsHide,
windowsVerbatimArguments: !!options.windowsVerbatimArguments,
});
let encoding;
const _stdout = [];
const _stderr = [];
if (options.encoding !== 'buffer' && Buffer.isEncoding(options.encoding)) {
encoding = options.encoding;
} else {
encoding = null;
}
let stdoutLen = 0;
let stderrLen = 0;
let killed = false;
let exited = false;
let timeoutId;
let ex = null;
let cmd = file;
function exithandler(code, signal) {
if (exited) return;
exited = true;
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = null;
}
if (!callback) return;
// merge chunks
let stdout;
let stderr;
if (encoding ||
(
child.stdout &&
child.stdout.readableEncoding
)) {
stdout = ArrayPrototypeJoin(_stdout, '');
} else {
stdout = Buffer.concat(_stdout);
}
if (encoding ||
(
child.stderr &&
child.stderr.readableEncoding
)) {
stderr = ArrayPrototypeJoin(_stderr, '');
} else {
stderr = Buffer.concat(_stderr);
}
if (!ex && code === 0 && signal === null) {
callback(null, stdout, stderr);
return;
}
if (args?.length)
cmd += ` ${ArrayPrototypeJoin(args, ' ')}`;
if (!ex) {
ex = genericNodeError(`Command failed: ${cmd}\n${stderr}`, {
code: code < 0 ? getSystemErrorName(code) : code,
killed: child.killed || killed,
signal: signal,
});
}
ex.cmd = cmd;
callback(ex, stdout, stderr);
}
function errorhandler(e) {
ex = e;
if (child.stdout)
child.stdout.destroy();
if (child.stderr)
child.stderr.destroy();
exithandler();
}
function kill() {
if (child.stdout)
child.stdout.destroy();
if (child.stderr)
child.stderr.destroy();
killed = true;
try {
child.kill(options.killSignal);
} catch (e) {
ex = e;
exithandler();
}
}
if (options.timeout > 0) {
timeoutId = setTimeout(function delayedKill() {
kill();
timeoutId = null;
}, options.timeout);
}
if (child.stdout) {
if (encoding)
child.stdout.setEncoding(encoding);
child.stdout.on('data', function onChildStdout(chunk) {
// Do not need to count the length
if (options.maxBuffer === Infinity) {
ArrayPrototypePush(_stdout, chunk);
return;
}
const encoding = child.stdout.readableEncoding;
const length = encoding ?
Buffer.byteLength(chunk, encoding) :
chunk.length;
const slice = encoding ? StringPrototypeSlice :
(buf, ...args) => buf.slice(...args);
stdoutLen += length;
if (stdoutLen > options.maxBuffer) {
const truncatedLen = options.maxBuffer - (stdoutLen - length);
ArrayPrototypePush(_stdout, slice(chunk, 0, truncatedLen));
ex = new ERR_CHILD_PROCESS_STDIO_MAXBUFFER('stdout');
kill();
} else {
ArrayPrototypePush(_stdout, chunk);
}
});
}
if (child.stderr) {
if (encoding)
child.stderr.setEncoding(encoding);
child.stderr.on('data', function onChildStderr(chunk) {
// Do not need to count the length
if (options.maxBuffer === Infinity) {
ArrayPrototypePush(_stderr, chunk);
return;
}
const encoding = child.stderr.readableEncoding;
const length = encoding ?
Buffer.byteLength(chunk, encoding) :
chunk.length;
stderrLen += length;
if (stderrLen > options.maxBuffer) {
const truncatedLen = options.maxBuffer - (stderrLen - length);
ArrayPrototypePush(_stderr,
chunk.slice(0, truncatedLen));
ex = new ERR_CHILD_PROCESS_STDIO_MAXBUFFER('stderr');
kill();
} else {
ArrayPrototypePush(_stderr, chunk);
}
});
}
child.addListener('close', exithandler);
child.addListener('error', errorhandler);
return child;
}
ObjectDefineProperty(execFile, promisify.custom, {
__proto__: null,
enumerable: false,
value: customPromiseExecFunction(execFile),
});
function copyProcessEnvToEnv(env, name, optionEnv) {
if (process.env[name] &&
(!optionEnv ||
!ObjectPrototypeHasOwnProperty(optionEnv, name))) {
env[name] = process.env[name];
}
}
function normalizeSpawnArguments(file, args, options) {
validateString(file, 'file');
validateArgumentNullCheck(file, 'file');
if (file.length === 0)
throw new ERR_INVALID_ARG_VALUE('file', file, 'cannot be empty');
if (ArrayIsArray(args)) {
args = ArrayPrototypeSlice(args);
} else if (args == null) {
args = [];
} else if (typeof args !== 'object') {
throw new ERR_INVALID_ARG_TYPE('args', 'object', args);
} else {
options = args;
args = [];
}
validateArgumentsNullCheck(args, 'args');
if (options === undefined)
options = kEmptyObject;
else
validateObject(options, 'options');
options = { __proto__: null, ...options };
let cwd = options.cwd;
// Validate the cwd, if present.
if (cwd != null) {
cwd = getValidatedPath(cwd, 'options.cwd');
}
// Validate detached, if present.
if (options.detached != null) {
validateBoolean(options.detached, 'options.detached');
}
// Validate the uid, if present.
if (options.uid != null && !isInt32(options.uid)) {
throw new ERR_INVALID_ARG_TYPE('options.uid', 'int32', options.uid);
}
// Validate the gid, if present.
if (options.gid != null && !isInt32(options.gid)) {
throw new ERR_INVALID_ARG_TYPE('options.gid', 'int32', options.gid);
}
// Validate the shell, if present.
if (options.shell != null &&
typeof options.shell !== 'boolean' &&
typeof options.shell !== 'string') {
throw new ERR_INVALID_ARG_TYPE('options.shell',
['boolean', 'string'], options.shell);
}
// Validate argv0, if present.
if (options.argv0 != null) {
validateString(options.argv0, 'options.argv0');
validateArgumentNullCheck(options.argv0, 'options.argv0');
}
// Validate windowsHide, if present.
if (options.windowsHide != null) {
validateBoolean(options.windowsHide, 'options.windowsHide');
}
// Validate windowsVerbatimArguments, if present.
let { windowsVerbatimArguments } = options;
if (windowsVerbatimArguments != null) {
validateBoolean(windowsVerbatimArguments,
'options.windowsVerbatimArguments');
}
if (options.shell) {
validateArgumentNullCheck(options.shell, 'options.shell');
const command = ArrayPrototypeJoin([file, ...args], ' ');
// Set the shell, switches, and commands.
if (process.platform === 'win32') {
if (typeof options.shell === 'string')
file = options.shell;
else
file = process.env.comspec || 'cmd.exe';
// '/d /s /c' is used only for cmd.exe.
if (RegExpPrototypeExec(/^(?:.*\\)?cmd(?:\.exe)?$/i, file) !== null) {
args = ['/d', '/s', '/c', `"${command}"`];
windowsVerbatimArguments = true;
} else {
args = ['-c', command];
}
} else {
if (typeof options.shell === 'string')
file = options.shell;
else if (process.platform === 'android')
file = '/system/bin/sh';
else
file = '/bin/sh';
args = ['-c', command];
}
}
if (typeof options.argv0 === 'string') {
ArrayPrototypeUnshift(args, options.argv0);
} else {
ArrayPrototypeUnshift(args, file);
}
const env = options.env || process.env;
const envPairs = [];
// process.env.NODE_V8_COVERAGE always propagates, making it possible to
// collect coverage for programs that spawn with white-listed environment.
copyProcessEnvToEnv(env, 'NODE_V8_COVERAGE', options.env);
if (isZOS) {
// The following environment variables must always propagate if set.
copyProcessEnvToEnv(env, '_BPXK_AUTOCVT', options.env);
copyProcessEnvToEnv(env, '_CEE_RUNOPTS', options.env);
copyProcessEnvToEnv(env, '_TAG_REDIR_ERR', options.env);
copyProcessEnvToEnv(env, '_TAG_REDIR_IN', options.env);
copyProcessEnvToEnv(env, '_TAG_REDIR_OUT', options.env);
copyProcessEnvToEnv(env, 'STEPLIB', options.env);
copyProcessEnvToEnv(env, 'LIBPATH', options.env);
copyProcessEnvToEnv(env, '_EDC_SIG_DFLT', options.env);
copyProcessEnvToEnv(env, '_EDC_SUSV3', options.env);
}
let envKeys = [];
// Prototype values are intentionally included.
for (const key in env) {
ArrayPrototypePush(envKeys, key);
}
if (process.platform === 'win32') {
// On Windows env keys are case insensitive. Filter out duplicates,
// keeping only the first one (in lexicographic order)
const sawKey = new SafeSet();
envKeys = ArrayPrototypeFilter(
ArrayPrototypeSort(envKeys),
(key) => {
const uppercaseKey = StringPrototypeToUpperCase(key);
if (sawKey.has(uppercaseKey)) {
return false;
}
sawKey.add(uppercaseKey);
return true;
},
);
}
for (const key of envKeys) {
const value = env[key];
if (value !== undefined) {
validateArgumentNullCheck(key, `options.env['${key}']`);
validateArgumentNullCheck(value, `options.env['${key}']`);
ArrayPrototypePush(envPairs, `${key}=${value}`);
}
}
return {
// Make a shallow copy so we don't clobber the user's options object.
__proto__: null,
...options,
args,
cwd,
detached: !!options.detached,
envPairs,
file,
windowsHide: !!options.windowsHide,
windowsVerbatimArguments: !!windowsVerbatimArguments,
};
}
function abortChildProcess(child, killSignal, reason) {
if (!child)
return;
try {
if (child.kill(killSignal)) {
child.emit('error', new AbortError(undefined, { cause: reason }));
}
} catch (err) {
child.emit('error', err);
}
}
/**
* Spawns a new process using the given `file`.
* @param {string} file
* @param {string[]} [args]
* @param {{
* cwd?: string | URL;
* env?: Record<string, string>;
* argv0?: string;
* stdio?: Array | string;
* detached?: boolean;
* uid?: number;
* gid?: number;
* serialization?: string;
* shell?: boolean | string;
* windowsVerbatimArguments?: boolean;
* windowsHide?: boolean;
* signal?: AbortSignal;
* timeout?: number;
* killSignal?: string | number;
* }} [options]
* @returns {ChildProcess}
*/
function spawn(file, args, options) {
options = normalizeSpawnArguments(file, args, options);
validateTimeout(options.timeout);
validateAbortSignal(options.signal, 'options.signal');
const killSignal = sanitizeKillSignal(options.killSignal);
const child = new ChildProcess();
debug('spawn', options);
child.spawn(options);
if (options.timeout > 0) {
let timeoutId = setTimeout(() => {
if (timeoutId) {
try {
child.kill(killSignal);
} catch (err) {
child.emit('error', err);
}
timeoutId = null;
}
}, options.timeout);
child.once('exit', () => {
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = null;
}
});
}
if (options.signal) {
const signal = options.signal;
if (signal.aborted) {
process.nextTick(onAbortListener);
} else {
addAbortListener ??= require('internal/events/abort_listener').addAbortListener;
const disposable = addAbortListener(signal, onAbortListener);
child.once('exit', disposable[SymbolDispose]);
}
function onAbortListener() {
abortChildProcess(child, killSignal, options.signal.reason);
}
}
return child;
}
/**
* Spawns a new process synchronously using the given `file`.
* @param {string} file
* @param {string[]} [args]
* @param {{
* cwd?: string | URL;
* input?: string | Buffer | TypedArray | DataView;
* argv0?: string;
* stdio?: string | Array;
* env?: Record<string, string>;
* uid?: number;
* gid?: number;
* timeout?: number;
* killSignal?: string | number;
* maxBuffer?: number;
* encoding?: string;
* shell?: boolean | string;
* windowsVerbatimArguments?: boolean;
* windowsHide?: boolean;
* }} [options]
* @returns {{
* pid: number;
* output: Array;
* stdout: Buffer | string;
* stderr: Buffer | string;
* status: number | null;
* signal: string | null;
* error: Error;
* }}
*/
function spawnSync(file, args, options) {
options = {
__proto__: null,
maxBuffer: MAX_BUFFER,
...normalizeSpawnArguments(file, args, options),
};
debug('spawnSync', options);
// Validate the timeout, if present.
validateTimeout(options.timeout);
// Validate maxBuffer, if present.
validateMaxBuffer(options.maxBuffer);
// Validate and translate the kill signal, if present.
options.killSignal = sanitizeKillSignal(options.killSignal);
options.stdio = getValidStdio(options.stdio || 'pipe', true).stdio;
if (options.input) {
const stdin = options.stdio[0] = { ...options.stdio[0] };
stdin.input = options.input;
}
// We may want to pass data in on any given fd, ensure it is a valid buffer
for (let i = 0; i < options.stdio.length; i++) {
const input = options.stdio[i] && options.stdio[i].input;
if (input != null) {
const pipe = options.stdio[i] = { ...options.stdio[i] };
if (isArrayBufferView(input)) {
pipe.input = input;
} else if (typeof input === 'string') {
pipe.input = Buffer.from(input, options.encoding);
} else {
throw new ERR_INVALID_ARG_TYPE(`options.stdio[${i}]`,
['Buffer',
'TypedArray',
'DataView',
'string'],
input);
}
}
}
return child_process.spawnSync(options);
}
function checkExecSyncError(ret, args, cmd) {
let err;
if (ret.error) {
err = ret.error;
ObjectAssign(err, ret);
} else if (ret.status !== 0) {
let msg = 'Command failed: ';
msg += cmd || ArrayPrototypeJoin(args, ' ');
if (ret.stderr && ret.stderr.length > 0)
msg += `\n${ret.stderr.toString()}`;
err = genericNodeError(msg, ret);
}
return err;
}
/**
* Spawns a file as a shell synchronously.
* @param {string} file
* @param {string[]} [args]
* @param {{
* cwd?: string | URL;
* input?: string | Buffer | TypedArray | DataView;
* stdio?: string | Array;
* env?: Record<string, string>;
* uid?: number;
* gid?: number;
* timeout?: number;
* killSignal?: string | number;
* maxBuffer?: number;
* encoding?: string;
* windowsHide?: boolean;
* shell?: boolean | string;
* }} [options]
* @returns {Buffer | string}
*/
function execFileSync(file, args, options) {
({ file, args, options } = normalizeExecFileArgs(file, args, options));
const inheritStderr = !options.stdio;
const ret = spawnSync(file, args, options);
if (inheritStderr && ret.stderr)
process.stderr.write(ret.stderr);
const errArgs = [options.argv0 || file];
ArrayPrototypePushApply(errArgs, args);
const err = checkExecSyncError(ret, errArgs);
if (err)
throw err;
return ret.stdout;
}
/**
* Spawns a shell executing the given `command` synchronously.
* @param {string} command
* @param {{
* cwd?: string | URL;
* input?: string | Buffer | TypedArray | DataView;
* stdio?: string | Array;
* env?: Record<string, string>;
* shell?: string;
* uid?: number;
* gid?: number;
* timeout?: number;
* killSignal?: string | number;
* maxBuffer?: number;
* encoding?: string;
* windowsHide?: boolean;
* }} [options]
* @returns {Buffer | string}
*/
function execSync(command, options) {
const opts = normalizeExecArgs(command, options, null);
const inheritStderr = !opts.options.stdio;
const ret = spawnSync(opts.file, opts.options);
if (inheritStderr && ret.stderr)
process.stderr.write(ret.stderr);
const err = checkExecSyncError(ret, undefined, command);
if (err)
throw err;
return ret.stdout;
}
function validateArgumentNullCheck(arg, propName) {
if (typeof arg === 'string' && StringPrototypeIncludes(arg, '\u0000')) {
throw new ERR_INVALID_ARG_VALUE(propName, arg, 'must be a string without null bytes');
}
}
function validateArgumentsNullCheck(args, propName) {
for (let i = 0; i < args.length; ++i) {
validateArgumentNullCheck(args[i], `${propName}[${i}]`);
}
}
function validateTimeout(timeout) {
if (timeout != null && !(NumberIsInteger(timeout) && timeout >= 0)) {
throw new ERR_OUT_OF_RANGE('timeout', 'an unsigned integer', timeout);
}
}
function validateMaxBuffer(maxBuffer) {
if (maxBuffer != null && !(typeof maxBuffer === 'number' && maxBuffer >= 0)) {
throw new ERR_OUT_OF_RANGE('options.maxBuffer',
'a positive number',
maxBuffer);
}
}
function sanitizeKillSignal(killSignal) {
if (typeof killSignal === 'string' || typeof killSignal === 'number') {
return convertToValidSignal(killSignal);
} else if (killSignal != null) {
throw new ERR_INVALID_ARG_TYPE('options.killSignal',
['string', 'number'],
killSignal);
}
}
module.exports = {
_forkChild,
ChildProcess,
exec,
execFile,
execFileSync,
execSync,
fork,
spawn,
spawnSync,
};