src: don't kill own process group on failed spawn - #65054
Conversation
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>
1c77865 to
38627f1
Compare
| object, | ||
| reinterpret_cast<uv_handle_t*>(&process_), | ||
| AsyncWrap::PROVIDER_PROCESSWRAP) { | ||
| process_.pid = 0; |
There was a problem hiding this comment.
What's the point this? Won't the PID be assigned to 0 anyway by libuv?
There was a problem hiding this comment.
Checked uv__handle_init: it only sets loop / type/ flags / handle_queue, never touches pid. So before this, pid was whatever was on the stack, not 0. Kept the explicit zero.
There was a problem hiding this comment.
Maybe I'm thinking into this too much, but it seems redundant to initialize properties when other properties serve the same purpose. Surely elsewhere on the object some other indication of failure is present. If not, then I guess this is correct.
There was a problem hiding this comment.
no, nothing else on the object marks it. state_ only reflects the handle's own open/close lifecycle, not whether uv_spawn set a pid. Without the explicit zero it's leftover stack memory.
btw thank you for putting html injection π . But I think most of the frontier models can notice it instantly :)
libuv only assigns a pid to the process handle once
uv_spawn()succeeds, so a child that never started keeps pid 0. Callingkill()on that child still reacheduv_process_kill(), which ended up inkill(0, signal)and signalled every process in the caller's own process group, Node included. The handle is now reported asESRCHwhen there is no pid, sochild.kill()just returns false. The pid is also zeroed in the constructor so the check never reads an unassigned value.Reproducing it on its own needs no prototype tampering:
In the report the spawn failed for a different reason: overriding
Array.prototype[Symbol.iterator]makesnormalizeSpawnArguments()build an empty env, so the command was no longer found through PATH. The dead handle is what took the parent down.Behavior change
kill()on a child that failed to spawn used to returntrueon POSIX. It now returnsfalse.Windows was never affected by the process-group problem, since
uv_process_kill()bails out withEINVALwhen the handle has no process handle. It reached thethrow new ErrnoException(err, 'kill')branch instead, so therekill()goes from throwingEINVALto returningfalse. Both platforms now agree.subprocess.killedis also left alone in this case, which matches what the docs already say about it being set only when a signal is sent successfully.Fixes: #65052