diff options
author | Peter Hurley <peter@hurleysoftware.com> | 2015-01-16 15:05:37 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2015-02-02 13:11:26 -0500 |
commit | 70aca71f92ca2c111978bf676287fab5580d2598 (patch) | |
tree | f9fc767837a07d5381f3c61e791dd02caeb754d4 /drivers/tty | |
parent | 5e28cca1539f3532cb2392710655cd3e562cee8b (diff) |
n_tty: Fix unordered accesses to lockless read buffer
Add commit_head buffer index, which the producer-side publishes
after input processing in non-canon mode. This ensures the consumer-side
observes correctly-ordered writes in non-canonical mode (ie., the buffer
data is written before the buffer index is advanced). Fix consumer-side
uses of read_cnt() to use commit_head instead.
Add required memory barriers to the tail index to guarantee
the consumer-side has completed the loads before the producer-side
begins writing new data. Open-code the producer-side receive_room()
into the i/o loop.
Remove no-longer-referenced receive_room().
Based on work by Christian Riesch <christian.riesch@omicron.at>
Cc: Christian Riesch <christian.riesch@omicron.at>
Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/tty')
-rw-r--r-- | drivers/tty/n_tty.c | 101 |
1 files changed, 48 insertions, 53 deletions
diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c index 7efabc4673b6..f63b25bbe895 100644 --- a/drivers/tty/n_tty.c +++ b/drivers/tty/n_tty.c | |||
@@ -90,6 +90,7 @@ | |||
90 | struct n_tty_data { | 90 | struct n_tty_data { |
91 | /* producer-published */ | 91 | /* producer-published */ |
92 | size_t read_head; | 92 | size_t read_head; |
93 | size_t commit_head; | ||
93 | size_t canon_head; | 94 | size_t canon_head; |
94 | size_t echo_head; | 95 | size_t echo_head; |
95 | size_t echo_commit; | 96 | size_t echo_commit; |
@@ -161,31 +162,6 @@ static inline int tty_put_user(struct tty_struct *tty, unsigned char x, | |||
161 | return put_user(x, ptr); | 162 | return put_user(x, ptr); |
162 | } | 163 | } |
163 | 164 | ||
164 | static int receive_room(struct tty_struct *tty) | ||
165 | { | ||
166 | struct n_tty_data *ldata = tty->disc_data; | ||
167 | int left; | ||
168 | |||
169 | if (I_PARMRK(tty)) { | ||
170 | /* Multiply read_cnt by 3, since each byte might take up to | ||
171 | * three times as many spaces when PARMRK is set (depending on | ||
172 | * its flags, e.g. parity error). */ | ||
173 | left = N_TTY_BUF_SIZE - read_cnt(ldata) * 3 - 1; | ||
174 | } else | ||
175 | left = N_TTY_BUF_SIZE - read_cnt(ldata) - 1; | ||
176 | |||
177 | /* | ||
178 | * If we are doing input canonicalization, and there are no | ||
179 | * pending newlines, let characters through without limit, so | ||
180 | * that erase characters will be handled. Other excess | ||
181 | * characters will be beeped. | ||
182 | */ | ||
183 | if (left <= 0) | ||
184 | left = ldata->icanon && ldata->canon_head == ldata->read_tail; | ||
185 | |||
186 | return left; | ||
187 | } | ||
188 | |||
189 | /** | 165 | /** |
190 | * n_tty_kick_worker - start input worker (if required) | 166 | * n_tty_kick_worker - start input worker (if required) |
191 | * @tty: terminal | 167 | * @tty: terminal |
@@ -224,7 +200,7 @@ static ssize_t chars_in_buffer(struct tty_struct *tty) | |||
224 | ssize_t n = 0; | 200 | ssize_t n = 0; |
225 | 201 | ||
226 | if (!ldata->icanon) | 202 | if (!ldata->icanon) |
227 | n = read_cnt(ldata); | 203 | n = ldata->commit_head - ldata->read_tail; |
228 | else | 204 | else |
229 | n = ldata->canon_head - ldata->read_tail; | 205 | n = ldata->canon_head - ldata->read_tail; |
230 | return n; | 206 | return n; |
@@ -318,10 +294,6 @@ static void n_tty_check_unthrottle(struct tty_struct *tty) | |||
318 | * | 294 | * |
319 | * n_tty_receive_buf()/producer path: | 295 | * n_tty_receive_buf()/producer path: |
320 | * caller holds non-exclusive termios_rwsem | 296 | * caller holds non-exclusive termios_rwsem |
321 | * modifies read_head | ||
322 | * | ||
323 | * read_head is only considered 'published' if canonical mode is | ||
324 | * not active. | ||
325 | */ | 297 | */ |
326 | 298 | ||
327 | static inline void put_tty_queue(unsigned char c, struct n_tty_data *ldata) | 299 | static inline void put_tty_queue(unsigned char c, struct n_tty_data *ldata) |
@@ -345,6 +317,7 @@ static void reset_buffer_flags(struct n_tty_data *ldata) | |||
345 | { | 317 | { |
346 | ldata->read_head = ldata->canon_head = ldata->read_tail = 0; | 318 | ldata->read_head = ldata->canon_head = ldata->read_tail = 0; |
347 | ldata->echo_head = ldata->echo_tail = ldata->echo_commit = 0; | 319 | ldata->echo_head = ldata->echo_tail = ldata->echo_commit = 0; |
320 | ldata->commit_head = 0; | ||
348 | ldata->echo_mark = 0; | 321 | ldata->echo_mark = 0; |
349 | ldata->line_start = 0; | 322 | ldata->line_start = 0; |
350 | 323 | ||
@@ -992,10 +965,6 @@ static inline void finish_erasing(struct n_tty_data *ldata) | |||
992 | * | 965 | * |
993 | * n_tty_receive_buf()/producer path: | 966 | * n_tty_receive_buf()/producer path: |
994 | * caller holds non-exclusive termios_rwsem | 967 | * caller holds non-exclusive termios_rwsem |
995 | * modifies read_head | ||
996 | * | ||
997 | * Modifying the read_head is not considered a publish in this context | ||
998 | * because canonical mode is active -- only canon_head publishes | ||
999 | */ | 968 | */ |
1000 | 969 | ||
1001 | static void eraser(unsigned char c, struct tty_struct *tty) | 970 | static void eraser(unsigned char c, struct tty_struct *tty) |
@@ -1144,7 +1113,6 @@ static void isig(int sig, struct tty_struct *tty) | |||
1144 | * | 1113 | * |
1145 | * n_tty_receive_buf()/producer path: | 1114 | * n_tty_receive_buf()/producer path: |
1146 | * caller holds non-exclusive termios_rwsem | 1115 | * caller holds non-exclusive termios_rwsem |
1147 | * publishes read_head via put_tty_queue() | ||
1148 | * | 1116 | * |
1149 | * Note: may get exclusive termios_rwsem if flushing input buffer | 1117 | * Note: may get exclusive termios_rwsem if flushing input buffer |
1150 | */ | 1118 | */ |
@@ -1214,7 +1182,6 @@ static void n_tty_receive_overrun(struct tty_struct *tty) | |||
1214 | * | 1182 | * |
1215 | * n_tty_receive_buf()/producer path: | 1183 | * n_tty_receive_buf()/producer path: |
1216 | * caller holds non-exclusive termios_rwsem | 1184 | * caller holds non-exclusive termios_rwsem |
1217 | * publishes read_head via put_tty_queue() | ||
1218 | */ | 1185 | */ |
1219 | static void n_tty_receive_parity_error(struct tty_struct *tty, unsigned char c) | 1186 | static void n_tty_receive_parity_error(struct tty_struct *tty, unsigned char c) |
1220 | { | 1187 | { |
@@ -1268,7 +1235,6 @@ n_tty_receive_signal_char(struct tty_struct *tty, int signal, unsigned char c) | |||
1268 | * n_tty_receive_buf()/producer path: | 1235 | * n_tty_receive_buf()/producer path: |
1269 | * caller holds non-exclusive termios_rwsem | 1236 | * caller holds non-exclusive termios_rwsem |
1270 | * publishes canon_head if canonical mode is active | 1237 | * publishes canon_head if canonical mode is active |
1271 | * otherwise, publishes read_head via put_tty_queue() | ||
1272 | * | 1238 | * |
1273 | * Returns 1 if LNEXT was received, else returns 0 | 1239 | * Returns 1 if LNEXT was received, else returns 0 |
1274 | */ | 1240 | */ |
@@ -1381,7 +1347,7 @@ n_tty_receive_char_special(struct tty_struct *tty, unsigned char c) | |||
1381 | handle_newline: | 1347 | handle_newline: |
1382 | set_bit(ldata->read_head & (N_TTY_BUF_SIZE - 1), ldata->read_flags); | 1348 | set_bit(ldata->read_head & (N_TTY_BUF_SIZE - 1), ldata->read_flags); |
1383 | put_tty_queue(c, ldata); | 1349 | put_tty_queue(c, ldata); |
1384 | ldata->canon_head = ldata->read_head; | 1350 | smp_store_release(&ldata->canon_head, ldata->read_head); |
1385 | kill_fasync(&tty->fasync, SIGIO, POLL_IN); | 1351 | kill_fasync(&tty->fasync, SIGIO, POLL_IN); |
1386 | if (waitqueue_active(&tty->read_wait)) | 1352 | if (waitqueue_active(&tty->read_wait)) |
1387 | wake_up_interruptible_poll(&tty->read_wait, POLLIN); | 1353 | wake_up_interruptible_poll(&tty->read_wait, POLLIN); |
@@ -1531,7 +1497,7 @@ n_tty_receive_char_lnext(struct tty_struct *tty, unsigned char c, char flag) | |||
1531 | * | 1497 | * |
1532 | * n_tty_receive_buf()/producer path: | 1498 | * n_tty_receive_buf()/producer path: |
1533 | * claims non-exclusive termios_rwsem | 1499 | * claims non-exclusive termios_rwsem |
1534 | * publishes read_head and canon_head | 1500 | * publishes commit_head or canon_head |
1535 | */ | 1501 | */ |
1536 | 1502 | ||
1537 | static void | 1503 | static void |
@@ -1542,16 +1508,14 @@ n_tty_receive_buf_real_raw(struct tty_struct *tty, const unsigned char *cp, | |||
1542 | size_t n, head; | 1508 | size_t n, head; |
1543 | 1509 | ||
1544 | head = ldata->read_head & (N_TTY_BUF_SIZE - 1); | 1510 | head = ldata->read_head & (N_TTY_BUF_SIZE - 1); |
1545 | n = N_TTY_BUF_SIZE - max(read_cnt(ldata), head); | 1511 | n = min_t(size_t, count, N_TTY_BUF_SIZE - head); |
1546 | n = min_t(size_t, count, n); | ||
1547 | memcpy(read_buf_addr(ldata, head), cp, n); | 1512 | memcpy(read_buf_addr(ldata, head), cp, n); |
1548 | ldata->read_head += n; | 1513 | ldata->read_head += n; |
1549 | cp += n; | 1514 | cp += n; |
1550 | count -= n; | 1515 | count -= n; |
1551 | 1516 | ||
1552 | head = ldata->read_head & (N_TTY_BUF_SIZE - 1); | 1517 | head = ldata->read_head & (N_TTY_BUF_SIZE - 1); |
1553 | n = N_TTY_BUF_SIZE - max(read_cnt(ldata), head); | 1518 | n = min_t(size_t, count, N_TTY_BUF_SIZE - head); |
1554 | n = min_t(size_t, count, n); | ||
1555 | memcpy(read_buf_addr(ldata, head), cp, n); | 1519 | memcpy(read_buf_addr(ldata, head), cp, n); |
1556 | ldata->read_head += n; | 1520 | ldata->read_head += n; |
1557 | } | 1521 | } |
@@ -1681,8 +1645,13 @@ static void __receive_buf(struct tty_struct *tty, const unsigned char *cp, | |||
1681 | tty->ops->flush_chars(tty); | 1645 | tty->ops->flush_chars(tty); |
1682 | } | 1646 | } |
1683 | 1647 | ||
1684 | if ((!ldata->icanon && (read_cnt(ldata) >= ldata->minimum_to_wake)) || | 1648 | if (ldata->icanon && !L_EXTPROC(tty)) |
1685 | L_EXTPROC(tty)) { | 1649 | return; |
1650 | |||
1651 | /* publish read_head to consumer */ | ||
1652 | smp_store_release(&ldata->commit_head, ldata->read_head); | ||
1653 | |||
1654 | if ((read_cnt(ldata) >= ldata->minimum_to_wake) || L_EXTPROC(tty)) { | ||
1686 | kill_fasync(&tty->fasync, SIGIO, POLL_IN); | 1655 | kill_fasync(&tty->fasync, SIGIO, POLL_IN); |
1687 | if (waitqueue_active(&tty->read_wait)) | 1656 | if (waitqueue_active(&tty->read_wait)) |
1688 | wake_up_interruptible_poll(&tty->read_wait, POLLIN); | 1657 | wake_up_interruptible_poll(&tty->read_wait, POLLIN); |
@@ -1699,7 +1668,31 @@ n_tty_receive_buf_common(struct tty_struct *tty, const unsigned char *cp, | |||
1699 | down_read(&tty->termios_rwsem); | 1668 | down_read(&tty->termios_rwsem); |
1700 | 1669 | ||
1701 | while (1) { | 1670 | while (1) { |
1702 | room = receive_room(tty); | 1671 | /* |
1672 | * When PARMRK is set, multiply read_cnt by 3, since each byte | ||
1673 | * might take up to three times as many spaces (depending on | ||
1674 | * its flags, e.g. parity error). [This calculation is wrong.] | ||
1675 | * | ||
1676 | * If we are doing input canonicalization, and there are no | ||
1677 | * pending newlines, let characters through without limit, so | ||
1678 | * that erase characters will be handled. Other excess | ||
1679 | * characters will be beeped. | ||
1680 | * | ||
1681 | * paired with store in *_copy_from_read_buf() -- guarantees | ||
1682 | * the consumer has loaded the data in read_buf up to the new | ||
1683 | * read_tail (so this producer will not overwrite unread data) | ||
1684 | */ | ||
1685 | size_t tail = smp_load_acquire(&ldata->read_tail); | ||
1686 | size_t head = ldata->read_head; | ||
1687 | |||
1688 | if (I_PARMRK(tty)) | ||
1689 | room = N_TTY_BUF_SIZE - (head - tail) * 3 - 1; | ||
1690 | else | ||
1691 | room = N_TTY_BUF_SIZE - (head - tail) - 1; | ||
1692 | |||
1693 | if (room <= 0) | ||
1694 | room = ldata->icanon && ldata->canon_head == tail; | ||
1695 | |||
1703 | n = min(count, room); | 1696 | n = min(count, room); |
1704 | if (!n) { | 1697 | if (!n) { |
1705 | if (flow && !room) | 1698 | if (flow && !room) |
@@ -1769,6 +1762,7 @@ static void n_tty_set_termios(struct tty_struct *tty, struct ktermios *old) | |||
1769 | ldata->canon_head = ldata->read_head; | 1762 | ldata->canon_head = ldata->read_head; |
1770 | ldata->push = 1; | 1763 | ldata->push = 1; |
1771 | } | 1764 | } |
1765 | ldata->commit_head = ldata->read_head; | ||
1772 | ldata->erasing = 0; | 1766 | ldata->erasing = 0; |
1773 | ldata->lnext = 0; | 1767 | ldata->lnext = 0; |
1774 | } | 1768 | } |
@@ -1909,7 +1903,7 @@ static inline int input_available_p(struct tty_struct *tty, int poll) | |||
1909 | if (ldata->icanon && !L_EXTPROC(tty)) | 1903 | if (ldata->icanon && !L_EXTPROC(tty)) |
1910 | return ldata->canon_head != ldata->read_tail; | 1904 | return ldata->canon_head != ldata->read_tail; |
1911 | else | 1905 | else |
1912 | return read_cnt(ldata) >= amt; | 1906 | return ldata->commit_head - ldata->read_tail >= amt; |
1913 | } | 1907 | } |
1914 | 1908 | ||
1915 | /** | 1909 | /** |
@@ -1941,10 +1935,11 @@ static int copy_from_read_buf(struct tty_struct *tty, | |||
1941 | int retval; | 1935 | int retval; |
1942 | size_t n; | 1936 | size_t n; |
1943 | bool is_eof; | 1937 | bool is_eof; |
1938 | size_t head = smp_load_acquire(&ldata->commit_head); | ||
1944 | size_t tail = ldata->read_tail & (N_TTY_BUF_SIZE - 1); | 1939 | size_t tail = ldata->read_tail & (N_TTY_BUF_SIZE - 1); |
1945 | 1940 | ||
1946 | retval = 0; | 1941 | retval = 0; |
1947 | n = min(read_cnt(ldata), N_TTY_BUF_SIZE - tail); | 1942 | n = min(head - ldata->read_tail, N_TTY_BUF_SIZE - tail); |
1948 | n = min(*nr, n); | 1943 | n = min(*nr, n); |
1949 | if (n) { | 1944 | if (n) { |
1950 | retval = copy_to_user(*b, read_buf_addr(ldata, tail), n); | 1945 | retval = copy_to_user(*b, read_buf_addr(ldata, tail), n); |
@@ -1952,9 +1947,10 @@ static int copy_from_read_buf(struct tty_struct *tty, | |||
1952 | is_eof = n == 1 && read_buf(ldata, tail) == EOF_CHAR(tty); | 1947 | is_eof = n == 1 && read_buf(ldata, tail) == EOF_CHAR(tty); |
1953 | tty_audit_add_data(tty, read_buf_addr(ldata, tail), n, | 1948 | tty_audit_add_data(tty, read_buf_addr(ldata, tail), n, |
1954 | ldata->icanon); | 1949 | ldata->icanon); |
1955 | ldata->read_tail += n; | 1950 | smp_store_release(&ldata->read_tail, ldata->read_tail + n); |
1956 | /* Turn single EOF into zero-length read */ | 1951 | /* Turn single EOF into zero-length read */ |
1957 | if (L_EXTPROC(tty) && ldata->icanon && is_eof && !read_cnt(ldata)) | 1952 | if (L_EXTPROC(tty) && ldata->icanon && is_eof && |
1953 | (head == ldata->read_tail)) | ||
1958 | n = 0; | 1954 | n = 0; |
1959 | *b += n; | 1955 | *b += n; |
1960 | *nr -= n; | 1956 | *nr -= n; |
@@ -1997,7 +1993,7 @@ static int canon_copy_from_read_buf(struct tty_struct *tty, | |||
1997 | bool eof_push = 0; | 1993 | bool eof_push = 0; |
1998 | 1994 | ||
1999 | /* N.B. avoid overrun if nr == 0 */ | 1995 | /* N.B. avoid overrun if nr == 0 */ |
2000 | n = min(*nr, read_cnt(ldata)); | 1996 | n = min(*nr, smp_load_acquire(&ldata->canon_head) - ldata->read_tail); |
2001 | if (!n) | 1997 | if (!n) |
2002 | return 0; | 1998 | return 0; |
2003 | 1999 | ||
@@ -2047,8 +2043,7 @@ static int canon_copy_from_read_buf(struct tty_struct *tty, | |||
2047 | 2043 | ||
2048 | if (found) | 2044 | if (found) |
2049 | clear_bit(eol, ldata->read_flags); | 2045 | clear_bit(eol, ldata->read_flags); |
2050 | smp_mb__after_atomic(); | 2046 | smp_store_release(&ldata->read_tail, ldata->read_tail + c); |
2051 | ldata->read_tail += c; | ||
2052 | 2047 | ||
2053 | if (found) { | 2048 | if (found) { |
2054 | if (!ldata->push) | 2049 | if (!ldata->push) |