[Acked] [Trusty][CVE-2015-1805] pipe: iovec: Fix memory corruption when retrying atomic copy as non-atomic

Andy Whitcroft apw at canonical.com
Wed Jul 1 08:42:49 UTC 2015


On Mon, Jun 29, 2015 at 02:10:57PM -0700, Kamal Mostafa wrote:
> From: Ben Hutchings <ben at decadent.org.uk>
> 
> 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 f0d1bec9d58d ("new helper:
> copy_page_from_iter()") and 637b58c2887e ("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=1202855
> Signed-off-by: Ben Hutchings <ben at decadent.org.uk>
> Signed-off-by: Kamal Mostafa <kamal at canonical.com>
> ---
>  fs/pipe.c | 55 ++++++++++++++++++++++++++++++++-----------------------
>  1 file changed, 32 insertions(+), 23 deletions(-)
> 
> diff --git a/fs/pipe.c b/fs/pipe.c
> index 0e0752e..3e7ab27 100644
> --- a/fs/pipe.c
> +++ b/fs/pipe.c
> @@ -117,25 +117,27 @@ void pipe_wait(struct pipe_inode_info *pipe)
>  }
>  
>  static int
> -pipe_iov_copy_from_user(void *to, struct iovec *iov, unsigned long len,
> -			int atomic)
> +pipe_iov_copy_from_user(void *addr, int *offset, struct iovec *iov,
> +			size_t *remaining, int atomic)
>  {
>  	unsigned long copy;
>  
> -	while (len > 0) {
> +	while (*remaining > 0) {
>  		while (!iov->iov_len)
>  			iov++;
> -		copy = min_t(unsigned long, len, iov->iov_len);
> +		copy = min_t(unsigned long, *remaining, iov->iov_len);
>  
>  		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;
>  		} else {
> -			if (copy_from_user(to, iov->iov_base, copy))
> +			if (copy_from_user(addr + *offset,
> +					   iov->iov_base, copy))
>  				return -EFAULT;
>  		}
> -		to += copy;
> -		len -= copy;
> +		*offset += copy;
> +		*remaining -= copy;
>  		iov->iov_base += copy;
>  		iov->iov_len -= copy;
>  	}
> @@ -143,25 +145,27 @@ pipe_iov_copy_from_user(void *to, struct iovec *iov, unsigned long len,
>  }
>  
>  static int
> -pipe_iov_copy_to_user(struct iovec *iov, const void *from, unsigned long len,
> -		      int atomic)
> +pipe_iov_copy_to_user(struct iovec *iov, void *addr, int *offset,
> +		      size_t *remaining, int atomic)
>  {
>  	unsigned long copy;
>  
> -	while (len > 0) {
> +	while (*remaining > 0) {
>  		while (!iov->iov_len)
>  			iov++;
> -		copy = min_t(unsigned long, len, iov->iov_len);
> +		copy = min_t(unsigned long, *remaining, iov->iov_len);
>  
>  		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;
>  		} else {
> -			if (copy_to_user(iov->iov_base, from, copy))
> +			if (copy_to_user(iov->iov_base,
> +					 addr + *offset, copy))
>  				return -EFAULT;
>  		}
> -		from += copy;
> -		len -= copy;
> +		*offset += copy;
> +		*remaining -= copy;
>  		iov->iov_base += copy;
>  		iov->iov_len -= copy;
>  	}
> @@ -395,7 +399,7 @@ pipe_read(struct kiocb *iocb, const struct iovec *_iov,
>  			struct pipe_buffer *buf = pipe->bufs + curbuf;
>  			const struct pipe_buf_operations *ops = buf->ops;
>  			void *addr;
> -			size_t chars = buf->len;
> +			size_t chars = buf->len, remaining;
>  			int error, atomic;
>  
>  			if (chars > total_len)
> @@ -409,9 +413,11 @@ pipe_read(struct kiocb *iocb, const struct iovec *_iov,
>  			}
>  
>  			atomic = !iov_fault_in_pages_write(iov, chars);
> +			remaining = chars;
>  redo:
>  			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);
>  			if (unlikely(error)) {
>  				/*
> @@ -426,7 +432,6 @@ redo:
>  				break;
>  			}
>  			ret += chars;
> -			buf->offset += chars;
>  			buf->len -= chars;
>  
>  			/* Was it a packet buffer? Clean up and exit */
> @@ -531,6 +536,7 @@ pipe_write(struct kiocb *iocb, const struct iovec *_iov,
>  		if (ops->can_merge && offset + chars <= PAGE_SIZE) {
>  			int error, atomic = 1;
>  			void *addr;
> +			size_t remaining = chars;
>  
>  			error = ops->confirm(pipe, buf);
>  			if (error)
> @@ -539,8 +545,8 @@ pipe_write(struct kiocb *iocb, const struct iovec *_iov,
>  			iov_fault_in_pages_read(iov, chars);
>  redo1:
>  			addr = ops->map(pipe, buf, atomic);
> -			error = pipe_iov_copy_from_user(offset + addr, iov,
> -							chars, atomic);
> +			error = pipe_iov_copy_from_user(addr, &offset, iov,
> +							&remaining, atomic);
>  			ops->unmap(pipe, buf, addr);
>  			ret = error;
>  			do_wakeup = 1;
> @@ -575,6 +581,8 @@ redo1:
>  			struct page *page = pipe->tmp_page;
>  			char *src;
>  			int error, atomic = 1;
> +			int offset = 0;
> +			size_t remaining;
>  
>  			if (!page) {
>  				page = alloc_page(GFP_HIGHUSER);
> @@ -595,14 +603,15 @@ redo1:
>  				chars = total_len;
>  
>  			iov_fault_in_pages_read(iov, chars);
> +			remaining = chars;
>  redo2:
>  			if (atomic)
>  				src = kmap_atomic(page);
>  			else
>  				src = kmap(page);
>  
> -			error = pipe_iov_copy_from_user(src, iov, chars,
> -							atomic);
> +			error = pipe_iov_copy_from_user(src, &offset, iov,
> +							&remaining, atomic);
>  			if (atomic)
>  				kunmap_atomic(src);
>  			else

Seems to track offset,remaining in a reasonable manner.  As it changes
an interface we should see explosions in build if this is used anywhere
else.

Acked-by: Andy Whitcroft <apw at canonical.com>

-apw




More information about the kernel-team mailing list