lkml.org 
[lkml]   [2022]   [Jun]   [3]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH 5.15 14/66] pipe: Fix missing lock in pipe_resize_ring()
    Date
    From: David Howells <dhowells@redhat.com>

    commit 189b0ddc245139af81198d1a3637cac74f96e13a upstream.

    pipe_resize_ring() needs to take the pipe->rd_wait.lock spinlock to
    prevent post_one_notification() from trying to insert into the ring
    whilst the ring is being replaced.

    The occupancy check must be done after the lock is taken, and the lock
    must be taken after the new ring is allocated.

    The bug can lead to an oops looking something like:

    BUG: KASAN: use-after-free in post_one_notification.isra.0+0x62e/0x840
    Read of size 4 at addr ffff88801cc72a70 by task poc/27196
    ...
    Call Trace:
    post_one_notification.isra.0+0x62e/0x840
    __post_watch_notification+0x3b7/0x650
    key_create_or_update+0xb8b/0xd20
    __do_sys_add_key+0x175/0x340
    __x64_sys_add_key+0xbe/0x140
    do_syscall_64+0x5c/0xc0
    entry_SYSCALL_64_after_hwframe+0x44/0xae

    Reported by Selim Enes Karaduman @Enesdex working with Trend Micro Zero
    Day Initiative.

    Fixes: c73be61cede5 ("pipe: Add general notification queue support")
    Reported-by: zdi-disclosures@trendmicro.com # ZDI-CAN-17291
    Signed-off-by: David Howells <dhowells@redhat.com>
    Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
    Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
    ---
    fs/pipe.c | 31 ++++++++++++++++++-------------
    1 file changed, 18 insertions(+), 13 deletions(-)

    --- a/fs/pipe.c
    +++ b/fs/pipe.c
    @@ -1244,30 +1244,33 @@ unsigned int round_pipe_size(unsigned lo

    /*
    * Resize the pipe ring to a number of slots.
    + *
    + * Note the pipe can be reduced in capacity, but only if the current
    + * occupancy doesn't exceed nr_slots; if it does, EBUSY will be
    + * returned instead.
    */
    int pipe_resize_ring(struct pipe_inode_info *pipe, unsigned int nr_slots)
    {
    struct pipe_buffer *bufs;
    unsigned int head, tail, mask, n;

    - /*
    - * We can shrink the pipe, if arg is greater than the ring occupancy.
    - * Since we don't expect a lot of shrink+grow operations, just free and
    - * allocate again like we would do for growing. If the pipe currently
    - * contains more buffers than arg, then return busy.
    - */
    - mask = pipe->ring_size - 1;
    - head = pipe->head;
    - tail = pipe->tail;
    - n = pipe_occupancy(pipe->head, pipe->tail);
    - if (nr_slots < n)
    - return -EBUSY;
    -
    bufs = kcalloc(nr_slots, sizeof(*bufs),
    GFP_KERNEL_ACCOUNT | __GFP_NOWARN);
    if (unlikely(!bufs))
    return -ENOMEM;

    + spin_lock_irq(&pipe->rd_wait.lock);
    + mask = pipe->ring_size - 1;
    + head = pipe->head;
    + tail = pipe->tail;
    +
    + n = pipe_occupancy(head, tail);
    + if (nr_slots < n) {
    + spin_unlock_irq(&pipe->rd_wait.lock);
    + kfree(bufs);
    + return -EBUSY;
    + }
    +
    /*
    * The pipe array wraps around, so just start the new one at zero
    * and adjust the indices.
    @@ -1299,6 +1302,8 @@ int pipe_resize_ring(struct pipe_inode_i
    pipe->tail = tail;
    pipe->head = head;

    + spin_unlock_irq(&pipe->rd_wait.lock);
    +
    /* This might have made more room for writers */
    wake_up_interruptible(&pipe->wr_wait);
    return 0;

    \
     
     \ /
      Last update: 2022-06-03 19:58    [W:4.174 / U:0.080 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site