Commit 638b9648 authored by Alan Cox's avatar Alan Cox Committed by Greg Kroah-Hartman

tty: Fix the ldisc hangup race

This was noticed by Matthias Urlichs and he proposed a fix. This patch
does the fixing a different way to avoid introducing several new race
conditions into the code.

The problem case is TTY_DRIVER_RESET_TERMIOS = 0. In that case while we
abort the ldisc change, the hangup processing has not cleaned up and restarted
the ldisc either.

We can't restart the ldisc stuff in the set_ldisc as we don't know what
the hangup did and may touch stuff we shouldn't as we are no longer
supposed to influence the tty at that point in case it has been re-opened
before we get rescheduled.

Instead do it the simple way. Always re-init the ldisc on the hangup, but
use TTY_DRIVER_RESET_TERMIOS to indicate that we should force N_TTY.
Signed-off-by: default avatarAlan Cox <alan@linux.intel.com>
Cc: stable <stable@kernel.org>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 97c22394
...@@ -706,12 +706,13 @@ static void tty_reset_termios(struct tty_struct *tty) ...@@ -706,12 +706,13 @@ static void tty_reset_termios(struct tty_struct *tty)
/** /**
* tty_ldisc_reinit - reinitialise the tty ldisc * tty_ldisc_reinit - reinitialise the tty ldisc
* @tty: tty to reinit * @tty: tty to reinit
* @ldisc: line discipline to reinitialize
* *
* Switch the tty back to N_TTY line discipline and leave the * Switch the tty to a line discipline and leave the ldisc
* ldisc state closed * state closed
*/ */
static void tty_ldisc_reinit(struct tty_struct *tty) static void tty_ldisc_reinit(struct tty_struct *tty, int ldisc)
{ {
struct tty_ldisc *ld; struct tty_ldisc *ld;
...@@ -721,10 +722,10 @@ static void tty_ldisc_reinit(struct tty_struct *tty) ...@@ -721,10 +722,10 @@ static void tty_ldisc_reinit(struct tty_struct *tty)
/* /*
* Switch the line discipline back * Switch the line discipline back
*/ */
ld = tty_ldisc_get(N_TTY); ld = tty_ldisc_get(ldisc);
BUG_ON(IS_ERR(ld)); BUG_ON(IS_ERR(ld));
tty_ldisc_assign(tty, ld); tty_ldisc_assign(tty, ld);
tty_set_termios_ldisc(tty, N_TTY); tty_set_termios_ldisc(tty, ldisc);
} }
/** /**
...@@ -745,6 +746,8 @@ static void tty_ldisc_reinit(struct tty_struct *tty) ...@@ -745,6 +746,8 @@ static void tty_ldisc_reinit(struct tty_struct *tty)
void tty_ldisc_hangup(struct tty_struct *tty) void tty_ldisc_hangup(struct tty_struct *tty)
{ {
struct tty_ldisc *ld; struct tty_ldisc *ld;
int reset = tty->driver->flags & TTY_DRIVER_RESET_TERMIOS;
int err = 0;
/* /*
* FIXME! What are the locking issues here? This may me overdoing * FIXME! What are the locking issues here? This may me overdoing
...@@ -772,25 +775,32 @@ void tty_ldisc_hangup(struct tty_struct *tty) ...@@ -772,25 +775,32 @@ void tty_ldisc_hangup(struct tty_struct *tty)
wake_up_interruptible_poll(&tty->read_wait, POLLIN); wake_up_interruptible_poll(&tty->read_wait, POLLIN);
/* /*
* Shutdown the current line discipline, and reset it to * Shutdown the current line discipline, and reset it to
* N_TTY. * N_TTY if need be.
*
* Avoid racing set_ldisc or tty_ldisc_release
*/ */
if (tty->driver->flags & TTY_DRIVER_RESET_TERMIOS) {
/* Avoid racing set_ldisc or tty_ldisc_release */
mutex_lock(&tty->ldisc_mutex); mutex_lock(&tty->ldisc_mutex);
tty_ldisc_halt(tty); tty_ldisc_halt(tty);
if (tty->ldisc) { /* Not yet closed */
/* Switch back to N_TTY */
tty_ldisc_reinit(tty);
/* At this point we have a closed ldisc and we want to /* At this point we have a closed ldisc and we want to
reopen it. We could defer this to the next open but reopen it. We could defer this to the next open but
it means auditing a lot of other paths so this is it means auditing a lot of other paths so this is
a FIXME */ a FIXME */
if (tty->ldisc) { /* Not yet closed */
if (reset == 0) {
tty_ldisc_reinit(tty, tty->termios->c_line);
err = tty_ldisc_open(tty, tty->ldisc);
}
/* If the re-open fails or we reset then go to N_TTY. The
N_TTY open cannot fail */
if (reset || err) {
tty_ldisc_reinit(tty, N_TTY);
WARN_ON(tty_ldisc_open(tty, tty->ldisc)); WARN_ON(tty_ldisc_open(tty, tty->ldisc));
}
tty_ldisc_enable(tty); tty_ldisc_enable(tty);
} }
mutex_unlock(&tty->ldisc_mutex); mutex_unlock(&tty->ldisc_mutex);
if (reset)
tty_reset_termios(tty); tty_reset_termios(tty);
}
} }
/** /**
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment