diff options
author | Peter Hurley <peter@hurleysoftware.com> | 2015-04-13 13:24:34 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2015-05-10 13:26:37 -0400 |
commit | 1a48632ffed61352a7810ce089dc5a8bcd505a60 (patch) | |
tree | 7b0e4c2879f2becc07c09d289f4f65b32114854a /include/linux/tty.h | |
parent | 8f9cfeed3eae86c70d3b04445a6f2036b27b6304 (diff) |
pty: Fix input race when closing
A read() from a pty master may mistakenly indicate EOF (errno == -EIO)
after the pty slave has closed, even though input data remains to be read.
For example,
pty slave | input worker | pty master
| |
| | n_tty_read()
pty_write() | | input avail? no
add data | | sleep
schedule worker --->| | .
|---> flush_to_ldisc() | .
pty_close() | fill read buffer | .
wait for worker | wakeup reader --->| .
| read buffer full? |---> input avail ? yes
|<--- yes - exit worker | copy 4096 bytes to user
TTY_OTHER_CLOSED <---| |<--- kick worker
| |
**** New read() before worker starts ****
| | n_tty_read()
| | input avail? no
| | TTY_OTHER_CLOSED? yes
| | return -EIO
Several conditions are required to trigger this race:
1. the ldisc read buffer must become full so the input worker exits
2. the read() count parameter must be >= 4096 so the ldisc read buffer
is empty
3. the subsequent read() occurs before the kicked worker has processed
more input
However, the underlying cause of the race is that data is pipelined, while
tty state is not; ie., data already written by the pty slave end is not
yet visible to the pty master end, but state changes by the pty slave end
are visible to the pty master end immediately.
Pipeline the TTY_OTHER_CLOSED state through input worker to the reader.
1. Introduce TTY_OTHER_DONE which is set by the input worker when
TTY_OTHER_CLOSED is set and either the input buffers are flushed or
input processing has completed. Readers/polls are woken when
TTY_OTHER_DONE is set.
2. Reader/poll checks TTY_OTHER_DONE instead of TTY_OTHER_CLOSED.
3. A new input worker is started from pty_close() after setting
TTY_OTHER_CLOSED, which ensures the TTY_OTHER_DONE state will be
set if the last input worker is already finished (or just about to
exit).
Remove tty_flush_to_ldisc(); no in-tree callers.
Fixes: 52bce7f8d4fc ("pty, n_tty: Simplify input processing on final close")
Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=96311
BugLink: http://bugs.launchpad.net/bugs/1429756
Cc: <stable@vger.kernel.org> # 3.19+
Reported-by: Andy Whitcroft <apw@canonical.com>
Reported-by: H.J. Lu <hjl.tools@gmail.com>
Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'include/linux/tty.h')
-rw-r--r-- | include/linux/tty.h | 2 |
1 files changed, 1 insertions, 1 deletions
diff --git a/include/linux/tty.h b/include/linux/tty.h index fe5623c9af71..d76631f615c2 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h | |||
@@ -339,6 +339,7 @@ struct tty_file_private { | |||
339 | #define TTY_EXCLUSIVE 3 /* Exclusive open mode */ | 339 | #define TTY_EXCLUSIVE 3 /* Exclusive open mode */ |
340 | #define TTY_DEBUG 4 /* Debugging */ | 340 | #define TTY_DEBUG 4 /* Debugging */ |
341 | #define TTY_DO_WRITE_WAKEUP 5 /* Call write_wakeup after queuing new */ | 341 | #define TTY_DO_WRITE_WAKEUP 5 /* Call write_wakeup after queuing new */ |
342 | #define TTY_OTHER_DONE 6 /* Closed pty has completed input processing */ | ||
342 | #define TTY_LDISC_OPEN 11 /* Line discipline is open */ | 343 | #define TTY_LDISC_OPEN 11 /* Line discipline is open */ |
343 | #define TTY_PTY_LOCK 16 /* pty private */ | 344 | #define TTY_PTY_LOCK 16 /* pty private */ |
344 | #define TTY_NO_WRITE_SPLIT 17 /* Preserve write boundaries to driver */ | 345 | #define TTY_NO_WRITE_SPLIT 17 /* Preserve write boundaries to driver */ |
@@ -462,7 +463,6 @@ extern int tty_hung_up_p(struct file *filp); | |||
462 | extern void do_SAK(struct tty_struct *tty); | 463 | extern void do_SAK(struct tty_struct *tty); |
463 | extern void __do_SAK(struct tty_struct *tty); | 464 | extern void __do_SAK(struct tty_struct *tty); |
464 | extern void no_tty(void); | 465 | extern void no_tty(void); |
465 | extern void tty_flush_to_ldisc(struct tty_struct *tty); | ||
466 | extern void tty_buffer_free_all(struct tty_port *port); | 466 | extern void tty_buffer_free_all(struct tty_port *port); |
467 | extern void tty_buffer_flush(struct tty_struct *tty, struct tty_ldisc *ld); | 467 | extern void tty_buffer_flush(struct tty_struct *tty, struct tty_ldisc *ld); |
468 | extern void tty_buffer_init(struct tty_port *port); | 468 | extern void tty_buffer_init(struct tty_port *port); |