src: don't kill own process group on failed spawn

libuv only assigns a pid to the process handle once uv_spawn() has
succeeded, so a child that never started keeps pid 0. Calling kill()
on such a child still reached uv_process_kill(), which ended up in
kill(0, signal) and signalled every process in the caller's own
process group, Node included.

Return ESRCH when the handle has no pid, and zero the pid in the
constructor so the check never reads an unassigned value.

Signed-off-by: Lazizbek Ergashev <lazerg2@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/65054
Fixes: https://github.com/nodejs/node/issues/65052
Reviewed-By: Aviv Keller <me@aviv.sh>
Reviewed-By: Robert Nagy <ronagy@icloud.com>
This commit is contained in:
Lazizbek Ergashev authored and GitHub committed 2026-09-12 10:43:37 +00:00
1 parent 343a4fc532
commit ce20f7acb3
3 files changed
+25 -1

No files matched your search

+5 -1
View File
@@ -112,6 +112,7 @@ class ProcessWrap : public HandleWrap {
object,
reinterpret_cast<uv_handle_t*>(&process_),
AsyncWrap::PROVIDER_PROCESSWRAP) {
process_.pid = 0;
MarkAsUninitialized();
}
@@ -355,7 +356,10 @@ class ProcessWrap : public HandleWrap {
signal = SIGKILL;
}
#endif
int err = uv_process_kill(&wrap->process_, signal);
// uv_spawn() only assigns a pid when it succeeds, and kill(0, signal)
// signals every process in our own process group.
int err = wrap->process_.pid > 0 ? uv_process_kill(&wrap->process_, signal)
: UV_ESRCH;
args.GetReturnValue().Set(err);
}
+4
View File
@@ -0,0 +1,4 @@
const { spawn } = require('child_process');
const child = spawn('foo123');
child.on('error', () => {});
if (child.kill() !== false || child.killed !== false) process.exit(1);
@@ -0,0 +1,16 @@
'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
const assert = require('assert');
const { spawn } = require('child_process');
// Killing a child process that never spawned must not signal the process
// group of the caller. The check runs in a detached child so that a
// regression cannot take the test runner down with it.
const childPath = fixtures.path('child-process-kill-spawn-error.js');
const child = spawn(process.execPath, [childPath], { detached: true });
child.on('exit', common.mustCall((code, signal) => {
assert.strictEqual(signal, null);
assert.strictEqual(code, 0);
}));