ext4: fix data exposure after a crash
Huang has reported that in his powerfail testing he is seeing stale block contents in some of recently allocated blocks although he mounts ext4 in data=ordered mode. After some investigation I have found out that indeed when delayed allocation is used, we don't add inode to transaction's list of inodes needing flushing before commit. Originally we were doing that but commitf3b59291a6
removed the logic with a flawed argument that it is not needed. The problem is that although for delayed allocated blocks we write their contents immediately after allocating them, there is no guarantee that the IO scheduler or device doesn't reorder things and thus transaction allocating blocks and attaching them to inode can reach stable storage before actual block contents. Actually whenever we attach freshly allocated blocks to inode using a written extent, we should add inode to transaction's ordered inode list to make sure we properly wait for block contents to be written before committing the transaction. So that is what we do in this patch. This also handles other cases where stale data exposure was possible - like filling hole via mmap in data=ordered,nodelalloc mode. The only exception to the above rule are extending direct IO writes where blkdev_direct_IO() waits for IO to complete before increasing i_size and thus stale data exposure is not possible. For now we don't complicate the code with optimizing this special case since the overhead is pretty low. In case this is observed to be a performance problem we can always handle it using a special flag to ext4_map_blocks(). CC: stable@vger.kernel.org Fixes:f3b59291a6
Reported-by: "HUANG Weller (CM/ESW12-CN)" <Weller.Huang@cn.bosch.com> Tested-by: "HUANG Weller (CM/ESW12-CN)" <Weller.Huang@cn.bosch.com> Signed-off-by: Jan Kara <jack@suse.cz> Signed-off-by: Theodore Ts'o <tytso@mit.edu>
This commit is contained in:
Родитель
1f60fbe727
Коммит
06bd3c36a7
|
@ -684,6 +684,21 @@ out_sem:
|
|||
ret = check_block_validity(inode, map);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Inodes with freshly allocated blocks where contents will be
|
||||
* visible after transaction commit must be on transaction's
|
||||
* ordered data list.
|
||||
*/
|
||||
if (map->m_flags & EXT4_MAP_NEW &&
|
||||
!(map->m_flags & EXT4_MAP_UNWRITTEN) &&
|
||||
!(flags & EXT4_GET_BLOCKS_ZERO) &&
|
||||
!IS_NOQUOTA(inode) &&
|
||||
ext4_should_order_data(inode)) {
|
||||
ret = ext4_jbd2_file_inode(handle, inode);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
@ -1289,15 +1304,6 @@ static int ext4_write_end(struct file *file,
|
|||
int i_size_changed = 0;
|
||||
|
||||
trace_ext4_write_end(inode, pos, len, copied);
|
||||
if (ext4_test_inode_state(inode, EXT4_STATE_ORDERED_MODE)) {
|
||||
ret = ext4_jbd2_file_inode(handle, inode);
|
||||
if (ret) {
|
||||
unlock_page(page);
|
||||
put_page(page);
|
||||
goto errout;
|
||||
}
|
||||
}
|
||||
|
||||
if (ext4_has_inline_data(inode)) {
|
||||
ret = ext4_write_inline_data_end(inode, pos, len,
|
||||
copied, page);
|
||||
|
|
Загрузка…
Ссылка в новой задаче