diff options
author | Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp> | 2018-05-25 20:53:14 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2018-06-28 08:30:16 -0400 |
commit | ebec3f8f5271139df618ebdf8427e24ba102ba94 (patch) | |
tree | 6badd6fd0faa57bf1947bd98af158590ef6857c9 /drivers/tty | |
parent | 3d63b7e4ae0dc5e02d28ddd2fa1f945defc68d81 (diff) |
n_tty: Access echo_* variables carefully.
syzbot is reporting stalls at __process_echoes() [1]. This is because
since ldata->echo_commit < ldata->echo_tail becomes true for some reason,
the discard loop is serving as almost infinite loop. This patch tries to
avoid falling into ldata->echo_commit < ldata->echo_tail situation by
making access to echo_* variables more carefully.
Since reset_buffer_flags() is called without output_lock held, it should
not touch echo_* variables. And omit a call to reset_buffer_flags() from
n_tty_open() by using vzalloc().
Since add_echo_byte() is called without output_lock held, it needs memory
barrier between storing into echo_buf[] and incrementing echo_head counter.
echo_buf() needs corresponding memory barrier before reading echo_buf[].
Lack of handling the possibility of not-yet-stored multi-byte operation
might be the reason of falling into ldata->echo_commit < ldata->echo_tail
situation, for if I do WARN_ON(ldata->echo_commit == tail + 1) prior to
echo_buf(ldata, tail + 1), the WARN_ON() fires.
Also, explicitly masking with buffer for the former "while" loop, and
use ldata->echo_commit > tail for the latter "while" loop.
[1] https://syzkaller.appspot.com/bug?id=17f23b094cd80df750e5b0f8982c521ee6bcbf40
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Reported-by: syzbot <syzbot+108696293d7a21ab688f@syzkaller.appspotmail.com>
Cc: 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_tty.c | 42 |
1 files changed, 24 insertions, 18 deletions
diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c index b279f8730e04..431742201709 100644 --- a/drivers/tty/n_tty.c +++ b/drivers/tty/n_tty.c | |||
@@ -143,6 +143,7 @@ static inline unsigned char *read_buf_addr(struct n_tty_data *ldata, size_t i) | |||
143 | 143 | ||
144 | static inline unsigned char echo_buf(struct n_tty_data *ldata, size_t i) | 144 | static inline unsigned char echo_buf(struct n_tty_data *ldata, size_t i) |
145 | { | 145 | { |
146 | smp_rmb(); /* Matches smp_wmb() in add_echo_byte(). */ | ||
146 | return ldata->echo_buf[i & (N_TTY_BUF_SIZE - 1)]; | 147 | return ldata->echo_buf[i & (N_TTY_BUF_SIZE - 1)]; |
147 | } | 148 | } |
148 | 149 | ||
@@ -318,9 +319,7 @@ static inline void put_tty_queue(unsigned char c, struct n_tty_data *ldata) | |||
318 | static void reset_buffer_flags(struct n_tty_data *ldata) | 319 | static void reset_buffer_flags(struct n_tty_data *ldata) |
319 | { | 320 | { |
320 | ldata->read_head = ldata->canon_head = ldata->read_tail = 0; | 321 | ldata->read_head = ldata->canon_head = ldata->read_tail = 0; |
321 | ldata->echo_head = ldata->echo_tail = ldata->echo_commit = 0; | ||
322 | ldata->commit_head = 0; | 322 | ldata->commit_head = 0; |
323 | ldata->echo_mark = 0; | ||
324 | ldata->line_start = 0; | 323 | ldata->line_start = 0; |
325 | 324 | ||
326 | ldata->erasing = 0; | 325 | ldata->erasing = 0; |
@@ -619,13 +618,20 @@ static size_t __process_echoes(struct tty_struct *tty) | |||
619 | old_space = space = tty_write_room(tty); | 618 | old_space = space = tty_write_room(tty); |
620 | 619 | ||
621 | tail = ldata->echo_tail; | 620 | tail = ldata->echo_tail; |
622 | while (ldata->echo_commit != tail) { | 621 | while (MASK(ldata->echo_commit) != MASK(tail)) { |
623 | c = echo_buf(ldata, tail); | 622 | c = echo_buf(ldata, tail); |
624 | if (c == ECHO_OP_START) { | 623 | if (c == ECHO_OP_START) { |
625 | unsigned char op; | 624 | unsigned char op; |
626 | int no_space_left = 0; | 625 | int no_space_left = 0; |
627 | 626 | ||
628 | /* | 627 | /* |
628 | * Since add_echo_byte() is called without holding | ||
629 | * output_lock, we might see only portion of multi-byte | ||
630 | * operation. | ||
631 | */ | ||
632 | if (MASK(ldata->echo_commit) == MASK(tail + 1)) | ||
633 | goto not_yet_stored; | ||
634 | /* | ||
629 | * If the buffer byte is the start of a multi-byte | 635 | * If the buffer byte is the start of a multi-byte |
630 | * operation, get the next byte, which is either the | 636 | * operation, get the next byte, which is either the |
631 | * op code or a control character value. | 637 | * op code or a control character value. |
@@ -636,6 +642,8 @@ static size_t __process_echoes(struct tty_struct *tty) | |||
636 | unsigned int num_chars, num_bs; | 642 | unsigned int num_chars, num_bs; |
637 | 643 | ||
638 | case ECHO_OP_ERASE_TAB: | 644 | case ECHO_OP_ERASE_TAB: |
645 | if (MASK(ldata->echo_commit) == MASK(tail + 2)) | ||
646 | goto not_yet_stored; | ||
639 | num_chars = echo_buf(ldata, tail + 2); | 647 | num_chars = echo_buf(ldata, tail + 2); |
640 | 648 | ||
641 | /* | 649 | /* |
@@ -730,7 +738,8 @@ static size_t __process_echoes(struct tty_struct *tty) | |||
730 | /* If the echo buffer is nearly full (so that the possibility exists | 738 | /* If the echo buffer is nearly full (so that the possibility exists |
731 | * of echo overrun before the next commit), then discard enough | 739 | * of echo overrun before the next commit), then discard enough |
732 | * data at the tail to prevent a subsequent overrun */ | 740 | * data at the tail to prevent a subsequent overrun */ |
733 | while (ldata->echo_commit - tail >= ECHO_DISCARD_WATERMARK) { | 741 | while (ldata->echo_commit > tail && |
742 | ldata->echo_commit - tail >= ECHO_DISCARD_WATERMARK) { | ||
734 | if (echo_buf(ldata, tail) == ECHO_OP_START) { | 743 | if (echo_buf(ldata, tail) == ECHO_OP_START) { |
735 | if (echo_buf(ldata, tail + 1) == ECHO_OP_ERASE_TAB) | 744 | if (echo_buf(ldata, tail + 1) == ECHO_OP_ERASE_TAB) |
736 | tail += 3; | 745 | tail += 3; |
@@ -740,6 +749,7 @@ static size_t __process_echoes(struct tty_struct *tty) | |||
740 | tail++; | 749 | tail++; |
741 | } | 750 | } |
742 | 751 | ||
752 | not_yet_stored: | ||
743 | ldata->echo_tail = tail; | 753 | ldata->echo_tail = tail; |
744 | return old_space - space; | 754 | return old_space - space; |
745 | } | 755 | } |
@@ -750,6 +760,7 @@ static void commit_echoes(struct tty_struct *tty) | |||
750 | size_t nr, old, echoed; | 760 | size_t nr, old, echoed; |
751 | size_t head; | 761 | size_t head; |
752 | 762 | ||
763 | mutex_lock(&ldata->output_lock); | ||
753 | head = ldata->echo_head; | 764 | head = ldata->echo_head; |
754 | ldata->echo_mark = head; | 765 | ldata->echo_mark = head; |
755 | old = ldata->echo_commit - ldata->echo_tail; | 766 | old = ldata->echo_commit - ldata->echo_tail; |
@@ -758,10 +769,12 @@ static void commit_echoes(struct tty_struct *tty) | |||
758 | * is over the threshold (and try again each time another | 769 | * is over the threshold (and try again each time another |
759 | * block is accumulated) */ | 770 | * block is accumulated) */ |
760 | nr = head - ldata->echo_tail; | 771 | nr = head - ldata->echo_tail; |
761 | if (nr < ECHO_COMMIT_WATERMARK || (nr % ECHO_BLOCK > old % ECHO_BLOCK)) | 772 | if (nr < ECHO_COMMIT_WATERMARK || |
773 | (nr % ECHO_BLOCK > old % ECHO_BLOCK)) { | ||
774 | mutex_unlock(&ldata->output_lock); | ||
762 | return; | 775 | return; |
776 | } | ||
763 | 777 | ||
764 | mutex_lock(&ldata->output_lock); | ||
765 | ldata->echo_commit = head; | 778 | ldata->echo_commit = head; |
766 | echoed = __process_echoes(tty); | 779 | echoed = __process_echoes(tty); |
767 | mutex_unlock(&ldata->output_lock); | 780 | mutex_unlock(&ldata->output_lock); |
@@ -812,7 +825,9 @@ static void flush_echoes(struct tty_struct *tty) | |||
812 | 825 | ||
813 | static inline void add_echo_byte(unsigned char c, struct n_tty_data *ldata) | 826 | static inline void add_echo_byte(unsigned char c, struct n_tty_data *ldata) |
814 | { | 827 | { |
815 | *echo_buf_addr(ldata, ldata->echo_head++) = c; | 828 | *echo_buf_addr(ldata, ldata->echo_head) = c; |
829 | smp_wmb(); /* Matches smp_rmb() in echo_buf(). */ | ||
830 | ldata->echo_head++; | ||
816 | } | 831 | } |
817 | 832 | ||
818 | /** | 833 | /** |
@@ -1881,30 +1896,21 @@ static int n_tty_open(struct tty_struct *tty) | |||
1881 | struct n_tty_data *ldata; | 1896 | struct n_tty_data *ldata; |
1882 | 1897 | ||
1883 | /* Currently a malloc failure here can panic */ | 1898 | /* Currently a malloc failure here can panic */ |
1884 | ldata = vmalloc(sizeof(*ldata)); | 1899 | ldata = vzalloc(sizeof(*ldata)); |
1885 | if (!ldata) | 1900 | if (!ldata) |
1886 | goto err; | 1901 | return -ENOMEM; |
1887 | 1902 | ||
1888 | ldata->overrun_time = jiffies; | 1903 | ldata->overrun_time = jiffies; |
1889 | mutex_init(&ldata->atomic_read_lock); | 1904 | mutex_init(&ldata->atomic_read_lock); |
1890 | mutex_init(&ldata->output_lock); | 1905 | mutex_init(&ldata->output_lock); |
1891 | 1906 | ||
1892 | tty->disc_data = ldata; | 1907 | tty->disc_data = ldata; |
1893 | reset_buffer_flags(tty->disc_data); | ||
1894 | ldata->column = 0; | ||
1895 | ldata->canon_column = 0; | ||
1896 | ldata->num_overrun = 0; | ||
1897 | ldata->no_room = 0; | ||
1898 | ldata->lnext = 0; | ||
1899 | tty->closing = 0; | 1908 | tty->closing = 0; |
1900 | /* indicate buffer work may resume */ | 1909 | /* indicate buffer work may resume */ |
1901 | clear_bit(TTY_LDISC_HALTED, &tty->flags); | 1910 | clear_bit(TTY_LDISC_HALTED, &tty->flags); |
1902 | n_tty_set_termios(tty, NULL); | 1911 | n_tty_set_termios(tty, NULL); |
1903 | tty_unthrottle(tty); | 1912 | tty_unthrottle(tty); |
1904 | |||
1905 | return 0; | 1913 | return 0; |
1906 | err: | ||
1907 | return -ENOMEM; | ||
1908 | } | 1914 | } |
1909 | 1915 | ||
1910 | static inline int input_available_p(struct tty_struct *tty, int poll) | 1916 | static inline int input_available_p(struct tty_struct *tty, int poll) |