node/test/parallel/test-child-process-spawn-windows-batch-file.js
RafaelGSS 39f207023a src: handle permissive extension on cmd check
PR-URL: https://github.com/nodejs-private/node-private/pull/596
Reviewed-By: Michael Dawson <midawson@redhat.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
CVE-ID: CVE-2024-36138
2024-07-08 15:38:39 -03:00

98 lines
2.7 KiB
JavaScript

'use strict';
// Node.js on Windows should not be able to spawn batch files directly,
// only when the 'shell' option is set. An undocumented feature of the
// Win32 CreateProcess API allows spawning .bat and .cmd files directly
// but it does not sanitize arguments. We cannot do that automatically
// because it's sometimes impossible to escape arguments unambiguously.
//
// Expectation: spawn() and spawnSync() raise EINVAL if and only if:
//
// 1. 'shell' option is unset
// 2. Platform is Windows
// 3. Filename ends in .bat or .cmd, case-insensitive
//
// exec() and execSync() are unchanged.
const common = require('../common');
const cp = require('child_process');
const assert = require('assert');
const { isWindows } = common;
const expectedCode = isWindows ? 'EINVAL' : 'ENOENT';
const expectedStatus = isWindows ? 1 : 127;
const suffixes =
'BAT|bAT|BaT|baT|BAt|bAt|Bat|bat|CMD|cMD|CmD|cmD|CMd|cMd|Cmd|cmd|cmd |cmd .|cmd ....'
.split('|');
function testExec(filename) {
return new Promise((resolve) => {
cp.exec(filename).once('exit', common.mustCall(function(status) {
assert.strictEqual(status, expectedStatus);
resolve();
}));
});
}
function testExecSync(filename) {
let e;
try {
cp.execSync(filename);
} catch (_e) {
e = _e;
}
if (!e) throw new Error(`Exception expected for ${filename}`);
assert.strictEqual(e.status, expectedStatus);
}
function testSpawn(filename, code) {
// Batch file case is a synchronous error, file-not-found is asynchronous.
if (code === 'EINVAL') {
let e;
try {
cp.spawn(filename);
} catch (_e) {
e = _e;
}
if (!e) throw new Error(`Exception expected for ${filename}`);
assert.strictEqual(e.code, code);
} else {
return new Promise((resolve) => {
cp.spawn(filename).once('error', common.mustCall(function(e) {
assert.strictEqual(e.code, code);
resolve();
}));
});
}
}
function testSpawnSync(filename, code) {
{
const r = cp.spawnSync(filename);
assert.strictEqual(r.error.code, code);
}
{
const r = cp.spawnSync(filename, { shell: true });
assert.strictEqual(r.status, expectedStatus);
}
}
testExecSync('./nosuchdir/nosuchfile');
testSpawnSync('./nosuchdir/nosuchfile', 'ENOENT');
for (const suffix of suffixes) {
testExecSync(`./nosuchdir/nosuchfile.${suffix}`);
testSpawnSync(`./nosuchdir/nosuchfile.${suffix}`, expectedCode);
}
go().catch((ex) => { throw ex; });
async function go() {
await testExec('./nosuchdir/nosuchfile');
await testSpawn('./nosuchdir/nosuchfile', 'ENOENT');
for (const suffix of suffixes) {
await testExec(`./nosuchdir/nosuchfile.${suffix}`);
await testSpawn(`./nosuchdir/nosuchfile.${suffix}`, expectedCode);
}
}