summaryrefslogtreecommitdiffstats
path: root/drivers/tty
diff options
context:
space:
mode:
authorTetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>2018-05-25 20:53:14 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2018-06-28 08:30:16 -0400
commitebec3f8f5271139df618ebdf8427e24ba102ba94 (patch)
tree6badd6fd0faa57bf1947bd98af158590ef6857c9 /drivers/tty
parent3d63b7e4ae0dc5e02d28ddd2fa1f945defc68d81 (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.c42
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
144static inline unsigned char echo_buf(struct n_tty_data *ldata, size_t i) 144static 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)
318static void reset_buffer_flags(struct n_tty_data *ldata) 319static 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
813static inline void add_echo_byte(unsigned char c, struct n_tty_data *ldata) 826static 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;
1906err:
1907 return -ENOMEM;
1908} 1914}
1909 1915
1910static inline int input_available_p(struct tty_struct *tty, int poll) 1916static inline int input_available_p(struct tty_struct *tty, int poll)