mirror of
https://github.com/torvalds/linux.git
synced 2024-11-21 19:41:42 +00:00
ext4: fix race in buffer_head read fault injection
When I enabled ext4 debug for fault injection testing, I encountered the
following warning:
EXT4-fs error (device sda): ext4_read_inode_bitmap:201: comm fsstress:
Cannot read inode bitmap - block_group = 8, inode_bitmap = 1051
WARNING: CPU: 0 PID: 511 at fs/buffer.c:1181 mark_buffer_dirty+0x1b3/0x1d0
The root cause of the issue lies in the improper implementation of ext4's
buffer_head read fault injection. The actual completion of buffer_head
read and the buffer_head fault injection are not atomic, which can lead
to the uptodate flag being cleared on normally used buffer_heads in race
conditions.
[CPU0] [CPU1] [CPU2]
ext4_read_inode_bitmap
ext4_read_bh()
<bh read complete>
ext4_read_inode_bitmap
if (buffer_uptodate(bh))
return bh
jbd2_journal_commit_transaction
__jbd2_journal_refile_buffer
__jbd2_journal_unfile_buffer
__jbd2_journal_temp_unlink_buffer
ext4_simulate_fail_bh()
clear_buffer_uptodate
mark_buffer_dirty
<report warning>
WARN_ON_ONCE(!buffer_uptodate(bh))
The best approach would be to perform fault injection in the IO completion
callback function, rather than after IO completion. However, the IO
completion callback function cannot get the fault injection code in sb.
Fix it by passing the result of fault injection into the bh read function,
we simulate faults within the bh read function itself. This requires adding
an extra parameter to the bh read functions that need fault injection.
Fixes: 46f870d690
("ext4: simulate various I/O and checksum errors when reading metadata")
Signed-off-by: Long Li <leo.lilong@huawei.com>
Link: https://patch.msgid.link/20240906091746.510163-1-leo.lilong@huawei.com
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
This commit is contained in:
parent
a90825898b
commit
2f3d93e210
@ -550,7 +550,8 @@ ext4_read_block_bitmap_nowait(struct super_block *sb, ext4_group_t block_group,
|
|||||||
trace_ext4_read_block_bitmap_load(sb, block_group, ignore_locked);
|
trace_ext4_read_block_bitmap_load(sb, block_group, ignore_locked);
|
||||||
ext4_read_bh_nowait(bh, REQ_META | REQ_PRIO |
|
ext4_read_bh_nowait(bh, REQ_META | REQ_PRIO |
|
||||||
(ignore_locked ? REQ_RAHEAD : 0),
|
(ignore_locked ? REQ_RAHEAD : 0),
|
||||||
ext4_end_bitmap_read);
|
ext4_end_bitmap_read,
|
||||||
|
ext4_simulate_fail(sb, EXT4_SIM_BBITMAP_EIO));
|
||||||
return bh;
|
return bh;
|
||||||
verify:
|
verify:
|
||||||
err = ext4_validate_block_bitmap(sb, desc, block_group, bh);
|
err = ext4_validate_block_bitmap(sb, desc, block_group, bh);
|
||||||
@ -577,7 +578,6 @@ int ext4_wait_block_bitmap(struct super_block *sb, ext4_group_t block_group,
|
|||||||
if (!desc)
|
if (!desc)
|
||||||
return -EFSCORRUPTED;
|
return -EFSCORRUPTED;
|
||||||
wait_on_buffer(bh);
|
wait_on_buffer(bh);
|
||||||
ext4_simulate_fail_bh(sb, bh, EXT4_SIM_BBITMAP_EIO);
|
|
||||||
if (!buffer_uptodate(bh)) {
|
if (!buffer_uptodate(bh)) {
|
||||||
ext4_error_err(sb, EIO, "Cannot read block bitmap - "
|
ext4_error_err(sb, EIO, "Cannot read block bitmap - "
|
||||||
"block_group = %u, block_bitmap = %llu",
|
"block_group = %u, block_bitmap = %llu",
|
||||||
|
@ -1865,14 +1865,6 @@ static inline bool ext4_simulate_fail(struct super_block *sb,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void ext4_simulate_fail_bh(struct super_block *sb,
|
|
||||||
struct buffer_head *bh,
|
|
||||||
unsigned long code)
|
|
||||||
{
|
|
||||||
if (!IS_ERR(bh) && ext4_simulate_fail(sb, code))
|
|
||||||
clear_buffer_uptodate(bh);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Error number codes for s_{first,last}_error_errno
|
* Error number codes for s_{first,last}_error_errno
|
||||||
*
|
*
|
||||||
@ -3100,9 +3092,9 @@ extern struct buffer_head *ext4_sb_bread(struct super_block *sb,
|
|||||||
extern struct buffer_head *ext4_sb_bread_unmovable(struct super_block *sb,
|
extern struct buffer_head *ext4_sb_bread_unmovable(struct super_block *sb,
|
||||||
sector_t block);
|
sector_t block);
|
||||||
extern void ext4_read_bh_nowait(struct buffer_head *bh, blk_opf_t op_flags,
|
extern void ext4_read_bh_nowait(struct buffer_head *bh, blk_opf_t op_flags,
|
||||||
bh_end_io_t *end_io);
|
bh_end_io_t *end_io, bool simu_fail);
|
||||||
extern int ext4_read_bh(struct buffer_head *bh, blk_opf_t op_flags,
|
extern int ext4_read_bh(struct buffer_head *bh, blk_opf_t op_flags,
|
||||||
bh_end_io_t *end_io);
|
bh_end_io_t *end_io, bool simu_fail);
|
||||||
extern int ext4_read_bh_lock(struct buffer_head *bh, blk_opf_t op_flags, bool wait);
|
extern int ext4_read_bh_lock(struct buffer_head *bh, blk_opf_t op_flags, bool wait);
|
||||||
extern void ext4_sb_breadahead_unmovable(struct super_block *sb, sector_t block);
|
extern void ext4_sb_breadahead_unmovable(struct super_block *sb, sector_t block);
|
||||||
extern int ext4_seq_options_show(struct seq_file *seq, void *offset);
|
extern int ext4_seq_options_show(struct seq_file *seq, void *offset);
|
||||||
|
@ -568,7 +568,7 @@ __read_extent_tree_block(const char *function, unsigned int line,
|
|||||||
|
|
||||||
if (!bh_uptodate_or_lock(bh)) {
|
if (!bh_uptodate_or_lock(bh)) {
|
||||||
trace_ext4_ext_load_extent(inode, pblk, _RET_IP_);
|
trace_ext4_ext_load_extent(inode, pblk, _RET_IP_);
|
||||||
err = ext4_read_bh(bh, 0, NULL);
|
err = ext4_read_bh(bh, 0, NULL, false);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto errout;
|
goto errout;
|
||||||
}
|
}
|
||||||
|
@ -193,8 +193,9 @@ ext4_read_inode_bitmap(struct super_block *sb, ext4_group_t block_group)
|
|||||||
* submit the buffer_head for reading
|
* submit the buffer_head for reading
|
||||||
*/
|
*/
|
||||||
trace_ext4_load_inode_bitmap(sb, block_group);
|
trace_ext4_load_inode_bitmap(sb, block_group);
|
||||||
ext4_read_bh(bh, REQ_META | REQ_PRIO, ext4_end_bitmap_read);
|
ext4_read_bh(bh, REQ_META | REQ_PRIO,
|
||||||
ext4_simulate_fail_bh(sb, bh, EXT4_SIM_IBITMAP_EIO);
|
ext4_end_bitmap_read,
|
||||||
|
ext4_simulate_fail(sb, EXT4_SIM_IBITMAP_EIO));
|
||||||
if (!buffer_uptodate(bh)) {
|
if (!buffer_uptodate(bh)) {
|
||||||
put_bh(bh);
|
put_bh(bh);
|
||||||
ext4_error_err(sb, EIO, "Cannot read inode bitmap - "
|
ext4_error_err(sb, EIO, "Cannot read inode bitmap - "
|
||||||
|
@ -170,7 +170,7 @@ static Indirect *ext4_get_branch(struct inode *inode, int depth,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!bh_uptodate_or_lock(bh)) {
|
if (!bh_uptodate_or_lock(bh)) {
|
||||||
if (ext4_read_bh(bh, 0, NULL) < 0) {
|
if (ext4_read_bh(bh, 0, NULL, false) < 0) {
|
||||||
put_bh(bh);
|
put_bh(bh);
|
||||||
goto failure;
|
goto failure;
|
||||||
}
|
}
|
||||||
|
@ -4504,10 +4504,10 @@ make_io:
|
|||||||
* Read the block from disk.
|
* Read the block from disk.
|
||||||
*/
|
*/
|
||||||
trace_ext4_load_inode(sb, ino);
|
trace_ext4_load_inode(sb, ino);
|
||||||
ext4_read_bh_nowait(bh, REQ_META | REQ_PRIO, NULL);
|
ext4_read_bh_nowait(bh, REQ_META | REQ_PRIO, NULL,
|
||||||
|
ext4_simulate_fail(sb, EXT4_SIM_INODE_EIO));
|
||||||
blk_finish_plug(&plug);
|
blk_finish_plug(&plug);
|
||||||
wait_on_buffer(bh);
|
wait_on_buffer(bh);
|
||||||
ext4_simulate_fail_bh(sb, bh, EXT4_SIM_INODE_EIO);
|
|
||||||
if (!buffer_uptodate(bh)) {
|
if (!buffer_uptodate(bh)) {
|
||||||
if (ret_block)
|
if (ret_block)
|
||||||
*ret_block = block;
|
*ret_block = block;
|
||||||
|
@ -94,7 +94,7 @@ static int read_mmp_block(struct super_block *sb, struct buffer_head **bh,
|
|||||||
}
|
}
|
||||||
|
|
||||||
lock_buffer(*bh);
|
lock_buffer(*bh);
|
||||||
ret = ext4_read_bh(*bh, REQ_META | REQ_PRIO, NULL);
|
ret = ext4_read_bh(*bh, REQ_META | REQ_PRIO, NULL, false);
|
||||||
if (ret)
|
if (ret)
|
||||||
goto warn_exit;
|
goto warn_exit;
|
||||||
|
|
||||||
|
@ -213,7 +213,7 @@ static int mext_page_mkuptodate(struct folio *folio, size_t from, size_t to)
|
|||||||
unlock_buffer(bh);
|
unlock_buffer(bh);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ext4_read_bh_nowait(bh, 0, NULL);
|
ext4_read_bh_nowait(bh, 0, NULL, false);
|
||||||
nr++;
|
nr++;
|
||||||
} while (block++, (bh = bh->b_this_page) != head);
|
} while (block++, (bh = bh->b_this_page) != head);
|
||||||
|
|
||||||
|
@ -1300,7 +1300,7 @@ static struct buffer_head *ext4_get_bitmap(struct super_block *sb, __u64 block)
|
|||||||
if (unlikely(!bh))
|
if (unlikely(!bh))
|
||||||
return NULL;
|
return NULL;
|
||||||
if (!bh_uptodate_or_lock(bh)) {
|
if (!bh_uptodate_or_lock(bh)) {
|
||||||
if (ext4_read_bh(bh, 0, NULL) < 0) {
|
if (ext4_read_bh(bh, 0, NULL, false) < 0) {
|
||||||
brelse(bh);
|
brelse(bh);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -161,8 +161,14 @@ MODULE_ALIAS("ext3");
|
|||||||
|
|
||||||
|
|
||||||
static inline void __ext4_read_bh(struct buffer_head *bh, blk_opf_t op_flags,
|
static inline void __ext4_read_bh(struct buffer_head *bh, blk_opf_t op_flags,
|
||||||
bh_end_io_t *end_io)
|
bh_end_io_t *end_io, bool simu_fail)
|
||||||
{
|
{
|
||||||
|
if (simu_fail) {
|
||||||
|
clear_buffer_uptodate(bh);
|
||||||
|
unlock_buffer(bh);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* buffer's verified bit is no longer valid after reading from
|
* buffer's verified bit is no longer valid after reading from
|
||||||
* disk again due to write out error, clear it to make sure we
|
* disk again due to write out error, clear it to make sure we
|
||||||
@ -176,7 +182,7 @@ static inline void __ext4_read_bh(struct buffer_head *bh, blk_opf_t op_flags,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ext4_read_bh_nowait(struct buffer_head *bh, blk_opf_t op_flags,
|
void ext4_read_bh_nowait(struct buffer_head *bh, blk_opf_t op_flags,
|
||||||
bh_end_io_t *end_io)
|
bh_end_io_t *end_io, bool simu_fail)
|
||||||
{
|
{
|
||||||
BUG_ON(!buffer_locked(bh));
|
BUG_ON(!buffer_locked(bh));
|
||||||
|
|
||||||
@ -184,10 +190,11 @@ void ext4_read_bh_nowait(struct buffer_head *bh, blk_opf_t op_flags,
|
|||||||
unlock_buffer(bh);
|
unlock_buffer(bh);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
__ext4_read_bh(bh, op_flags, end_io);
|
__ext4_read_bh(bh, op_flags, end_io, simu_fail);
|
||||||
}
|
}
|
||||||
|
|
||||||
int ext4_read_bh(struct buffer_head *bh, blk_opf_t op_flags, bh_end_io_t *end_io)
|
int ext4_read_bh(struct buffer_head *bh, blk_opf_t op_flags,
|
||||||
|
bh_end_io_t *end_io, bool simu_fail)
|
||||||
{
|
{
|
||||||
BUG_ON(!buffer_locked(bh));
|
BUG_ON(!buffer_locked(bh));
|
||||||
|
|
||||||
@ -196,7 +203,7 @@ int ext4_read_bh(struct buffer_head *bh, blk_opf_t op_flags, bh_end_io_t *end_io
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
__ext4_read_bh(bh, op_flags, end_io);
|
__ext4_read_bh(bh, op_flags, end_io, simu_fail);
|
||||||
|
|
||||||
wait_on_buffer(bh);
|
wait_on_buffer(bh);
|
||||||
if (buffer_uptodate(bh))
|
if (buffer_uptodate(bh))
|
||||||
@ -208,10 +215,10 @@ int ext4_read_bh_lock(struct buffer_head *bh, blk_opf_t op_flags, bool wait)
|
|||||||
{
|
{
|
||||||
lock_buffer(bh);
|
lock_buffer(bh);
|
||||||
if (!wait) {
|
if (!wait) {
|
||||||
ext4_read_bh_nowait(bh, op_flags, NULL);
|
ext4_read_bh_nowait(bh, op_flags, NULL, false);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return ext4_read_bh(bh, op_flags, NULL);
|
return ext4_read_bh(bh, op_flags, NULL, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -266,7 +273,7 @@ void ext4_sb_breadahead_unmovable(struct super_block *sb, sector_t block)
|
|||||||
|
|
||||||
if (likely(bh)) {
|
if (likely(bh)) {
|
||||||
if (trylock_buffer(bh))
|
if (trylock_buffer(bh))
|
||||||
ext4_read_bh_nowait(bh, REQ_RAHEAD, NULL);
|
ext4_read_bh_nowait(bh, REQ_RAHEAD, NULL, false);
|
||||||
brelse(bh);
|
brelse(bh);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user