Patch "ext4: fix races between page faults and hole punching" has been added to the 4.4-stable tree
Subsystems (2)
This is a note to let you know that I've just added the patch titled
ext4: fix races between page faults and hole punching
to the 4.4-stable tree which can be found at:
http://www.kernel.org/git/?p=linux/kernel/git/stable/stable-queue.git;a=summary
The filename of the patch is:
ext4-fix-races-between-page-faults-and-hole-punching.patch
and it can be found in the queue-4.4 subdirectory.
If you, or anyone else, feels it should not be added to the stable tree, please let <[email protected]> know about it.
Quoted 1 line
From
ea3d7209ca01da209cda6f0dea8be9cc4b7a933bMon Sep 17 00:00:00 2001
From: Jan Kara <[email protected]>
Date: Mon, 7 Dec 2015 14:28:03 -0500
Subject: ext4: fix races between page faults and hole punching
From: Jan Kara <[email protected]>
commit ea3d7209ca01da209cda6f0dea8be9cc4b7a933b upstream.
Currently, page faults and hole punching are completely unsynchronized. This can result in page fault faulting in a page into a range that we are punching after truncate_pagecache_range() has been called and thus we can end up with a page mapped to disk blocks that will be shortly freed. Filesystem corruption will shortly follow. Note that the same race is avoided for truncate by checking page fault offset against i_size but there isn't similar mechanism available for punching holes.
Fix the problem by creating new rw semaphore i_mmap_sem in inode and grab it for writing over truncate, hole punching, and other functions removing blocks from extent tree and for read over page faults. We cannot easily use i_data_sem for this since that ranks below transaction start and we need something ranking above it so that it can be held over the whole truncate / hole punching operation. Also remove various workarounds we had in the code to reduce race window when page fault could have created pages with stale mapping information.
--- fs/ext4/ext4.h | 10 ++++++++ fs/ext4/extents.c | 54 ++++++++++++++++++++++++------------------- fs/ext4/file.c | 66 +++++++++++++++++++++++++++++++++++++++++++++-------- fs/ext4/inode.c | 36 +++++++++++++++++++++------- fs/ext4/super.c | 1 fs/ext4/truncate.h | 2 + 6 files changed, 127 insertions(+), 42 deletions(-)
fs/ext4/ext4.h +10 -0
@@ -933,6 +933,15 @@ struct ext4_inode_info {* by other means, so we have i_data_sem. */ struct rw_semaphore i_data_sem; +/* +* i_mmap_sem is for serializing page faults with truncate / punch hole +* operations. We have to make sure that new page cannot be faulted in +* a section of the inode that is being punched. We cannot easily use +* i_data_sem for this since we need protection for the whole punch +* operation and i_data_sem ranks below transaction start so we have +* to occasionally drop it. +*/ +struct rw_semaphore i_mmap_sem;struct inode vfs_inode;struct jbd2_inode *jinode;
@@ -2507,6 +2516,7 @@ extern int ext4_chunk_trans_blocks(strucextern int ext4_zero_partial_blocks (handle_t *handle,struct inode *inode,loff_t lstart,loff_t lend);extern int ext4_page_mkwrite (struct vm_area_struct *vma,struct vm_fault *vmf); +extern int ext4_filemap_fault (struct vm_area_struct *vma,struct vm_fault *vmf);extern qsize_t *ext4_get_reserved_space (struct inode *inode);extern void ext4_da_update_reserve_space (struct inode *inode,int used,int quota_claim);
fs/ext4/extents.c +30 -24
118 lines not on this page · raw message
fs/ext4/file.c +57 -9
120 lines not on this page · raw message
fs/ext4/inode.c +27 -9
@@ -3651,6 +3651,15 @@ int ext4_punch_hole(struct inode *inode, } +/* Wait all existing dio workers, newcomers will block on i_mutex */ +ext4_inode_block_unlocked_dio (inode); +inode_dio_wait (inode); + +/* +* Prevent page faults from reinstantiating pages we have released from +* page cache. +*/ +down_write (&EXT4_I (inode)->i_mmap_sem); first_block_offset =round_up (offset, sb->s_blocksize); last_block_offset =round_down ((offset + length), sb->s_blocksize) -1 ;
@@ -3659,10 +3668,6 @@ int ext4_punch_hole(struct inode *inode,truncate_pagecache_range (inode, first_block_offset, last_block_offset); -/* Wait all existing dio workers, newcomers will block on i_mutex */ -ext4_inode_block_unlocked_dio (inode); -inode_dio_wait (inode); -if (ext4_test_inode_flag (inode,EXT4_INODE_EXTENTS )) credits =ext4_writepage_trans_blocks (inode);else
@@ -3708,16 +3713,12 @@ int ext4_punch_hole(struct inode *inode,if (IS_SYNC (inode))ext4_handle_sync (handle); -/* Now release the pages again to reduce race window */ -if (last_block_offset > first_block_offset) -truncate_pagecache_range (inode, first_block_offset, - last_block_offset); - inode->i_mtime = inode->i_ctime =ext4_current_time (inode);ext4_mark_inode_dirty (handle, inode); out_stop:ext4_journal_stop (handle); out_dio: +up_write (&EXT4_I (inode)->i_mmap_sem);ext4_inode_resume_unlocked_dio (inode); out_mutex:mutex_unlock (&inode->i_mutex);
@@ -4851,6 +4852,7 @@ int ext4_setattr(struct dentry *dentry, }else ext4_wait_for_tail_page_commit (inode); } +down_write (&EXT4_I (inode)->i_mmap_sem);/* * Truncate pagecache after we've waited for commit * in data=journal mode to make pages freeable.
@@ -4858,6 +4860,7 @@ int ext4_setattr(struct dentry *dentry,truncate_pagecache (inode, inode->i_size);if (shrink)ext4_truncate (inode); +up_write (&EXT4_I (inode)->i_mmap_sem); }if (!rc) {
@@ -5306,6 +5309,8 @@ int ext4_page_mkwrite(struct vm_area_strsb_start_pagefault (inode->i_sb);file_update_time (vma->vm_file); + +down_read (&EXT4_I (inode)->i_mmap_sem);/* Delalloc case is easy... */ if (test_opt(inode->i_sb, DELALLOC) && !ext4_should_journal_data(inode) &&
@@ -5375,6 +5380,19 @@ retry_alloc: out_ret: ret =block_page_mkwrite_return (ret); out: +up_read (&EXT4_I (inode)->i_mmap_sem);sb_end_pagefault (inode->i_sb);return ret; } + +int ext4_filemap_fault (struct vm_area_struct *vma,struct vm_fault *vmf) +{ +struct inode *inode =file_inode (vma->vm_file); +int err; + +down_read (&EXT4_I (inode)->i_mmap_sem); + err =filemap_fault (vma, vmf); +up_read (&EXT4_I (inode)->i_mmap_sem); + +return err; +}
fs/ext4/super.c +1 -0
@@ -958,6 +958,7 @@ static void init_once(void *foo)INIT_LIST_HEAD (&ei->i_orphan);init_rwsem (&ei->xattr_sem);init_rwsem (&ei->i_data_sem); +init_rwsem (&ei->i_mmap_sem);inode_init_once (&ei->vfs_inode); }
fs/ext4/truncate.h +2 -0
@@ -10,8 +10,10 @@*/ static inline void ext4_truncate_failed_write (struct inode *inode) { +down_write (&EXT4_I (inode)->i_mmap_sem);truncate_inode_pages (inode->i_mapping, inode->i_size);ext4_truncate (inode); +up_write (&EXT4_I (inode)->i_mmap_sem); }/*
Patches currently in stable-queue which might be from [email protected] are
queue-4.4/ext4-fix-races-between-page-faults-and-hole-punching.patch queue-4.4/ext4-move-unlocked-dio-protection-from-ext4_alloc_file_blocks.patch queue-4.4/ext4-fix-races-between-buffered-io-and-collapse-insert-range.patch queue-4.4/ext4-fix-races-of-writeback-with-punch-hole-and-zero-range.patch