diff mbox series

btrfs: fix race between quota enable and quota rescan ioctl

Message ID a95c38a253bedfa00d073e120a2599faa0f8139d.1661254155.git.fdmanana@suse.com (mailing list archive)
State New, archived
Headers show
Series btrfs: fix race between quota enable and quota rescan ioctl | expand

Commit Message

Filipe Manana Aug. 23, 2022, 11:45 a.m. UTC
From: Filipe Manana <fdmanana@suse.com>

When enabling quotas, at btrfs_quota_enable(), after committing the
transaction, we change fs_info->quota_root to point to the quota root we
created and set BTRFS_FS_QUOTA_ENABLED at fs_info->flags. Then we try
to start the qgroup rescan worker, first by initalizing it with a call
to qgroup_rescan_init() - however if that fails we end up freeing the
quota root but we leave fs_info->quota_root still pointing to it, this
can later result in a use-after-free somewhere else.

We have previously set the flags BTRFS_FS_QUOTA_ENABLED and
BTRFS_QGROUP_STATUS_FLAG_ON, so we can only fail with -EINPROGRESS at
btrfs_quota_enable(), which is possible if someone already called the
quota rescan ioctl, and therefore started the rescan worker.

So fix this by ignoring an -EINPROGRESS and asserting we can't get any
other error.

Reported-by: Ye Bin <yebin10@huawei.com>
Link: https://lore.kernel.org/linux-btrfs/20220823015931.421355-1-yebin10@huawei.com/
Signed-off-by: Filipe Manana <fdmanana@suse.com>
---
 fs/btrfs/qgroup.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

Comments

David Sterba Aug. 23, 2022, 8:09 p.m. UTC | #1
On Tue, Aug 23, 2022 at 12:45:42PM +0100, fdmanana@kernel.org wrote:
> From: Filipe Manana <fdmanana@suse.com>
> 
> When enabling quotas, at btrfs_quota_enable(), after committing the
> transaction, we change fs_info->quota_root to point to the quota root we
> created and set BTRFS_FS_QUOTA_ENABLED at fs_info->flags. Then we try
> to start the qgroup rescan worker, first by initalizing it with a call
> to qgroup_rescan_init() - however if that fails we end up freeing the
> quota root but we leave fs_info->quota_root still pointing to it, this
> can later result in a use-after-free somewhere else.
> 
> We have previously set the flags BTRFS_FS_QUOTA_ENABLED and
> BTRFS_QGROUP_STATUS_FLAG_ON, so we can only fail with -EINPROGRESS at
> btrfs_quota_enable(), which is possible if someone already called the
> quota rescan ioctl, and therefore started the rescan worker.
> 
> So fix this by ignoring an -EINPROGRESS and asserting we can't get any
> other error.
> 
> Reported-by: Ye Bin <yebin10@huawei.com>
> Link: https://lore.kernel.org/linux-btrfs/20220823015931.421355-1-yebin10@huawei.com/
> Signed-off-by: Filipe Manana <fdmanana@suse.com>

Added to misc-next, thanks.
Qu Wenruo Aug. 23, 2022, 11:19 p.m. UTC | #2
On 2022/8/23 19:45, fdmanana@kernel.org wrote:
> From: Filipe Manana <fdmanana@suse.com>
>
> When enabling quotas, at btrfs_quota_enable(), after committing the
> transaction, we change fs_info->quota_root to point to the quota root we
> created and set BTRFS_FS_QUOTA_ENABLED at fs_info->flags. Then we try
> to start the qgroup rescan worker, first by initalizing it with a call
> to qgroup_rescan_init() - however if that fails we end up freeing the
> quota root but we leave fs_info->quota_root still pointing to it, this
> can later result in a use-after-free somewhere else.
>
> We have previously set the flags BTRFS_FS_QUOTA_ENABLED and
> BTRFS_QGROUP_STATUS_FLAG_ON, so we can only fail with -EINPROGRESS at
> btrfs_quota_enable(), which is possible if someone already called the
> quota rescan ioctl, and therefore started the rescan worker.
>
> So fix this by ignoring an -EINPROGRESS and asserting we can't get any
> other error.
>
> Reported-by: Ye Bin <yebin10@huawei.com>
> Link: https://lore.kernel.org/linux-btrfs/20220823015931.421355-1-yebin10@huawei.com/
> Signed-off-by: Filipe Manana <fdmanana@suse.com>

Reviewed-by: Qu Wenruo <wqu@suse.com>

Thanks,
Qu
> ---
>   fs/btrfs/qgroup.c | 15 +++++++++++++++
>   1 file changed, 15 insertions(+)
>
> diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c
> index db723c0026bd..ba323dcb0a0b 100644
> --- a/fs/btrfs/qgroup.c
> +++ b/fs/btrfs/qgroup.c
> @@ -1174,6 +1174,21 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info)
>   		fs_info->qgroup_rescan_running = true;
>   	        btrfs_queue_work(fs_info->qgroup_rescan_workers,
>   	                         &fs_info->qgroup_rescan_work);
> +	} else {
> +		/*
> +		 * We have set both BTRFS_FS_QUOTA_ENABLED and
> +		 * BTRFS_QGROUP_STATUS_FLAG_ON, so we can only fail with
> +		 * -EINPROGRESS. That can happen because someone started the
> +		 * rescan worker by calling quota rescan ioctl before we
> +		 * attempted to initialize the rescan worker. Failure due to
> +		 * quotas disabled in the meanwhile is not possible, because
> +		 * we are holding a write lock on fs_info->subvol_sem, which
> +		 * is also acquired when disabling quotas.
> +		 * Ignore such error, and any other error would need to undo
> +		 * everything we did in the transaction we just committed.
> +		 */
> +		ASSERT(ret == -EINPROGRESS);
> +		ret = 0;
>   	}
>
>   out_free_path:
diff mbox series

Patch

diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c
index db723c0026bd..ba323dcb0a0b 100644
--- a/fs/btrfs/qgroup.c
+++ b/fs/btrfs/qgroup.c
@@ -1174,6 +1174,21 @@  int btrfs_quota_enable(struct btrfs_fs_info *fs_info)
 		fs_info->qgroup_rescan_running = true;
 	        btrfs_queue_work(fs_info->qgroup_rescan_workers,
 	                         &fs_info->qgroup_rescan_work);
+	} else {
+		/*
+		 * We have set both BTRFS_FS_QUOTA_ENABLED and
+		 * BTRFS_QGROUP_STATUS_FLAG_ON, so we can only fail with
+		 * -EINPROGRESS. That can happen because someone started the
+		 * rescan worker by calling quota rescan ioctl before we
+		 * attempted to initialize the rescan worker. Failure due to
+		 * quotas disabled in the meanwhile is not possible, because
+		 * we are holding a write lock on fs_info->subvol_sem, which
+		 * is also acquired when disabling quotas.
+		 * Ignore such error, and any other error would need to undo
+		 * everything we did in the transaction we just committed.
+		 */
+		ASSERT(ret == -EINPROGRESS);
+		ret = 0;
 	}
 
 out_free_path: