diff options
| author | Matthew Wilcox <willy@infradead.org> | 2019-04-05 17:02:10 -0400 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2019-04-14 13:00:04 -0400 |
| commit | 15fab63e1e57be9fdb5eec1bbc5916e9825e9acb (patch) | |
| tree | 1353971310401012cf4b108a375e525065d8bec0 | |
| parent | 8fde12ca79aff9b5ba951fce1a2641901b8d8e64 (diff) | |
fs: prevent page refcount overflow in pipe_buf_get
Change pipe_buf_get() to return a bool indicating whether it succeeded
in raising the refcount of the page (if the thing in the pipe is a page).
This removes another mechanism for overflowing the page refcount. All
callers converted to handle a failure.
Reported-by: Jann Horn <jannh@google.com>
Signed-off-by: Matthew Wilcox <willy@infradead.org>
Cc: stable@kernel.org
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
| -rw-r--r-- | fs/fuse/dev.c | 12 | ||||
| -rw-r--r-- | fs/pipe.c | 4 | ||||
| -rw-r--r-- | fs/splice.c | 12 | ||||
| -rw-r--r-- | include/linux/pipe_fs_i.h | 10 | ||||
| -rw-r--r-- | kernel/trace/trace.c | 6 |
5 files changed, 29 insertions, 15 deletions
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 809c0f2f9942..64f4de983468 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c | |||
| @@ -2034,10 +2034,8 @@ static ssize_t fuse_dev_splice_write(struct pipe_inode_info *pipe, | |||
| 2034 | rem += pipe->bufs[(pipe->curbuf + idx) & (pipe->buffers - 1)].len; | 2034 | rem += pipe->bufs[(pipe->curbuf + idx) & (pipe->buffers - 1)].len; |
| 2035 | 2035 | ||
| 2036 | ret = -EINVAL; | 2036 | ret = -EINVAL; |
| 2037 | if (rem < len) { | 2037 | if (rem < len) |
| 2038 | pipe_unlock(pipe); | 2038 | goto out_free; |
| 2039 | goto out; | ||
| 2040 | } | ||
| 2041 | 2039 | ||
| 2042 | rem = len; | 2040 | rem = len; |
| 2043 | while (rem) { | 2041 | while (rem) { |
| @@ -2055,7 +2053,9 @@ static ssize_t fuse_dev_splice_write(struct pipe_inode_info *pipe, | |||
| 2055 | pipe->curbuf = (pipe->curbuf + 1) & (pipe->buffers - 1); | 2053 | pipe->curbuf = (pipe->curbuf + 1) & (pipe->buffers - 1); |
| 2056 | pipe->nrbufs--; | 2054 | pipe->nrbufs--; |
| 2057 | } else { | 2055 | } else { |
| 2058 | pipe_buf_get(pipe, ibuf); | 2056 | if (!pipe_buf_get(pipe, ibuf)) |
| 2057 | goto out_free; | ||
| 2058 | |||
| 2059 | *obuf = *ibuf; | 2059 | *obuf = *ibuf; |
| 2060 | obuf->flags &= ~PIPE_BUF_FLAG_GIFT; | 2060 | obuf->flags &= ~PIPE_BUF_FLAG_GIFT; |
| 2061 | obuf->len = rem; | 2061 | obuf->len = rem; |
| @@ -2078,11 +2078,11 @@ static ssize_t fuse_dev_splice_write(struct pipe_inode_info *pipe, | |||
| 2078 | ret = fuse_dev_do_write(fud, &cs, len); | 2078 | ret = fuse_dev_do_write(fud, &cs, len); |
| 2079 | 2079 | ||
| 2080 | pipe_lock(pipe); | 2080 | pipe_lock(pipe); |
| 2081 | out_free: | ||
| 2081 | for (idx = 0; idx < nbuf; idx++) | 2082 | for (idx = 0; idx < nbuf; idx++) |
| 2082 | pipe_buf_release(pipe, &bufs[idx]); | 2083 | pipe_buf_release(pipe, &bufs[idx]); |
| 2083 | pipe_unlock(pipe); | 2084 | pipe_unlock(pipe); |
| 2084 | 2085 | ||
| 2085 | out: | ||
| 2086 | kvfree(bufs); | 2086 | kvfree(bufs); |
| 2087 | return ret; | 2087 | return ret; |
| 2088 | } | 2088 | } |
| @@ -189,9 +189,9 @@ EXPORT_SYMBOL(generic_pipe_buf_steal); | |||
| 189 | * in the tee() system call, when we duplicate the buffers in one | 189 | * in the tee() system call, when we duplicate the buffers in one |
| 190 | * pipe into another. | 190 | * pipe into another. |
| 191 | */ | 191 | */ |
| 192 | void generic_pipe_buf_get(struct pipe_inode_info *pipe, struct pipe_buffer *buf) | 192 | bool generic_pipe_buf_get(struct pipe_inode_info *pipe, struct pipe_buffer *buf) |
| 193 | { | 193 | { |
| 194 | get_page(buf->page); | 194 | return try_get_page(buf->page); |
| 195 | } | 195 | } |
| 196 | EXPORT_SYMBOL(generic_pipe_buf_get); | 196 | EXPORT_SYMBOL(generic_pipe_buf_get); |
| 197 | 197 | ||
diff --git a/fs/splice.c b/fs/splice.c index de2ede048473..f30af82b850d 100644 --- a/fs/splice.c +++ b/fs/splice.c | |||
| @@ -1588,7 +1588,11 @@ retry: | |||
| 1588 | * Get a reference to this pipe buffer, | 1588 | * Get a reference to this pipe buffer, |
| 1589 | * so we can copy the contents over. | 1589 | * so we can copy the contents over. |
| 1590 | */ | 1590 | */ |
| 1591 | pipe_buf_get(ipipe, ibuf); | 1591 | if (!pipe_buf_get(ipipe, ibuf)) { |
| 1592 | if (ret == 0) | ||
| 1593 | ret = -EFAULT; | ||
| 1594 | break; | ||
| 1595 | } | ||
| 1592 | *obuf = *ibuf; | 1596 | *obuf = *ibuf; |
| 1593 | 1597 | ||
| 1594 | /* | 1598 | /* |
| @@ -1660,7 +1664,11 @@ static int link_pipe(struct pipe_inode_info *ipipe, | |||
| 1660 | * Get a reference to this pipe buffer, | 1664 | * Get a reference to this pipe buffer, |
| 1661 | * so we can copy the contents over. | 1665 | * so we can copy the contents over. |
| 1662 | */ | 1666 | */ |
| 1663 | pipe_buf_get(ipipe, ibuf); | 1667 | if (!pipe_buf_get(ipipe, ibuf)) { |
| 1668 | if (ret == 0) | ||
| 1669 | ret = -EFAULT; | ||
| 1670 | break; | ||
| 1671 | } | ||
| 1664 | 1672 | ||
| 1665 | obuf = opipe->bufs + nbuf; | 1673 | obuf = opipe->bufs + nbuf; |
| 1666 | *obuf = *ibuf; | 1674 | *obuf = *ibuf; |
diff --git a/include/linux/pipe_fs_i.h b/include/linux/pipe_fs_i.h index 5a3bb3b7c9ad..3f2a42c11e20 100644 --- a/include/linux/pipe_fs_i.h +++ b/include/linux/pipe_fs_i.h | |||
| @@ -108,18 +108,20 @@ struct pipe_buf_operations { | |||
| 108 | /* | 108 | /* |
| 109 | * Get a reference to the pipe buffer. | 109 | * Get a reference to the pipe buffer. |
| 110 | */ | 110 | */ |
| 111 | void (*get)(struct pipe_inode_info *, struct pipe_buffer *); | 111 | bool (*get)(struct pipe_inode_info *, struct pipe_buffer *); |
| 112 | }; | 112 | }; |
| 113 | 113 | ||
| 114 | /** | 114 | /** |
| 115 | * pipe_buf_get - get a reference to a pipe_buffer | 115 | * pipe_buf_get - get a reference to a pipe_buffer |
| 116 | * @pipe: the pipe that the buffer belongs to | 116 | * @pipe: the pipe that the buffer belongs to |
| 117 | * @buf: the buffer to get a reference to | 117 | * @buf: the buffer to get a reference to |
| 118 | * | ||
| 119 | * Return: %true if the reference was successfully obtained. | ||
| 118 | */ | 120 | */ |
| 119 | static inline void pipe_buf_get(struct pipe_inode_info *pipe, | 121 | static inline __must_check bool pipe_buf_get(struct pipe_inode_info *pipe, |
| 120 | struct pipe_buffer *buf) | 122 | struct pipe_buffer *buf) |
| 121 | { | 123 | { |
| 122 | buf->ops->get(pipe, buf); | 124 | return buf->ops->get(pipe, buf); |
| 123 | } | 125 | } |
| 124 | 126 | ||
| 125 | /** | 127 | /** |
| @@ -178,7 +180,7 @@ struct pipe_inode_info *alloc_pipe_info(void); | |||
| 178 | void free_pipe_info(struct pipe_inode_info *); | 180 | void free_pipe_info(struct pipe_inode_info *); |
| 179 | 181 | ||
| 180 | /* Generic pipe buffer ops functions */ | 182 | /* Generic pipe buffer ops functions */ |
| 181 | void generic_pipe_buf_get(struct pipe_inode_info *, struct pipe_buffer *); | 183 | bool generic_pipe_buf_get(struct pipe_inode_info *, struct pipe_buffer *); |
| 182 | int generic_pipe_buf_confirm(struct pipe_inode_info *, struct pipe_buffer *); | 184 | int generic_pipe_buf_confirm(struct pipe_inode_info *, struct pipe_buffer *); |
| 183 | int generic_pipe_buf_steal(struct pipe_inode_info *, struct pipe_buffer *); | 185 | int generic_pipe_buf_steal(struct pipe_inode_info *, struct pipe_buffer *); |
| 184 | void generic_pipe_buf_release(struct pipe_inode_info *, struct pipe_buffer *); | 186 | void generic_pipe_buf_release(struct pipe_inode_info *, struct pipe_buffer *); |
diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index c4238b441624..0f300d488c9f 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c | |||
| @@ -6835,12 +6835,16 @@ static void buffer_pipe_buf_release(struct pipe_inode_info *pipe, | |||
| 6835 | buf->private = 0; | 6835 | buf->private = 0; |
| 6836 | } | 6836 | } |
| 6837 | 6837 | ||
| 6838 | static void buffer_pipe_buf_get(struct pipe_inode_info *pipe, | 6838 | static bool buffer_pipe_buf_get(struct pipe_inode_info *pipe, |
| 6839 | struct pipe_buffer *buf) | 6839 | struct pipe_buffer *buf) |
| 6840 | { | 6840 | { |
| 6841 | struct buffer_ref *ref = (struct buffer_ref *)buf->private; | 6841 | struct buffer_ref *ref = (struct buffer_ref *)buf->private; |
| 6842 | 6842 | ||
| 6843 | if (ref->ref > INT_MAX/2) | ||
| 6844 | return false; | ||
| 6845 | |||
| 6843 | ref->ref++; | 6846 | ref->ref++; |
| 6847 | return true; | ||
| 6844 | } | 6848 | } |
| 6845 | 6849 | ||
| 6846 | /* Pipe buffer operations for a buffer. */ | 6850 | /* Pipe buffer operations for a buffer. */ |
