test: do not assume process.execPath contains no spaces

We had a bunch of tests that would fail if run from an executable that
contains any char that should be escaped when run from a shell.

PR-URL: https://github.com/nodejs/node/pull/55028
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Antoine du Hamel 2024-09-22 15:03:30 +02:00 committed by GitHub
parent 7ecc48d061
commit 1fcb128771
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 151 additions and 88 deletions

View File

@ -11,7 +11,8 @@ const invalidArgTypeError = {
};
const waitCommand = common.isWindows ?
`${process.execPath} -e "setInterval(()=>{}, 99)"` :
// `"` is forbidden for Windows paths, no need for escaping.
`"${process.execPath}" -e "setInterval(()=>{}, 99)"` :
'sleep 2m';
{

View File

@ -10,12 +10,25 @@ function runChecks(err, stdio, streamName, expected) {
assert.deepStrictEqual(stdio[streamName], expected);
}
// The execPath might contain chars that should be escaped in a shell context.
// On non-Windows, we can pass the path via the env; `"` is not a valid char on
// Windows, so we can simply pass the path.
const execNode = (args, optionsOrCallback, callback) => {
let options = optionsOrCallback;
if (typeof optionsOrCallback === 'function') {
options = undefined;
callback = optionsOrCallback;
}
return cp.exec(
`"${common.isWindows ? process.execPath : '$NODE'}" ${args}`,
common.isWindows ? options : { ...options, env: { ...process.env, NODE: process.execPath } },
callback,
);
};
// default value
{
const cmd =
`"${process.execPath}" -e "console.log('a'.repeat(1024 * 1024))"`;
cp.exec(cmd, common.mustCall((err) => {
execNode(`-e "console.log('a'.repeat(1024 * 1024))"`, common.mustCall((err) => {
assert(err instanceof RangeError);
assert.strictEqual(err.message, 'stdout maxBuffer length exceeded');
assert.strictEqual(err.code, 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER');
@ -24,20 +37,16 @@ function runChecks(err, stdio, streamName, expected) {
// default value
{
const cmd =
`${process.execPath} -e "console.log('a'.repeat(1024 * 1024 - 1))"`;
cp.exec(cmd, common.mustSucceed((stdout, stderr) => {
execNode(`-e "console.log('a'.repeat(1024 * 1024 - 1))"`, common.mustSucceed((stdout, stderr) => {
assert.strictEqual(stdout.trim(), 'a'.repeat(1024 * 1024 - 1));
assert.strictEqual(stderr, '');
}));
}
{
const cmd = `"${process.execPath}" -e "console.log('hello world');"`;
const options = { maxBuffer: Infinity };
cp.exec(cmd, options, common.mustSucceed((stdout, stderr) => {
execNode(`-e "console.log('hello world');"`, options, common.mustSucceed((stdout, stderr) => {
assert.strictEqual(stdout.trim(), 'hello world');
assert.strictEqual(stderr, '');
}));
@ -57,11 +66,8 @@ function runChecks(err, stdio, streamName, expected) {
// default value
{
const cmd =
`"${process.execPath}" -e "console.log('a'.repeat(1024 * 1024))"`;
cp.exec(
cmd,
execNode(
`-e "console.log('a'.repeat(1024 * 1024))"`,
common.mustCall((err, stdout, stderr) => {
runChecks(
err,
@ -75,10 +81,7 @@ function runChecks(err, stdio, streamName, expected) {
// default value
{
const cmd =
`"${process.execPath}" -e "console.log('a'.repeat(1024 * 1024 - 1))"`;
cp.exec(cmd, common.mustSucceed((stdout, stderr) => {
execNode(`-e "console.log('a'.repeat(1024 * 1024 - 1))"`, common.mustSucceed((stdout, stderr) => {
assert.strictEqual(stdout.trim(), 'a'.repeat(1024 * 1024 - 1));
assert.strictEqual(stderr, '');
}));
@ -87,10 +90,8 @@ function runChecks(err, stdio, streamName, expected) {
const unicode = '中文测试'; // length = 4, byte length = 12
{
const cmd = `"${process.execPath}" -e "console.log('${unicode}');"`;
cp.exec(
cmd,
execNode(
`-e "console.log('${unicode}');"`,
{ maxBuffer: 10 },
common.mustCall((err, stdout, stderr) => {
runChecks(err, { stdout, stderr }, 'stdout', '中文测试\n');
@ -99,10 +100,8 @@ const unicode = '中文测试'; // length = 4, byte length = 12
}
{
const cmd = `"${process.execPath}" -e "console.error('${unicode}');"`;
cp.exec(
cmd,
execNode(
`-e "console.error('${unicode}');"`,
{ maxBuffer: 3 },
common.mustCall((err, stdout, stderr) => {
runChecks(err, { stdout, stderr }, 'stderr', '中文测');
@ -111,10 +110,8 @@ const unicode = '中文测试'; // length = 4, byte length = 12
}
{
const cmd = `"${process.execPath}" -e "console.log('${unicode}');"`;
const child = cp.exec(
cmd,
const child = execNode(
`-e "console.log('${unicode}');"`,
{ encoding: null, maxBuffer: 10 },
common.mustCall((err, stdout, stderr) => {
runChecks(err, { stdout, stderr }, 'stdout', '中文测试\n');
@ -125,10 +122,8 @@ const unicode = '中文测试'; // length = 4, byte length = 12
}
{
const cmd = `"${process.execPath}" -e "console.error('${unicode}');"`;
const child = cp.exec(
cmd,
const child = execNode(
`-e "console.error('${unicode}');"`,
{ encoding: null, maxBuffer: 3 },
common.mustCall((err, stdout, stderr) => {
runChecks(err, { stdout, stderr }, 'stderr', '中文测');
@ -139,10 +134,8 @@ const unicode = '中文测试'; // length = 4, byte length = 12
}
{
const cmd = `"${process.execPath}" -e "console.error('${unicode}');"`;
cp.exec(
cmd,
execNode(
`-e "console.error('${unicode}');"`,
{ encoding: null, maxBuffer: 5 },
common.mustCall((err, stdout, stderr) => {
const buf = Buffer.from(unicode).slice(0, 5);

View File

@ -7,8 +7,16 @@ const { promisify } = require('util');
const exec = promisify(child_process.exec);
const execFile = promisify(child_process.execFile);
// The execPath might contain chars that should be escaped in a shell context.
// On non-Windows, we can pass the path via the env; `"` is not a valid char on
// Windows, so we can simply pass the path.
const execNode = (args) => exec(
`"${common.isWindows ? process.execPath : '$NODE'}" ${args}`,
common.isWindows ? undefined : { env: { ...process.env, NODE: process.execPath } },
);
{
const promise = exec(`${process.execPath} -p 42`);
const promise = execNode('-p 42');
assert(promise.child instanceof child_process.ChildProcess);
promise.then(common.mustCall((obj) => {
@ -45,7 +53,7 @@ const execFile = promisify(child_process.execFile);
const failingCodeWithStdoutErr =
'console.log(42);console.error(43);process.exit(1)';
{
exec(`${process.execPath} -e "${failingCodeWithStdoutErr}"`)
execNode(`-e "${failingCodeWithStdoutErr}"`)
.catch(common.mustCall((err) => {
assert.strictEqual(err.code, 1);
assert.strictEqual(err.stdout, '42\n');

View File

@ -49,8 +49,8 @@ command.on('close', common.mustCall((code, signal) => {
}));
// Verify that the environment is properly inherited
const env = cp.spawn(`"${process.execPath}" -pe process.env.BAZ`, {
env: { ...process.env, BAZ: 'buzz' },
const env = cp.spawn(`"${common.isWindows ? process.execPath : '$NODE'}" -pe process.env.BAZ`, {
env: { ...process.env, BAZ: 'buzz', NODE: process.execPath },
encoding: 'utf8',
shell: true
});

View File

@ -36,8 +36,8 @@ const command = cp.spawnSync(cmd, { shell: true });
assert.strictEqual(command.stdout.toString().trim(), 'bar');
// Verify that the environment is properly inherited
const env = cp.spawnSync(`"${process.execPath}" -pe process.env.BAZ`, {
env: { ...process.env, BAZ: 'buzz' },
const env = cp.spawnSync(`"${common.isWindows ? process.execPath : '$NODE'}" -pe process.env.BAZ`, {
env: { ...process.env, BAZ: 'buzz', NODE: process.execPath },
shell: true
});

View File

@ -11,9 +11,18 @@ const { exec, spawn } = require('child_process');
const { once } = require('events');
let stdOut;
// The execPath might contain chars that should be escaped in a shell context.
// On non-Windows, we can pass the path via the env; `"` is not a valid char on
// Windows, so we can simply pass the path.
const execNode = (args, callback) => exec(
`"${common.isWindows ? process.execPath : '$NODE'}" ${args}`,
common.isWindows ? undefined : { env: { ...process.env, NODE: process.execPath } },
callback,
);
function startPrintHelpTest() {
exec(`${process.execPath} --help`, common.mustSucceed((stdout, stderr) => {
execNode('--help', common.mustSucceed((stdout, stderr) => {
stdOut = stdout;
validateNodePrintHelp();
}));

View File

@ -4,19 +4,26 @@ const common = require('../common');
const assert = require('assert');
const { exec } = require('child_process');
const node = process.execPath;
// The execPath might contain chars that should be escaped in a shell context.
// On non-Windows, we can pass the path via the env; `"` is not a valid char on
// Windows, so we can simply pass the path.
const execNode = (args, callback) => exec(
`"${common.isWindows ? process.execPath : '$NODE'}" ${args}`,
common.isWindows ? undefined : { env: { ...process.env, NODE: process.execPath } },
callback,
);
// Should throw if -c and -e flags are both passed
['-c', '--check'].forEach(function(checkFlag) {
['-e', '--eval'].forEach(function(evalFlag) {
const args = [checkFlag, evalFlag, 'foo'];
const cmd = [node, ...args].join(' ');
exec(cmd, common.mustCall((err, stdout, stderr) => {
execNode(args.join(' '), common.mustCall((err, stdout, stderr) => {
assert.strictEqual(err instanceof Error, true);
assert.strictEqual(err.code, 9);
assert(
stderr.startsWith(
`${node}: either --check or --eval can be used, not both`
`${process.execPath}: either --check or --eval can be used, not both`
)
);
}));

View File

@ -43,14 +43,21 @@ const filename = tmpdir.resolve('big');
let server;
function executeRequest(cb) {
cp.exec([`"${process.execPath}"`,
`"${__filename}"`,
// The execPath might contain chars that should be escaped in a shell context.
// On non-Windows, we can pass the path via the env; `"` is not a valid char on
// Windows, so we can simply pass the path.
const node = `"${common.isWindows ? process.execPath : '$NODE'}"`;
const file = `"${common.isWindows ? __filename : '$FILE'}"`;
const env = common.isWindows ? process.env : { ...process.env, NODE: process.execPath, FILE: __filename };
cp.exec([node,
file,
'request',
server.address().port,
'|',
`"${process.execPath}"`,
`"${__filename}"`,
node,
file,
'shasum' ].join(' '),
{ env },
(err, stdout, stderr) => {
if (stderr.trim() !== '') {
console.log(stderr);

View File

@ -40,13 +40,15 @@ fs.writeFileSync(pkgPath, pkgContent);
const env = { ...process.env,
PATH: path.dirname(process.execPath),
NODE: process.execPath,
NPM: npmPath,
NPM_CONFIG_PREFIX: path.join(npmSandbox, 'npm-prefix'),
NPM_CONFIG_TMP: path.join(npmSandbox, 'npm-tmp'),
NPM_CONFIG_AUDIT: false,
NPM_CONFIG_UPDATE_NOTIFIER: false,
HOME: homeDir };
exec(`${process.execPath} ${npmPath} install`, {
exec(`"${common.isWindows ? process.execPath : '$NODE'}" "${common.isWindows ? npmPath : '$NPM'}" install`, {
cwd: installDir,
env: env
}, common.mustCall(handleExit));

View File

@ -15,12 +15,20 @@ if (process.argv[2] === 'child') {
assert.ok(process.permission.has('child'));
}
// The execPath might contain chars that should be escaped in a shell context.
// On non-Windows, we can pass the path via the env; `"` is not a valid char on
// Windows, so we can simply pass the path.
const execNode = (args) => childProcess.execSync(
`"${common.isWindows ? process.execPath : '$NODE'}" ${args}`,
common.isWindows ? undefined : { env: { ...process.env, NODE: process.execPath } },
);
// When a permission is set by cli, the process shouldn't be able
// to spawn unless --allow-child-process is sent
{
// doesNotThrow
childProcess.spawnSync(process.execPath, ['--version']);
childProcess.execSync(process.execPath, ['--version']);
execNode('--version');
childProcess.fork(__filename, ['child']);
childProcess.execFileSync(process.execPath, ['--version']);
}

View File

@ -15,6 +15,14 @@ if (process.argv[2] === 'child') {
assert.ok(!process.permission.has('child'));
}
// The execPath might contain chars that should be escaped in a shell context.
// On non-Windows, we can pass the path via the env; `"` is not a valid char on
// Windows, so we can simply pass the path.
const execNode = (args) => [
`"${common.isWindows ? process.execPath : '$NODE'}" ${args}`,
common.isWindows ? undefined : { env: { ...process.env, NODE: process.execPath } },
];
// When a permission is set by cli, the process shouldn't be able
// to spawn
{
@ -31,13 +39,13 @@ if (process.argv[2] === 'child') {
permission: 'ChildProcess',
}));
assert.throws(() => {
childProcess.exec(process.execPath, ['--version']);
childProcess.exec(...execNode('--version'));
}, common.expectsError({
code: 'ERR_ACCESS_DENIED',
permission: 'ChildProcess',
}));
assert.throws(() => {
childProcess.execSync(process.execPath, ['--version']);
childProcess.execSync(...execNode('--version'));
}, common.expectsError({
code: 'ERR_ACCESS_DENIED',
permission: 'ChildProcess',
@ -49,13 +57,13 @@ if (process.argv[2] === 'child') {
permission: 'ChildProcess',
}));
assert.throws(() => {
childProcess.execFile(process.execPath, ['--version']);
childProcess.execFile(...execNode('--version'));
}, common.expectsError({
code: 'ERR_ACCESS_DENIED',
permission: 'ChildProcess',
}));
assert.throws(() => {
childProcess.execFileSync(process.execPath, ['--version']);
childProcess.execFileSync(...execNode('--version'));
}, common.expectsError({
code: 'ERR_ACCESS_DENIED',
permission: 'ChildProcess',

View File

@ -5,12 +5,13 @@ const assert = require('assert');
const exec = require('child_process').exec;
const nodePath = process.argv[0];
const script = fixtures.path('print-10-lines.js');
const cmd = `"${nodePath}" "${script}" | head -2`;
const cmd = `"${common.isWindows ? process.execPath : '$NODE'}" "${common.isWindows ? script : '$FILE'}" | head -2`;
exec(cmd, common.mustSucceed((stdout, stderr) => {
exec(cmd, {
env: common.isWindows ? process.env : { ...process.env, NODE: process.execPath, FILE: script },
}, common.mustSucceed((stdout, stderr) => {
const lines = stdout.split('\n');
assert.strictEqual(lines.length, 3);
}));

View File

@ -39,4 +39,13 @@ setTimeout(() => {
}, 100);
`);
execSync(`${process.argv[0]} ${tmpJsFile} < ${tmpCmdFile}`);
// The execPath might contain chars that should be escaped in a shell context.
// On non-Windows, we can pass the path via the env; `"` is not a valid char on
// Windows, so we can simply pass the path.
execSync(
`"${common.isWindows ? process.execPath : '$NODE'}" "${
common.isWindows ? tmpJsFile : '$FILE'}" < "${common.isWindows ? tmpCmdFile : '$CMD_FILE'}"`,
common.isWindows ? undefined : {
env: { ...process.env, NODE: process.execPath, FILE: tmpJsFile, CMD_FILE: tmpCmdFile },
},
);

View File

@ -38,8 +38,6 @@ if (common.isWindows) {
SLEEP = 10000;
}
const execOpts = { encoding: 'utf8', shell: true };
// Verify that stderr is not accessed when a bad shell is used
assert.throws(
function() { execSync('exit -1', { shell: 'bad_shell' }); },
@ -54,8 +52,8 @@ let caught = false;
let ret, err;
const start = Date.now();
try {
const cmd = `"${process.execPath}" -e "setTimeout(function(){}, ${SLEEP});"`;
ret = execSync(cmd, { timeout: TIMER });
const cmd = `"${common.isWindows ? process.execPath : '$NODE'}" -e "setTimeout(function(){}, ${SLEEP});"`;
ret = execSync(cmd, { env: { ...process.env, NODE: process.execPath }, timeout: TIMER });
} catch (e) {
caught = true;
assert.strictEqual(getSystemErrorName(e.errno), 'ETIMEDOUT');
@ -78,16 +76,17 @@ const msgBuf = Buffer.from(`${msg}\n`);
// console.log ends every line with just '\n', even on Windows.
const cmd = `"${process.execPath}" -e "console.log('${msg}');"`;
const cmd = `"${common.isWindows ? process.execPath : '$NODE'}" -e "console.log('${msg}');"`;
const env = common.isWindows ? process.env : { ...process.env, NODE: process.execPath };
{
const ret = execSync(cmd);
const ret = execSync(cmd, common.isWindows ? undefined : { env });
assert.strictEqual(ret.length, msgBuf.length);
assert.deepStrictEqual(ret, msgBuf);
}
{
const ret = execSync(cmd, { encoding: 'utf8' });
const ret = execSync(cmd, { encoding: 'utf8', env });
assert.strictEqual(ret, `${msg}\n`);
}
@ -156,4 +155,6 @@ const args = [
}
// Verify the shell option works properly
execFileSync(process.execPath, [], execOpts);
execFileSync(`"${common.isWindows ? process.execPath : '$NODE'}"`, [], {
encoding: 'utf8', shell: true, env
});

View File

@ -5,12 +5,19 @@ const assert = require('assert');
const { exec } = require('child_process');
const fixtures = require('../common/fixtures');
const node = process.execPath;
// The execPath might contain chars that should be escaped in a shell context.
// On non-Windows, we can pass the path via the env; `"` is not a valid char on
// Windows, so we can simply pass the path.
const execNode = (flag, file, callback) => exec(
`"${common.isWindows ? process.execPath : '$NODE'}" ${flag} "${common.isWindows ? file : '$FILE'}"`,
common.isWindows ? undefined : { env: { ...process.env, NODE: process.execPath, FILE: file } },
callback,
);
// Test both sets of arguments that check syntax
const syntaxArgs = [
['-c'],
['--check'],
'-c',
'--check',
];
const notFoundRE = /^Error: Cannot find module/m;
@ -23,10 +30,8 @@ const notFoundRE = /^Error: Cannot find module/m;
file = fixtures.path(file);
// Loop each possible option, `-c` or `--check`
syntaxArgs.forEach(function(args) {
const _args = args.concat(file);
const cmd = [node, ..._args].join(' ');
exec(cmd, common.mustCall((err, stdout, stderr) => {
syntaxArgs.forEach(function(flag) {
execNode(flag, file, common.mustCall((err, stdout, stderr) => {
// No stdout should be produced
assert.strictEqual(stdout, '');

View File

@ -5,12 +5,19 @@ const assert = require('assert');
const { exec } = require('child_process');
const fixtures = require('../common/fixtures');
const node = process.execPath;
// The execPath might contain chars that should be escaped in a shell context.
// On non-Windows, we can pass the path via the env; `"` is not a valid char on
// Windows, so we can simply pass the path.
const execNode = (flag, file, callback) => exec(
`"${common.isWindows ? process.execPath : '$NODE'}" ${flag} "${common.isWindows ? file : '$FILE'}"`,
common.isWindows ? undefined : { env: { ...process.env, NODE: process.execPath, FILE: file } },
callback,
);
// Test both sets of arguments that check syntax
const syntaxArgs = [
['-c'],
['--check'],
'-c',
'--check',
];
// Test good syntax with and without shebang
@ -25,11 +32,8 @@ const syntaxArgs = [
file = fixtures.path(file);
// Loop each possible option, `-c` or `--check`
syntaxArgs.forEach(function(args) {
const _args = args.concat(file);
const cmd = [node, ..._args].join(' ');
exec(cmd, common.mustCall((err, stdout, stderr) => {
syntaxArgs.forEach(function(flag) {
execNode(flag, file, common.mustCall((err, stdout, stderr) => {
if (err) {
console.log('-- stdout --');
console.log(stdout);