btrfs: zoned: clone zoned device info when cloning a device
authorJohannes Thumshirn <johannes.thumshirn@wdc.com>
Fri, 4 Nov 2022 14:12:33 +0000 (07:12 -0700)
committerDavid Sterba <dsterba@suse.com>
Mon, 7 Nov 2022 13:35:21 +0000 (14:35 +0100)
When cloning a btrfs_device, we're not cloning the associated
btrfs_zoned_device_info structure of the device in case of a zoned
filesystem.

Later on this leads to a NULL pointer dereference when accessing the
device's zone_info for instance when setting a zone as active.

This was uncovered by fstests' testcase btrfs/161.

CC: stable@vger.kernel.org # 5.15+
Signed-off-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/volumes.c
fs/btrfs/zoned.c
fs/btrfs/zoned.h

index f09d09c259f5fbcc5c378f944d8eae8b8dc884a1..3cb968ede6759b9b8d25b5e741dd55ce5ab47b88 100644 (file)
@@ -1011,6 +1011,18 @@ static struct btrfs_fs_devices *clone_fs_devices(struct btrfs_fs_devices *orig)
                        rcu_assign_pointer(device->name, name);
                }
 
+               if (orig_dev->zone_info) {
+                       struct btrfs_zoned_device_info *zone_info;
+
+                       zone_info = btrfs_clone_dev_zone_info(orig_dev);
+                       if (!zone_info) {
+                               btrfs_free_device(device);
+                               ret = -ENOMEM;
+                               goto error;
+                       }
+                       device->zone_info = zone_info;
+               }
+
                list_add(&device->dev_list, &fs_devices->devices);
                device->fs_devices = fs_devices;
                fs_devices->num_devices++;
index e2d073b08a7d28cbff66b78cac3133bc11a51bf2..1912abf6d02075cb2e21aff34c37d957fb75deda 100644 (file)
@@ -639,6 +639,46 @@ void btrfs_destroy_dev_zone_info(struct btrfs_device *device)
        device->zone_info = NULL;
 }
 
+struct btrfs_zoned_device_info *btrfs_clone_dev_zone_info(struct btrfs_device *orig_dev)
+{
+       struct btrfs_zoned_device_info *zone_info;
+
+       zone_info = kmemdup(orig_dev->zone_info, sizeof(*zone_info), GFP_KERNEL);
+       if (!zone_info)
+               return NULL;
+
+       zone_info->seq_zones = bitmap_zalloc(zone_info->nr_zones, GFP_KERNEL);
+       if (!zone_info->seq_zones)
+               goto out;
+
+       bitmap_copy(zone_info->seq_zones, orig_dev->zone_info->seq_zones,
+                   zone_info->nr_zones);
+
+       zone_info->empty_zones = bitmap_zalloc(zone_info->nr_zones, GFP_KERNEL);
+       if (!zone_info->empty_zones)
+               goto out;
+
+       bitmap_copy(zone_info->empty_zones, orig_dev->zone_info->empty_zones,
+                   zone_info->nr_zones);
+
+       zone_info->active_zones = bitmap_zalloc(zone_info->nr_zones, GFP_KERNEL);
+       if (!zone_info->active_zones)
+               goto out;
+
+       bitmap_copy(zone_info->active_zones, orig_dev->zone_info->active_zones,
+                   zone_info->nr_zones);
+       zone_info->zone_cache = NULL;
+
+       return zone_info;
+
+out:
+       bitmap_free(zone_info->seq_zones);
+       bitmap_free(zone_info->empty_zones);
+       bitmap_free(zone_info->active_zones);
+       kfree(zone_info);
+       return NULL;
+}
+
 int btrfs_get_dev_zone(struct btrfs_device *device, u64 pos,
                       struct blk_zone *zone)
 {
index e17462db3a842c632e8ec5b08d82fe3ad458ee96..8bd16d40b7c65befa495df76860ff4da619b944e 100644 (file)
@@ -36,6 +36,7 @@ int btrfs_get_dev_zone(struct btrfs_device *device, u64 pos,
 int btrfs_get_dev_zone_info_all_devices(struct btrfs_fs_info *fs_info);
 int btrfs_get_dev_zone_info(struct btrfs_device *device, bool populate_cache);
 void btrfs_destroy_dev_zone_info(struct btrfs_device *device);
+struct btrfs_zoned_device_info *btrfs_clone_dev_zone_info(struct btrfs_device *orig_dev);
 int btrfs_check_zoned_mode(struct btrfs_fs_info *fs_info);
 int btrfs_check_mountopts_zoned(struct btrfs_fs_info *info);
 int btrfs_sb_log_location_bdev(struct block_device *bdev, int mirror, int rw,
@@ -103,6 +104,16 @@ static inline int btrfs_get_dev_zone_info(struct btrfs_device *device,
 
 static inline void btrfs_destroy_dev_zone_info(struct btrfs_device *device) { }
 
+/*
+ * In case the kernel is compiled without CONFIG_BLK_DEV_ZONED we'll never call
+ * into btrfs_clone_dev_zone_info() so it's safe to return NULL here.
+ */
+static inline struct btrfs_zoned_device_info *btrfs_clone_dev_zone_info(
+                                                struct btrfs_device *orig_dev)
+{
+       return NULL;
+}
+
 static inline int btrfs_check_zoned_mode(const struct btrfs_fs_info *fs_info)
 {
        if (!btrfs_is_zoned(fs_info))