Linux XFS filesystem development
 help / color / mirror / Atom feed
From: "Darrick J. Wong" <djwong@kernel.org>
To: Norbert Szetei <norbert@doyensec.com>
Cc: Dave Chinner <dgc@kernel.org>, Carlos Maiolino <cem@kernel.org>,
	linux-xfs@vger.kernel.org, Christoph Hellwig <hch@infradead.org>
Subject: Re: [PATCH] xfs: fix exchange-range-to-eof file size exchange
Date: Tue, 6 Oct 2026 16:40:44 -0700	[thread overview]
Message-ID: <20261006234044.GS1615495@frogsfrogsfrogs> (raw)
In-Reply-To: <6363D9F4-4DD0-4E63-90C0-38721B244783@doyensec.com>

On Tue, Oct 06, 2026 at 10:12:17PM +0200, Norbert Szetei wrote:
> > On Oct 6, 2026, at 17:00, Darrick J. Wong <djwong@kernel.org> wrote:
> > 
> > On Tue, Oct 06, 2026 at 06:25:17PM +1100, Dave Chinner wrote:
> >> On Mon, Oct 05, 2026 at 10:09:14PM -0700, Darrick J. Wong wrote:
> >>> From: Darrick J. Wong <djwong@kernel.org>
> >>> 
> >>> Norbert Szetei posted a patch containing an incomplete description of a
> >>> bug in XFS_IOC_EXCHANGE_RANGE's TO_EOF flag.  The bug tricks
> >>> exchange-range into modifying a file so that it has shared blocks
> >>> starting beyond EOF, which is never allowed because files never have
> >>> written data beyond EOF, and you can only share written data blocks.
> >>> This is key to all the incorrect behavior that follows.
> >> ....
> >>> 
> >>> Having confused the file sizes, the PoC uses a second exchange-range
> >>> call on what looks like a non-reflinked single-block file.  Because the
> >>> file size is set incorrectly, exchange-range thinks it's exchanging the
> >>> full contents of two files and clears the reflink flag on the broken
> >>> file.
> >> 
> >> Hmmm, another "exchrange reflink flag coherency" bug.
> >> 
> >> Aside from fixing this specific issue, how can we prevent
> >> other/future logic bugs from failing to set the reflink flag
> >> appropriately and so never expose such a bug to userspace again?
> >> 
> >> I'm thinking that we should not try to swap the reflink flag along
> >> with the extents. Instead, if one of the files had the reflink flag
> >> set before the swap, then we scan scan the extents of both files
> >> after the swap for shared extents and set the reflink flags for each
> >> file appropriately.  EXCHRANGE isn't really performance sensitive,
> >> so the cost of the refcount scans shouldn't be an issue.
> >> 
> >> Setting the flags this way means it doesn't matter what file shared
> >> extent(s) ends up in, the inode(s) that owns it(them) will always
> >> have the reflink flag set correctly. Hence there is no way to use
> >> EXCHRANGE creatively to expose this "didn't COW when it should have'
> >> class of corruption bugs ever again...
> > 
> > That is what Norbert's patch actually does.  If you think removing
> > xfs_exchmaps_clear_reflink and all the stuff that calls it is a good
> > idea (and it probably is!) then let's move that discussion and review to
> > that patch's thread.
> 
> I realized the patch I proposed is not complete on its own either.
> XFS_IOC_SWAPEXT swaps the reflink flags as well, in xfs_swap_extents()
> (xfs_bmap_util.c:1704), which my patch does not touch. The test guarding it
> 
>         /* Verify all data are being swapped */
>         if (sxp->sx_offset != 0 ||
>             sxp->sx_length != ip->i_disk_size ||
>             sxp->sx_length != tip->i_disk_size) {
> 
> only proves the requested length matches i_disk_size, and i_disk_size does
> not bound the inode's mappings, so xfs_swap_extent_rmap() leaves mappings
> behind and the flags are traded anyway.

<nod> Could you repost your original patch but with this chunk removed
as well, please?  I no longer think that cross referencing every data
fork mapping with the refcount data on every exchrange is worth the
trouble.

--D

> N.
> 
> > That enables an extending write of the broken file to rewrite the
> >>> shared block that's just past EOF.  This has become known colloquially
> >>> as refluxfs.
> >>> 
> >>> Once the file sizes are set correctly, the second exchange-range no
> >>> longer thinks that it's doing a full-contents swap, so it won't clear
> >>> the reflink flag on either of its file arguments.
> >>> 
> >>> Link: https://lore.kernel.org/linux-xfs/1E196589-DEBE-40AC-AFEA-D420DAAB067F@doyensec.com/
> >>> Reported-by: Norbert Szetei <norbert@doyensec.com>
> >>> Cc: <stable@vger.kernel.org> # v6.10
> >>> Fixes: 966ceafc7a4371 ("xfs: create deferred log items for file mapping exchanges")
> >>> Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
> >>> ---
> >>> fs/xfs/libxfs/xfs_exchmaps.c |    8 ++++++--
> >>> fs/xfs/xfs_exchrange.c       |   10 ++++++----
> >>> 2 files changed, 12 insertions(+), 6 deletions(-)
> >>> 
> >>> diff --git a/fs/xfs/libxfs/xfs_exchmaps.c b/fs/xfs/libxfs/xfs_exchmaps.c
> >>> index 6a66b6075e0af4..c9d9464e9d39f5 100644
> >>> --- a/fs/xfs/libxfs/xfs_exchmaps.c
> >>> +++ b/fs/xfs/libxfs/xfs_exchmaps.c
> >>> @@ -987,6 +987,7 @@ xfs_exchmaps_init_intent(
> >>> const struct xfs_exchmaps_req *req)
> >>> {
> >>> struct xfs_exchmaps_intent *xmi;
> >>> + struct xfs_mount *mp = req->ip1->i_mount;
> >>> unsigned int rs = 0;
> >>> 
> >>> xmi = kmem_cache_zalloc(xfs_exchmaps_intent_cache,
> >>> @@ -1006,9 +1007,12 @@ xfs_exchmaps_init_intent(
> >>> }
> >>> 
> >>> if (req->flags & XFS_EXCHMAPS_SET_SIZES) {
> >>> + loff_t off1 = XFS_FSB_TO_B(mp, xmi->xmi_startoff1);
> >>> + loff_t off2 = XFS_FSB_TO_B(mp, xmi->xmi_startoff2);
> >>> +
> >>> xmi->xmi_flags |= XFS_EXCHMAPS_SET_SIZES;
> >>> - xmi->xmi_isize1 = req->ip2->i_disk_size;
> >>> - xmi->xmi_isize2 = req->ip1->i_disk_size;
> >>> + xmi->xmi_isize1 = off1 + (req->ip2->i_disk_size - off2);
> >>> + xmi->xmi_isize2 = off2 + (req->ip1->i_disk_size - off1);
> >>> }
> >> 
> >> I think this really needs a comment to explain why the calculation
> >> is structured this way. I think it is trying to set the file sizes
> >> to the "destination offset" + "swap length from other file" because
> >> the ranges in each file might be different lengths, but I could be
> >> wrong...
> > 
> > That's correct.  I'll add that as a code comment.
> > 
> > --D
> > 
> >> 
> >> Cheers,
> >> 
> >> Dave.
> >> -- 
> >> Dave Chinner
> >> dgc@kernel.org
> 
> 

  reply	other threads:[~2026-10-06 23:40 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-06  5:09 [PATCH] xfs: fix exchange-range-to-eof file size exchange Darrick J. Wong
2026-10-06  6:15 ` [PATCH] xfs: add regression test for exchangerange-to-eof reflux Darrick J. Wong
2026-10-06  7:25 ` [PATCH] xfs: fix exchange-range-to-eof file size exchange Dave Chinner
2026-10-06 15:00   ` Darrick J. Wong
2026-10-06 20:12     ` Norbert Szetei
2026-10-06 23:40       ` Darrick J. Wong [this message]
2026-10-06 20:02 ` Norbert Szetei
2026-10-06 21:47   ` Darrick J. Wong
2026-10-06 22:43     ` Darrick J. Wong

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=20261006234044.GS1615495@frogsfrogsfrogs \
    --to=djwong@kernel.org \
    --cc=cem@kernel.org \
    --cc=dgc@kernel.org \
    --cc=hch@infradead.org \
    --cc=linux-xfs@vger.kernel.org \
    --cc=norbert@doyensec.com \
    /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