lkml.org 
[lkml]   [2022]   [Jan]   [24]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH 5.16 0900/1039] ext4: fix an use-after-free issue about data=journal writeback mode
    Date
    From: Zhang Yi <yi.zhang@huawei.com>

    commit 5c48a7df91499e371ef725895b2e2d21a126e227 upstream.

    Our syzkaller report an use-after-free issue that accessing the freed
    buffer_head on the writeback page in __ext4_journalled_writepage(). The
    problem is that if there was a truncate racing with the data=journalled
    writeback procedure, the writeback length could become zero and
    bget_one() refuse to get buffer_head's refcount, then the truncate
    procedure release buffer once we drop page lock, finally, the last
    ext4_walk_page_buffers() trigger the use-after-free problem.

    sync truncate
    ext4_sync_file()
    file_write_and_wait_range()
    ext4_setattr(0)
    inode->i_size = 0
    ext4_writepage()
    len = 0
    __ext4_journalled_writepage()
    page_bufs = page_buffers(page)
    ext4_walk_page_buffers(bget_one) <- does not get refcount
    do_invalidatepage()
    free_buffer_head()
    ext4_walk_page_buffers(page_bufs) <- trigger use-after-free

    After commit bdf96838aea6 ("ext4: fix race between truncate and
    __ext4_journalled_writepage()"), we have already handled the racing
    case, so the bget_one() and bput_one() are not needed. So this patch
    simply remove these hunk, and recheck the i_size to make it safe.

    Fixes: bdf96838aea6 ("ext4: fix race between truncate and __ext4_journalled_writepage()")
    Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
    Cc: stable@vger.kernel.org
    Link: https://lore.kernel.org/r/20211225090937.712867-1-yi.zhang@huawei.com
    Signed-off-by: Theodore Ts'o <tytso@mit.edu>
    Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
    ---
    fs/ext4/inode.c | 37 ++++++++++---------------------------
    1 file changed, 10 insertions(+), 27 deletions(-)

    --- a/fs/ext4/inode.c
    +++ b/fs/ext4/inode.c
    @@ -1845,30 +1845,16 @@ int ext4_da_get_block_prep(struct inode
    return 0;
    }

    -static int bget_one(handle_t *handle, struct inode *inode,
    - struct buffer_head *bh)
    -{
    - get_bh(bh);
    - return 0;
    -}
    -
    -static int bput_one(handle_t *handle, struct inode *inode,
    - struct buffer_head *bh)
    -{
    - put_bh(bh);
    - return 0;
    -}
    -
    static int __ext4_journalled_writepage(struct page *page,
    unsigned int len)
    {
    struct address_space *mapping = page->mapping;
    struct inode *inode = mapping->host;
    - struct buffer_head *page_bufs = NULL;
    handle_t *handle = NULL;
    int ret = 0, err = 0;
    int inline_data = ext4_has_inline_data(inode);
    struct buffer_head *inode_bh = NULL;
    + loff_t size;

    ClearPageChecked(page);

    @@ -1878,14 +1864,6 @@ static int __ext4_journalled_writepage(s
    inode_bh = ext4_journalled_write_inline_data(inode, len, page);
    if (inode_bh == NULL)
    goto out;
    - } else {
    - page_bufs = page_buffers(page);
    - if (!page_bufs) {
    - BUG();
    - goto out;
    - }
    - ext4_walk_page_buffers(handle, inode, page_bufs, 0, len,
    - NULL, bget_one);
    }
    /*
    * We need to release the page lock before we start the
    @@ -1906,7 +1884,8 @@ static int __ext4_journalled_writepage(s

    lock_page(page);
    put_page(page);
    - if (page->mapping != mapping) {
    + size = i_size_read(inode);
    + if (page->mapping != mapping || page_offset(page) > size) {
    /* The page got truncated from under us */
    ext4_journal_stop(handle);
    ret = 0;
    @@ -1916,6 +1895,13 @@ static int __ext4_journalled_writepage(s
    if (inline_data) {
    ret = ext4_mark_inode_dirty(handle, inode);
    } else {
    + struct buffer_head *page_bufs = page_buffers(page);
    +
    + if (page->index == size >> PAGE_SHIFT)
    + len = size & ~PAGE_MASK;
    + else
    + len = PAGE_SIZE;
    +
    ret = ext4_walk_page_buffers(handle, inode, page_bufs, 0, len,
    NULL, do_journal_get_write_access);

    @@ -1936,9 +1922,6 @@ static int __ext4_journalled_writepage(s
    out:
    unlock_page(page);
    out_no_pagelock:
    - if (!inline_data && page_bufs)
    - ext4_walk_page_buffers(NULL, inode, page_bufs, 0, len,
    - NULL, bput_one);
    brelse(inode_bh);
    return ret;
    }

    \
     
     \ /
      Last update: 2022-01-25 04:10    [W:3.975 / U:0.820 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site