On Wed, Apr 29, 2020 at 11:12:37AM +0200, Martin Pieuchot wrote:
> Program below is the smaller version of a syzkaller report [0].  After
> running it one is left without usable console.  A second execution will
> make openpty(3) pick a different "/dev/tty*" node:
> 
>   50361 crash    CALL  ioctl(3,PTMGET,0x7f7ffffeda80)
>   50361 crash    NAMI  "/dev/ptypd"
>   50361 crash    NAMI  "/dev/ttypd"
>   50361 crash    NAMI  "/dev/ttypd"
>   50361 crash    RET   ioctl 0
> 
> After some more tries:
> 
>   65559 crash    CALL  ioctl(3,PTMGET,0x7f7ffffc36a0)
>   65559 crash    NAMI  "/dev/ptypm"
>   65559 crash    NAMI  "/dev/ttypm"
>   65559 crash    NAMI  "/dev/ttypm"
>   65559 crash    RET   ioctl 0
> 
> [0] 
> https://syzkaller.appspot.com/bug?id=a74718ca902617e6aa7327aa008b25844eccf2d3
> 
> ----- crash.c -----
> 
> #include <unistd.h>
> #include <util.h>
> 
> int 
> main(void)
> {
>       char garbage[100];
>       int master, slave;
> 
>       if (openpty(&master, &slave, NULL, NULL, NULL) == -1)
>               return -1;
>       if (dup2(master, master + 100) != -1)
>               close(master);
> 
>       write(slave, garbage, 99);
> 
>       return 0;
> }
> 

The order in which the pty master/slave is closed seems to be the
trigger here. While not duping the master, it's closed before the slave.
In the opposite scenario, the slave is closed before the master. While
closing the slave, it ends up here expressed as a simplified backtrace:

  tsleep()
  ttysleep()
  ttywait()
  ttywflush()
  ttylclose()
  ptsclose()
  fdfree()
  exit1()

In order words, it ends up doing a tsleep(INFSLP) causing the thread to
hang. Note that this is not the case when the master is closed before
the slave since `tp->t_oproc == NULL' causing ttywait() to bail early.

NetBSD does a sleep with a timeout in ttywflush(). I've applied the same
approach in the diff below which does fix the hang.

Index: kern/kern_proc.c
===================================================================
RCS file: /cvs/src/sys/kern/kern_proc.c,v
retrieving revision 1.86
diff -u -p -r1.86 kern_proc.c
--- kern/kern_proc.c    30 Jan 2020 08:51:27 -0000      1.86
+++ kern/kern_proc.c    1 May 2020 10:11:12 -0000
@@ -410,7 +410,7 @@ killjobc(struct process *pr)
                        if (sp->s_ttyp->t_session == sp) {
                                if (sp->s_ttyp->t_pgrp)
                                        pgsignal(sp->s_ttyp->t_pgrp, SIGHUP, 1);
-                               ttywait(sp->s_ttyp);
+                               ttywait(sp->s_ttyp, 0);
                                /*
                                 * The tty could have been revoked
                                 * if we blocked.
Index: kern/tty.c
===================================================================
RCS file: /cvs/src/sys/kern/tty.c,v
retrieving revision 1.154
diff -u -p -r1.154 tty.c
--- kern/tty.c  7 Apr 2020 13:27:51 -0000       1.154
+++ kern/tty.c  1 May 2020 10:11:12 -0000
@@ -809,7 +809,7 @@ ttioctl(struct tty *tp, u_long cmd, cadd
                break;
        }
        case TIOCDRAIN:                 /* wait till output drained */
-               if ((error = ttywait(tp)) != 0)
+               if ((error = ttywait(tp, 0)) != 0)
                        return (error);
                break;
        case TIOCGETA: {                /* get termios struct */
@@ -866,7 +866,7 @@ ttioctl(struct tty *tp, u_long cmd, cadd
 
                s = spltty();
                if (cmd == TIOCSETAW || cmd == TIOCSETAF) {
-                       if ((error = ttywait(tp)) != 0) {
+                       if ((error = ttywait(tp, 0)) != 0) {
                                splx(s);
                                return (error);
                        }
@@ -1205,7 +1205,7 @@ ttnread(struct tty *tp)
  * Wait for output to drain.
  */
 int
-ttywait(struct tty *tp)
+ttywait(struct tty *tp, int timo)
 {
        int error, s;
 
@@ -1219,7 +1219,8 @@ ttywait(struct tty *tp)
                    (ISSET(tp->t_state, TS_CARR_ON) || ISSET(tp->t_cflag, 
CLOCAL))
                    && tp->t_oproc) {
                        SET(tp->t_state, TS_ASLEEP);
-                       error = ttysleep(tp, &tp->t_outq, TTOPRI | PCATCH, 
ttyout);
+                       error = ttysleep_timo(tp, &tp->t_outq, TTOPRI | PCATCH,
+                           ttyout, timo);
                        if (error)
                                break;
                } else
@@ -1237,7 +1238,8 @@ ttywflush(struct tty *tp)
 {
        int error;
 
-       if ((error = ttywait(tp)) == 0)
+       error = ttywait(tp, 5 * hz);
+       if (error == 0 || error == EWOULDBLOCK)
                ttyflush(tp, FREAD);
        return (error);
 }
@@ -2281,11 +2283,18 @@ tputchar(int c, struct tty *tp)
 int
 ttysleep(struct tty *tp, void *chan, int pri, char *wmesg)
 {
+
+       return (ttysleep_timo(tp, chan, pri, wmesg, INFSLP));
+}
+
+int
+ttysleep_timo(struct tty *tp, void *chan, int pri, char *wmesg, int timo)
+{
        int error;
        short gen;
 
        gen = tp->t_gen;
-       if ((error = tsleep_nsec(chan, pri, wmesg, INFSLP)) != 0)
+       if ((error = tsleep_nsec(chan, pri, wmesg, timo)) != 0)
                return (error);
        return (tp->t_gen == gen ? 0 : ERESTART);
 }
Index: sys/tty.h
===================================================================
RCS file: /cvs/src/sys/sys/tty.h,v
retrieving revision 1.38
diff -u -p -r1.38 tty.h
--- sys/tty.h   19 Jul 2019 00:17:16 -0000      1.38
+++ sys/tty.h   1 May 2020 10:11:12 -0000
@@ -293,7 +293,9 @@ void         ttypend(struct tty *tp);
 void    ttyretype(struct tty *tp);
 void    ttyrub(int c, struct tty *tp);
 int     ttysleep(struct tty *tp, void *chan, int pri, char *wmesg);
-int     ttywait(struct tty *tp);
+int     ttysleep_timo(struct tty *tp, void *chan, int pri, char *wmesg,
+           int timo);
+int     ttywait(struct tty *tp, int timo);
 int     ttywflush(struct tty *tp);
 void    ttytstamp(struct tty *tp, int octs, int ncts, int odcd, int ndcd);
 

Reply via email to