jwboyer pushed to kernel (f20). "Add patch to fix tty closure race (rhbz 1208953)"
notifications at fedoraproject.org
notifications at fedoraproject.org
Wed Apr 15 17:18:36 UTC 2015
>From d53f072910d673420dbe70b4f0a09934d68e4cca Mon Sep 17 00:00:00 2001
From: Josh Boyer <jwboyer at fedoraproject.org>
Date: Wed, 15 Apr 2015 12:53:24 -0400
Subject: Add patch to fix tty closure race (rhbz 1208953)
diff --git a/kernel.spec b/kernel.spec
index 6db242c..9be5cb9 100644
--- a/kernel.spec
+++ b/kernel.spec
@@ -775,6 +775,9 @@ Patch26177: tg3-Hold-tp-lock-before-calling-tg3_halt-from-tg3_in.patch
#CVE-2015-XXXX rhbz 1203712 1208491
Patch26178: ipv6-Don-t-reduce-hop-limit-for-an-interface.patch
+#rhbz 1208953
+Patch26179: pty-Fix-input-race-when-closing.patch
+
# END OF PATCH DEFINITIONS
%endif
@@ -1519,6 +1522,9 @@ ApplyPatch tg3-Hold-tp-lock-before-calling-tg3_halt-from-tg3_in.patch
#CVE-2015-XXXX rhbz 1203712 1208491
ApplyPatch ipv6-Don-t-reduce-hop-limit-for-an-interface.patch
+#rhbz 1208953
+ApplyPatch pty-Fix-input-race-when-closing.patch
+
# END OF PATCH APPLICATIONS
%endif
@@ -2330,6 +2336,9 @@ fi
# ||----w |
# || ||
%changelog
+* Wed Apr 15 2015 Josh Boyer <jwboyer at fedoraproject.org>
+- Add patch to fix tty closure race (rhbz 1208953)
+
* Mon Apr 13 2015 Justin M. Forbes <jforbes at fedoraproject.org> - 3.19.4-100
- Linux v3.19.4
diff --git a/pty-Fix-input-race-when-closing.patch b/pty-Fix-input-race-when-closing.patch
new file mode 100644
index 0000000..5a82b9b
--- /dev/null
+++ b/pty-Fix-input-race-when-closing.patch
@@ -0,0 +1,287 @@
+From: Peter Hurley <peter at hurleysoftware.com>
+Date: Mon, 13 Apr 2015 13:24:34 -0400
+Subject: [PATCH] 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 at vger.kernel.org> # 3.19+
+Reported-by: Andy Whitcroft <apw at canonical.com>
+Reported-by: H.J. Lu <hjl.tools at gmail.com>
+Signed-off-by: Peter Hurley <peter at hurleysoftware.com>
+---
+ Documentation/serial/tty.txt | 3 +++
+ drivers/tty/n_hdlc.c | 4 ++--
+ drivers/tty/n_tty.c | 22 ++++++++++++++++++----
+ drivers/tty/pty.c | 5 +++--
+ drivers/tty/tty_buffer.c | 41 +++++++++++++++++++++++++++--------------
+ include/linux/tty.h | 2 +-
+ 6 files changed, 54 insertions(+), 23 deletions(-)
+
+diff --git a/Documentation/serial/tty.txt b/Documentation/serial/tty.txt
+index 1e52d67d0abf..dbe6623fed1c 100644
+--- a/Documentation/serial/tty.txt
++++ b/Documentation/serial/tty.txt
+@@ -198,6 +198,9 @@ TTY_IO_ERROR If set, causes all subsequent userspace read/write
+
+ TTY_OTHER_CLOSED Device is a pty and the other side has closed.
+
++TTY_OTHER_DONE Device is a pty and the other side has closed and
++ all pending input processing has been completed.
++
+ TTY_NO_WRITE_SPLIT Prevent driver from splitting up writes into
+ smaller chunks.
+
+diff --git a/drivers/tty/n_hdlc.c b/drivers/tty/n_hdlc.c
+index 644ddb841d9f..bbc4ce66c2c1 100644
+--- a/drivers/tty/n_hdlc.c
++++ b/drivers/tty/n_hdlc.c
+@@ -600,7 +600,7 @@ static ssize_t n_hdlc_tty_read(struct tty_struct *tty, struct file *file,
+ add_wait_queue(&tty->read_wait, &wait);
+
+ for (;;) {
+- if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) {
++ if (test_bit(TTY_OTHER_DONE, &tty->flags)) {
+ ret = -EIO;
+ break;
+ }
+@@ -828,7 +828,7 @@ static unsigned int n_hdlc_tty_poll(struct tty_struct *tty, struct file *filp,
+ /* set bits for operations that won't block */
+ if (n_hdlc->rx_buf_list.head)
+ mask |= POLLIN | POLLRDNORM; /* readable */
+- if (test_bit(TTY_OTHER_CLOSED, &tty->flags))
++ if (test_bit(TTY_OTHER_DONE, &tty->flags))
+ mask |= POLLHUP;
+ if (tty_hung_up_p(filp))
+ mask |= POLLHUP;
+diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c
+index 4ddfa60c9222..b52d61784e98 100644
+--- a/drivers/tty/n_tty.c
++++ b/drivers/tty/n_tty.c
+@@ -1908,6 +1908,18 @@ static inline int input_available_p(struct tty_struct *tty, int poll)
+ return read_cnt(ldata) >= amt;
+ }
+
++static inline int check_other_done(struct tty_struct *tty)
++{
++ int done = test_bit(TTY_OTHER_DONE, &tty->flags);
++ if (done) {
++ /* paired with cmpxchg() in check_other_closed(); ensures
++ * read buffer head index is not stale
++ */
++ smp_mb__after_atomic();
++ }
++ return done;
++}
++
+ /**
+ * copy_from_read_buf - copy read data directly
+ * @tty: terminal device
+@@ -2125,7 +2137,7 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,
+ struct n_tty_data *ldata = tty->disc_data;
+ unsigned char __user *b = buf;
+ DEFINE_WAIT_FUNC(wait, woken_wake_function);
+- int c;
++ int c, done;
+ int minimum, time;
+ ssize_t retval = 0;
+ long timeout;
+@@ -2191,8 +2203,10 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,
+ ((minimum - (b - buf)) >= 1))
+ ldata->minimum_to_wake = (minimum - (b - buf));
+
++ done = check_other_done(tty);
++
+ if (!input_available_p(tty, 0)) {
+- if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) {
++ if (done) {
+ retval = -EIO;
+ break;
+ }
+@@ -2399,12 +2413,12 @@ static unsigned int n_tty_poll(struct tty_struct *tty, struct file *file,
+
+ poll_wait(file, &tty->read_wait, wait);
+ poll_wait(file, &tty->write_wait, wait);
++ if (check_other_done(tty))
++ mask |= POLLHUP;
+ if (input_available_p(tty, 1))
+ mask |= POLLIN | POLLRDNORM;
+ if (tty->packet && tty->link->ctrl_status)
+ mask |= POLLPRI | POLLIN | POLLRDNORM;
+- if (test_bit(TTY_OTHER_CLOSED, &tty->flags))
+- mask |= POLLHUP;
+ if (tty_hung_up_p(file))
+ mask |= POLLHUP;
+ if (!(mask & (POLLHUP | POLLIN | POLLRDNORM))) {
+diff --git a/drivers/tty/pty.c b/drivers/tty/pty.c
+index 6e1f1505f04e..ca7b68968203 100644
+--- a/drivers/tty/pty.c
++++ b/drivers/tty/pty.c
+@@ -53,9 +53,8 @@ static void pty_close(struct tty_struct *tty, struct file *filp)
+ /* Review - krefs on tty_link ?? */
+ if (!tty->link)
+ return;
+- tty_flush_to_ldisc(tty->link);
+ set_bit(TTY_OTHER_CLOSED, &tty->link->flags);
+- wake_up_interruptible(&tty->link->read_wait);
++ tty_flip_buffer_push(tty->link->port);
+ wake_up_interruptible(&tty->link->write_wait);
+ if (tty->driver->subtype == PTY_TYPE_MASTER) {
+ set_bit(TTY_OTHER_CLOSED, &tty->flags);
+@@ -250,7 +249,9 @@ static int pty_open(struct tty_struct *tty, struct file *filp)
+ goto out;
+
+ clear_bit(TTY_IO_ERROR, &tty->flags);
++ /* TTY_OTHER_CLOSED must be cleared before TTY_OTHER_DONE */
+ clear_bit(TTY_OTHER_CLOSED, &tty->link->flags);
++ clear_bit(TTY_OTHER_DONE, &tty->link->flags);
+ set_bit(TTY_THROTTLED, &tty->flags);
+ return 0;
+
+diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c
+index 3605103fc1ac..79bcdf93568b 100644
+--- a/drivers/tty/tty_buffer.c
++++ b/drivers/tty/tty_buffer.c
+@@ -37,6 +37,28 @@
+
+ #define TTY_BUFFER_PAGE (((PAGE_SIZE - sizeof(struct tty_buffer)) / 2) & ~0xFF)
+
++/*
++ * If all tty flip buffers have been processed by flush_to_ldisc() or
++ * dropped by tty_buffer_flush(), check if the linked pty has been closed.
++ * If so, wake the reader/poll to process
++ */
++static inline void check_other_closed(struct tty_struct *tty)
++{
++ unsigned long flags, old;
++
++ /* transition from TTY_OTHER_CLOSED => TTY_OTHER_DONE must be atomic */
++ for (flags = ACCESS_ONCE(tty->flags);
++ test_bit(TTY_OTHER_CLOSED, &flags);
++ ) {
++ old = flags;
++ __set_bit(TTY_OTHER_DONE, &flags);
++ flags = cmpxchg(&tty->flags, old, flags);
++ if (old == flags) {
++ wake_up_interruptible(&tty->read_wait);
++ break;
++ }
++ }
++}
+
+ /**
+ * tty_buffer_lock_exclusive - gain exclusive access to buffer
+@@ -229,6 +251,8 @@ void tty_buffer_flush(struct tty_struct *tty, struct tty_ldisc *ld)
+ if (ld && ld->ops->flush_buffer)
+ ld->ops->flush_buffer(tty);
+
++ check_other_closed(tty);
++
+ atomic_dec(&buf->priority);
+ mutex_unlock(&buf->lock);
+ }
+@@ -471,8 +495,10 @@ static void flush_to_ldisc(struct work_struct *work)
+ smp_rmb();
+ count = head->commit - head->read;
+ if (!count) {
+- if (next == NULL)
++ if (next == NULL) {
++ check_other_closed(tty);
+ break;
++ }
+ buf->head = next;
+ tty_buffer_free(port, head);
+ continue;
+@@ -489,19 +515,6 @@ static void flush_to_ldisc(struct work_struct *work)
+ }
+
+ /**
+- * tty_flush_to_ldisc
+- * @tty: tty to push
+- *
+- * Push the terminal flip buffers to the line discipline.
+- *
+- * Must not be called from IRQ context.
+- */
+-void tty_flush_to_ldisc(struct tty_struct *tty)
+-{
+- flush_work(&tty->port->buf.work);
+-}
+-
+-/**
+ * tty_flip_buffer_push - terminal
+ * @port: tty port to push
+ *
+diff --git a/include/linux/tty.h b/include/linux/tty.h
+index 7d66ae508e5c..8716524f0b25 100644
+--- a/include/linux/tty.h
++++ b/include/linux/tty.h
+@@ -316,6 +316,7 @@ struct tty_file_private {
+ #define TTY_EXCLUSIVE 3 /* Exclusive open mode */
+ #define TTY_DEBUG 4 /* Debugging */
+ #define TTY_DO_WRITE_WAKEUP 5 /* Call write_wakeup after queuing new */
++#define TTY_OTHER_DONE 6 /* Closed pty has completed input processing */
+ #define TTY_LDISC_OPEN 11 /* Line discipline is open */
+ #define TTY_PTY_LOCK 16 /* pty private */
+ #define TTY_NO_WRITE_SPLIT 17 /* Preserve write boundaries to driver */
+@@ -439,7 +440,6 @@ extern int tty_hung_up_p(struct file *filp);
+ extern void do_SAK(struct tty_struct *tty);
+ extern void __do_SAK(struct tty_struct *tty);
+ extern void no_tty(void);
+-extern void tty_flush_to_ldisc(struct tty_struct *tty);
+ extern void tty_buffer_free_all(struct tty_port *port);
+ extern void tty_buffer_flush(struct tty_struct *tty, struct tty_ldisc *ld);
+ extern void tty_buffer_init(struct tty_port *port);
+--
+2.1.0
+
--
cgit v0.10.2
http://pkgs.fedoraproject.org/cgit/kernel.git/commit/?h=f20&id=d53f072910d673420dbe70b4f0a09934d68e4cca
More information about the scm-commits
mailing list