[Bug 3981] New: sshd can lose SIGTERM delivered during SIGHUP restart/re-exec
bugzilla-daemon at mindrot.org
bugzilla-daemon at mindrot.org
Wed Jul 15 20:38:55 AEST 2026
https://bugzilla.mindrot.org/show_bug.cgi?id=3981
Bug ID: 3981
Summary: sshd can lose SIGTERM delivered during SIGHUP
restart/re-exec
Product: Portable OpenSSH
Version: 10.3p1
Hardware: amd64
OS: Solaris
Status: NEW
Severity: normal
Priority: P5
Component: sshd
Assignee: unassigned-bugs at mindrot.org
Reporter: dambi at tio.cz
Description
-----------
Evaluating problem with OpenSSH on Oracle Solaris, it was narrowed down
to sshd missing a termination request if SIGTERM is delivered while the
listener process is already processing a SIGHUP restart.
In the listener loop, SIGTERM and SIGHUP are represented by separate
flags:
sighup_handler() -> received_sighup = 1
sigterm_handler() -> received_sigterm = sig
The listener loop checks received_sigterm before received_sighup, so if
both flags are visible at the top of the loop, SIGTERM correctly wins
and sshd exits.
However, once the listener enters the SIGHUP restart path, it restores
the old signal mask immediately before calling sighup_restart():
if (listening <= 0) {
sigprocmask(SIG_SETMASK, &osigset, NULL);
sighup_restart();
}
sighup_restart() then performs cleanup and calls:
execv(saved_argv[0], saved_argv);
If SIGTERM is delivered after the loop's received_sigterm check, but
before execv() completes, the handler may run and set received_sigterm.
That flag is then lost when sshd execs itself. The new sshd continues
running instead of honoring the termination request.
Race sequence
-------------
1. SIGHUP is received.
2. listener loop checks received_sigterm first; it is not set.
3. listener loop enters received_sighup handling.
4. listener restores osigset, unblocking SIGTERM.
5. SIGTERM arrives.
6. sigterm_handler() sets received_sigterm.
7. sighup_restart() continues to execv().
8. received_sigterm is lost across exec.
9. new sshd continues running.
Expected Behavior
-----------------
A SIGTERM delivered to the listener during SIGHUP restart should cause
sshd to terminate, not restart successfully.
Actual Behavior
---------------
A SIGTERM delivered during the restart window can be converted into an
in-memory flag and then lost across execv().
Impact
------
This affects service managers that send a single SIGTERM and then wait
for the service to exit. For example, Solaris/illumos SMF svc.startd
sends SIGTERM for :kill and waits for the process contract to empty; it
does not repeatedly send SIGTERM unless the timeout path escalates to
SIGKILL.
Suggested Fix
-------------
Keep SIGTERM blocked across the SIGHUP restart/re-exec path. For
example, before restoring osigset, add SIGTERM to it:
if (listening <= 0) {
/*
* Keep SIGTERM blocked across restart. If it arrives
before
* execv(), it remains pending and will be delivered when
the new
* image clears its signal mask during startup.
*/
sigaddset(&osigset, SIGTERM);
sigprocmask(SIG_SETMASK, &osigset, NULL);
sighup_restart();
}
This lets a late SIGTERM remain pending across execv(). The new sshd
clears its signal mask early in main(), at which point the pending
SIGTERM is delivered.
--
You are receiving this mail because:
You are watching the assignee of the bug.
More information about the openssh-bugs
mailing list