Commit 26e57507 authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull btrfs fixes from David Sterba:

 - fix potential out-of-bounds access to leaf data when seeking in an
   inline file

 - fix potential crash in quota when rescan races with disable

 - reimplement super block signature scratching by marking page/folio
   dirty and syncing block device, allow removing write_one_page

* tag 'for-6.2-rc4-tag' of git://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux:
  btrfs: fix race between quota rescan and disable leading to NULL pointer deref
  btrfs: fix invalid leaf access due to inline extent during lseek
  btrfs: stop using write_one_page in btrfs_scratch_superblock
  btrfs: factor out scratching of one regular super block
parents d9166cb3 b7adbf9a
Loading
Loading
Loading
Loading
+10 −3
Original line number Diff line number Diff line
@@ -3541,6 +3541,7 @@ static loff_t find_desired_extent(struct file *file, loff_t offset, int whence)
		struct extent_buffer *leaf = path->nodes[0];
		struct btrfs_file_extent_item *extent;
		u64 extent_end;
		u8 type;

		if (path->slots[0] >= btrfs_header_nritems(leaf)) {
			ret = btrfs_next_leaf(root, path);
@@ -3596,10 +3597,16 @@ static loff_t find_desired_extent(struct file *file, loff_t offset, int whence)

		extent = btrfs_item_ptr(leaf, path->slots[0],
					struct btrfs_file_extent_item);
		type = btrfs_file_extent_type(leaf, extent);

		if (btrfs_file_extent_disk_bytenr(leaf, extent) == 0 ||
		    btrfs_file_extent_type(leaf, extent) ==
		    BTRFS_FILE_EXTENT_PREALLOC) {
		/*
		 * Can't access the extent's disk_bytenr field if this is an
		 * inline extent, since at that offset, it's where the extent
		 * data starts.
		 */
		if (type == BTRFS_FILE_EXTENT_PREALLOC ||
		    (type == BTRFS_FILE_EXTENT_REG &&
		     btrfs_file_extent_disk_bytenr(leaf, extent) == 0)) {
			/*
			 * Explicit hole or prealloc extent, search for delalloc.
			 * A prealloc extent is treated like a hole.
+17 −8
Original line number Diff line number Diff line
@@ -3367,6 +3367,7 @@ static void btrfs_qgroup_rescan_worker(struct btrfs_work *work)
	int err = -ENOMEM;
	int ret = 0;
	bool stopped = false;
	bool did_leaf_rescans = false;

	path = btrfs_alloc_path();
	if (!path)
@@ -3387,6 +3388,7 @@ static void btrfs_qgroup_rescan_worker(struct btrfs_work *work)
		}

		err = qgroup_rescan_leaf(trans, path);
		did_leaf_rescans = true;

		if (err > 0)
			btrfs_commit_transaction(trans);
@@ -3407,9 +3409,13 @@ static void btrfs_qgroup_rescan_worker(struct btrfs_work *work)
	mutex_unlock(&fs_info->qgroup_rescan_lock);

	/*
	 * only update status, since the previous part has already updated the
	 * qgroup info.
	 * Only update status, since the previous part has already updated the
	 * qgroup info, and only if we did any actual work. This also prevents
	 * race with a concurrent quota disable, which has already set
	 * fs_info->quota_root to NULL and cleared BTRFS_FS_QUOTA_ENABLED at
	 * btrfs_quota_disable().
	 */
	if (did_leaf_rescans) {
		trans = btrfs_start_transaction(fs_info->quota_root, 1);
		if (IS_ERR(trans)) {
			err = PTR_ERR(trans);
@@ -3418,6 +3424,9 @@ static void btrfs_qgroup_rescan_worker(struct btrfs_work *work)
				  "fail to start transaction for status update: %d",
				  err);
		}
	} else {
		trans = NULL;
	}

	mutex_lock(&fs_info->qgroup_rescan_lock);
	if (!stopped ||
+25 −25
Original line number Diff line number Diff line
@@ -2014,42 +2014,42 @@ static u64 btrfs_num_devices(struct btrfs_fs_info *fs_info)
	return num_devices;
}

static void btrfs_scratch_superblock(struct btrfs_fs_info *fs_info,
				     struct block_device *bdev, int copy_num)
{
	struct btrfs_super_block *disk_super;
	const size_t len = sizeof(disk_super->magic);
	const u64 bytenr = btrfs_sb_offset(copy_num);
	int ret;

	disk_super = btrfs_read_disk_super(bdev, bytenr, bytenr);
	if (IS_ERR(disk_super))
		return;

	memset(&disk_super->magic, 0, len);
	folio_mark_dirty(virt_to_folio(disk_super));
	btrfs_release_disk_super(disk_super);

	ret = sync_blockdev_range(bdev, bytenr, bytenr + len - 1);
	if (ret)
		btrfs_warn(fs_info, "error clearing superblock number %d (%d)",
			copy_num, ret);
}

void btrfs_scratch_superblocks(struct btrfs_fs_info *fs_info,
			       struct block_device *bdev,
			       const char *device_path)
{
	struct btrfs_super_block *disk_super;
	int copy_num;

	if (!bdev)
		return;

	for (copy_num = 0; copy_num < BTRFS_SUPER_MIRROR_MAX; copy_num++) {
		struct page *page;
		int ret;

		disk_super = btrfs_read_dev_one_super(bdev, copy_num, false);
		if (IS_ERR(disk_super))
			continue;

		if (bdev_is_zoned(bdev)) {
		if (bdev_is_zoned(bdev))
			btrfs_reset_sb_log_zones(bdev, copy_num);
			continue;
		}

		memset(&disk_super->magic, 0, sizeof(disk_super->magic));

		page = virt_to_page(disk_super);
		set_page_dirty(page);
		lock_page(page);
		/* write_on_page() unlocks the page */
		ret = write_one_page(page);
		if (ret)
			btrfs_warn(fs_info,
				"error clearing superblock number %d (%d)",
				copy_num, ret);
		btrfs_release_disk_super(disk_super);

		else
			btrfs_scratch_superblock(fs_info, bdev, copy_num);
	}

	/* Notify udev that device has changed */