mirror of
https://github.com/followmsi/android_kernel_google_msm.git
synced 2024-11-06 23:17:41 +00:00
splice: fix racy pipe->buffers uses
commit047fe36052
upstream. Dave Jones reported a kernel BUG at mm/slub.c:3474! triggered by splice_shrink_spd() called from vmsplice_to_pipe() commit35f3d14dbb
(pipe: add support for shrinking and growing pipes) added capability to adjust pipe->buffers. Problem is some paths don't hold pipe mutex and assume pipe->buffers doesn't change for their duration. Fix this by adding nr_pages_max field in struct splice_pipe_desc, and use it in place of pipe->buffers where appropriate. splice_shrink_spd() loses its struct pipe_inode_info argument. Reported-by: Dave Jones <davej@redhat.com> Signed-off-by: Eric Dumazet <edumazet@google.com> Cc: Jens Axboe <axboe@kernel.dk> Cc: Alexander Viro <viro@zeniv.linux.org.uk> Cc: Tom Herbert <therbert@google.com> Tested-by: Dave Jones <davej@redhat.com> Signed-off-by: Jens Axboe <axboe@kernel.dk> [bwh: Backported to 3.2: - Adjust context in vmsplice_to_pipe() - Update one more call to splice_shrink_spd(), from skb_splice_bits()] Signed-off-by: Ben Hutchings <ben@decadent.org.uk> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
5318edefb6
commit
2c07f25ea7
6 changed files with 35 additions and 25 deletions
35
fs/splice.c
35
fs/splice.c
|
@ -273,13 +273,16 @@ void spd_release_page(struct splice_pipe_desc *spd, unsigned int i)
|
||||||
* Check if we need to grow the arrays holding pages and partial page
|
* Check if we need to grow the arrays holding pages and partial page
|
||||||
* descriptions.
|
* descriptions.
|
||||||
*/
|
*/
|
||||||
int splice_grow_spd(struct pipe_inode_info *pipe, struct splice_pipe_desc *spd)
|
int splice_grow_spd(const struct pipe_inode_info *pipe, struct splice_pipe_desc *spd)
|
||||||
{
|
{
|
||||||
if (pipe->buffers <= PIPE_DEF_BUFFERS)
|
unsigned int buffers = ACCESS_ONCE(pipe->buffers);
|
||||||
|
|
||||||
|
spd->nr_pages_max = buffers;
|
||||||
|
if (buffers <= PIPE_DEF_BUFFERS)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
spd->pages = kmalloc(pipe->buffers * sizeof(struct page *), GFP_KERNEL);
|
spd->pages = kmalloc(buffers * sizeof(struct page *), GFP_KERNEL);
|
||||||
spd->partial = kmalloc(pipe->buffers * sizeof(struct partial_page), GFP_KERNEL);
|
spd->partial = kmalloc(buffers * sizeof(struct partial_page), GFP_KERNEL);
|
||||||
|
|
||||||
if (spd->pages && spd->partial)
|
if (spd->pages && spd->partial)
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -289,10 +292,9 @@ int splice_grow_spd(struct pipe_inode_info *pipe, struct splice_pipe_desc *spd)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
void splice_shrink_spd(struct pipe_inode_info *pipe,
|
void splice_shrink_spd(struct splice_pipe_desc *spd)
|
||||||
struct splice_pipe_desc *spd)
|
|
||||||
{
|
{
|
||||||
if (pipe->buffers <= PIPE_DEF_BUFFERS)
|
if (spd->nr_pages_max <= PIPE_DEF_BUFFERS)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
kfree(spd->pages);
|
kfree(spd->pages);
|
||||||
|
@ -315,6 +317,7 @@ __generic_file_splice_read(struct file *in, loff_t *ppos,
|
||||||
struct splice_pipe_desc spd = {
|
struct splice_pipe_desc spd = {
|
||||||
.pages = pages,
|
.pages = pages,
|
||||||
.partial = partial,
|
.partial = partial,
|
||||||
|
.nr_pages_max = PIPE_DEF_BUFFERS,
|
||||||
.flags = flags,
|
.flags = flags,
|
||||||
.ops = &page_cache_pipe_buf_ops,
|
.ops = &page_cache_pipe_buf_ops,
|
||||||
.spd_release = spd_release_page,
|
.spd_release = spd_release_page,
|
||||||
|
@ -326,7 +329,7 @@ __generic_file_splice_read(struct file *in, loff_t *ppos,
|
||||||
index = *ppos >> PAGE_CACHE_SHIFT;
|
index = *ppos >> PAGE_CACHE_SHIFT;
|
||||||
loff = *ppos & ~PAGE_CACHE_MASK;
|
loff = *ppos & ~PAGE_CACHE_MASK;
|
||||||
req_pages = (len + loff + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT;
|
req_pages = (len + loff + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT;
|
||||||
nr_pages = min(req_pages, pipe->buffers);
|
nr_pages = min(req_pages, spd.nr_pages_max);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Lookup the (hopefully) full range of pages we need.
|
* Lookup the (hopefully) full range of pages we need.
|
||||||
|
@ -497,7 +500,7 @@ fill_it:
|
||||||
if (spd.nr_pages)
|
if (spd.nr_pages)
|
||||||
error = splice_to_pipe(pipe, &spd);
|
error = splice_to_pipe(pipe, &spd);
|
||||||
|
|
||||||
splice_shrink_spd(pipe, &spd);
|
splice_shrink_spd(&spd);
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -598,6 +601,7 @@ ssize_t default_file_splice_read(struct file *in, loff_t *ppos,
|
||||||
struct splice_pipe_desc spd = {
|
struct splice_pipe_desc spd = {
|
||||||
.pages = pages,
|
.pages = pages,
|
||||||
.partial = partial,
|
.partial = partial,
|
||||||
|
.nr_pages_max = PIPE_DEF_BUFFERS,
|
||||||
.flags = flags,
|
.flags = flags,
|
||||||
.ops = &default_pipe_buf_ops,
|
.ops = &default_pipe_buf_ops,
|
||||||
.spd_release = spd_release_page,
|
.spd_release = spd_release_page,
|
||||||
|
@ -608,8 +612,8 @@ ssize_t default_file_splice_read(struct file *in, loff_t *ppos,
|
||||||
|
|
||||||
res = -ENOMEM;
|
res = -ENOMEM;
|
||||||
vec = __vec;
|
vec = __vec;
|
||||||
if (pipe->buffers > PIPE_DEF_BUFFERS) {
|
if (spd.nr_pages_max > PIPE_DEF_BUFFERS) {
|
||||||
vec = kmalloc(pipe->buffers * sizeof(struct iovec), GFP_KERNEL);
|
vec = kmalloc(spd.nr_pages_max * sizeof(struct iovec), GFP_KERNEL);
|
||||||
if (!vec)
|
if (!vec)
|
||||||
goto shrink_ret;
|
goto shrink_ret;
|
||||||
}
|
}
|
||||||
|
@ -617,7 +621,7 @@ ssize_t default_file_splice_read(struct file *in, loff_t *ppos,
|
||||||
offset = *ppos & ~PAGE_CACHE_MASK;
|
offset = *ppos & ~PAGE_CACHE_MASK;
|
||||||
nr_pages = (len + offset + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT;
|
nr_pages = (len + offset + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT;
|
||||||
|
|
||||||
for (i = 0; i < nr_pages && i < pipe->buffers && len; i++) {
|
for (i = 0; i < nr_pages && i < spd.nr_pages_max && len; i++) {
|
||||||
struct page *page;
|
struct page *page;
|
||||||
|
|
||||||
page = alloc_page(GFP_USER);
|
page = alloc_page(GFP_USER);
|
||||||
|
@ -665,7 +669,7 @@ ssize_t default_file_splice_read(struct file *in, loff_t *ppos,
|
||||||
shrink_ret:
|
shrink_ret:
|
||||||
if (vec != __vec)
|
if (vec != __vec)
|
||||||
kfree(vec);
|
kfree(vec);
|
||||||
splice_shrink_spd(pipe, &spd);
|
splice_shrink_spd(&spd);
|
||||||
return res;
|
return res;
|
||||||
|
|
||||||
err:
|
err:
|
||||||
|
@ -1612,6 +1616,7 @@ static long vmsplice_to_pipe(struct file *file, const struct iovec __user *iov,
|
||||||
struct splice_pipe_desc spd = {
|
struct splice_pipe_desc spd = {
|
||||||
.pages = pages,
|
.pages = pages,
|
||||||
.partial = partial,
|
.partial = partial,
|
||||||
|
.nr_pages_max = PIPE_DEF_BUFFERS,
|
||||||
.flags = flags,
|
.flags = flags,
|
||||||
.ops = &user_page_pipe_buf_ops,
|
.ops = &user_page_pipe_buf_ops,
|
||||||
.spd_release = spd_release_page,
|
.spd_release = spd_release_page,
|
||||||
|
@ -1627,13 +1632,13 @@ static long vmsplice_to_pipe(struct file *file, const struct iovec __user *iov,
|
||||||
|
|
||||||
spd.nr_pages = get_iovec_page_array(iov, nr_segs, spd.pages,
|
spd.nr_pages = get_iovec_page_array(iov, nr_segs, spd.pages,
|
||||||
spd.partial, flags & SPLICE_F_GIFT,
|
spd.partial, flags & SPLICE_F_GIFT,
|
||||||
pipe->buffers);
|
spd.nr_pages_max);
|
||||||
if (spd.nr_pages <= 0)
|
if (spd.nr_pages <= 0)
|
||||||
ret = spd.nr_pages;
|
ret = spd.nr_pages;
|
||||||
else
|
else
|
||||||
ret = splice_to_pipe(pipe, &spd);
|
ret = splice_to_pipe(pipe, &spd);
|
||||||
|
|
||||||
splice_shrink_spd(pipe, &spd);
|
splice_shrink_spd(&spd);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,8 @@ struct partial_page {
|
||||||
struct splice_pipe_desc {
|
struct splice_pipe_desc {
|
||||||
struct page **pages; /* page map */
|
struct page **pages; /* page map */
|
||||||
struct partial_page *partial; /* pages[] may not be contig */
|
struct partial_page *partial; /* pages[] may not be contig */
|
||||||
int nr_pages; /* number of pages in map */
|
int nr_pages; /* number of populated pages in map */
|
||||||
|
unsigned int nr_pages_max; /* pages[] & partial[] arrays size */
|
||||||
unsigned int flags; /* splice flags */
|
unsigned int flags; /* splice flags */
|
||||||
const struct pipe_buf_operations *ops;/* ops associated with output pipe */
|
const struct pipe_buf_operations *ops;/* ops associated with output pipe */
|
||||||
void (*spd_release)(struct splice_pipe_desc *, unsigned int);
|
void (*spd_release)(struct splice_pipe_desc *, unsigned int);
|
||||||
|
@ -85,9 +86,8 @@ extern ssize_t splice_direct_to_actor(struct file *, struct splice_desc *,
|
||||||
/*
|
/*
|
||||||
* for dynamic pipe sizing
|
* for dynamic pipe sizing
|
||||||
*/
|
*/
|
||||||
extern int splice_grow_spd(struct pipe_inode_info *, struct splice_pipe_desc *);
|
extern int splice_grow_spd(const struct pipe_inode_info *, struct splice_pipe_desc *);
|
||||||
extern void splice_shrink_spd(struct pipe_inode_info *,
|
extern void splice_shrink_spd(struct splice_pipe_desc *);
|
||||||
struct splice_pipe_desc *);
|
|
||||||
extern void spd_release_page(struct splice_pipe_desc *, unsigned int);
|
extern void spd_release_page(struct splice_pipe_desc *, unsigned int);
|
||||||
|
|
||||||
extern const struct pipe_buf_operations page_cache_pipe_buf_ops;
|
extern const struct pipe_buf_operations page_cache_pipe_buf_ops;
|
||||||
|
|
|
@ -1235,6 +1235,7 @@ static ssize_t subbuf_splice_actor(struct file *in,
|
||||||
struct splice_pipe_desc spd = {
|
struct splice_pipe_desc spd = {
|
||||||
.pages = pages,
|
.pages = pages,
|
||||||
.nr_pages = 0,
|
.nr_pages = 0,
|
||||||
|
.nr_pages_max = PIPE_DEF_BUFFERS,
|
||||||
.partial = partial,
|
.partial = partial,
|
||||||
.flags = flags,
|
.flags = flags,
|
||||||
.ops = &relay_pipe_buf_ops,
|
.ops = &relay_pipe_buf_ops,
|
||||||
|
@ -1302,8 +1303,8 @@ static ssize_t subbuf_splice_actor(struct file *in,
|
||||||
ret += padding;
|
ret += padding;
|
||||||
|
|
||||||
out:
|
out:
|
||||||
splice_shrink_spd(pipe, &spd);
|
splice_shrink_spd(&spd);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t relay_file_splice_read(struct file *in,
|
static ssize_t relay_file_splice_read(struct file *in,
|
||||||
|
|
|
@ -3565,6 +3565,7 @@ static ssize_t tracing_splice_read_pipe(struct file *filp,
|
||||||
.pages = pages_def,
|
.pages = pages_def,
|
||||||
.partial = partial_def,
|
.partial = partial_def,
|
||||||
.nr_pages = 0, /* This gets updated below. */
|
.nr_pages = 0, /* This gets updated below. */
|
||||||
|
.nr_pages_max = PIPE_DEF_BUFFERS,
|
||||||
.flags = flags,
|
.flags = flags,
|
||||||
.ops = &tracing_pipe_buf_ops,
|
.ops = &tracing_pipe_buf_ops,
|
||||||
.spd_release = tracing_spd_release_pipe,
|
.spd_release = tracing_spd_release_pipe,
|
||||||
|
@ -3636,7 +3637,7 @@ static ssize_t tracing_splice_read_pipe(struct file *filp,
|
||||||
|
|
||||||
ret = splice_to_pipe(pipe, &spd);
|
ret = splice_to_pipe(pipe, &spd);
|
||||||
out:
|
out:
|
||||||
splice_shrink_spd(pipe, &spd);
|
splice_shrink_spd(&spd);
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
out_err:
|
out_err:
|
||||||
|
@ -4126,6 +4127,7 @@ tracing_buffers_splice_read(struct file *file, loff_t *ppos,
|
||||||
struct splice_pipe_desc spd = {
|
struct splice_pipe_desc spd = {
|
||||||
.pages = pages_def,
|
.pages = pages_def,
|
||||||
.partial = partial_def,
|
.partial = partial_def,
|
||||||
|
.nr_pages_max = PIPE_DEF_BUFFERS,
|
||||||
.flags = flags,
|
.flags = flags,
|
||||||
.ops = &buffer_pipe_buf_ops,
|
.ops = &buffer_pipe_buf_ops,
|
||||||
.spd_release = buffer_spd_release,
|
.spd_release = buffer_spd_release,
|
||||||
|
@ -4213,7 +4215,7 @@ tracing_buffers_splice_read(struct file *file, loff_t *ppos,
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = splice_to_pipe(pipe, &spd);
|
ret = splice_to_pipe(pipe, &spd);
|
||||||
splice_shrink_spd(pipe, &spd);
|
splice_shrink_spd(&spd);
|
||||||
out:
|
out:
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1365,6 +1365,7 @@ static ssize_t shmem_file_splice_read(struct file *in, loff_t *ppos,
|
||||||
struct splice_pipe_desc spd = {
|
struct splice_pipe_desc spd = {
|
||||||
.pages = pages,
|
.pages = pages,
|
||||||
.partial = partial,
|
.partial = partial,
|
||||||
|
.nr_pages_max = PIPE_DEF_BUFFERS,
|
||||||
.flags = flags,
|
.flags = flags,
|
||||||
.ops = &page_cache_pipe_buf_ops,
|
.ops = &page_cache_pipe_buf_ops,
|
||||||
.spd_release = spd_release_page,
|
.spd_release = spd_release_page,
|
||||||
|
@ -1453,7 +1454,7 @@ static ssize_t shmem_file_splice_read(struct file *in, loff_t *ppos,
|
||||||
if (spd.nr_pages)
|
if (spd.nr_pages)
|
||||||
error = splice_to_pipe(pipe, &spd);
|
error = splice_to_pipe(pipe, &spd);
|
||||||
|
|
||||||
splice_shrink_spd(pipe, &spd);
|
splice_shrink_spd(&spd);
|
||||||
|
|
||||||
if (error > 0) {
|
if (error > 0) {
|
||||||
*ppos += error;
|
*ppos += error;
|
||||||
|
|
|
@ -1712,6 +1712,7 @@ int skb_splice_bits(struct sk_buff *skb, unsigned int offset,
|
||||||
struct splice_pipe_desc spd = {
|
struct splice_pipe_desc spd = {
|
||||||
.pages = pages,
|
.pages = pages,
|
||||||
.partial = partial,
|
.partial = partial,
|
||||||
|
.nr_pages_max = MAX_SKB_FRAGS,
|
||||||
.flags = flags,
|
.flags = flags,
|
||||||
.ops = &sock_pipe_buf_ops,
|
.ops = &sock_pipe_buf_ops,
|
||||||
.spd_release = sock_spd_release,
|
.spd_release = sock_spd_release,
|
||||||
|
@ -1758,7 +1759,7 @@ done:
|
||||||
lock_sock(sk);
|
lock_sock(sk);
|
||||||
}
|
}
|
||||||
|
|
||||||
splice_shrink_spd(pipe, &spd);
|
splice_shrink_spd(&spd);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue