Linux Btrfs filesystem development
 help / color / mirror / Atom feed
From: Anand Jain <anajain.sg@gmail.com>
To: Boris Burkov <boris@bur.io>,
	linux-btrfs@vger.kernel.org, kernel-team@fb.com,
	fstests@vger.kernel.org
Subject: Re: [PATCH 2/2] btrfs: test qgroup deletion races with squota writes
Date: Mon, 18 May 2026 17:18:18 +0800	[thread overview]
Message-ID: <e29e3b26-bd22-41d7-92f0-8d89bf73f5ea@gmail.com> (raw)
In-Reply-To: <2c8668284ded13c7cc63ece213d9e1edc3e6ea85.1778632843.git.boris@bur.io>



It appears that transaction commit could race with the testcase.


--------
SECTION       -- gen
FSTYP         -- btrfs
PLATFORM      -- Linux/x86_64 vm1 7.1.0-rc3-gf5d9f327e4b2 #14 SMP
PREEMPT_DYNAMIC Sun May 17 21:25:06 +08 2026
MKFS_OPTIONS  -- -f /dev/vde
MOUNT_OPTIONS -- -o context=system_u:object_r:root_t:s0 /dev/vde
/mnt/scratch

btrfs/348    10s ... - output mismatch (see
/Volumes/work/ws/fstests/results//gen/btrfs/348.out.bad)
    --- tests/btrfs/348.out	2026-05-18 14:04:31.427635000 +0800
    +++ /Volumes/work/ws/fstests/results//gen/btrfs/348.out.bad
2026-05-18 17:13:33.000000000 +0800
    @@ -5,6 +5,4 @@
     ERROR: unable to destroy quota group: Device or resource busy
     ERROR: unable to create quota group: Invalid argument
     same txn destroy race
    -ERROR: unable to destroy quota group: Device or resource busy
     pre-existing data destroy
    -ERROR: unable to destroy quota group: Device or resource busy
    ...
    (Run 'diff -u /Volumes/work/ws/fstests/tests/btrfs/348.out
/Volumes/work/ws/fstests/results//gen/btrfs/348.out.bad'  to see the
entire di)

HINT: You _MAY_ be missing kernel fix:
      XXXXXXXXXXXX btrfs: check for subvolume before deleting squota qgroup

HINT: You _MAY_ be missing kernel fix:
      0c309d66dacd btrfs: forbid creating subvol qgroups

Ran: btrfs/348
Failures: btrfs/348
Failed 1 of 1 tests


Thanks, Anand







On 13/5/26 08:43, Boris Burkov wrote:
> When using simple quotas, an extent's EXTENT_OWNER_REF can long outlive
> the subvolume that created it, since the extent can pick up additional
> references that keep it alive after the owning subvolume is deleted.
> 
> Several lifecycle bugs around the owning qgroup arise from this:
> 
>   1. Freeing an extent whose owner qgroup is gone: must not cause an
>      underflow nor a transaction abort.
> 
>   2. Creating an extent in the same transaction that the owner subvolume
>      is deleted: the qgroup may already be gone by the time the squota
>      delta is recorded, leaving behind an EXTENT_OWNER_REF that points
>      at a qgroup that never accumulated a usage delta.  Manually
>      re-creating that qgroup and then freeing the extent would underflow
>      it.
> 
>   3. Destroying a live subvolume's qgroup while delayed refs for newly
>      allocated tree blocks have not yet flushed: rfer/excl read as 0,
>      so the destroy looked safe, but the alloc delta was silently lost.
> 
>   4. Destroying the qgroup of a live subvolume whose data predates
>      'btrfs quota enable --simple': pre-existing extents are not
>      accounted (generation < qgroup_enable_gen), so rfer/excl stay 0
>      permanently and a usage-only check happily destroys a qgroup
>      belonging to a still-mounted, still-writeable subvolume.
> 
> Add four cases covering these scenarios.  On a fixed kernel all four
> 'qgroup destroy' (and the re-create in case 2) operations are refused
> because the subvol check rejects the request; on an unfixed kernel
> they would succeed and leave the filesystem with broken accounting or
> trigger a transaction abort.
> 
> Signed-off-by: Boris Burkov <boris@bur.io>
> ---
>  tests/btrfs/348     | 136 ++++++++++++++++++++++++++++++++++++++++++++
>  tests/btrfs/348.out |  10 ++++
>  2 files changed, 146 insertions(+)
>  create mode 100755 tests/btrfs/348
>  create mode 100644 tests/btrfs/348.out
> 
> diff --git a/tests/btrfs/348 b/tests/btrfs/348
> new file mode 100755
> index 00000000..ab816301
> --- /dev/null
> +++ b/tests/btrfs/348
> @@ -0,0 +1,136 @@
> +#! /bin/bash
> +# SPDX-License-Identifier: GPL-2.0
> +# Copyright (c) 2024 Meta Platforms, Inc.  All Rights Reserved.
> +#
> +# FS QA Test 348
> +#
> +# Test various race conditions between qgroup deletion and squota writes
> +#
> +. ./common/preamble
> +_begin_fstest auto qgroup subvol clone
> +
> +# Import common functions.
> +. ./common/reflink
> +
> +# real QA test starts here
> +
> +_require_scratch_reflink
> +_require_cp_reflink
> +_require_scratch_enable_simple_quota
> +
> +_fixed_by_kernel_commit XXXXXXXXXXXX \
> +	"btrfs: check for subvolume before deleting squota qgroup"
> +_fixed_by_kernel_commit 0c309d66dacd \
> +	"btrfs: forbid creating subvol qgroups"
> +
> +subv1=$SCRATCH_MNT/subv1
> +subv2=$SCRATCH_MNT/subv2
> +
> +prepare()
> +{
> +	_scratch_mkfs >> $seqres.full
> +	_scratch_mount
> +	$BTRFS_UTIL_PROG quota enable --simple $SCRATCH_MNT
> +	$BTRFS_UTIL_PROG subvolume create $subv1 >> $seqres.full
> +	$BTRFS_UTIL_PROG subvolume create $subv2 >> $seqres.full
> +	$XFS_IO_PROG -fc "pwrite -q 0 128K" $subv1/f
> +	_cp_reflink $subv1/f $subv2/f
> +}
> +
> +# An extent can long outlive its owner. Test this by deleting the owning
> +# subvolume, committing the transaction, then deleting the reflinked copy.
> +free_from_deleted_owner()
> +{
> +	echo "free from deleted owner"
> +	prepare
> +	local subvid1=$(_btrfs_get_subvolid $SCRATCH_MNT subv1)
> +
> +	$BTRFS_UTIL_PROG filesystem sync $SCRATCH_MNT
> +	$BTRFS_UTIL_PROG subvolume delete $subv1 >> $seqres.full
> +	$BTRFS_UTIL_PROG qgroup destroy 0/$subvid1 $SCRATCH_MNT >> $seqres.full
> +	$BTRFS_UTIL_PROG filesystem sync $SCRATCH_MNT
> +	rm $subv2/f
> +	_scratch_unmount
> +}
> +
> +# A race where we delete the owner in the same transaction as writing the
> +# extent leads to incrementing the squota usage of the missing qgroup.
> +# This leaves behind an owner ref with an owner id that cannot exist, so
> +# freeing the extent now frees from that qgroup, but there has never
> +# been a corresponding usage to free.
> +add_to_deleted_owner()
> +{
> +	echo "add to deleted owner"
> +	prepare
> +	local subvid1=$(_btrfs_get_subvolid $SCRATCH_MNT subv1)
> +
> +	$BTRFS_UTIL_PROG filesystem sync $SCRATCH_MNT
> +	$BTRFS_UTIL_PROG subvolume delete $subv1 >> $seqres.full
> +	$BTRFS_UTIL_PROG qgroup destroy 0/$subvid1 $SCRATCH_MNT >> $seqres.full
> +	$BTRFS_UTIL_PROG filesystem sync $SCRATCH_MNT
> +	$BTRFS_UTIL_PROG qgroup create 0/$subvid1 $SCRATCH_MNT >> $seqres.full
> +	rm $subv2/f
> +	_scratch_unmount
> +}
> +
> +# Create a subvol, destroy its qgroup before delayed refs update rfer/excl.
> +# On an unfixed kernel the qgroup destroy succeeds (rfer == 0), silently
> +# losing the alloc delta. On a fixed kernel the destroy is refused because
> +# the ROOT_ITEM still exists.
> +same_txn_destroy_race()
> +{
> +	echo "same txn destroy race"
> +	prepare
> +
> +	# Commit so the next subvol create starts a fresh transaction.
> +	$BTRFS_UTIL_PROG filesystem sync $SCRATCH_MNT
> +
> +	# New subvol -- tree block alloc delayed ref is pending, rfer/excl=0.
> +	local subv3=$SCRATCH_MNT/subv3
> +	$BTRFS_UTIL_PROG subvolume create $subv3 >> $seqres.full
> +	local subvid3=$(_btrfs_get_subvolid $SCRATCH_MNT subv3)
> +
> +	# rfer/excl still 0 (delayed refs not flushed). Destroy should fail
> +	# because the ROOT_ITEM exists.
> +	$BTRFS_UTIL_PROG qgroup destroy 0/$subvid3 $SCRATCH_MNT 2>&1
> +
> +	_scratch_unmount
> +}
> +
> +# Enable squotas on a filesystem that already has a subvolume with data.
> +# Pre-existing extents have generation < qgroup_enable_gen, so the qgroup's
> +# rfer/excl stay 0 permanently. On an unfixed kernel can_delete_squota_qgroup
> +# only checks rfer/excl, so the qgroup can be destroyed for a live subvol.
> +# On a fixed kernel, the subvol check prevents deletion.
> +pre_existing_data_destroy()
> +{
> +	echo "pre-existing data destroy"
> +	_scratch_mkfs >> $seqres.full
> +	_scratch_mount
> +
> +	# Create subvol and write data BEFORE enabling squotas.
> +	$BTRFS_UTIL_PROG subvolume create $subv1 >> $seqres.full
> +	$XFS_IO_PROG -fc "pwrite -q 0 128K" $subv1/f
> +	sync
> +
> +	# Enable squotas. Pre-existing data is not accounted (gen < enable_gen).
> +	$BTRFS_UTIL_PROG quota enable --simple $SCRATCH_MNT
> +	$BTRFS_UTIL_PROG filesystem sync $SCRATCH_MNT
> +
> +	local subvid1=$(_btrfs_get_subvolid $SCRATCH_MNT subv1)
> +
> +	# Destroy the qgroup. rfer/excl = 0 because data predates squotas.
> +	# Should fail because the ROOT_ITEM still exists.
> +	$BTRFS_UTIL_PROG qgroup destroy 0/$subvid1 $SCRATCH_MNT 2>&1
> +
> +	_scratch_unmount
> +}
> +
> +free_from_deleted_owner
> +add_to_deleted_owner
> +same_txn_destroy_race
> +pre_existing_data_destroy
> +
> +# success, all done
> +status=0
> +exit
> diff --git a/tests/btrfs/348.out b/tests/btrfs/348.out
> new file mode 100644
> index 00000000..0a1cf11f
> --- /dev/null
> +++ b/tests/btrfs/348.out
> @@ -0,0 +1,10 @@
> +QA output created by 348
> +free from deleted owner
> +ERROR: unable to destroy quota group: Device or resource busy
> +add to deleted owner
> +ERROR: unable to destroy quota group: Device or resource busy
> +ERROR: unable to create quota group: Invalid argument
> +same txn destroy race
> +ERROR: unable to destroy quota group: Device or resource busy
> +pre-existing data destroy
> +ERROR: unable to destroy quota group: Device or resource busy


  reply	other threads:[~2026-05-18  9:18 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-13  0:43 [PATCH 0/2] squota delete fstests Boris Burkov
2026-05-13  0:43 ` [PATCH 1/2] btrfs: inline enable_quota helper in test 301 Boris Burkov
2026-05-14  3:34   ` Zorro Lang
2026-05-15 21:15     ` Boris Burkov
2026-05-16 13:55       ` Zorro Lang
2026-05-19  6:18   ` Anand Jain
2026-05-13  0:43 ` [PATCH 2/2] btrfs: test qgroup deletion races with squota writes Boris Burkov
2026-05-18  9:18   ` Anand Jain [this message]
2026-05-18 16:28     ` Boris Burkov
2026-05-19  6:15       ` Anand Jain

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=e29e3b26-bd22-41d7-92f0-8d89bf73f5ea@gmail.com \
    --to=anajain.sg@gmail.com \
    --cc=boris@bur.io \
    --cc=fstests@vger.kernel.org \
    --cc=kernel-team@fb.com \
    --cc=linux-btrfs@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox