tty: make n_tty_read() always abort if hangup is in progress
authorTejun Heo <>
Tue, 13 Feb 2018 15:38:08 +0000 (07:38 -0800)
committerBen Hutchings <>
Thu, 31 May 2018 23:30:19 +0000 (00:30 +0100)
commit 28b0f8a6962a24ed21737578f3b1b07424635c9e upstream.

A tty is hung up by __tty_hangup() setting file->f_op to
hung_up_tty_fops, which is skipped on ttys whose write operation isn't
tty_write().  This means that, for example, /dev/console whose write
op is redirected_tty_write() is never actually marked hung up.

Because n_tty_read() uses the hung up status to decide whether to
abort the waiting readers, the lack of hung-up marking can lead to the
following scenario.

 1. A session contains two processes.  The leader and its child.  The
    child ignores SIGHUP.

 2. The leader exits and starts disassociating from the controlling
    terminal (/dev/console).

 3. __tty_hangup() skips setting f_op to hung_up_tty_fops.

 4. SIGHUP is delivered and ignored.

 5. tty_ldisc_hangup() is invoked.  It wakes up the waits which should
    clear the read lockers of tty->ldisc_sem.

 6. The reader wakes up but because tty_hung_up_p() is false, it
    doesn't abort and goes back to sleep while read-holding

 7. The leader progresses to tty_ldisc_lock() in tty_ldisc_hangup()
    and is now stuck in D sleep indefinitely waiting for

The following is Alan's explanation on why some ttys aren't hung up.

 1. It broke the serial consoles because they would hang up and close
    down the hardware. With tty_port that *should* be fixable properly
    for any cases remaining.

 2. The console layer was (and still is) completely broken and doens't
    refcount properly. So if you turn on console hangups it breaks (as
    indeed does freeing consoles and half a dozen other things).

As neither can be fixed quickly, this patch works around the problem
by introducing a new flag, TTY_HUPPING, which is used solely to tell
n_tty_read() that hang-up is in progress for the console and the
readers should be aborted regardless of the hung-up status of the

The following is a sample hung task warning caused by this issue.

  INFO: task agetty:2662 blocked for more than 120 seconds.
        Not tainted 4.11.3-dbg-tty-lockup-02478-gfd6c7ee-dirty #28
  "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
      0  2662      1 0x00000086
  Call Trace:

The following is the repro.  Run "$PROG /dev/console".  The parent
process hangs in D state.

  #include <sys/types.h>
  #include <sys/stat.h>
  #include <sys/wait.h>
  #include <sys/ioctl.h>
  #include <fcntl.h>
  #include <unistd.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <errno.h>
  #include <signal.h>
  #include <time.h>
  #include <termios.h>

  int main(int argc, char **argv)
  struct sigaction sact = { .sa_handler = SIG_IGN };
  struct timespec ts1s = { .tv_sec = 1 };
  pid_t pid;
  int fd;

  if (argc < 2) {
  fprintf(stderr, "test-hung-tty /dev/$TTY\n");
  return 1;

  /* fork a child to ensure that it isn't already the session leader */
  pid = fork();
  if (pid < 0) {
  return 1;

  if (pid > 0) {
  /* top parent, wait for everyone */
  while (waitpid(-1, NULL, 0) >= 0)
  if (errno != ECHILD)
  return 0;

  /* new session, start a new session and set the controlling tty */
  if (setsid() < 0) {
  return 1;

  fd = open(argv[1], O_RDWR);
  if (fd < 0) {
  return 1;

  if (ioctl(fd, TIOCSCTTY, 1) < 0) {
  return 1;

  /* fork a child, sleep a bit and exit */
  pid = fork();
  if (pid < 0) {
  return 1;

  if (pid > 0) {
  nanosleep(&ts1s, NULL);
  printf("Session leader exiting\n");

   * The child ignores SIGHUP and keeps reading from the controlling
   * tty.  Because SIGHUP is ignored, the child doesn't get killed on
   * parent exit and the bug in n_tty makes the read(2) block the
   * parent's control terminal hangup attempt.  The parent ends up in
   * D sleep until the child is explicitly killed.
  sigaction(SIGHUP, &sact, NULL);
  printf("Child reading tty\n");
  while (1) {
  char buf[1024];

  if (read(fd, buf, sizeof(buf)) < 0) {
  return 1;

  return 0;

Signed-off-by: Tejun Heo <>
Cc: Alan Cox <>
Signed-off-by: Greg Kroah-Hartman <>
[bwh: Backported to 3.2: TTY_HUPPING is not really a new flag; it's an old flag
 that was wrongly removed in 3.19.  Just add the test for it in n_tty_read().]
Signed-off-by: Ben Hutchings <>

index 304f58e..8e31696 100644 (file)
@@ -1809,6 +1809,12 @@ do_it_again:
                        if (tty_hung_up_p(file))
+                       /*
+                        * Abort readers for ttys which never actually
+                        * get hung up.  See __tty_hangup().
+                        */
+                       if (test_bit(TTY_HUPPING, &tty->flags))
+                               break;
                        if (!timeout)
                        if (file->f_flags & O_NONBLOCK) {