Index | Thread | Search

From:
Damien Miller <djm@mindrot.org>
Subject:
Re: sshd: mask SIGTERM/SIGQUIT across SIGHUP restart
To:
tech@openbsd.org
Cc:
openssh@openssh.com
Date:
Tue, 15 Sep 2026 20:59:04 +1000

Download raw body.

Thread
On Tue, 15 Sep 2026, Damien Miller wrote:

> Hi,
> 
> This is https://bugzilla.mindrot.org/show_bug.cgi?id=3981
> 
> sshd restarts on SIGHUP by re-executing itself. If a SIGTERM/SIGQUIT
> comes after this has begun then the termination signal can be lost.
> The bug contains a description of the 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.
> 
> This patch keeps SIGTERM/SIGQUIT masked across such a restart.
> The first thing in sshd.c:main() is a sigprocmask(2) call with an
> empty signal set, so one of these signals will be delivered then.
> 
> ok?
> 
> Another alternative might be to reset the handlers for these signals
> to SIGDFL, but this seems simpler.
> 
> commit 01b5ab90cd89a91dc41a493a017439f0501a8e64
> Author: Damien Miller <djm@mindrot.org>
> Date:   Tue Sep 15 17:49:11 2026 +1000
> 
>     keep SIGTERM/SIGQUIT masked across SIGHUP restart
>     
>     Keep these signals masked so they don't get lost across a sshd restart
>     initiated by SIGHUP. bz3981
>     
>     Note, the first thing sshd does in main() is clear the sigprocmask.
> 
> diff --git a/sshd.c b/sshd.c
> index a568091..65d21fb 100644
> --- a/sshd.c
> +++ b/sshd.c
> @@ -964,6 +964,12 @@ server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s,
>  				lameduck = 1;
>  			}
>  			if (listening <= 0) {
> +				/*
> +				 * Leave termination signals blocked, so
> +				 * they don't get lost across a restart.
> +				 */
> +				sigdelset(&nsigset, SIGTERM);
> +				sigdelset(&nsigset, SIGQUIT);

These should of course be osigset


commit 17c7dca55648d955c3bf144745da08b24c54bf78
Author: Damien Miller <djm@mindrot.org>
Date:   Tue Sep 15 17:49:11 2026 +1000

    keep SIGTERM/SIGQUIT masked across SIGHUP restart
    
    Keep these signals masked so they don't get lost across a sshd restart
    initiated by SIGHUP. bz3981
    
    Note, the first thing sshd does in main() is clear the sigprocmask.

diff --git a/sshd.c b/sshd.c
index a568091..e55bec2 100644
--- a/sshd.c
+++ b/sshd.c
@@ -964,6 +964,12 @@ server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s,
 				lameduck = 1;
 			}
 			if (listening <= 0) {
+				/*
+				 * Leave termination signals blocked, so
+				 * they don't get lost across a restart.
+				 */
+				sigdelset(&osigset, SIGTERM);
+				sigdelset(&osigset, SIGQUIT);
 				sigprocmask(SIG_SETMASK, &osigset, NULL);
 				sighup_restart();
 			}