diff options
author | Alan Cox <alan@redhat.com> | 2007-07-16 02:39:43 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-07-16 12:05:41 -0400 |
commit | 9c1729db3e6d738f872bcb090212af00473bf666 (patch) | |
tree | be68b99784607953fb50d9330d34c2728215be57 | |
parent | c9c64155f5a81b4b41e98f9fb9c464a565c1bf72 (diff) |
Prevent an O_NDELAY writer from blocking when a tty write is blocked by the tty atomic writer mutex
Without this a tty write could block if a previous blocking tty write was
in progress on the same tty and blocked by a line discipline or hardware
event. Originally found and reported by Dave Johnson.
Signed-off-by: Alan Cox <alan@redhat.com>
Acked-by: Dave Johnson <djohnson+linux-kernel@sw.starentnetworks.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r-- | drivers/char/n_hdlc.c | 9 | ||||
-rw-r--r-- | drivers/char/n_tty.c | 3 | ||||
-rw-r--r-- | drivers/char/tty_io.c | 40 | ||||
-rw-r--r-- | drivers/char/tty_ioctl.c | 4 | ||||
-rw-r--r-- | include/linux/tty.h | 6 |
5 files changed, 43 insertions, 19 deletions
diff --git a/drivers/char/n_hdlc.c b/drivers/char/n_hdlc.c index 337a87f86a3b..37f7d3403040 100644 --- a/drivers/char/n_hdlc.c +++ b/drivers/char/n_hdlc.c | |||
@@ -780,13 +780,14 @@ static unsigned int n_hdlc_tty_poll(struct tty_struct *tty, struct file *filp, | |||
780 | poll_wait(filp, &tty->write_wait, wait); | 780 | poll_wait(filp, &tty->write_wait, wait); |
781 | 781 | ||
782 | /* set bits for operations that won't block */ | 782 | /* set bits for operations that won't block */ |
783 | if(n_hdlc->rx_buf_list.head) | 783 | if (n_hdlc->rx_buf_list.head) |
784 | mask |= POLLIN | POLLRDNORM; /* readable */ | 784 | mask |= POLLIN | POLLRDNORM; /* readable */ |
785 | if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) | 785 | if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) |
786 | mask |= POLLHUP; | 786 | mask |= POLLHUP; |
787 | if(tty_hung_up_p(filp)) | 787 | if (tty_hung_up_p(filp)) |
788 | mask |= POLLHUP; | 788 | mask |= POLLHUP; |
789 | if(n_hdlc->tx_free_buf_list.head) | 789 | if (!tty_is_writelocked(tty) && |
790 | n_hdlc->tx_free_buf_list.head) | ||
790 | mask |= POLLOUT | POLLWRNORM; /* writable */ | 791 | mask |= POLLOUT | POLLWRNORM; /* writable */ |
791 | } | 792 | } |
792 | return mask; | 793 | return mask; |
@@ -861,7 +862,7 @@ static void n_hdlc_buf_put(struct n_hdlc_buf_list *list, | |||
861 | spin_lock_irqsave(&list->spinlock,flags); | 862 | spin_lock_irqsave(&list->spinlock,flags); |
862 | 863 | ||
863 | buf->link=NULL; | 864 | buf->link=NULL; |
864 | if(list->tail) | 865 | if (list->tail) |
865 | list->tail->link = buf; | 866 | list->tail->link = buf; |
866 | else | 867 | else |
867 | list->head = buf; | 868 | list->head = buf; |
diff --git a/drivers/char/n_tty.c b/drivers/char/n_tty.c index 154f42203b05..371631f4bfb9 100644 --- a/drivers/char/n_tty.c +++ b/drivers/char/n_tty.c | |||
@@ -1538,7 +1538,8 @@ static unsigned int normal_poll(struct tty_struct * tty, struct file * file, pol | |||
1538 | else | 1538 | else |
1539 | tty->minimum_to_wake = 1; | 1539 | tty->minimum_to_wake = 1; |
1540 | } | 1540 | } |
1541 | if (tty->driver->chars_in_buffer(tty) < WAKEUP_CHARS && | 1541 | if (!tty_is_writelocked(tty) && |
1542 | tty->driver->chars_in_buffer(tty) < WAKEUP_CHARS && | ||
1542 | tty->driver->write_room(tty) > 0) | 1543 | tty->driver->write_room(tty) > 0) |
1543 | mask |= POLLOUT | POLLWRNORM; | 1544 | mask |= POLLOUT | POLLWRNORM; |
1544 | return mask; | 1545 | return mask; |
diff --git a/drivers/char/tty_io.c b/drivers/char/tty_io.c index a96f26a63fa2..a37e6330db8a 100644 --- a/drivers/char/tty_io.c +++ b/drivers/char/tty_io.c | |||
@@ -1726,6 +1726,23 @@ static ssize_t tty_read(struct file * file, char __user * buf, size_t count, | |||
1726 | return i; | 1726 | return i; |
1727 | } | 1727 | } |
1728 | 1728 | ||
1729 | void tty_write_unlock(struct tty_struct *tty) | ||
1730 | { | ||
1731 | mutex_unlock(&tty->atomic_write_lock); | ||
1732 | wake_up_interruptible(&tty->write_wait); | ||
1733 | } | ||
1734 | |||
1735 | int tty_write_lock(struct tty_struct *tty, int ndelay) | ||
1736 | { | ||
1737 | if (!mutex_trylock(&tty->atomic_write_lock)) { | ||
1738 | if (ndelay) | ||
1739 | return -EAGAIN; | ||
1740 | if (mutex_lock_interruptible(&tty->atomic_write_lock)) | ||
1741 | return -ERESTARTSYS; | ||
1742 | } | ||
1743 | return 0; | ||
1744 | } | ||
1745 | |||
1729 | /* | 1746 | /* |
1730 | * Split writes up in sane blocksizes to avoid | 1747 | * Split writes up in sane blocksizes to avoid |
1731 | * denial-of-service type attacks | 1748 | * denial-of-service type attacks |
@@ -1737,13 +1754,12 @@ static inline ssize_t do_tty_write( | |||
1737 | const char __user *buf, | 1754 | const char __user *buf, |
1738 | size_t count) | 1755 | size_t count) |
1739 | { | 1756 | { |
1740 | ssize_t ret = 0, written = 0; | 1757 | ssize_t ret, written = 0; |
1741 | unsigned int chunk; | 1758 | unsigned int chunk; |
1742 | 1759 | ||
1743 | /* FIXME: O_NDELAY ... */ | 1760 | ret = tty_write_lock(tty, file->f_flags & O_NDELAY); |
1744 | if (mutex_lock_interruptible(&tty->atomic_write_lock)) { | 1761 | if (ret < 0) |
1745 | return -ERESTARTSYS; | 1762 | return ret; |
1746 | } | ||
1747 | 1763 | ||
1748 | /* | 1764 | /* |
1749 | * We chunk up writes into a temporary buffer. This | 1765 | * We chunk up writes into a temporary buffer. This |
@@ -1776,8 +1792,8 @@ static inline ssize_t do_tty_write( | |||
1776 | 1792 | ||
1777 | buf = kmalloc(chunk, GFP_KERNEL); | 1793 | buf = kmalloc(chunk, GFP_KERNEL); |
1778 | if (!buf) { | 1794 | if (!buf) { |
1779 | mutex_unlock(&tty->atomic_write_lock); | 1795 | ret = -ENOMEM; |
1780 | return -ENOMEM; | 1796 | goto out; |
1781 | } | 1797 | } |
1782 | kfree(tty->write_buf); | 1798 | kfree(tty->write_buf); |
1783 | tty->write_cnt = chunk; | 1799 | tty->write_cnt = chunk; |
@@ -1812,7 +1828,8 @@ static inline ssize_t do_tty_write( | |||
1812 | inode->i_mtime = current_fs_time(inode->i_sb); | 1828 | inode->i_mtime = current_fs_time(inode->i_sb); |
1813 | ret = written; | 1829 | ret = written; |
1814 | } | 1830 | } |
1815 | mutex_unlock(&tty->atomic_write_lock); | 1831 | out: |
1832 | tty_write_unlock(tty); | ||
1816 | return ret; | 1833 | return ret; |
1817 | } | 1834 | } |
1818 | 1835 | ||
@@ -3163,14 +3180,13 @@ static int tiocsetd(struct tty_struct *tty, int __user *p) | |||
3163 | 3180 | ||
3164 | static int send_break(struct tty_struct *tty, unsigned int duration) | 3181 | static int send_break(struct tty_struct *tty, unsigned int duration) |
3165 | { | 3182 | { |
3166 | if (mutex_lock_interruptible(&tty->atomic_write_lock)) | 3183 | if (tty_write_lock(tty, 0) < 0) |
3167 | return -EINTR; | 3184 | return -EINTR; |
3168 | tty->driver->break_ctl(tty, -1); | 3185 | tty->driver->break_ctl(tty, -1); |
3169 | if (!signal_pending(current)) { | 3186 | if (!signal_pending(current)) |
3170 | msleep_interruptible(duration); | 3187 | msleep_interruptible(duration); |
3171 | } | ||
3172 | tty->driver->break_ctl(tty, 0); | 3188 | tty->driver->break_ctl(tty, 0); |
3173 | mutex_unlock(&tty->atomic_write_lock); | 3189 | tty_write_unlock(tty); |
3174 | if (signal_pending(current)) | 3190 | if (signal_pending(current)) |
3175 | return -EINTR; | 3191 | return -EINTR; |
3176 | return 0; | 3192 | return 0; |
diff --git a/drivers/char/tty_ioctl.c b/drivers/char/tty_ioctl.c index fd471cb3338f..918e24c885f1 100644 --- a/drivers/char/tty_ioctl.c +++ b/drivers/char/tty_ioctl.c | |||
@@ -667,7 +667,7 @@ static int send_prio_char(struct tty_struct *tty, char ch) | |||
667 | return 0; | 667 | return 0; |
668 | } | 668 | } |
669 | 669 | ||
670 | if (mutex_lock_interruptible(&tty->atomic_write_lock)) | 670 | if (tty_write_lock(tty, 0) < 0) |
671 | return -ERESTARTSYS; | 671 | return -ERESTARTSYS; |
672 | 672 | ||
673 | if (was_stopped) | 673 | if (was_stopped) |
@@ -675,7 +675,7 @@ static int send_prio_char(struct tty_struct *tty, char ch) | |||
675 | tty->driver->write(tty, &ch, 1); | 675 | tty->driver->write(tty, &ch, 1); |
676 | if (was_stopped) | 676 | if (was_stopped) |
677 | stop_tty(tty); | 677 | stop_tty(tty); |
678 | mutex_unlock(&tty->atomic_write_lock); | 678 | tty_write_unlock(tty); |
679 | return 0; | 679 | return 0; |
680 | } | 680 | } |
681 | 681 | ||
diff --git a/include/linux/tty.h b/include/linux/tty.h index bb4576085203..deaba9ec5004 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h | |||
@@ -338,6 +338,12 @@ extern struct tty_struct *get_current_tty(void); | |||
338 | 338 | ||
339 | extern struct mutex tty_mutex; | 339 | extern struct mutex tty_mutex; |
340 | 340 | ||
341 | extern void tty_write_unlock(struct tty_struct *tty); | ||
342 | extern int tty_write_lock(struct tty_struct *tty, int ndelay); | ||
343 | #define tty_is_writelocked(tty) (mutex_is_locked(&tty->atomic_write_lock)) | ||
344 | |||
345 | |||
346 | |||
341 | /* n_tty.c */ | 347 | /* n_tty.c */ |
342 | extern struct tty_ldisc tty_ldisc_N_TTY; | 348 | extern struct tty_ldisc tty_ldisc_N_TTY; |
343 | 349 | ||