[3.11.y.z extended stable] Patch "vfs: fix subtle use-after-free of pipe_inode_info" has been added to staging queue

Keng-Yu Lin kengyu at canonical.com
Thu Dec 12 14:29:41 UTC 2013


Hi there:
  By any chance can this patch make itself included in the 12.04.4
point release?

  It is found that this patch fixes kernel oops occurring with the
current Ubuntu 3.11-based kernel. (the oops happens in roughly 30 hrs
in a stress test environment on a server machine).

  The patch is already in the upstream 3.10.y and 3.12.y stable trees,
but has no change to be in the last 3.11.y release (this is 3.11.10) .

  cheers,
-kengyu

On Wed, Dec 11, 2013 at 10:41 PM, Luis Henriques
<luis.henriques at canonical.com> wrote:
> This is a note to let you know that I have just added a patch titled
>
>     vfs: fix subtle use-after-free of pipe_inode_info
>
> to the linux-3.11.y-queue branch of the 3.11.y.z extended stable tree
> which can be found at:
>
>  http://kernel.ubuntu.com/git?p=ubuntu/linux.git;a=shortlog;h=refs/heads/linux-3.11.y-queue
>
> If you, or anyone else, feels it should not be added to this tree, please
> reply to this email.
>
> For more information about the 3.11.y.z tree, see
> https://wiki.ubuntu.com/Kernel/Dev/ExtendedStable
>
> Thanks.
> -Luis
>
> ------
>
> From ddd487a2f0b5b804751a2972e99071d657d8a53d Mon Sep 17 00:00:00 2001
> From: Linus Torvalds <torvalds at linux-foundation.org>
> Date: Mon, 2 Dec 2013 09:44:51 -0800
> Subject: vfs: fix subtle use-after-free of pipe_inode_info
>
> commit b0d8d2292160bb63de1972361ebed100c64b5b37 upstream.
>
> The pipe code was trying (and failing) to be very careful about freeing
> the pipe info only after the last access, with a pattern like:
>
>         spin_lock(&inode->i_lock);
>         if (!--pipe->files) {
>                 inode->i_pipe = NULL;
>                 kill = 1;
>         }
>         spin_unlock(&inode->i_lock);
>         __pipe_unlock(pipe);
>         if (kill)
>                 free_pipe_info(pipe);
>
> where the final freeing is done last.
>
> HOWEVER.  The above is actually broken, because while the freeing is
> done at the end, if we have two racing processes releasing the pipe
> inode info, the one that *doesn't* free it will decrement the ->files
> count, and unlock the inode i_lock, but then still use the
> "pipe_inode_info" afterwards when it does the "__pipe_unlock(pipe)".
>
> This is *very* hard to trigger in practice, since the race window is
> very small, and adding debug options seems to just hide it by slowing
> things down.
>
> Simon originally reported this way back in July as an Oops in
> kmem_cache_allocate due to a single bit corruption (due to the final
> "spin_unlock(pipe->mutex.wait_lock)" incrementing a field in a different
> allocation that had re-used the free'd pipe-info), it's taken this long
> to figure out.
>
> Since the 'pipe->files' accesses aren't even protected by the pipe lock
> (we very much use the inode lock for that), the simple solution is to
> just drop the pipe lock early.  And since there were two users of this
> pattern, create a helper function for it.
>
> Introduced commit ba5bb147330a ("pipe: take allocation and freeing of
> pipe_inode_info out of ->i_mutex").
>
> Reported-by: Simon Kirby <sim at hostway.ca>
> Reported-by: Ian Applegate <ia at cloudflare.com>
> Acked-by: Al Viro <viro at zeniv.linux.org.uk>
> Signed-off-by: Linus Torvalds <torvalds at linux-foundation.org>
> Signed-off-by: Luis Henriques <luis.henriques at canonical.com>
> ---
>  fs/pipe.c | 39 +++++++++++++++++++--------------------
>  1 file changed, 19 insertions(+), 20 deletions(-)
>
> diff --git a/fs/pipe.c b/fs/pipe.c
> index d2c45e1..0e0752e 100644
> --- a/fs/pipe.c
> +++ b/fs/pipe.c
> @@ -726,11 +726,25 @@ pipe_poll(struct file *filp, poll_table *wait)
>         return mask;
>  }
>
> +static void put_pipe_info(struct inode *inode, struct pipe_inode_info *pipe)
> +{
> +       int kill = 0;
> +
> +       spin_lock(&inode->i_lock);
> +       if (!--pipe->files) {
> +               inode->i_pipe = NULL;
> +               kill = 1;
> +       }
> +       spin_unlock(&inode->i_lock);
> +
> +       if (kill)
> +               free_pipe_info(pipe);
> +}
> +
>  static int
>  pipe_release(struct inode *inode, struct file *file)
>  {
> -       struct pipe_inode_info *pipe = inode->i_pipe;
> -       int kill = 0;
> +       struct pipe_inode_info *pipe = file->private_data;
>
>         __pipe_lock(pipe);
>         if (file->f_mode & FMODE_READ)
> @@ -743,17 +757,9 @@ pipe_release(struct inode *inode, struct file *file)
>                 kill_fasync(&pipe->fasync_readers, SIGIO, POLL_IN);
>                 kill_fasync(&pipe->fasync_writers, SIGIO, POLL_OUT);
>         }
> -       spin_lock(&inode->i_lock);
> -       if (!--pipe->files) {
> -               inode->i_pipe = NULL;
> -               kill = 1;
> -       }
> -       spin_unlock(&inode->i_lock);
>         __pipe_unlock(pipe);
>
> -       if (kill)
> -               free_pipe_info(pipe);
> -
> +       put_pipe_info(inode, pipe);
>         return 0;
>  }
>
> @@ -1014,7 +1020,6 @@ static int fifo_open(struct inode *inode, struct file *filp)
>  {
>         struct pipe_inode_info *pipe;
>         bool is_pipe = inode->i_sb->s_magic == PIPEFS_MAGIC;
> -       int kill = 0;
>         int ret;
>
>         filp->f_version = 0;
> @@ -1130,15 +1135,9 @@ err_wr:
>         goto err;
>
>  err:
> -       spin_lock(&inode->i_lock);
> -       if (!--pipe->files) {
> -               inode->i_pipe = NULL;
> -               kill = 1;
> -       }
> -       spin_unlock(&inode->i_lock);
>         __pipe_unlock(pipe);
> -       if (kill)
> -               free_pipe_info(pipe);
> +
> +       put_pipe_info(inode, pipe);
>         return ret;
>  }
>
> --
> 1.8.3.2
>
>
> --
> kernel-team mailing list
> kernel-team at lists.ubuntu.com
> https://lists.ubuntu.com/mailman/listinfo/kernel-team




More information about the kernel-team mailing list