Package: util-linux
Version: 2.20.1-1ubuntu3.1

more(1) may attempt to call tsetattr(3) on stderr even if its process is
not in a process group with a controlling terminal.  As a result,
SIGTTOU will be generated, suspending the process.

The following example illustrates the issue:

$ /bin/sh -c "timeout 60 /bin/sh -c \"ls | more\""

This command will now hang until the timeout, since the shell, ls(1) and
more(1) commands invoked by timeout(1) will be suspended.

Note the difference to

$ timeout 60 /bin/sh -c "ls | more"

which behaves as expected, since here the entire shell command is in the
process group that has the controlling terminal.


The problem is in text-utils/more.c, where a call to tcsetattr(3) is
made on stderr.  The test for whether or not a tty is present is not
sufficient here, since a tty may be present but not be in the process
group of the controlling terminal.

A simplified PoC of the problematic code looks like this:

--- 8< --------- 8< --------- 8< ---------

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>

int
main(int argc, char ** argv) {
        struct termios otty;
        int no_tty;

        if (setpgid(0, 0) == -1) {
                fprintf(stderr, "Unable to setpgid: %s\n", strerror(errno));
                exit(1);
        }

        no_tty = tcgetattr(fileno(stdout), &otty);

        if (!no_tty) {
#ifdef FIX
                if (tcgetpgrp(fileno(stderr)) == getpgrp()) {
#endif
                        if (tcsetattr(fileno(stderr), TCSANOW, &otty) == -1) {
                                fprintf(stderr, "Unable to tcsetattr: %s\n", 
strerror(errno));
                                exit(1);
                        }
#ifdef FIX
                }
#endif
        }
        return 0;
}

--- 8< --------- 8< --------- 8< ---------

If compiled without '-DFIX', then we observe the following:

$ ./a.out
$ /bin/sh -c ./a.out
[hangs, can't ^C, need to suspend and kill]

If compiled with '-DFIX', we get the desired behaviour.

The fix to text-utils/more.c should then be:

--- 8< --------- 8< --------- 8< ---------

--- more.c      2016-10-12 18:19:00.858761448 -0400
+++ more.c.orig 2011-09-26 05:50:25.000000000 -0400
@@ -1769,16 +1769,6 @@
 retry:
 #endif /* do_SIGTTOU */
     no_tty = tcgetattr(fileno(stdout), &otty);
-    no_intty = tcgetattr(fileno(stdin), &otty);
-
-    /* If we are not in the process group with the controlling terminal,
-     * then we have on business trying to interact with it, so let's
-     * pretend there isn't a tty. */
-    if (tcgetpgrp(fileno(stdout)) != getpgrp()) {
-       no_tty = 1;
-       no_intty = 1;
-    }
-
     if (!no_tty) {
        docrterase = (otty.c_cc[VERASE] != 255);
        docrtkill =  (otty.c_cc[VKILL] != 255);
@@ -1880,7 +1870,7 @@
        if ((shell = getenv("SHELL")) == NULL)
            shell = "/bin/sh";
     }
-
+    no_intty = tcgetattr(fileno(stdin), &otty);
     tcgetattr(fileno(stderr), &otty);
     savetty0 = otty;
     slow_tty = cfgetispeed(&otty) < B1200;

--- 8< --------- 8< --------- 8< ---------

That is, if the current process group is not the same as the process
group of the controlling terminal, then it's best to pretend that we
don't have a terminal and cause more(1) to merely copy the input rather
than attempt to paginate.

Reply via email to