--- /dev/null
+From 0090d6e1b210551e63cf43958dc7a1ec942cdde9 Mon Sep 17 00:00:00 2001
+From: Filipe Manana <fdmanana@suse.com>
+Date: Wed, 8 May 2024 11:51:07 +0100
+Subject: btrfs: zoned: fix use-after-free due to race with dev replace
+
+From: Filipe Manana <fdmanana@suse.com>
+
+commit 0090d6e1b210551e63cf43958dc7a1ec942cdde9 upstream.
+
+While loading a zone's info during creation of a block group, we can race
+with a device replace operation and then trigger a use-after-free on the
+device that was just replaced (source device of the replace operation).
+
+This happens because at btrfs_load_zone_info() we extract a device from
+the chunk map into a local variable and then use the device while not
+under the protection of the device replace rwsem. So if there's a device
+replace operation happening when we extract the device and that device
+is the source of the replace operation, we will trigger a use-after-free
+if before we finish using the device the replace operation finishes and
+frees the device.
+
+Fix this by enlarging the critical section under the protection of the
+device replace rwsem so that all uses of the device are done inside the
+critical section.
+
+CC: stable@vger.kernel.org # 6.1.x: 15c12fcc50a1: btrfs: zoned: introduce a zone_info struct in btrfs_load_block_group_zone_info
+CC: stable@vger.kernel.org # 6.1.x: 09a46725cc84: btrfs: zoned: factor out per-zone logic from btrfs_load_block_group_zone_info
+CC: stable@vger.kernel.org # 6.1.x: 9e0e3e74dc69: btrfs: zoned: factor out single bg handling from btrfs_load_block_group_zone_info
+CC: stable@vger.kernel.org # 6.1.x: 87463f7e0250: btrfs: zoned: factor out DUP bg handling from btrfs_load_block_group_zone_info
+CC: stable@vger.kernel.org # 6.1.x
+Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
+Signed-off-by: Filipe Manana <fdmanana@suse.com>
+Reviewed-by: David Sterba <dsterba@suse.com>
+Signed-off-by: David Sterba <dsterba@suse.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/btrfs/zoned.c | 13 ++++++++++---
+ 1 file changed, 10 insertions(+), 3 deletions(-)
+
+--- a/fs/btrfs/zoned.c
++++ b/fs/btrfs/zoned.c
+@@ -1290,7 +1290,7 @@ static int btrfs_load_zone_info(struct b
+ struct btrfs_chunk_map *map)
+ {
+ struct btrfs_dev_replace *dev_replace = &fs_info->dev_replace;
+- struct btrfs_device *device = map->stripes[zone_idx].dev;
++ struct btrfs_device *device;
+ int dev_replace_is_ongoing = 0;
+ unsigned int nofs_flag;
+ struct blk_zone zone;
+@@ -1298,7 +1298,11 @@ static int btrfs_load_zone_info(struct b
+
+ info->physical = map->stripes[zone_idx].physical;
+
++ down_read(&dev_replace->rwsem);
++ device = map->stripes[zone_idx].dev;
++
+ if (!device->bdev) {
++ up_read(&dev_replace->rwsem);
+ info->alloc_offset = WP_MISSING_DEV;
+ return 0;
+ }
+@@ -1308,6 +1312,7 @@ static int btrfs_load_zone_info(struct b
+ __set_bit(zone_idx, active);
+
+ if (!btrfs_dev_is_sequential(device, info->physical)) {
++ up_read(&dev_replace->rwsem);
+ info->alloc_offset = WP_CONVENTIONAL;
+ return 0;
+ }
+@@ -1315,11 +1320,9 @@ static int btrfs_load_zone_info(struct b
+ /* This zone will be used for allocation, so mark this zone non-empty. */
+ btrfs_dev_clear_zone_empty(device, info->physical);
+
+- down_read(&dev_replace->rwsem);
+ dev_replace_is_ongoing = btrfs_dev_replace_is_ongoing(dev_replace);
+ if (dev_replace_is_ongoing && dev_replace->tgtdev != NULL)
+ btrfs_dev_clear_zone_empty(dev_replace->tgtdev, info->physical);
+- up_read(&dev_replace->rwsem);
+
+ /*
+ * The group is mapped to a sequential zone. Get the zone write pointer
+@@ -1330,6 +1333,7 @@ static int btrfs_load_zone_info(struct b
+ ret = btrfs_get_dev_zone(device, info->physical, &zone);
+ memalloc_nofs_restore(nofs_flag);
+ if (ret) {
++ up_read(&dev_replace->rwsem);
+ if (ret != -EIO && ret != -EOPNOTSUPP)
+ return ret;
+ info->alloc_offset = WP_MISSING_DEV;
+@@ -1341,6 +1345,7 @@ static int btrfs_load_zone_info(struct b
+ "zoned: unexpected conventional zone %llu on device %s (devid %llu)",
+ zone.start << SECTOR_SHIFT, rcu_str_deref(device->name),
+ device->devid);
++ up_read(&dev_replace->rwsem);
+ return -EIO;
+ }
+
+@@ -1368,6 +1373,8 @@ static int btrfs_load_zone_info(struct b
+ break;
+ }
+
++ up_read(&dev_replace->rwsem);
++
+ return 0;
+ }
+