Commit 21e61ec6 authored by Johannes Thumshirn's avatar Johannes Thumshirn Committed by David Sterba
Browse files

btrfs: zoned: clone zoned device info when cloning a device



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: default avatarJohannes Thumshirn <johannes.thumshirn@wdc.com>
Reviewed-by: default avatarDavid Sterba <dsterba@suse.com>
Signed-off-by: default avatarDavid Sterba <dsterba@suse.com>
parent b75b51f8
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -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++;
+40 −0
Original line number Diff line number Diff line
@@ -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)
{
+11 −0
Original line number Diff line number Diff line
@@ -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))