diff options
author | Brian Bloniarz <brian.bloniarz@gmail.com> | 2016-03-06 16:16:30 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2016-05-01 16:22:54 -0400 |
commit | 0f40fbbcc34e093255a2b2d70b6b0fb48c3f39aa (patch) | |
tree | edfa996be5ce50cd79dcfdfa96bb9d8490418660 /drivers/tty | |
parent | d11df61853f1d45035b7f5a6702c9b48d5f09a49 (diff) |
Fix OpenSSH pty regression on close
OpenSSH expects the (non-blocking) read() of pty master to return
EAGAIN only if it has received all of the slave-side output after
it has received SIGCHLD. This used to work on pre-3.12 kernels.
This fix effectively forces non-blocking read() and poll() to
block for parallel i/o to complete for all ttys. It also unwinds
these changes:
1) f8747d4a466ab2cafe56112c51b3379f9fdb7a12
tty: Fix pty master read() after slave closes
2) 52bce7f8d4fc633c9a9d0646eef58ba6ae9a3b73
pty, n_tty: Simplify input processing on final close
3) 1a48632ffed61352a7810ce089dc5a8bcd505a60
pty: Fix input race when closing
Inspired by analysis and patch from Marc Aurele La France <tsi@tuyoix.net>
Reported-by: Volth <openssh@volth.com>
Reported-by: Marc Aurele La France <tsi@tuyoix.net>
BugLink: https://bugzilla.mindrot.org/show_bug.cgi?id=52
BugLink: https://bugzilla.mindrot.org/show_bug.cgi?id=2492
Signed-off-by: Brian Bloniarz <brian.bloniarz@gmail.com>
Reviewed-by: Peter Hurley <peter@hurleysoftware.com>
Cc: stable <stable@vger.kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/tty')
-rw-r--r-- | drivers/tty/n_hdlc.c | 4 | ||||
-rw-r--r-- | drivers/tty/n_tty.c | 70 | ||||
-rw-r--r-- | drivers/tty/pty.c | 4 | ||||
-rw-r--r-- | drivers/tty/tty_buffer.c | 34 |
4 files changed, 42 insertions, 70 deletions
diff --git a/drivers/tty/n_hdlc.c b/drivers/tty/n_hdlc.c index bcaba17688f6..a7fa016f31eb 100644 --- a/drivers/tty/n_hdlc.c +++ b/drivers/tty/n_hdlc.c | |||
@@ -599,7 +599,7 @@ static ssize_t n_hdlc_tty_read(struct tty_struct *tty, struct file *file, | |||
599 | add_wait_queue(&tty->read_wait, &wait); | 599 | add_wait_queue(&tty->read_wait, &wait); |
600 | 600 | ||
601 | for (;;) { | 601 | for (;;) { |
602 | if (test_bit(TTY_OTHER_DONE, &tty->flags)) { | 602 | if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) { |
603 | ret = -EIO; | 603 | ret = -EIO; |
604 | break; | 604 | break; |
605 | } | 605 | } |
@@ -827,7 +827,7 @@ static unsigned int n_hdlc_tty_poll(struct tty_struct *tty, struct file *filp, | |||
827 | /* set bits for operations that won't block */ | 827 | /* set bits for operations that won't block */ |
828 | if (n_hdlc->rx_buf_list.head) | 828 | if (n_hdlc->rx_buf_list.head) |
829 | mask |= POLLIN | POLLRDNORM; /* readable */ | 829 | mask |= POLLIN | POLLRDNORM; /* readable */ |
830 | if (test_bit(TTY_OTHER_DONE, &tty->flags)) | 830 | if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) |
831 | mask |= POLLHUP; | 831 | mask |= POLLHUP; |
832 | if (tty_hung_up_p(filp)) | 832 | if (tty_hung_up_p(filp)) |
833 | mask |= POLLHUP; | 833 | mask |= POLLHUP; |
diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c index fb76a7d80e7e..bdf0e6e89991 100644 --- a/drivers/tty/n_tty.c +++ b/drivers/tty/n_tty.c | |||
@@ -1917,18 +1917,6 @@ static inline int input_available_p(struct tty_struct *tty, int poll) | |||
1917 | return ldata->commit_head - ldata->read_tail >= amt; | 1917 | return ldata->commit_head - ldata->read_tail >= amt; |
1918 | } | 1918 | } |
1919 | 1919 | ||
1920 | static inline int check_other_done(struct tty_struct *tty) | ||
1921 | { | ||
1922 | int done = test_bit(TTY_OTHER_DONE, &tty->flags); | ||
1923 | if (done) { | ||
1924 | /* paired with cmpxchg() in check_other_closed(); ensures | ||
1925 | * read buffer head index is not stale | ||
1926 | */ | ||
1927 | smp_mb__after_atomic(); | ||
1928 | } | ||
1929 | return done; | ||
1930 | } | ||
1931 | |||
1932 | /** | 1920 | /** |
1933 | * copy_from_read_buf - copy read data directly | 1921 | * copy_from_read_buf - copy read data directly |
1934 | * @tty: terminal device | 1922 | * @tty: terminal device |
@@ -2124,7 +2112,7 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file, | |||
2124 | struct n_tty_data *ldata = tty->disc_data; | 2112 | struct n_tty_data *ldata = tty->disc_data; |
2125 | unsigned char __user *b = buf; | 2113 | unsigned char __user *b = buf; |
2126 | DEFINE_WAIT_FUNC(wait, woken_wake_function); | 2114 | DEFINE_WAIT_FUNC(wait, woken_wake_function); |
2127 | int c, done; | 2115 | int c; |
2128 | int minimum, time; | 2116 | int minimum, time; |
2129 | ssize_t retval = 0; | 2117 | ssize_t retval = 0; |
2130 | long timeout; | 2118 | long timeout; |
@@ -2183,32 +2171,35 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file, | |||
2183 | break; | 2171 | break; |
2184 | } | 2172 | } |
2185 | 2173 | ||
2186 | done = check_other_done(tty); | ||
2187 | |||
2188 | if (!input_available_p(tty, 0)) { | 2174 | if (!input_available_p(tty, 0)) { |
2189 | if (done) { | ||
2190 | retval = -EIO; | ||
2191 | break; | ||
2192 | } | ||
2193 | if (tty_hung_up_p(file)) | ||
2194 | break; | ||
2195 | if (!timeout) | ||
2196 | break; | ||
2197 | if (file->f_flags & O_NONBLOCK) { | ||
2198 | retval = -EAGAIN; | ||
2199 | break; | ||
2200 | } | ||
2201 | if (signal_pending(current)) { | ||
2202 | retval = -ERESTARTSYS; | ||
2203 | break; | ||
2204 | } | ||
2205 | up_read(&tty->termios_rwsem); | 2175 | up_read(&tty->termios_rwsem); |
2176 | tty_buffer_flush_work(tty->port); | ||
2177 | down_read(&tty->termios_rwsem); | ||
2178 | if (!input_available_p(tty, 0)) { | ||
2179 | if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) { | ||
2180 | retval = -EIO; | ||
2181 | break; | ||
2182 | } | ||
2183 | if (tty_hung_up_p(file)) | ||
2184 | break; | ||
2185 | if (!timeout) | ||
2186 | break; | ||
2187 | if (file->f_flags & O_NONBLOCK) { | ||
2188 | retval = -EAGAIN; | ||
2189 | break; | ||
2190 | } | ||
2191 | if (signal_pending(current)) { | ||
2192 | retval = -ERESTARTSYS; | ||
2193 | break; | ||
2194 | } | ||
2195 | up_read(&tty->termios_rwsem); | ||
2206 | 2196 | ||
2207 | timeout = wait_woken(&wait, TASK_INTERRUPTIBLE, | 2197 | timeout = wait_woken(&wait, TASK_INTERRUPTIBLE, |
2208 | timeout); | 2198 | timeout); |
2209 | 2199 | ||
2210 | down_read(&tty->termios_rwsem); | 2200 | down_read(&tty->termios_rwsem); |
2211 | continue; | 2201 | continue; |
2202 | } | ||
2212 | } | 2203 | } |
2213 | 2204 | ||
2214 | if (ldata->icanon && !L_EXTPROC(tty)) { | 2205 | if (ldata->icanon && !L_EXTPROC(tty)) { |
@@ -2386,12 +2377,17 @@ static unsigned int n_tty_poll(struct tty_struct *tty, struct file *file, | |||
2386 | 2377 | ||
2387 | poll_wait(file, &tty->read_wait, wait); | 2378 | poll_wait(file, &tty->read_wait, wait); |
2388 | poll_wait(file, &tty->write_wait, wait); | 2379 | poll_wait(file, &tty->write_wait, wait); |
2389 | if (check_other_done(tty)) | ||
2390 | mask |= POLLHUP; | ||
2391 | if (input_available_p(tty, 1)) | 2380 | if (input_available_p(tty, 1)) |
2392 | mask |= POLLIN | POLLRDNORM; | 2381 | mask |= POLLIN | POLLRDNORM; |
2382 | else { | ||
2383 | tty_buffer_flush_work(tty->port); | ||
2384 | if (input_available_p(tty, 1)) | ||
2385 | mask |= POLLIN | POLLRDNORM; | ||
2386 | } | ||
2393 | if (tty->packet && tty->link->ctrl_status) | 2387 | if (tty->packet && tty->link->ctrl_status) |
2394 | mask |= POLLPRI | POLLIN | POLLRDNORM; | 2388 | mask |= POLLPRI | POLLIN | POLLRDNORM; |
2389 | if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) | ||
2390 | mask |= POLLHUP; | ||
2395 | if (tty_hung_up_p(file)) | 2391 | if (tty_hung_up_p(file)) |
2396 | mask |= POLLHUP; | 2392 | mask |= POLLHUP; |
2397 | if (tty->ops->write && !tty_is_writelocked(tty) && | 2393 | if (tty->ops->write && !tty_is_writelocked(tty) && |
diff --git a/drivers/tty/pty.c b/drivers/tty/pty.c index a8a292fd564f..ee0e84798399 100644 --- a/drivers/tty/pty.c +++ b/drivers/tty/pty.c | |||
@@ -59,7 +59,7 @@ static void pty_close(struct tty_struct *tty, struct file *filp) | |||
59 | if (!tty->link) | 59 | if (!tty->link) |
60 | return; | 60 | return; |
61 | set_bit(TTY_OTHER_CLOSED, &tty->link->flags); | 61 | set_bit(TTY_OTHER_CLOSED, &tty->link->flags); |
62 | tty_flip_buffer_push(tty->link->port); | 62 | wake_up_interruptible(&tty->link->read_wait); |
63 | wake_up_interruptible(&tty->link->write_wait); | 63 | wake_up_interruptible(&tty->link->write_wait); |
64 | if (tty->driver->subtype == PTY_TYPE_MASTER) { | 64 | if (tty->driver->subtype == PTY_TYPE_MASTER) { |
65 | set_bit(TTY_OTHER_CLOSED, &tty->flags); | 65 | set_bit(TTY_OTHER_CLOSED, &tty->flags); |
@@ -247,9 +247,7 @@ static int pty_open(struct tty_struct *tty, struct file *filp) | |||
247 | goto out; | 247 | goto out; |
248 | 248 | ||
249 | clear_bit(TTY_IO_ERROR, &tty->flags); | 249 | clear_bit(TTY_IO_ERROR, &tty->flags); |
250 | /* TTY_OTHER_CLOSED must be cleared before TTY_OTHER_DONE */ | ||
251 | clear_bit(TTY_OTHER_CLOSED, &tty->link->flags); | 250 | clear_bit(TTY_OTHER_CLOSED, &tty->link->flags); |
252 | clear_bit(TTY_OTHER_DONE, &tty->link->flags); | ||
253 | set_bit(TTY_THROTTLED, &tty->flags); | 251 | set_bit(TTY_THROTTLED, &tty->flags); |
254 | return 0; | 252 | return 0; |
255 | 253 | ||
diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c index a946e49a2626..aa80dc94ddc2 100644 --- a/drivers/tty/tty_buffer.c +++ b/drivers/tty/tty_buffer.c | |||
@@ -37,29 +37,6 @@ | |||
37 | 37 | ||
38 | #define TTY_BUFFER_PAGE (((PAGE_SIZE - sizeof(struct tty_buffer)) / 2) & ~0xFF) | 38 | #define TTY_BUFFER_PAGE (((PAGE_SIZE - sizeof(struct tty_buffer)) / 2) & ~0xFF) |
39 | 39 | ||
40 | /* | ||
41 | * If all tty flip buffers have been processed by flush_to_ldisc() or | ||
42 | * dropped by tty_buffer_flush(), check if the linked pty has been closed. | ||
43 | * If so, wake the reader/poll to process | ||
44 | */ | ||
45 | static inline void check_other_closed(struct tty_struct *tty) | ||
46 | { | ||
47 | unsigned long flags, old; | ||
48 | |||
49 | /* transition from TTY_OTHER_CLOSED => TTY_OTHER_DONE must be atomic */ | ||
50 | for (flags = ACCESS_ONCE(tty->flags); | ||
51 | test_bit(TTY_OTHER_CLOSED, &flags); | ||
52 | ) { | ||
53 | old = flags; | ||
54 | __set_bit(TTY_OTHER_DONE, &flags); | ||
55 | flags = cmpxchg(&tty->flags, old, flags); | ||
56 | if (old == flags) { | ||
57 | wake_up_interruptible(&tty->read_wait); | ||
58 | break; | ||
59 | } | ||
60 | } | ||
61 | } | ||
62 | |||
63 | /** | 40 | /** |
64 | * tty_buffer_lock_exclusive - gain exclusive access to buffer | 41 | * tty_buffer_lock_exclusive - gain exclusive access to buffer |
65 | * tty_buffer_unlock_exclusive - release exclusive access | 42 | * tty_buffer_unlock_exclusive - release exclusive access |
@@ -254,8 +231,6 @@ void tty_buffer_flush(struct tty_struct *tty, struct tty_ldisc *ld) | |||
254 | if (ld && ld->ops->flush_buffer) | 231 | if (ld && ld->ops->flush_buffer) |
255 | ld->ops->flush_buffer(tty); | 232 | ld->ops->flush_buffer(tty); |
256 | 233 | ||
257 | check_other_closed(tty); | ||
258 | |||
259 | atomic_dec(&buf->priority); | 234 | atomic_dec(&buf->priority); |
260 | mutex_unlock(&buf->lock); | 235 | mutex_unlock(&buf->lock); |
261 | } | 236 | } |
@@ -522,10 +497,8 @@ static void flush_to_ldisc(struct work_struct *work) | |||
522 | */ | 497 | */ |
523 | count = smp_load_acquire(&head->commit) - head->read; | 498 | count = smp_load_acquire(&head->commit) - head->read; |
524 | if (!count) { | 499 | if (!count) { |
525 | if (next == NULL) { | 500 | if (next == NULL) |
526 | check_other_closed(tty); | ||
527 | break; | 501 | break; |
528 | } | ||
529 | buf->head = next; | 502 | buf->head = next; |
530 | tty_buffer_free(port, head); | 503 | tty_buffer_free(port, head); |
531 | continue; | 504 | continue; |
@@ -614,3 +587,8 @@ bool tty_buffer_cancel_work(struct tty_port *port) | |||
614 | { | 587 | { |
615 | return cancel_work_sync(&port->buf.work); | 588 | return cancel_work_sync(&port->buf.work); |
616 | } | 589 | } |
590 | |||
591 | void tty_buffer_flush_work(struct tty_port *port) | ||
592 | { | ||
593 | flush_work(&port->buf.work); | ||
594 | } | ||