btrfs: fix data race when accessing the last_trans field of a root
KCSAN complains about a data race when accessing the last_trans field of a root: [ 199.553628] BUG: KCSAN: data-race in btrfs_record_root_in_trans [btrfs] / record_root_in_trans [btrfs] [ 199.555186] read to 0x000000008801e308 of 8 bytes by task 2812 on cpu 1: [ 199.555210] btrfs_record_root_in_trans+0x9a/0x128 [btrfs] [ 199.555999] start_transaction+0x154/0xcd8 [btrfs] [ 199.556780] btrfs_join_transaction+0x44/0x60 [btrfs] [ 199.557559] btrfs_dirty_inode+0x9c/0x140 [btrfs] [ 199.558339] btrfs_update_time+0x8c/0xb0 [btrfs] [ 199.559123] touch_atime+0x16c/0x1e0 [ 199.559151] pipe_read+0x6a8/0x7d0 [ 199.559179] vfs_read+0x466/0x498 [ 199.559204] ksys_read+0x108/0x150 [ 199.559230] __s390x_sys_read+0x68/0x88 [ 199.559257] do_syscall+0x1c6/0x210 [ 199.559286] __do_syscall+0xc8/0xf0 [ 199.559318] system_call+0x70/0x98 [ 199.559431] write to 0x000000008801e308 of 8 bytes by task 2808 on cpu 0: [ 199.559464] record_root_in_trans+0x196/0x228 [btrfs] [ 199.560236] btrfs_record_root_in_trans+0xfe/0x128 [btrfs] [ 199.561097] start_transaction+0x154/0xcd8 [btrfs] [ 199.561927] btrfs_join_transaction+0x44/0x60 [btrfs] [ 199.562700] btrfs_dirty_inode+0x9c/0x140 [btrfs] [ 199.563493] btrfs_update_time+0x8c/0xb0 [btrfs] [ 199.564277] file_update_time+0xb8/0xf0 [ 199.564301] pipe_write+0x8ac/0xab8 [ 199.564326] vfs_write+0x33c/0x588 [ 199.564349] ksys_write+0x108/0x150 [ 199.564372] __s390x_sys_write+0x68/0x88 [ 199.564397] do_syscall+0x1c6/0x210 [ 199.564424] __do_syscall+0xc8/0xf0 [ 199.564452] system_call+0x70/0x98 This is because we update and read last_trans concurrently without any type of synchronization. This should be generally harmless and in the worst case it can make us do extra locking (btrfs_record_root_in_trans()) trigger some warnings at ctree.c or do extra work during relocation - this would probably only happen in case of load or store tearing. So fix this by always reading and updating the field using READ_ONCE() and WRITE_ONCE(), this silences KCSAN and prevents load and store tearing. Reviewed-by: Josef Bacik <josef@toxicpanda.com> Signed-off-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
This commit is contained in:
parent
0fbf6cbd72
commit
ca84529a84
@ -321,7 +321,7 @@ int btrfs_copy_root(struct btrfs_trans_handle *trans,
|
|||||||
WARN_ON(test_bit(BTRFS_ROOT_SHAREABLE, &root->state) &&
|
WARN_ON(test_bit(BTRFS_ROOT_SHAREABLE, &root->state) &&
|
||||||
trans->transid != fs_info->running_transaction->transid);
|
trans->transid != fs_info->running_transaction->transid);
|
||||||
WARN_ON(test_bit(BTRFS_ROOT_SHAREABLE, &root->state) &&
|
WARN_ON(test_bit(BTRFS_ROOT_SHAREABLE, &root->state) &&
|
||||||
trans->transid != root->last_trans);
|
trans->transid != btrfs_get_root_last_trans(root));
|
||||||
|
|
||||||
level = btrfs_header_level(buf);
|
level = btrfs_header_level(buf);
|
||||||
if (level == 0)
|
if (level == 0)
|
||||||
@ -556,7 +556,7 @@ int btrfs_force_cow_block(struct btrfs_trans_handle *trans,
|
|||||||
WARN_ON(test_bit(BTRFS_ROOT_SHAREABLE, &root->state) &&
|
WARN_ON(test_bit(BTRFS_ROOT_SHAREABLE, &root->state) &&
|
||||||
trans->transid != fs_info->running_transaction->transid);
|
trans->transid != fs_info->running_transaction->transid);
|
||||||
WARN_ON(test_bit(BTRFS_ROOT_SHAREABLE, &root->state) &&
|
WARN_ON(test_bit(BTRFS_ROOT_SHAREABLE, &root->state) &&
|
||||||
trans->transid != root->last_trans);
|
trans->transid != btrfs_get_root_last_trans(root));
|
||||||
|
|
||||||
level = btrfs_header_level(buf);
|
level = btrfs_header_level(buf);
|
||||||
|
|
||||||
|
@ -356,6 +356,16 @@ static inline void btrfs_set_root_last_log_commit(struct btrfs_root *root, int c
|
|||||||
WRITE_ONCE(root->last_log_commit, commit_id);
|
WRITE_ONCE(root->last_log_commit, commit_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline u64 btrfs_get_root_last_trans(const struct btrfs_root *root)
|
||||||
|
{
|
||||||
|
return READ_ONCE(root->last_trans);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void btrfs_set_root_last_trans(struct btrfs_root *root, u64 transid)
|
||||||
|
{
|
||||||
|
WRITE_ONCE(root->last_trans, transid);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Structure that conveys information about an extent that is going to replace
|
* Structure that conveys information about an extent that is going to replace
|
||||||
* all the extents in a file range.
|
* all the extents in a file range.
|
||||||
|
@ -139,7 +139,7 @@ int btrfs_add_inode_defrag(struct btrfs_trans_handle *trans,
|
|||||||
if (trans)
|
if (trans)
|
||||||
transid = trans->transid;
|
transid = trans->transid;
|
||||||
else
|
else
|
||||||
transid = inode->root->last_trans;
|
transid = btrfs_get_root_last_trans(root);
|
||||||
|
|
||||||
defrag = kmem_cache_zalloc(btrfs_inode_defrag_cachep, GFP_NOFS);
|
defrag = kmem_cache_zalloc(btrfs_inode_defrag_cachep, GFP_NOFS);
|
||||||
if (!defrag)
|
if (!defrag)
|
||||||
|
@ -658,7 +658,7 @@ static void __setup_root(struct btrfs_root *root, struct btrfs_fs_info *fs_info,
|
|||||||
root->state = 0;
|
root->state = 0;
|
||||||
RB_CLEAR_NODE(&root->rb_node);
|
RB_CLEAR_NODE(&root->rb_node);
|
||||||
|
|
||||||
root->last_trans = 0;
|
btrfs_set_root_last_trans(root, 0);
|
||||||
root->free_objectid = 0;
|
root->free_objectid = 0;
|
||||||
root->nr_delalloc_inodes = 0;
|
root->nr_delalloc_inodes = 0;
|
||||||
root->nr_ordered_extents = 0;
|
root->nr_ordered_extents = 0;
|
||||||
@ -1002,7 +1002,7 @@ int btrfs_add_log_tree(struct btrfs_trans_handle *trans,
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
log_root->last_trans = trans->transid;
|
btrfs_set_root_last_trans(log_root, trans->transid);
|
||||||
log_root->root_key.offset = btrfs_root_id(root);
|
log_root->root_key.offset = btrfs_root_id(root);
|
||||||
|
|
||||||
inode_item = &log_root->root_item.inode;
|
inode_item = &log_root->root_item.inode;
|
||||||
|
@ -817,7 +817,7 @@ static struct btrfs_root *create_reloc_root(struct btrfs_trans_handle *trans,
|
|||||||
goto abort;
|
goto abort;
|
||||||
}
|
}
|
||||||
set_bit(BTRFS_ROOT_SHAREABLE, &reloc_root->state);
|
set_bit(BTRFS_ROOT_SHAREABLE, &reloc_root->state);
|
||||||
reloc_root->last_trans = trans->transid;
|
btrfs_set_root_last_trans(reloc_root, trans->transid);
|
||||||
return reloc_root;
|
return reloc_root;
|
||||||
fail:
|
fail:
|
||||||
kfree(root_item);
|
kfree(root_item);
|
||||||
@ -864,7 +864,7 @@ int btrfs_init_reloc_root(struct btrfs_trans_handle *trans,
|
|||||||
*/
|
*/
|
||||||
if (root->reloc_root) {
|
if (root->reloc_root) {
|
||||||
reloc_root = root->reloc_root;
|
reloc_root = root->reloc_root;
|
||||||
reloc_root->last_trans = trans->transid;
|
btrfs_set_root_last_trans(reloc_root, trans->transid);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1739,7 +1739,7 @@ static noinline_for_stack int merge_reloc_root(struct reloc_control *rc,
|
|||||||
* btrfs_update_reloc_root() and update our root item
|
* btrfs_update_reloc_root() and update our root item
|
||||||
* appropriately.
|
* appropriately.
|
||||||
*/
|
*/
|
||||||
reloc_root->last_trans = trans->transid;
|
btrfs_set_root_last_trans(reloc_root, trans->transid);
|
||||||
trans->block_rsv = rc->block_rsv;
|
trans->block_rsv = rc->block_rsv;
|
||||||
|
|
||||||
replaced = 0;
|
replaced = 0;
|
||||||
@ -2082,7 +2082,7 @@ static int record_reloc_root_in_trans(struct btrfs_trans_handle *trans,
|
|||||||
struct btrfs_root *root;
|
struct btrfs_root *root;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if (reloc_root->last_trans == trans->transid)
|
if (btrfs_get_root_last_trans(reloc_root) == trans->transid)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
root = btrfs_get_fs_root(fs_info, reloc_root->root_key.offset, false);
|
root = btrfs_get_fs_root(fs_info, reloc_root->root_key.offset, false);
|
||||||
|
@ -405,7 +405,7 @@ static int record_root_in_trans(struct btrfs_trans_handle *trans,
|
|||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
if ((test_bit(BTRFS_ROOT_SHAREABLE, &root->state) &&
|
if ((test_bit(BTRFS_ROOT_SHAREABLE, &root->state) &&
|
||||||
root->last_trans < trans->transid) || force) {
|
btrfs_get_root_last_trans(root) < trans->transid) || force) {
|
||||||
WARN_ON(!force && root->commit_root != root->node);
|
WARN_ON(!force && root->commit_root != root->node);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -421,7 +421,7 @@ static int record_root_in_trans(struct btrfs_trans_handle *trans,
|
|||||||
smp_wmb();
|
smp_wmb();
|
||||||
|
|
||||||
spin_lock(&fs_info->fs_roots_radix_lock);
|
spin_lock(&fs_info->fs_roots_radix_lock);
|
||||||
if (root->last_trans == trans->transid && !force) {
|
if (btrfs_get_root_last_trans(root) == trans->transid && !force) {
|
||||||
spin_unlock(&fs_info->fs_roots_radix_lock);
|
spin_unlock(&fs_info->fs_roots_radix_lock);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -429,7 +429,7 @@ static int record_root_in_trans(struct btrfs_trans_handle *trans,
|
|||||||
(unsigned long)btrfs_root_id(root),
|
(unsigned long)btrfs_root_id(root),
|
||||||
BTRFS_ROOT_TRANS_TAG);
|
BTRFS_ROOT_TRANS_TAG);
|
||||||
spin_unlock(&fs_info->fs_roots_radix_lock);
|
spin_unlock(&fs_info->fs_roots_radix_lock);
|
||||||
root->last_trans = trans->transid;
|
btrfs_set_root_last_trans(root, trans->transid);
|
||||||
|
|
||||||
/* this is pretty tricky. We don't want to
|
/* this is pretty tricky. We don't want to
|
||||||
* take the relocation lock in btrfs_record_root_in_trans
|
* take the relocation lock in btrfs_record_root_in_trans
|
||||||
@ -491,7 +491,7 @@ int btrfs_record_root_in_trans(struct btrfs_trans_handle *trans,
|
|||||||
* and barriers
|
* and barriers
|
||||||
*/
|
*/
|
||||||
smp_rmb();
|
smp_rmb();
|
||||||
if (root->last_trans == trans->transid &&
|
if (btrfs_get_root_last_trans(root) == trans->transid &&
|
||||||
!test_bit(BTRFS_ROOT_IN_TRANS_SETUP, &root->state))
|
!test_bit(BTRFS_ROOT_IN_TRANS_SETUP, &root->state))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user