Skip to content

Commit 892d1fa

Browse files
peterhurleygregkh
authored andcommitted
tty: Destroy ldisc instance on hangup
Currently, when the tty is hungup, the ldisc is re-instanced; ie., the current instance is destroyed and a new instance is created. The purpose of this design was to guarantee a valid, open ldisc for the lifetime of the tty. However, now that tty buffers are owned by and have lifetime equivalent to the tty_port (since v3.10), any data received immediately after the ldisc is re-instanced may cause continued driver i/o operations concurrently with the driver's hangup() operation. For drivers that shutdown h/w on hangup, this is unexpected and usually bad. For example, the serial core may free the xmit buffer page concurrently with an in-progress write() operation (triggered by echo). With the existing stable and robust ldisc reference handling, the cleaned-up tty_reopen(), the straggling unsafe ldisc use cleaned up, and the preparation to properly handle a NULL tty->ldisc, the ldisc instance can be destroyed and only re-instanced when the tty is re-opened. If the tty was opened as /dev/console or /dev/tty0, the original behavior of re-instancing the ldisc is retained (the 'reinit' parameter to tty_ldisc_hangup() is true). This is required since those file descriptors are never hungup. This patch has neglible impact on userspace; the tty file_operations ptr is changed to point to the hungup file operations _before_ the ldisc instance is destroyed, so only racing file operations might now retrieve a NULL ldisc reference (which is simply handled as if the hungup file operation had been called instead -- see "tty: Prepare for destroying line discipline on hangup"). This resolves a long-standing FIXME and several crash reports. Signed-off-by: Peter Hurley <peter@hurleysoftware.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1 parent 7896f30 commit 892d1fa

File tree

3 files changed

+25
-30
lines changed

3 files changed

+25
-30
lines changed

drivers/tty/tty_io.c

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -727,7 +727,7 @@ static void __tty_hangup(struct tty_struct *tty, int exit_session)
727727
while (refs--)
728728
tty_kref_put(tty);
729729

730-
tty_ldisc_hangup(tty);
730+
tty_ldisc_hangup(tty, cons_filp != NULL);
731731

732732
spin_lock_irq(&tty->ctrl_lock);
733733
clear_bit(TTY_THROTTLED, &tty->flags);
@@ -752,10 +752,9 @@ static void __tty_hangup(struct tty_struct *tty, int exit_session)
752752
} else if (tty->ops->hangup)
753753
tty->ops->hangup(tty);
754754
/*
755-
* We don't want to have driver/ldisc interactions beyond
756-
* the ones we did here. The driver layer expects no
757-
* calls after ->hangup() from the ldisc side. However we
758-
* can't yet guarantee all that.
755+
* We don't want to have driver/ldisc interactions beyond the ones
756+
* we did here. The driver layer expects no calls after ->hangup()
757+
* from the ldisc side, which is now guaranteed.
759758
*/
760759
set_bit(TTY_HUPPED, &tty->flags);
761760
tty_unlock(tty);
@@ -1475,7 +1474,8 @@ static int tty_reopen(struct tty_struct *tty)
14751474

14761475
tty->count++;
14771476

1478-
WARN_ON(!tty->ldisc);
1477+
if (!tty->ldisc)
1478+
return tty_ldisc_reinit(tty, tty->termios.c_line);
14791479

14801480
return 0;
14811481
}

