Commit e3d4ed26 authored by Ben Hutchings's avatar Ben Hutchings Committed by Jiri Slaby

pipe: iovec: Fix memory corruption when retrying atomic copy as non-atomic

pipe_iov_copy_{from,to}_user() may be tried twice with the same iovec,
the first time atomically and the second time not.  The second attempt
needs to continue from the iovec position, pipe buffer offset and
remaining length where the first attempt failed, but currently the
pipe buffer offset and remaining length are reset.  This will corrupt
the piped data (possibly also leading to an information leak between
processes) and may also corrupt kernel memory.

This was fixed upstream by commits f0d1bec9 ("new helper:
copy_page_from_iter()") and 637b58c2 ("switch pipe_read() to
copy_page_to_iter()"), but those aren't suitable for stable.  This fix
for older kernel versions was made by Seth Jennings for RHEL and I
have extracted it from their update.

CVE-2015-1805

References: https://bugzilla.redhat.com/show_bug.cgi?id=1202855Signed-off-by: default avatarBen Hutchings <ben@decadent.org.uk>
Signed-off-by: default avatarJiri Slaby <jslaby@suse.cz>
parent 7ae9f79d
...@@ -117,25 +117,27 @@ void pipe_wait(struct pipe_inode_info *pipe) ...@@ -117,25 +117,27 @@ void pipe_wait(struct pipe_inode_info *pipe)
} }
static int static int
pipe_iov_copy_from_user(void *to, struct iovec *iov, unsigned long len, pipe_iov_copy_from_user(void *addr, int *offset, struct iovec *iov,
int atomic) size_t *remaining, int atomic)
{ {
unsigned long copy; unsigned long copy;
while (len > 0) { while (*remaining > 0) {
while (!iov->iov_len) while (!iov->iov_len)
iov++; iov++;
copy = min_t(unsigned long, len, iov->iov_len); copy = min_t(unsigned long, *remaining, iov->iov_len);
if (atomic) { if (atomic) {
if (__copy_from_user_inatomic(to, iov->iov_base, copy)) if (__copy_from_user_inatomic(addr + *offset,
iov->iov_base, copy))
return -EFAULT; return -EFAULT;
} else { } else {
if (copy_from_user(to, iov->iov_base, copy)) if (copy_from_user(addr + *offset,
iov->iov_base, copy))
return -EFAULT; return -EFAULT;
} }
to += copy; *offset += copy;
len -= copy; *remaining -= copy;
iov->iov_base += copy; iov->iov_base += copy;
iov->iov_len -= copy; iov->iov_len -= copy;
} }
...@@ -143,25 +145,27 @@ pipe_iov_copy_from_user(void *to, struct iovec *iov, unsigned long len, ...@@ -143,25 +145,27 @@ pipe_iov_copy_from_user(void *to, struct iovec *iov, unsigned long len,
} }
static int static int
pipe_iov_copy_to_user(struct iovec *iov, const void *from, unsigned long len, pipe_iov_copy_to_user(struct iovec *iov, void *addr, int *offset,
int atomic) size_t *remaining, int atomic)
{ {
unsigned long copy; unsigned long copy;
while (len > 0) { while (*remaining > 0) {
while (!iov->iov_len) while (!iov->iov_len)
iov++; iov++;
copy = min_t(unsigned long, len, iov->iov_len); copy = min_t(unsigned long, *remaining, iov->iov_len);
if (atomic) { if (atomic) {
if (__copy_to_user_inatomic(iov->iov_base, from, copy)) if (__copy_to_user_inatomic(iov->iov_base,
addr + *offset, copy))
return -EFAULT; return -EFAULT;
} else { } else {
if (copy_to_user(iov->iov_base, from, copy)) if (copy_to_user(iov->iov_base,
addr + *offset, copy))
return -EFAULT; return -EFAULT;
} }
from += copy; *offset += copy;
len -= copy; *remaining -= copy;
iov->iov_base += copy; iov->iov_base += copy;
iov->iov_len -= copy; iov->iov_len -= copy;
} }
...@@ -395,7 +399,7 @@ pipe_read(struct kiocb *iocb, const struct iovec *_iov, ...@@ -395,7 +399,7 @@ pipe_read(struct kiocb *iocb, const struct iovec *_iov,
struct pipe_buffer *buf = pipe->bufs + curbuf; struct pipe_buffer *buf = pipe->bufs + curbuf;
const struct pipe_buf_operations *ops = buf->ops; const struct pipe_buf_operations *ops = buf->ops;
void *addr; void *addr;
size_t chars = buf->len; size_t chars = buf->len, remaining;
int error, atomic; int error, atomic;
if (chars > total_len) if (chars > total_len)
...@@ -409,9 +413,11 @@ pipe_read(struct kiocb *iocb, const struct iovec *_iov, ...@@ -409,9 +413,11 @@ pipe_read(struct kiocb *iocb, const struct iovec *_iov,
} }
atomic = !iov_fault_in_pages_write(iov, chars); atomic = !iov_fault_in_pages_write(iov, chars);
remaining = chars;
redo: redo:
addr = ops->map(pipe, buf, atomic); addr = ops->map(pipe, buf, atomic);
error = pipe_iov_copy_to_user(iov, addr + buf->offset, chars, atomic); error = pipe_iov_copy_to_user(iov, addr, &buf->offset,
&remaining, atomic);
ops->unmap(pipe, buf, addr); ops->unmap(pipe, buf, addr);
if (unlikely(error)) { if (unlikely(error)) {
/* /*
...@@ -426,7 +432,6 @@ pipe_read(struct kiocb *iocb, const struct iovec *_iov, ...@@ -426,7 +432,6 @@ pipe_read(struct kiocb *iocb, const struct iovec *_iov,
break; break;
} }
ret += chars; ret += chars;
buf->offset += chars;
buf->len -= chars; buf->len -= chars;
/* Was it a packet buffer? Clean up and exit */ /* Was it a packet buffer? Clean up and exit */
...@@ -531,6 +536,7 @@ pipe_write(struct kiocb *iocb, const struct iovec *_iov, ...@@ -531,6 +536,7 @@ pipe_write(struct kiocb *iocb, const struct iovec *_iov,
if (ops->can_merge && offset + chars <= PAGE_SIZE) { if (ops->can_merge && offset + chars <= PAGE_SIZE) {
int error, atomic = 1; int error, atomic = 1;
void *addr; void *addr;
size_t remaining = chars;
error = ops->confirm(pipe, buf); error = ops->confirm(pipe, buf);
if (error) if (error)
...@@ -539,8 +545,8 @@ pipe_write(struct kiocb *iocb, const struct iovec *_iov, ...@@ -539,8 +545,8 @@ pipe_write(struct kiocb *iocb, const struct iovec *_iov,
iov_fault_in_pages_read(iov, chars); iov_fault_in_pages_read(iov, chars);
redo1: redo1:
addr = ops->map(pipe, buf, atomic); addr = ops->map(pipe, buf, atomic);
error = pipe_iov_copy_from_user(offset + addr, iov, error = pipe_iov_copy_from_user(addr, &offset, iov,
chars, atomic); &remaining, atomic);
ops->unmap(pipe, buf, addr); ops->unmap(pipe, buf, addr);
ret = error; ret = error;
do_wakeup = 1; do_wakeup = 1;
...@@ -575,6 +581,8 @@ pipe_write(struct kiocb *iocb, const struct iovec *_iov, ...@@ -575,6 +581,8 @@ pipe_write(struct kiocb *iocb, const struct iovec *_iov,
struct page *page = pipe->tmp_page; struct page *page = pipe->tmp_page;
char *src; char *src;
int error, atomic = 1; int error, atomic = 1;
int offset = 0;
size_t remaining;
if (!page) { if (!page) {
page = alloc_page(GFP_HIGHUSER); page = alloc_page(GFP_HIGHUSER);
...@@ -595,14 +603,15 @@ pipe_write(struct kiocb *iocb, const struct iovec *_iov, ...@@ -595,14 +603,15 @@ pipe_write(struct kiocb *iocb, const struct iovec *_iov,
chars = total_len; chars = total_len;
iov_fault_in_pages_read(iov, chars); iov_fault_in_pages_read(iov, chars);
remaining = chars;
redo2: redo2:
if (atomic) if (atomic)
src = kmap_atomic(page); src = kmap_atomic(page);
else else
src = kmap(page); src = kmap(page);
error = pipe_iov_copy_from_user(src, iov, chars, error = pipe_iov_copy_from_user(src, &offset, iov,
atomic); &remaining, atomic);
if (atomic) if (atomic)
kunmap_atomic(src); kunmap_atomic(src);
else else
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment