[PATCH v2] btrfs: prevent deleting the wrong subvolume by ID
From: guolingxing
Date: Sun Oct 18 2026 - 11:38:51 EST
When deleting a subvolume by ID, btrfs_ioctl_snap_destroy() first
resolves the requested ID to a name, then looks up that name again
under the parent directory lock.
A concurrent rename can change which subvolume is associated with
the name between these operations. Since the deletion path does not
verify the ID of the resolved subvolume, it can delete a different
subvolume while leaving the requested one intact.
This was reproduced by repeatedly exchanging the names of two
subvolumes using renameat2(RENAME_EXCHANGE) while deleting one of
them by ID. The wrong subvolume was deleted in 9 out of 14 independent
tests.
Fix this by checking that the resolved subvolume ID matches the
requested ID while holding the parent directory lock. Return -EAGAIN
on a mismatch so that userspace can retry the operation instead of
being told that the requested subvolume does not exist.
Fixes: 949964c92843 ("btrfs: add new BTRFS_IOC_SNAP_DESTROY_V2 ioctl")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: guolingxing <guolingxing@xxxxxxxxxx>
---
Changes since v1:
- Return -EAGAIN instead of -ENOENT on a subvolume ID mismatch,
as suggested by Qu Wenruo.
Testing:
- Unpatched kernel: 9 wrong deletions in 14 independent runs.
- v2 patched kernel: 0 wrong deletions in 30 independent runs.
- EAGAIN was observed in 14 runs; all 30 runs successfully deleted
the requested subvolume while preserving the other one.
fs/btrfs/ioctl.c | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c
index 576cd76ca0bf..d82a7f3f3d14 100644
--- a/fs/btrfs/ioctl.c
+++ b/fs/btrfs/ioctl.c
@@ -2380,6 +2380,17 @@ static noinline int btrfs_ioctl_snap_destroy(struct file *file,
inode = d_inode(dentry);
dest = BTRFS_I(inode)->root;
+
+ /*
+ * A concurrent rename may have changed which subvolume the name
+ * refers to. Verify that the resolved ID still matches the requested ID.
+ */
+ if (destroy_v2 && (vol_args2->flags & BTRFS_SUBVOL_SPEC_BY_ID) &&
+ btrfs_root_id(dest) != vol_args2->subvolid) {
+ ret = -EAGAIN;
+ goto out_end_removing;
+ }
+
if (!capable(CAP_SYS_ADMIN)) {
/*
* Regular user. Only allow this with a special mount
--
2.34.1