Skip to content

Commit 5362544

Browse files
dvyukovgregkh
authored andcommitted
tty: don't panic on OOM in tty_set_ldisc()
If tty_ldisc_open() fails in tty_set_ldisc(), it tries to go back to the old discipline or N_TTY. But that can fail as well, in such case it panics. This is not a graceful way to handle OOM. Leave ldisc==NULL if all attempts fail instead. Also use existing tty_ldisc_reinit() helper function instead of tty_ldisc_restore(). Also don't WARN/BUG in tty_ldisc_reinit() if N_TTY fails, which would have the same net effect of bringing kernel down on OOM. Instead print a single line message about what has happened. Signed-off-by: Dmitry Vyukov <dvyukov@google.com> Cc: syzkaller@googlegroups.com Cc: linux-kernel@vger.kernel.org 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> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1 parent b767ad7 commit 5362544

File tree

1 file changed

+16
-69
lines changed

1 file changed

+16
-69
lines changed

drivers/tty/tty_ldisc.c

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

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

537502
int tty_set_ldisc(struct tty_struct *tty, int disc)
538503
{
539-
int retval;
540-
struct tty_ldisc *old_ldisc, *new_ldisc;
541-
542-
new_ldisc = tty_ldisc_get(tty, disc);
543-
if (IS_ERR(new_ldisc))
544-
return PTR_ERR(new_ldisc);
504+
int retval, old_disc;
545505

546506
tty_lock(tty);
547507
retval = tty_ldisc_lock(tty, 5 * HZ);
@@ -554,7 +514,8 @@ int tty_set_ldisc(struct tty_struct *tty, int disc)
554514
}
555515

556516
/* Check the no-op case */
557-
if (tty->ldisc->ops->num == disc)
517+
old_disc = tty->ldisc->ops->num;
518+
if (old_disc == disc)
558519
goto out;
559520

560521
if (test_bit(TTY_HUPPED, &tty->flags)) {
@@ -563,42 +524,32 @@ int tty_set_ldisc(struct tty_struct *tty, int disc)
563524
goto out;
564525
}
565526

566-
old_ldisc = tty->ldisc;
567-
568-
/* Shutdown the old discipline. */
569-
tty_ldisc_close(tty, old_ldisc);
570-
571-
/* Now set up the new line discipline. */
572-
tty->ldisc = new_ldisc;
573-
tty_set_termios_ldisc(tty, disc);
574-
575-
retval = tty_ldisc_open(tty, new_ldisc);
527+
retval = tty_ldisc_reinit(tty, disc);
576528
if (retval < 0) {
577529
/* Back to the old one or N_TTY if we can't */
578-
tty_ldisc_put(new_ldisc);
579-
tty_ldisc_restore(tty, old_ldisc);
530+
if (tty_ldisc_reinit(tty, old_disc) < 0) {
531+
pr_err("tty: TIOCSETD failed, reinitializing N_TTY\n");
532+
if (tty_ldisc_reinit(tty, N_TTY) < 0) {
533+
/* At this point we have tty->ldisc == NULL. */
534+
pr_err("tty: reinitializing N_TTY failed\n");
535+
}
536+
}
580537
}
581538

582-
if (tty->ldisc->ops->num != old_ldisc->ops->num && tty->ops->set_ldisc) {
539+
if (tty->ldisc && tty->ldisc->ops->num != old_disc &&
540+
tty->ops->set_ldisc) {
583541
down_read(&tty->termios_rwsem);
584542
tty->ops->set_ldisc(tty);
585543
up_read(&tty->termios_rwsem);
586544
}
587545

588-
/* At this point we hold a reference to the new ldisc and a
589-
reference to the old ldisc, or we hold two references to
590-
the old ldisc (if it was restored as part of error cleanup
591-
above). In either case, releasing a single reference from
592-
the old ldisc is correct. */
593-
new_ldisc = old_ldisc;
594546
out:
595547
tty_ldisc_unlock(tty);
596548

597549
/* Restart the work queue in case no characters kick it off. Safe if
598550
already running */
599551
tty_buffer_restart_work(tty->port);
600552
err:
601-
tty_ldisc_put(new_ldisc); /* drop the extra reference */
602553
tty_unlock(tty);
603554
return retval;
604555
}
@@ -659,10 +610,8 @@ int tty_ldisc_reinit(struct tty_struct *tty, int disc)
659610
int retval;
660611

661612
ld = tty_ldisc_get(tty, disc);
662-
if (IS_ERR(ld)) {
663-
BUG_ON(disc == N_TTY);
613+
if (IS_ERR(ld))
664614
return PTR_ERR(ld);
665-
}
666615

667616
if (tty->ldisc) {
668617
tty_ldisc_close(tty, tty->ldisc);
@@ -674,10 +623,8 @@ int tty_ldisc_reinit(struct tty_struct *tty, int disc)
674623
tty_set_termios_ldisc(tty, disc);
675624
retval = tty_ldisc_open(tty, tty->ldisc);
676625
if (retval) {
677-
if (!WARN_ON(disc == N_TTY)) {
678-
tty_ldisc_put(tty->ldisc);
679-
tty->ldisc = NULL;
680-
}
626+
tty_ldisc_put(tty->ldisc);
627+
tty->ldisc = NULL;
681628
}
682629
return retval;
683630
}

0 commit comments

Comments
 (0)