Commit 26e57507 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'for-6.2-rc4-tag' of git://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux

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
......@@ -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.
......
......@@ -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 ||
......
......@@ -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 */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment