Dear Bash maintainers,
I would like to clarify the evidence level and narrow the proposed change in my
previous report.
The report is based on source inspection. I do not yet have a confirmed runtime
result for the supplied sleep 10 | true example. Please treat it as a proposed
reproducer rather than an executed test.
The problematic continue and skipped pointer advancement are present in the
official Bash 5.2.21 and 5.3 sources, and in the devel jobs.c retrieved during
this review.
The precise branch condition is:
jobs[job]->pgrp == shell_pgrp
It is not a direct job_control == 0 test.
Within that branch, the control-flow issue is as follows:
*
If the head element is already non-alive, continue reaches a false loop
condition and later elements are skipped.
*
If a later element is already non-alive, continue leaves p unchanged while the
loop condition remains true.
These are source-level conclusions. Execution frequency and operational impact
have not been measured.
My previous replacement also omitted the existing conditional SIGCONT call. A
narrower candidate preserves the body and moves pointer advancement into the
loop condition, so it also occurs after continue:
p = jobs[job]->pipe;
do
{
if (PALIVE (p) == 0)
continue;
kill (p->pid, sig);
if (PEXITED (p) && (sig == SIGTERM || sig == SIGHUP))
kill (p->pid, SIGCONT);
}
while ((p = p->next) != jobs[job]->pipe);
This candidate has not been compiled or runtime-tested. Validation should cover
both a dead head followed by a live process and a live head followed by a dead
process, with the job-table state established before signalling.
The relevant release source is Bash 5.3
jobs.c<https://git.savannah.gnu.org/cgit/bash.git/tree/jobs.c?h=bash-5.3>.
Thank you for your time and review.
Best regards,
Edgar Magdelaine
________________________________
De : Edgar MAGDELAINE <[email protected]>
Envoyé : mardi 13 octobre 2026 23:00
À : [email protected] <[email protected]>
Objet : [Bash 5.2.21] Infinite loop or skipped processes in kill_pid (jobs.c)
when signalling a multi-process job without job control
Dear Bash Maintainers,
First of all, please accept my sincere gratitude and highest admiration for the
monumental work you have dedicated over the decades to building and maintaining
GNU Bash. While carefully studying `jobs.c` to understand how Bash safely
handles signal delivery against PID reuse races, we noticed a loop control flaw
in `kill_pid` that we wanted to respectfully share with you.
Configuration Information [Automatically generated, do not change]:
Machine: x86_64
OS: linux-gnu
Bash Version: 5.2
Patch Level: 21
Release Status: release
Description:
In `jobs.c` (`kill_pid`, around lines 3705-3715), when sending a signal to a job
spec (`kill -TERM %1`) while job control is disabled (`job_control == 0`), Bash
iterates over the circular process list `jobs[job]->pipe`:
p = jobs[job]->pipe;
do
{
if (PALIVE (p) == 0)
continue;
kill (p->pid, sig);
p = p->next;
}
while (p != jobs[job]->pipe);
Because `continue` jumps directly to the `while (p != jobs[job]->pipe)` test
without executing `p = p->next;`, two issues occur on multi-process pipelines:
1.
If the first process in the pipeline is alive (`PALIVE(p) != 0`) and the
*
second (or any subsequent) process has already exited (`PALIVE(p) == 0`), `p`
advances to the dead process and then `continue` loops forever because
`p != jobs[job]->pipe` remains true and `p` is never incremented (100% CPU
hang with `SIGCHLD` blocked).
2.
Conversely, if the first process (`jobs[job]->pipe`) has already exited while
*
downstream processes in the pipeline are still alive, `continue` on the first
iteration evaluates `p != jobs[job]->pipe` (which is false) and exits the
loop immediately without signalling the remaining live processes.
Repeat-By:
Run the following script with job control disabled (default in scripts):
#!/usr/bin/env bash
set +m
sleep 10 | true &
sleep 0.2 # Let the second process ('true') exit so PALIVE(p) == 0
kill -TERM %1
Fix:
Advance `p = p->next` unconditionally before checking the loop condition, by
guarding only the `kill()` call:
p = jobs[job]->pipe;
do
{
if (PALIVE (p))
kill (p->pid, sig);
p = p->next;
}
while (p != jobs[job]->pipe);
Thank you very much for your time, your review, and your tireless stewardship of
GNU Bash.
With my highest regards and most respectful salutations,
Edgar Magdelaine