Skip to content

Commit a8983d0

Browse files
committed
Revert "tty: don't panic on OOM in tty_set_ldisc()"
This reverts commit 5362544 as it is reported to cause a reproducable crash. Fixes: 5362544 ("tty: don't panic on OOM in tty_set_ldisc()") Reported-by: Vegard Nossum <vegard.nossum@gmail.com> Cc: Dmitry Vyukov <dvyukov@google.com> Cc: <syzkaller@googlegroups.com> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Cc: Jiri Slaby <jslaby@suse.com> Cc: Peter Hurley <peter@hurleysoftware.com> Cc: One Thousand Gnomes <gnomes@lxorguk.ukuu.org.uk> Cc: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Greg Kroah-Hartman gregkh@linuxfoundation.org
1 parent a71c9a1 commit a8983d0

File tree

1 file changed

+69
-16
lines changed

1 file changed

+69
-16
lines changed

drivers/tty/tty_ldisc.c

Lines changed: 69 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,41 @@ static void tty_ldisc_close(struct tty_struct *tty, struct tty_ldisc *ld)
491491
tty_ldisc_debug(tty, "%p: closed\n", ld);
492492
}
493493

494+
/**
495+
* tty_ldisc_restore - helper for tty ldisc change
496+
* @tty: tty to recover
497+
* @old: previous ldisc
498+
*
499+
* Restore the previous line discipline or N_TTY when a line discipline
500+
* change fails due to an open error
501+
*/
502+
503+
static void tty_ldisc_restore(struct tty_struct *tty, struct tty_ldisc *old)
504+
{
505+
struct tty_ldisc *new_ldisc;
506+
int r;
507+
508+
/* There is an outstanding reference here so this is safe */
509+
old = tty_ldisc_get(tty, old->ops->num);
510+
WARN_ON(IS_ERR(old));
511+
tty->ldisc = old;
512+
tty_set_termios_ldisc(tty, old->ops->num);
513+
if (tty_ldisc_open(tty, old) < 0) {
514+
tty_ldisc_put(old);
515+
/* This driver is always present */
516+
new_ldisc = tty_ldisc_get(tty, N_TTY);
517+
if (IS_ERR(new_ldisc))
518+
panic("n_tty: get");
519+
tty->ldisc = new_ldisc;
520+
tty_set_termios_ldisc(tty, N_TTY);
521+
r = tty_ldisc_open(tty, new_ldisc);
522+
if (r < 0)
523+
panic("Couldn't open N_TTY ldisc for "
524+
"%s --- error %d.",
525+
tty_name(tty), r);
526+
}
527+
}
528+
494529
/**
495530
* tty_set_ldisc - set line discipline
496531
* @tty: the terminal to set
@@ -504,7 +539,12 @@ static void tty_ldisc_close(struct tty_struct *tty, struct tty_ldisc *ld)
504539

505540
int tty_set_ldisc(struct tty_struct *tty, int disc)
506541
{
507-
int retval, old_disc;
542+
int retval;
543+
struct tty_ldisc *old_ldisc, *new_ldisc;
544+
545+
new_ldisc = tty_ldisc_get(tty, disc);
546+
if (IS_ERR(new_ldisc))
547+
return PTR_ERR(new_ldisc);
508548

509549
tty_lock(tty);
510550
retval = tty_ldisc_lock(tty, 5 * HZ);
@@ -517,8 +557,7 @@ int tty_set_ldisc(struct tty_struct *tty, int disc)
517557
}
518558

519559
/* Check the no-op case */
520-
old_disc = tty->ldisc->ops->num;
521-
if (old_disc == disc)
560+
if (tty->ldisc->ops->num == disc)
522561
goto out;
523562

524563
if (test_bit(TTY_HUPPED, &tty->flags)) {
@@ -527,32 +566,42 @@ int tty_set_ldisc(struct tty_struct *tty, int disc)
527566
goto out;
528567
}
529568

530-
retval = tty_ldisc_reinit(tty, disc);
569+
old_ldisc = tty->ldisc;
570+
571+
/* Shutdown the old discipline. */
572+
tty_ldisc_close(tty, old_ldisc);
573+
574+
/* Now set up the new line discipline. */
575+
tty->ldisc = new_ldisc;
576+
tty_set_termios_ldisc(tty, disc);
577+
578+
retval = tty_ldisc_open(tty, new_ldisc);
531579
if (retval < 0) {
532580
/* Back to the old one or N_TTY if we can't */
533-
if (tty_ldisc_reinit(tty, old_disc) < 0) {
534-
pr_err("tty: TIOCSETD failed, reinitializing N_TTY\n");
535-
if (tty_ldisc_reinit(tty, N_TTY) < 0) {
536-
/* At this point we have tty->ldisc == NULL. */
537-
pr_err("tty: reinitializing N_TTY failed\n");
538-
}
539-
}
581+
tty_ldisc_put(new_ldisc);
582+
tty_ldisc_restore(tty, old_ldisc);
540583
}
541584

542-
if (tty->ldisc && tty->ldisc->ops->num != old_disc &&
543-
tty->ops->set_ldisc) {
585+
if (tty->ldisc->ops->num != old_ldisc->ops->num && tty->ops->set_ldisc) {
544586
down_read(&tty->termios_rwsem);
545587
tty->ops->set_ldisc(tty);
546588
up_read(&tty->termios_rwsem);
547589
}
548590

591+
/* At this point we hold a reference to the new ldisc and a
592+
reference to the old ldisc, or we hold two references to
593+
the old ldisc (if it was restored as part of error cleanup
594+
above). In either case, releasing a single reference from
595+
the old ldisc is correct. */
596+
new_ldisc = old_ldisc;
549597
out:
550598
tty_ldisc_unlock(tty);
551599

552600
/* Restart the work queue in case no characters kick it off. Safe if
553601
already running */
554602
tty_buffer_restart_work(tty->port);
555603
err:
604+
tty_ldisc_put(new_ldisc); /* drop the extra reference */
556605
tty_unlock(tty);
557606
return retval;
558607
}
@@ -613,8 +662,10 @@ int tty_ldisc_reinit(struct tty_struct *tty, int disc)
613662
int retval;
614663

615664
ld = tty_ldisc_get(tty, disc);
616-
if (IS_ERR(ld))
665+
if (IS_ERR(ld)) {
666+
BUG_ON(disc == N_TTY);
617667
return PTR_ERR(ld);
668+
}
618669

619670
if (tty->ldisc) {
620671
tty_ldisc_close(tty, tty->ldisc);
@@ -626,8 +677,10 @@ int tty_ldisc_reinit(struct tty_struct *tty, int disc)
626677
tty_set_termios_ldisc(tty, disc);
627678
retval = tty_ldisc_open(tty, tty->ldisc);
628679
if (retval) {
629-
tty_ldisc_put(tty->ldisc);
630-
tty->ldisc = NULL;
680+
if (!WARN_ON(disc == N_TTY)) {
681+
tty_ldisc_put(tty->ldisc);
682+
tty->ldisc = NULL;
683+
}
631684
}
632685
return retval;
633686
}

0 commit comments

Comments
 (0)