drivers/tty/tty_ldisc.c

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,9 @@ const struct file_operations tty_ldiscs_proc_fops = {
257257
* reference to it. If the line discipline is in flux then
258258
* wait patiently until it changes.
259259
*
260+
* Returns: NULL if the tty has been hungup and not re-opened with
261+
* a new file descriptor, otherwise valid ldisc reference
262+
*
260263
* Note: Must not be called from an IRQ/timer context. The caller
261264
* must also be careful not to hold other locks that will deadlock
262265
* against a discipline change, such as an existing ldisc reference
@@ -642,14 +645,15 @@ static void tty_reset_termios(struct tty_struct *tty)
642645
* @disc: line discipline to reinitialize
643646
*
644647
* Completely reinitialize the line discipline state, by closing the
645-
* current instance and opening a new instance. If an error occurs opening
646-
* the new non-N_TTY instance, the instance is dropped and tty->ldisc reset
647-
* to NULL. The caller can then retry with N_TTY instead.
648+
* current instance, if there is one, and opening a new instance. If
649+
* an error occurs opening the new non-N_TTY instance, the instance
650+
* is dropped and tty->ldisc reset to NULL. The caller can then retry
651+
* with N_TTY instead.
648652
*
649653
* Returns 0 if successful, otherwise error code < 0
650654
*/
651655

652-
static int tty_ldisc_reinit(struct tty_struct *tty, int disc)
656+
int tty_ldisc_reinit(struct tty_struct *tty, int disc)
653657
{
654658
struct tty_ldisc *ld;
655659
int retval;
@@ -693,11 +697,9 @@ static int tty_ldisc_reinit(struct tty_struct *tty, int disc)
693697
* tty itself so we must be careful about locking rules.
694698
*/
695699

696-
void tty_ldisc_hangup(struct tty_struct *tty)
700+
void tty_ldisc_hangup(struct tty_struct *tty, bool reinit)
697701
{
698702
struct tty_ldisc *ld;
699-
int reset = tty->driver->flags & TTY_DRIVER_RESET_TERMIOS;
700-
int err = 0;
701703

702704
tty_ldisc_debug(tty, "%p: hangup\n", tty->ldisc);
703705

@@ -725,25 +727,17 @@ void tty_ldisc_hangup(struct tty_struct *tty)
725727
*/
726728
tty_ldisc_lock(tty, MAX_SCHEDULE_TIMEOUT);
727729

728-
if (tty->ldisc) {
730+
if (tty->driver->flags & TTY_DRIVER_RESET_TERMIOS)
731+
tty_reset_termios(tty);
729732

730-
/* At this point we have a halted ldisc; we want to close it and
731-
reopen a new ldisc. We could defer the reopen to the next
732-
open but it means auditing a lot of other paths so this is
733-
a FIXME */
734-
if (reset == 0)
735-
err = tty_ldisc_reinit(tty, tty->termios.c_line);
736-
737-
/* If the re-open fails or we reset then go to N_TTY. The
738-
N_TTY open cannot fail */
739-
if (reset || err < 0)
740-
tty_ldisc_reinit(tty, N_TTY);
733+
if (tty->ldisc) {
734+
if (reinit) {
735+
if (tty_ldisc_reinit(tty, tty->termios.c_line) < 0)
736+
tty_ldisc_reinit(tty, N_TTY);
737+
} else
738+
tty_ldisc_kill(tty);
741739
}
742740
tty_ldisc_unlock(tty);
743-
if (reset)
744-
tty_reset_termios(tty);
745-
746-
tty_ldisc_debug(tty, "%p: re-opened\n", tty->ldisc);
747741
}
748742

749743
/**

include/linux/tty.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,8 @@ extern int tty_set_termios(struct tty_struct *tty, struct ktermios *kt);
490490
extern struct tty_ldisc *tty_ldisc_ref(struct tty_struct *);
491491
extern void tty_ldisc_deref(struct tty_ldisc *);
492492
extern struct tty_ldisc *tty_ldisc_ref_wait(struct tty_struct *);
493-
extern void tty_ldisc_hangup(struct tty_struct *tty);
493+
extern void tty_ldisc_hangup(struct tty_struct *tty, bool reset);
494+
extern int tty_ldisc_reinit(struct tty_struct *tty, int disc);
494495
extern const struct file_operations tty_ldiscs_proc_fops;
495496

496497
extern void tty_wakeup(struct tty_struct *tty);

0 commit comments

Comments
 (0)