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

Reply via email to