btrfs_init_new_device() publishes the new device on the RCU-protected fs_devices->devices list with list_add_rcu() before it adds the device item, finishes a sprout and commits the transaction. If a later step fails, the error_sysfs unwind removes the device with list_del_rcu() and then frees it via btrfs_free_device(), which kfree()s the device synchronously: list_del_rcu(&device->dev_list); ... mutex_unlock(&fs_info->fs_devices->device_list_mutex); ... btrfs_free_device(device); /* kfree(device) */ There is no grace period between the list_del_rcu() and the kfree(), so a reader walking fs_devices->devices under rcu_read_lock() alone can dereference the freed device. Such readers exist and are reachable from unprivileged context, e.g. btrfs_ioctl_fs_info() (reads device->devid) and btrfs_calc_avail_data_space() (reads device->dev_state and total_bytes). The window is hit on the common, non-seeding "btrfs device add" path when btrfs_add_dev_item() fails (e.g. -ENOMEM, -EIO, -ENOSPC) and jumps to error_sysfs. Every other device removal path waits for a grace period between the list_del_rcu() and btrfs_free_device() -- btrfs_rm_device(), btrfs_destroy_dev_replace_tgtdev() and the source device removal in btrfs_dev_replace_finishing() all call synchronize_rcu() first. Only this error path is missing it. Add the synchronize_rcu() after device_list_mutex is dropped and before the device is freed, matching the other paths. Fixes: 39379faaad79 ("btrfs: revert fs_devices state on error of btrfs_init_new_device") Cc: stable@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/btrfs/volumes.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c index 6eab4cc73ce4..9c4cd8bdda05 100644 --- a/fs/btrfs/volumes.c +++ b/fs/btrfs/volumes.c @@ -3086,6 +3086,8 @@ int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *device_path btrfs_update_per_profile_avail(fs_info); mutex_unlock(&fs_info->chunk_mutex); mutex_unlock(&fs_info->fs_devices->device_list_mutex); + /* Pair the list_del_rcu() above with a grace period before the free. */ + synchronize_rcu(); error_trans: if (trans) btrfs_end_transaction(trans); -- 2.47.3