git: 1fe93549714d - stable/14 - timeout(1): Fix the inheritance of signal dispositions

From: Konstantin Belousov <kib_at_FreeBSD.org>
Date: Mon, 16 Jun 2025 08:54:06 UTC
The branch stable/14 has been updated by kib:

URL: https://cgit.FreeBSD.org/src/commit/?id=1fe93549714d01e16e76af0d279bd05c5a7e0c7d

commit 1fe93549714d01e16e76af0d279bd05c5a7e0c7d
Author:     Aaron LI <aly@aaronly.me>
AuthorDate: 2025-04-03 01:07:52 +0000
Commit:     Konstantin Belousov <kib@FreeBSD.org>
CommitDate: 2025-06-16 08:51:53 +0000

    timeout(1): Fix the inheritance of signal dispositions
    
    (cherry picked from commit 2390cbfe55f55916eca25e8ba94a3320535e01c9)
---
 bin/timeout/timeout.1 | 10 +++++++++-
 bin/timeout/timeout.c | 50 ++++++++++++++++++++++++++++++--------------------
 2 files changed, 39 insertions(+), 21 deletions(-)

diff --git a/bin/timeout/timeout.1 b/bin/timeout/timeout.1
index 14fc19292684..371a167d19f3 100644
--- a/bin/timeout/timeout.1
+++ b/bin/timeout/timeout.1
@@ -24,7 +24,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd April 2, 2025
+.Dd April 3, 2025
 .Dt TIMEOUT 1
 .Os
 .Sh NAME
@@ -65,6 +65,14 @@ Therefore, a signal is never sent if
 .Ar duration
 is 0.
 .Pp
+The signal dispositions inherited by the
+.Ar command
+are the same as the dispositions that
+.Nm
+inherited, except for the signal that will be sent upon timeout,
+which is reset to take the default action and should terminate
+the process.
+.Pp
 The options are as follows:
 .Bl -tag -width indent
 .It Fl f , Fl -foreground
diff --git a/bin/timeout/timeout.c b/bin/timeout/timeout.c
index 8a2f0faecd83..1c4cfa6e017d 100644
--- a/bin/timeout/timeout.c
+++ b/bin/timeout/timeout.c
@@ -224,6 +224,7 @@ main(int argc, char **argv)
 	bool timedout = false;
 	bool do_second_kill = false;
 	bool child_done = false;
+	sigset_t zeromask, allmask, oldmask;
 	struct sigaction signals;
 	struct procctl_reaper_status info;
 	int signums[] = {
@@ -288,6 +289,33 @@ main(int argc, char **argv)
 			err(EXIT_FAILURE, "procctl(PROC_REAP_ACQUIRE)");
 	}
 
+	/* Block all signals to avoid racing against the child. */
+	sigfillset(&allmask);
+	if (sigprocmask(SIG_BLOCK, &allmask, &oldmask) == -1)
+		err(EXIT_FAILURE, "sigprocmask()");
+
+	pid = fork();
+	if (pid == -1) {
+		err(EXIT_FAILURE, "fork()");
+	} else if (pid == 0) {
+		/*
+		 * child process
+		 *
+		 * POSIX.1-2024 requires that the child process inherit the
+		 * same signal dispositions as the timeout(1) utility
+		 * inherited, except for the signal to be sent upon timeout.
+		 */
+		signal(killsig, SIG_DFL);
+		if (sigprocmask(SIG_SETMASK, &oldmask, NULL) == -1)
+			err(EXIT_FAILURE, "sigprocmask(oldmask)");
+
+		execvp(argv[0], argv);
+		warn("exec(%s)", argv[0]);
+		_exit(errno == ENOENT ? EXIT_CMD_NOENT : EXIT_CMD_ERROR);
+	}
+
+	/* parent continues here */
+
 	memset(&signals, 0, sizeof(signals));
 	sigemptyset(&signals.sa_mask);
 
@@ -310,29 +338,11 @@ main(int argc, char **argv)
 	signal(SIGTTIN, SIG_IGN);
 	signal(SIGTTOU, SIG_IGN);
 
-	pid = fork();
-	if (pid == -1) {
-		err(EXIT_FAILURE, "fork()");
-	} else if (pid == 0) {
-		/* child process */
-		signal(SIGTTIN, SIG_DFL);
-		signal(SIGTTOU, SIG_DFL);
-
-		execvp(argv[0], argv);
-		warn("exec(%s)", argv[0]);
-		_exit(errno == ENOENT ? EXIT_CMD_NOENT : EXIT_CMD_ERROR);
-	}
-
-	/* parent continues here */
-
-	if (sigprocmask(SIG_BLOCK, &signals.sa_mask, NULL) == -1)
-		err(EXIT_FAILURE, "sigprocmask()");
-
 	set_interval(first_kill);
-	sigemptyset(&signals.sa_mask);
+	sigemptyset(&zeromask);
 
 	for (;;) {
-		sigsuspend(&signals.sa_mask);
+		sigsuspend(&zeromask);
 
 		if (sig_chld) {
 			sig_chld = 0;