bug-bash
[Top][All Lists]
Advanced

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

fg tcsetpgrp() SIGTTIN/SIGTTOU race


From: Earl Chew
Subject: fg tcsetpgrp() SIGTTIN/SIGTTOU race
Date: Tue, 18 Jul 2023 20:50:43 -0700
User-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:102.0) Gecko/20100101 Thunderbird/102.13.0

Bash uses tcsetpgrp() to move a program to the foreground, and
conditionally resumes the program with SIGCONT if the program was
stopped. Consider the following script:


   $ ( cmd1 ; read X ; cmd2 ) &
   $ ...
   $ fg

When fg is issued, the background program is either stopped, or running.
The expectation is that fg will move the program to the foreground,
and run the program to completion.

The conditional check before sending SIGCONT opens up the opportunity
for a race. Programs running in the background are stopped with SIGTTIN
if an attempt is made to read from the controlling terminal, or stopped with SIGTTOU if an attempt is made to write to the controlling terminal
configured with TOSTOP.

The program could be stopping just as the fg command is moving it to
the foreground. This results the surprising appearance that the program
seems to stop just after fg completes.

The test program below illustrates the race.


Invoking the test with arguments 1 0 moves the running test to
the foregound.

    + jobs
    [1]- Running (sleep ${1}1; trap 'touch READ ; exit 15' 15; read X) &
    [2]+ Running (sleep ${1}1; sleep ${3}2; kill $!) &
    + fg %1
    ( sleep ${1}1; trap 'touch READ ; exit 15' 15; read X )
    + trap 'touch READ ; exit 15' 15
    + read X
    + sleep 0.000474502
    + kill 1094194
    ++ touch READ
    ++ exit 15


Invoking the test with arguments 0 1 moves the stopped test to
the foreground.

    + jobs
    [1]+ Stopped (sleep ${1}1; trap 'touch READ ; exit 15' 15; read X)
    [2]  Done    (sleep ${1}1; sleep ${3}2; kill $!)
    + fg %1
    ( sleep ${1}1; trap 'touch READ ; exit 15' 15; read X )
    ++ touch READ
    ++ exit 15


Invoking the test without arguments races the fg and read to trigger
the behaviour.

    + jobs
    [1]- Running (sleep ${1}1; trap 'touch READ ; exit 15' 15; rea
d X) &
    [2]+ Running (sleep ${1}1; sleep ${3}2; kill $!) &
    + fg %1
    + read X
    ( sleep ${1}1; trap 'touch READ ; exit 15' 15; read X )
    + echo 149
    149


Interestingly dash/ash does not exhibit this behaviour. Looking at the
implementation since 1994 shows that it issues SIGCONT unconditionally
after TIOCSPGRP:

https://svnweb.freebsd.org/base/head/bin/sh/jobs.c?revision=1556&view=markup#l175

https://svnweb.freebsd.org/base/head/bin/sh/jobs.c?revision=1556&view=markup#l207

The prior art suggested by dash/ash is that bash should send SIGCONT
unconditionally after tcsetpgrp() when moving a program to the
foreground.


Earl


#!/bin/sh

set -mbux

race()
{
    set -- $1 $2 $(od -An -N2 -i /dev/random)
    set -- $1 $2 0.000$3

    rm -f READ
    ( sleep ${1}1 ; trap 'touch READ ; exit 15' 15 ; read X ) &
    ( sleep ${1}1 ; sleep ${3}2 ; kill $! ) &
    sleep ${2}3
    jobs
    fg %1 || echo $?
    [ -e READ ] || {
        ps wwwaxjfh
        jobs
        exit 1
    }

    wait %1
    wait %2
    wait %3
}

main()
{
    set -- ${1-0} ${2-0}
    while : ; do
        set -- ${1%.*} ${2%.*}
        set -- "$@" $(od -An -N2 -i /dev/random)
        set -- "$@" $(od -An -N2 -i /dev/random)
        set -- $1.000$3 $2.000$4
        race "$@"
    done
}

main "$@"



reply via email to

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