Skip to content

Commit

Permalink
tty: don't panic on OOM in tty_set_ldisc()
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Dmitry Vyukov authored and Greg Kroah-Hartman committed Mar 17, 2017
1 parent b767ad7 commit 5362544
Showing 1 changed file with 16 additions and 69 deletions.
85 changes: 16 additions & 69 deletions drivers/tty/tty_ldisc.c
Original file line number Diff line number Diff line change
Expand Up @@ -488,41 +488,6 @@ static void tty_ldisc_close(struct tty_struct *tty, struct tty_ldisc *ld)
tty_ldisc_debug(tty, "%p: closed\n", ld);
}

/**
* tty_ldisc_restore - helper for tty ldisc change
* @tty: tty to recover
* @old: previous ldisc
*
* Restore the previous line discipline or N_TTY when a line discipline
* change fails due to an open error
*/

static void tty_ldisc_restore(struct tty_struct *tty, struct tty_ldisc *old)
{
struct tty_ldisc *new_ldisc;
int r;

/* There is an outstanding reference here so this is safe */
old = tty_ldisc_get(tty, old->ops->num);
WARN_ON(IS_ERR(old));
tty->ldisc = old;
tty_set_termios_ldisc(tty, old->ops->num);
if (tty_ldisc_open(tty, old) < 0) {
tty_ldisc_put(old);
/* This driver is always present */
new_ldisc = tty_ldisc_get(tty, N_TTY);
if (IS_ERR(new_ldisc))
panic("n_tty: get");
tty->ldisc = new_ldisc;
tty_set_termios_ldisc(tty, N_TTY);
r = tty_ldisc_open(tty, new_ldisc);
if (r < 0)
panic("Couldn't open N_TTY ldisc for "
"%s --- error %d.",
tty_name(tty), r);
}
}

/**
* tty_set_ldisc - set line discipline
* @tty: the terminal to set
Expand All @@ -536,12 +501,7 @@ static void tty_ldisc_restore(struct tty_struct *tty, struct tty_ldisc *old)

int tty_set_ldisc(struct tty_struct *tty, int disc)
{
int retval;
struct tty_ldisc *old_ldisc, *new_ldisc;

new_ldisc = tty_ldisc_get(tty, disc);
if (IS_ERR(new_ldisc))
return PTR_ERR(new_ldisc);
int retval, old_disc;

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

/* Check the no-op case */
if (tty->ldisc->ops->num == disc)
old_disc = tty->ldisc->ops->num;
if (old_disc == disc)
goto out;

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

old_ldisc = tty->ldisc;

/* Shutdown the old discipline. */
tty_ldisc_close(tty, old_ldisc);

/* Now set up the new line discipline. */
tty->ldisc = new_ldisc;
tty_set_termios_ldisc(tty, disc);

retval = tty_ldisc_open(tty, new_ldisc);
retval = tty_ldisc_reinit(tty, disc);
if (retval < 0) {
/* Back to the old one or N_TTY if we can't */
tty_ldisc_put(new_ldisc);
tty_ldisc_restore(tty, old_ldisc);
if (tty_ldisc_reinit(tty, old_disc) < 0) {
pr_err("tty: TIOCSETD failed, reinitializing N_TTY\n");
if (tty_ldisc_reinit(tty, N_TTY) < 0) {
/* At this point we have tty->ldisc == NULL. */
pr_err("tty: reinitializing N_TTY failed\n");
}
}
}

if (tty->ldisc->ops->num != old_ldisc->ops->num && tty->ops->set_ldisc) {
if (tty->ldisc && tty->ldisc->ops->num != old_disc &&
tty->ops->set_ldisc) {
down_read(&tty->termios_rwsem);
tty->ops->set_ldisc(tty);
up_read(&tty->termios_rwsem);
}

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

/* Restart the work queue in case no characters kick it off. Safe if
already running */
tty_buffer_restart_work(tty->port);
err:
tty_ldisc_put(new_ldisc); /* drop the extra reference */
tty_unlock(tty);
return retval;
}
Expand Down Expand Up @@ -659,10 +610,8 @@ int tty_ldisc_reinit(struct tty_struct *tty, int disc)
int retval;

ld = tty_ldisc_get(tty, disc);
if (IS_ERR(ld)) {
BUG_ON(disc == N_TTY);
if (IS_ERR(ld))
return PTR_ERR(ld);
}

if (tty->ldisc) {
tty_ldisc_close(tty, tty->ldisc);
Expand All @@ -674,10 +623,8 @@ int tty_ldisc_reinit(struct tty_struct *tty, int disc)
tty_set_termios_ldisc(tty, disc);
retval = tty_ldisc_open(tty, tty->ldisc);
if (retval) {
if (!WARN_ON(disc == N_TTY)) {
tty_ldisc_put(tty->ldisc);
tty->ldisc = NULL;
}
tty_ldisc_put(tty->ldisc);
tty->ldisc = NULL;
}
return retval;
}
Expand Down

0 comments on commit 5362544

Please sign in to comment.