Index | Thread | Search

From:
Damien Miller <djm@mindrot.org>
Subject:
readpassphrase(3): avoid spin on ignored signals
To:
tech@openbsd.org
Date:
Tue, 15 Sep 2026 16:15:33 +1000

Download raw body.

Thread
Hi,

This was a bug report made to portable OpenSSH via
https://bugzilla.mindrot.org/attachment.cgi?id=3983

> When ssh-add (or any OpenSSH tool) is run as a background process
> without a controlling TTY and with SIGTTOU or SIGTTIN set to SIG_IGN
> (e.g. from a systemd service or a daemon that ignores job-control
> signals), readpassphrase() spins at near-100% CPU indefinitely.
> 
> Root cause: after readpassphrase() installs its own signal handlers,
> reads from the TTY cause SIGTTOU or SIGTTIN to fire. The handler sets
> signo[SIGTTIN/SIGTTOU]=1 and the read returns EINTR. readpassphrase then
> restores the original SIG_IGN disposition and re-raises those signals.
> Because SIG_IGN was restored, kill(getpid(), SIGTTIN) is a no-op, but
> need_restart is still set to 1, causing an unconditional "goto restart".
> The next iteration opens /dev/tty again, writes the prompt to stderr,
> hits the same SIGTTIN/SIGTTOU, and the loop repeats forever.
> 
> Fix: before setting need_restart for SIGTSTP/SIGTTIN/SIGTTOU, check
> whether the original (saved) handler was SIG_IGN. If it was, do not
> restart, because restarting would never make progress: the signal will
> be ignored indefinitely and the process can never become the terminal
> foreground group.
> 
> This is the ssh-add equivalent of the fix applied to the ssh client for
> Launchpad bug #1646813 (Ubuntu openssh 1:7.4p1-5).

Ok?

Index: lib/libc/gen/readpassphrase.c
===================================================================
RCS file: /cvs/src/lib/libc/gen/readpassphrase.c,v
diff -u -p -r1.29 readpassphrase.c
--- lib/libc/gen/readpassphrase.c	10 Mar 2026 16:27:33 -0000	1.29
+++ lib/libc/gen/readpassphrase.c	15 Sep 2026 06:12:21 -0000
@@ -164,7 +164,28 @@ restart:
 			case SIGTSTP:
 			case SIGTTIN:
 			case SIGTTOU:
-				need_restart = 1;
+				/*
+				 * Do not restart if the original handler for
+				 * this signal was SIG_IGN.  Restarting in that
+				 * case would spin forever: the signal is
+				 * re-raised but immediately discarded (SIG_IGN
+				 * was restored above), so the tty condition
+				 * never resolves and readpassphrase loops at
+				 * ~100% CPU.  This matches the behaviour of a
+				 * background process that has no way to acquire
+				 * the terminal (e.g. ssh-add running without a
+				 * controlling TTY or with SIGTTOU/SIGTTIN
+				 * ignored by its parent).
+				 */
+				if (i == SIGTSTP &&
+				    savetstp.sa_handler != SIG_IGN)
+					need_restart = 1;
+				if (i == SIGTTIN &&
+				    savettin.sa_handler != SIG_IGN)
+					need_restart = 1;
+				if (i == SIGTTOU &&
+				    savettou.sa_handler != SIG_IGN)
+					need_restart = 1;
 			}
 		}
 	}