mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
ext4: defer i_disksize update while DISKSIZE_GROW_PENDING is set
Operations like append allocate, zero range, and truncate update i_disksize directly. If the new i_disksize exceeds the original value while the zeroed EOF block is still awaiting writeback, metadata may be persisted before the zeroed data, exposing stale data on crash. Defer i_disksize updates while EXT4_STATE_DISKSIZE_GROW_PENDING is set; the ioend worker for the pending block will advance i_disksize to i_size once the zeroed data is written back. The tradeoff is that i_disksize may lag i_size transiently, but this is observable only to callers that read i_disksize directly. Introduce __ext4_set_i_disksize() to centralize the bit check for callers already holding i_data_sem (ext4_ext_truncate and ext4_set_inode_size), and refactor ext4_update_inode_size() to take i_data_sem itself and check the bit atomically with i_size_write(), so the ioend worker observes the latest i_size under the same lock. Note that in the O_SYNC mode of ext4_do_fallocate() or ext4_zero_range(), we need to flush out pending blocks that can update i_disksize synchronously. This will be handled later, and EXT4_STATE_DISKSIZE_GROW_PENDING will also be set after everything is done. Suggested-by: Jan Kara <jack@suse.cz> Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Tested-by: syzbot@syzkaller.appspotmail.com Link: https://patch.msgid.link/20261009103033.2920530-25-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
This commit is contained in:
parent
02a59b693e
commit
ee6539fc9e
|
|
@ -3601,30 +3601,67 @@ do { \
|
|||
#define EXT4_FREECLUSTERS_WATERMARK 0
|
||||
#endif
|
||||
|
||||
/* Update i_disksize. Requires i_rwsem to avoid races with truncate */
|
||||
/*
|
||||
* Update i_disksize. Requires i_rwsem to avoid races with truncate.
|
||||
*
|
||||
* In the iomap buffered I/O path, the EXT4_STATE_DISKSIZE_GROW_PENDING
|
||||
* inode state bit indicates that the zeroed EOF partial block which
|
||||
* straddles i_disksize is still waiting writeback. In that case,
|
||||
* i_disksize will be updated after the pending zeroed data has been
|
||||
* written out.
|
||||
*/
|
||||
static inline void ext4_update_i_disksize(struct inode *inode, loff_t newsize)
|
||||
{
|
||||
WARN_ON_ONCE(S_ISREG(inode->i_mode) &&
|
||||
!inode_is_locked(inode));
|
||||
down_write(&EXT4_I(inode)->i_data_sem);
|
||||
if (newsize > EXT4_I(inode)->i_disksize)
|
||||
if (newsize > EXT4_I(inode)->i_disksize &&
|
||||
!ext4_test_inode_state(inode, EXT4_STATE_DISKSIZE_GROW_PENDING))
|
||||
WRITE_ONCE(EXT4_I(inode)->i_disksize, newsize);
|
||||
up_write(&EXT4_I(inode)->i_data_sem);
|
||||
}
|
||||
|
||||
/* Update i_size, i_disksize. Requires i_rwsem to avoid races with truncate */
|
||||
static inline void __ext4_set_i_disksize(struct inode *inode, loff_t newsize)
|
||||
{
|
||||
WARN_ON_ONCE(!rwsem_is_locked(&EXT4_I(inode)->i_data_sem));
|
||||
|
||||
if (newsize < EXT4_I(inode)->i_disksize ||
|
||||
!ext4_test_inode_state(inode, EXT4_STATE_DISKSIZE_GROW_PENDING))
|
||||
WRITE_ONCE(EXT4_I(inode)->i_disksize, newsize);
|
||||
}
|
||||
|
||||
/*
|
||||
* Update i_size and i_disksize to @newsize. Requires i_rwsem to avoid
|
||||
* races with truncate.
|
||||
*
|
||||
* In the iomap buffered I/O path, i_disksize is updated only if no zeroed
|
||||
* pending block straddles i_disksize (EXT4_STATE_DISKSIZE_GROW_PENDING
|
||||
* clear), otherwise the ioend worker for the pending block will advance
|
||||
* i_disksize once the pending block is written back. Both updates happen
|
||||
* under i_data_sem so that the writeback ioend worker can always see the
|
||||
* latest i_size under the same semaphore.
|
||||
*
|
||||
* Returns 0 if nothing changed, 1 if i_size was raised, 2 if i_disksize
|
||||
* was raised, or 3 if both were.
|
||||
*/
|
||||
static inline int ext4_update_inode_size(struct inode *inode, loff_t newsize)
|
||||
{
|
||||
int changed = 0;
|
||||
|
||||
if (newsize <= inode->i_size && newsize <= EXT4_I(inode)->i_disksize)
|
||||
return 0;
|
||||
|
||||
down_write(&EXT4_I(inode)->i_data_sem);
|
||||
if (newsize > inode->i_size) {
|
||||
i_size_write(inode, newsize);
|
||||
changed = 1;
|
||||
}
|
||||
if (newsize > EXT4_I(inode)->i_disksize) {
|
||||
ext4_update_i_disksize(inode, newsize);
|
||||
if (newsize > EXT4_I(inode)->i_disksize &&
|
||||
!ext4_test_inode_state(inode, EXT4_STATE_DISKSIZE_GROW_PENDING)) {
|
||||
WRITE_ONCE(EXT4_I(inode)->i_disksize, newsize);
|
||||
changed |= 2;
|
||||
}
|
||||
up_write(&EXT4_I(inode)->i_data_sem);
|
||||
return changed;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4561,7 +4561,7 @@ int ext4_ext_truncate(handle_t *handle, struct inode *inode)
|
|||
*/
|
||||
|
||||
/* we have to know where to truncate from in crash case */
|
||||
EXT4_I(inode)->i_disksize = inode->i_size;
|
||||
__ext4_set_i_disksize(inode, inode->i_size);
|
||||
err = ext4_mark_inode_dirty(handle, inode);
|
||||
if (err)
|
||||
return err;
|
||||
|
|
|
|||
|
|
@ -6635,8 +6635,10 @@ static void ext4_wait_for_tail_page_commit(struct inode *inode)
|
|||
* Set i_size and i_disksize to 'newsize'.
|
||||
*
|
||||
* Both i_rwsem and i_data_sem are required here to avoid races between
|
||||
* generic append writeback and concurrent truncate that also modify
|
||||
* i_size and i_disksize.
|
||||
* generic append writeback (or zeroed pending I/O writeback) and
|
||||
* concurrent operations (e.g., fallocate, truncate) that also modify
|
||||
* i_size and i_disksize. This also ensures that the writeback ioend worker
|
||||
* observes the latest i_size under the same lock protection.
|
||||
*/
|
||||
static inline void ext4_set_inode_size(struct inode *inode, loff_t newsize)
|
||||
{
|
||||
|
|
@ -6644,7 +6646,7 @@ static inline void ext4_set_inode_size(struct inode *inode, loff_t newsize)
|
|||
|
||||
down_write(&EXT4_I(inode)->i_data_sem);
|
||||
i_size_write(inode, newsize);
|
||||
EXT4_I(inode)->i_disksize = newsize;
|
||||
__ext4_set_i_disksize(inode, newsize);
|
||||
up_write(&EXT4_I(inode)->i_data_sem);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user