bug-bash
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH] Use SIGCONT to avoid fg tcsetpgrp() SIGTTIN/SIGTTOU race


From: Earl Chew
Subject: [PATCH] Use SIGCONT to avoid fg tcsetpgrp() SIGTTIN/SIGTTOU race
Date: Fri, 25 Aug 2023 17:59:02 -0700

A running background job can be stopped by SIGTTOU or SIGTTIN,
just as the fg command issues tcsetprgp() to give the job
the controlling terminal. This causes the fg command to appear
to have caused the job to stop.

Prevent this from happening by always issuing SIGCONT
after the fg command calls tcsetpgrp().

The following script reproduces the issue with the fg command
returning status 149 (SIGTTIN 21):

        + sleep 0.0008305541
        + sleep 0.00062593
        + sleep 0.004898882
        + jobs
        [1]-  Running                 ( sleep ${1}1; read X ) &
        [2]+  Running                 ( sleep ${3}2; kill $! ) &
        + RC=0
        + read X
        + fg %1
        ( sleep ${1}1; read X )
        + RC=149
        + '[' 149 = 143 -o 149 = 1 -o 149 = 2 ']'
        + jobs
        [1]+  Stopped                 ( sleep ${1}1; read X )
        [2]-  Running                 ( sleep ${3}2; kill $! ) &
        + exit 1

    #!/bin/sh

    set -mbux

    rand()
    {
        set -- $(( $( od -An -N1 -t u1 /dev/random ) % 5 + 6 ))
        printf "%0$1u" $(( $(od -An -N4 -t u4 /dev/random) % 1000000 ))
    }

    race()
    {
        set -- $1 $2 $(rand)
        set -- $1 $2 0.$3

        rm -f READ
        ( sleep ${1}1 ; read X ) &
        ( sleep ${3}2 ; kill $! ) &
        sleep ${2}3
        jobs
        RC=0
        fg %1 || RC=$?
        #
        # bash fg returns 1, and dash fg can return 2, if
        # the job has already terminated.
        #
        [ $RC = 143 -o $RC = 1 -o $RC = 2 ] || {
            #ps wwwaxjfh
            jobs
            exit 1
        }

        wait %1
        wait %2
        wait %3
    }

    main()
    {
        set -- ${1-0} ${2-0}
        while : ; do
            set -- ${1%.*} ${2%.*}
            set -- "$@" $(rand)
            set -- "$@" $(rand)
            set -- $1.$3 $2.$4
            race "$@"
        done
    }

    main "$@"

Signed-off-by: Earl Chew <earl_chew@yahoo.com>
---
 jobs.c | 22 ++++++++++++++--------
 1 file changed, 14 insertions(+), 8 deletions(-)

diff --git a/jobs.c b/jobs.c
index 6b986ed7..e11b1a9f 100644
--- a/jobs.c
+++ b/jobs.c
@@ -3543,6 +3543,7 @@ start_job (job, foreground)
 {
   register PROCESS *p;
   int already_running;
+  int jump_start;
   sigset_t set, oset;
   char *wd, *s;
   static TTYSTRUCT save_stty;
@@ -3564,6 +3565,7 @@ start_job (job, foreground)
     }
 
   already_running = RUNNING (job);
+  jump_start = !already_running;
 
   if (foreground == 0 && already_running)
     {
@@ -3616,7 +3618,10 @@ start_job (job, foreground)
 
   /* Run the job. */
   if (already_running == 0)
-    set_job_running (job);
+    {
+      set_job_running (job);
+      jobs[job]->flags |= J_NOTIFIED;
+    }
 
   /* Save the tty settings before we start the job in the foreground. */
   if (foreground)
@@ -3625,17 +3630,18 @@ start_job (job, foreground)
       save_stty = shell_tty_info;
       /* Give the terminal to this job. */
       if (IS_JOBCONTROL (job))
-       give_terminal_to (jobs[job]->pgrp, 0);
+        {
+         give_terminal_to (jobs[job]->pgrp, 0);
+         jump_start = 1;
+        }
     }
   else
     jobs[job]->flags &= ~J_FOREGROUND;
 
-  /* If the job is already running, then don't bother jump-starting it. */
-  if (already_running == 0)
-    {
-      jobs[job]->flags |= J_NOTIFIED;
-      killpg (jobs[job]->pgrp, SIGCONT);
-    }
+  /* Jump start if not running. Also jump start if running as foreground
+     via job control to avoid racing with SIGTTOU and SIGTTIN signals. */
+  if (jump_start)
+    killpg (jobs[job]->pgrp, SIGCONT);
 
   if (foreground)
     {
-- 
2.39.1



reply via email to

[Prev in Thread] Current Thread [Next in Thread]