* [PATCH 1/6] xfs: delete attr leaf freemap entries when empty
2026-01-21 6:34 [PATCHSET 2/3] xfs: fix problems in the attr leaf freemap code Darrick J. Wong
@ 2026-01-21 6:37 ` Darrick J. Wong
2026-01-21 15:05 ` Christoph Hellwig
2026-01-21 6:38 ` [PATCH 2/6] xfs: fix freemap adjustments when adding xattrs to leaf blocks Darrick J. Wong
` (2 subsequent siblings)
3 siblings, 1 reply; 10+ messages in thread
From: Darrick J. Wong @ 2026-01-21 6:37 UTC (permalink / raw)
To: djwong, cem; +Cc: stable, linux-xfs
From: Darrick J. Wong <djwong@kernel.org>
Back in commit 2a2b5932db6758 ("xfs: fix attr leaf header freemap.size
underflow"), Brian Foster observed that it's possible for a small
freemap at the end of the end of the xattr entries array to experience
a size underflow when subtracting the space consumed by an expansion of
the entries array. There are only three freemap entries, which means
that it is not a complete index of all free space in the leaf block.
This code can leave behind a zero-length freemap entry with a nonzero
base. Subsequent setxattr operations can increase the base up to the
point that it overlaps with another freemap entry. This isn't in and of
itself a problem because the code in _leaf_add that finds free space
ignores any freemap entry with zero size.
However, there's another bug in the freemap update code in _leaf_add,
which is that it fails to update a freemap entry that begins midway
through the xattr entry that was just appended to the array. That can
result in the freemap containing two entries with the same base but
different sizes (0 for the "pushed-up" entry, nonzero for the entry
that's actually tracking free space). A subsequent _leaf_add can then
allocate xattr namevalue entries on top of the entries array, leading to
data loss. But fixing that is for later.
For now, eliminate the possibility of confusion by zeroing out the base
of any freemap entry that has zero size. Because the freemap is not
intended to be a complete index of free space, a subsequent failure to
find any free space for a new xattr will trigger block compaction, which
regenerates the freemap.
It looks like this bug has been in the codebase for quite a long time.
Cc: <stable@vger.kernel.org> # v2.6.12
Fixes: 1da177e4c3f415 ("Linux-2.6.12-rc2")
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/xfs/libxfs/xfs_attr_leaf.c | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/fs/xfs/libxfs/xfs_attr_leaf.c b/fs/xfs/libxfs/xfs_attr_leaf.c
index 91c1b30ebaab31..33c6c468ad8d55 100644
--- a/fs/xfs/libxfs/xfs_attr_leaf.c
+++ b/fs/xfs/libxfs/xfs_attr_leaf.c
@@ -1580,6 +1580,19 @@ xfs_attr3_leaf_add_work(
min_t(uint16_t, ichdr->freemap[i].size,
sizeof(xfs_attr_leaf_entry_t));
}
+
+ /*
+ * Don't leave zero-length freemaps with nonzero base lying
+ * around, because we don't want the code in _remove that
+ * matches on base address to get confused and create
+ * overlapping freemaps. If we end up with no freemap entries
+ * then the next _add will compact the leaf block and
+ * regenerate the freemaps.
+ */
+ if (ichdr->freemap[i].size == 0 && ichdr->freemap[i].base > 0) {
+ ichdr->freemap[i].base = 0;
+ ichdr->holes = 1;
+ }
}
ichdr->usedbytes += xfs_attr_leaf_entsize(leaf, args->index);
}
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH 2/6] xfs: fix freemap adjustments when adding xattrs to leaf blocks
2026-01-21 6:34 [PATCHSET 2/3] xfs: fix problems in the attr leaf freemap code Darrick J. Wong
2026-01-21 6:37 ` [PATCH 1/6] xfs: delete attr leaf freemap entries when empty Darrick J. Wong
@ 2026-01-21 6:38 ` Darrick J. Wong
2026-01-21 15:06 ` Christoph Hellwig
2026-01-21 6:38 ` [PATCH 5/6] xfs: fix the xattr scrub to detect freemap/entries array collisions Darrick J. Wong
2026-01-21 6:39 ` [PATCH 6/6] xfs: fix remote xattr valuelblk check Darrick J. Wong
3 siblings, 1 reply; 10+ messages in thread
From: Darrick J. Wong @ 2026-01-21 6:38 UTC (permalink / raw)
To: djwong, cem; +Cc: stable, linux-xfs
From: Darrick J. Wong <djwong@kernel.org>
xfs/592 and xfs/794 both trip this assertion in the leaf block freemap
adjustment code after ~20 minutes of running on my test VMs:
ASSERT(ichdr->firstused >= ichdr->count * sizeof(xfs_attr_leaf_entry_t)
+ xfs_attr3_leaf_hdr_size(leaf));
Upon enabling quite a lot more debugging code, I narrowed this down to
fsstress trying to set a local extended attribute with namelen=3 and
valuelen=71. This results in an entry size of 80 bytes.
At the start of xfs_attr3_leaf_add_work, the freemap looks like this:
i 0 base 448 size 0 rhs 448 count 46
i 1 base 388 size 132 rhs 448 count 46
i 2 base 2120 size 4 rhs 448 count 46
firstused = 520
where "rhs" is the first byte past the end of the leaf entry array.
This is inconsistent -- the entries array ends at byte 448, but
freemap[1] says there's free space starting at byte 388!
By the end of the function, the freemap is in worse shape:
i 0 base 456 size 0 rhs 456 count 47
i 1 base 388 size 52 rhs 456 count 47
i 2 base 2120 size 4 rhs 456 count 47
firstused = 440
Important note: 388 is not aligned with the entries array element size
of 8 bytes.
Based on the incorrect freemap, the name area starts at byte 440, which
is below the end of the entries array! That's why the assertion
triggers and the filesystem shuts down.
How did we end up here? First, recall from the previous patch that the
freemap array in an xattr leaf block is not intended to be a
comprehensive map of all free space in the leaf block. In other words,
it's perfectly legal to have a leaf block with:
* 376 bytes in use by the entries array
* freemap[0] has [base = 376, size = 8]
* freemap[1] has [base = 388, size = 1500]
* the space between 376 and 388 is free, but the freemap stopped
tracking that some time ago
If we add one xattr, the entries array grows to 384 bytes, and
freemap[0] becomes [base = 384, size = 0]. So far, so good. But if we
add a second xattr, the entries array grows to 392 bytes, and freemap[0]
gets pushed up to [base = 392, size = 0]. This is bad, because
freemap[1] hasn't been updated, and now the entries array and the free
space claim the same space.
The fix here is to adjust all freemap entries so that none of them
collide with the entries array. Note that this fix relies on commit
2a2b5932db6758 ("xfs: fix attr leaf header freemap.size underflow") and
the previous patch that resets zero length freemap entries to have
base = 0.
Cc: <stable@vger.kernel.org> # v2.6.12
Fixes: 1da177e4c3f415 ("Linux-2.6.12-rc2")
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/xfs/libxfs/xfs_attr_leaf.c | 36 ++++++++++++++++++++++++++++--------
1 file changed, 28 insertions(+), 8 deletions(-)
diff --git a/fs/xfs/libxfs/xfs_attr_leaf.c b/fs/xfs/libxfs/xfs_attr_leaf.c
index 33c6c468ad8d55..b858e3c2ad50a2 100644
--- a/fs/xfs/libxfs/xfs_attr_leaf.c
+++ b/fs/xfs/libxfs/xfs_attr_leaf.c
@@ -1476,6 +1476,7 @@ xfs_attr3_leaf_add_work(
struct xfs_attr_leaf_name_local *name_loc;
struct xfs_attr_leaf_name_remote *name_rmt;
struct xfs_mount *mp;
+ int old_end, new_end;
int tmp;
int i;
@@ -1568,17 +1569,36 @@ xfs_attr3_leaf_add_work(
if (be16_to_cpu(entry->nameidx) < ichdr->firstused)
ichdr->firstused = be16_to_cpu(entry->nameidx);
- ASSERT(ichdr->firstused >= ichdr->count * sizeof(xfs_attr_leaf_entry_t)
- + xfs_attr3_leaf_hdr_size(leaf));
- tmp = (ichdr->count - 1) * sizeof(xfs_attr_leaf_entry_t)
- + xfs_attr3_leaf_hdr_size(leaf);
+ new_end = ichdr->count * sizeof(struct xfs_attr_leaf_entry) +
+ xfs_attr3_leaf_hdr_size(leaf);
+ old_end = new_end - sizeof(struct xfs_attr_leaf_entry);
+
+ ASSERT(ichdr->firstused >= new_end);
for (i = 0; i < XFS_ATTR_LEAF_MAPSIZE; i++) {
- if (ichdr->freemap[i].base == tmp) {
- ichdr->freemap[i].base += sizeof(xfs_attr_leaf_entry_t);
+ int diff = 0;
+
+ if (ichdr->freemap[i].base == old_end) {
+ /*
+ * This freemap entry starts at the old end of the
+ * leaf entry array, so we need to adjust its base
+ * upward to accomodate the larger array.
+ */
+ diff = sizeof(struct xfs_attr_leaf_entry);
+ } else if (ichdr->freemap[i].size > 0 &&
+ ichdr->freemap[i].base < new_end) {
+ /*
+ * This freemap entry starts in the space claimed by
+ * the new leaf entry. Adjust its base upward to
+ * reflect that.
+ */
+ diff = new_end - ichdr->freemap[i].base;
+ }
+
+ if (diff) {
+ ichdr->freemap[i].base += diff;
ichdr->freemap[i].size -=
- min_t(uint16_t, ichdr->freemap[i].size,
- sizeof(xfs_attr_leaf_entry_t));
+ min_t(uint16_t, ichdr->freemap[i].size, diff);
}
/*
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH 5/6] xfs: fix the xattr scrub to detect freemap/entries array collisions
2026-01-21 6:34 [PATCHSET 2/3] xfs: fix problems in the attr leaf freemap code Darrick J. Wong
2026-01-21 6:37 ` [PATCH 1/6] xfs: delete attr leaf freemap entries when empty Darrick J. Wong
2026-01-21 6:38 ` [PATCH 2/6] xfs: fix freemap adjustments when adding xattrs to leaf blocks Darrick J. Wong
@ 2026-01-21 6:38 ` Darrick J. Wong
2026-01-21 15:08 ` Christoph Hellwig
2026-01-21 6:39 ` [PATCH 6/6] xfs: fix remote xattr valuelblk check Darrick J. Wong
3 siblings, 1 reply; 10+ messages in thread
From: Darrick J. Wong @ 2026-01-21 6:38 UTC (permalink / raw)
To: djwong, cem; +Cc: stable, linux-xfs
From: Darrick J. Wong <djwong@kernel.org>
In the previous patches, we observed that it's possible for there to be
freemap entries with zero size but a nonzero base. This isn't an
inconsistency per se, but older kernels can get confused by this and
corrupt the block, leading to corruption.
If we see this, flag the xattr structure for optimization so that it
gets rebuilt.
Cc: <stable@vger.kernel.org> # v4.15
Fixes: 13791d3b833428 ("xfs: scrub extended attribute leaf space")
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/xfs/scrub/attr.c | 54 ++++++++++++++++++++++++++-------------------------
1 file changed, 27 insertions(+), 27 deletions(-)
diff --git a/fs/xfs/scrub/attr.c b/fs/xfs/scrub/attr.c
index 708334f9b2bd13..ef299be01de5ea 100644
--- a/fs/xfs/scrub/attr.c
+++ b/fs/xfs/scrub/attr.c
@@ -287,32 +287,6 @@ xchk_xattr_set_map(
return ret;
}
-/*
- * Check the leaf freemap from the usage bitmap. Returns false if the
- * attr freemap has problems or points to used space.
- */
-STATIC bool
-xchk_xattr_check_freemap(
- struct xfs_scrub *sc,
- struct xfs_attr3_icleaf_hdr *leafhdr)
-{
- struct xchk_xattr_buf *ab = sc->buf;
- unsigned int mapsize = sc->mp->m_attr_geo->blksize;
- int i;
-
- /* Construct bitmap of freemap contents. */
- bitmap_zero(ab->freemap, mapsize);
- for (i = 0; i < XFS_ATTR_LEAF_MAPSIZE; i++) {
- if (!xchk_xattr_set_map(sc, ab->freemap,
- leafhdr->freemap[i].base,
- leafhdr->freemap[i].size))
- return false;
- }
-
- /* Look for bits that are set in freemap and are marked in use. */
- return !bitmap_intersects(ab->freemap, ab->usedmap, mapsize);
-}
-
/*
* Check this leaf entry's relations to everything else.
* Returns the number of bytes used for the name/value data.
@@ -403,6 +377,7 @@ xchk_xattr_block(
*last_checked = blk->blkno;
bitmap_zero(ab->usedmap, mp->m_attr_geo->blksize);
+ bitmap_zero(ab->freemap, mp->m_attr_geo->blksize);
/* Check all the padding. */
if (xfs_has_crc(ds->sc->mp)) {
@@ -449,6 +424,9 @@ xchk_xattr_block(
if ((char *)&entries[leafhdr.count] > (char *)leaf + leafhdr.firstused)
xchk_da_set_corrupt(ds, level);
+ if (ds->sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
+ goto out;
+
buf_end = (char *)bp->b_addr + mp->m_attr_geo->blksize;
for (i = 0, ent = entries; i < leafhdr.count; ent++, i++) {
/* Mark the leaf entry itself. */
@@ -467,7 +445,29 @@ xchk_xattr_block(
goto out;
}
- if (!xchk_xattr_check_freemap(ds->sc, &leafhdr))
+ /* Construct bitmap of freemap contents. */
+ for (i = 0; i < XFS_ATTR_LEAF_MAPSIZE; i++) {
+ if (!xchk_xattr_set_map(ds->sc, ab->freemap,
+ leafhdr.freemap[i].base,
+ leafhdr.freemap[i].size))
+ xchk_da_set_corrupt(ds, level);
+
+ /*
+ * freemap entries with zero length and nonzero base can cause
+ * problems with older kernels, so we mark these for preening
+ * even though there's no inconsistency.
+ */
+ if (leafhdr.freemap[i].size == 0 &&
+ leafhdr.freemap[i].base > 0)
+ xchk_da_set_preen(ds, level);
+
+ if (ds->sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
+ goto out;
+ }
+
+ /* Look for bits that are set in freemap and are marked in use. */
+ if (bitmap_intersects(ab->freemap, ab->usedmap,
+ mp->m_attr_geo->blksize))
xchk_da_set_corrupt(ds, level);
if (leafhdr.usedbytes != usedbytes)
^ permalink raw reply related [flat|nested] 10+ messages in thread* Re: [PATCH 5/6] xfs: fix the xattr scrub to detect freemap/entries array collisions
2026-01-21 6:38 ` [PATCH 5/6] xfs: fix the xattr scrub to detect freemap/entries array collisions Darrick J. Wong
@ 2026-01-21 15:08 ` Christoph Hellwig
0 siblings, 0 replies; 10+ messages in thread
From: Christoph Hellwig @ 2026-01-21 15:08 UTC (permalink / raw)
To: Darrick J. Wong; +Cc: cem, stable, linux-xfs
On Tue, Jan 20, 2026 at 10:38:59PM -0800, Darrick J. Wong wrote:
> From: Darrick J. Wong <djwong@kernel.org>
>
> In the previous patches, we observed that it's possible for there to be
> freemap entries with zero size but a nonzero base. This isn't an
> inconsistency per se, but older kernels can get confused by this and
> corrupt the block, leading to corruption.
A bit hard to follow between removing the helper and the logic
change, but the result looks good:
Reviewed-by: Christoph Hellwig <hch@lst.de>
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH 6/6] xfs: fix remote xattr valuelblk check
2026-01-21 6:34 [PATCHSET 2/3] xfs: fix problems in the attr leaf freemap code Darrick J. Wong
` (2 preceding siblings ...)
2026-01-21 6:38 ` [PATCH 5/6] xfs: fix the xattr scrub to detect freemap/entries array collisions Darrick J. Wong
@ 2026-01-21 6:39 ` Darrick J. Wong
2026-01-21 15:08 ` Christoph Hellwig
3 siblings, 1 reply; 10+ messages in thread
From: Darrick J. Wong @ 2026-01-21 6:39 UTC (permalink / raw)
To: djwong, cem; +Cc: stable, linux-xfs
From: Darrick J. Wong <djwong@kernel.org>
In debugging other problems with generic/753, it turns out that it's
possible for the system go to down in the middle of a remote xattr set
operation such that the leaf block entry is marked incomplete and
valueblk is set to zero. Make this no longer a failure.
Cc: <stable@vger.kernel.org> # v4.15
Fixes: 13791d3b833428 ("xfs: scrub extended attribute leaf space")
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/xfs/scrub/attr.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/fs/xfs/scrub/attr.c b/fs/xfs/scrub/attr.c
index ef299be01de5ea..a0878fdbcf3866 100644
--- a/fs/xfs/scrub/attr.c
+++ b/fs/xfs/scrub/attr.c
@@ -338,7 +338,10 @@ xchk_xattr_entry(
rentry = xfs_attr3_leaf_name_remote(leaf, idx);
namesize = xfs_attr_leaf_entsize_remote(rentry->namelen);
name_end = (char *)rentry + namesize;
- if (rentry->namelen == 0 || rentry->valueblk == 0)
+ if (rentry->namelen == 0)
+ xchk_da_set_corrupt(ds, level);
+ if (rentry->valueblk == 0 &&
+ !(ent->flags & XFS_ATTR_INCOMPLETE))
xchk_da_set_corrupt(ds, level);
}
if (name_end > buf_end)
^ permalink raw reply related [flat|nested] 10+ messages in thread* Re: [PATCH 6/6] xfs: fix remote xattr valuelblk check
2026-01-21 6:39 ` [PATCH 6/6] xfs: fix remote xattr valuelblk check Darrick J. Wong
@ 2026-01-21 15:08 ` Christoph Hellwig
0 siblings, 0 replies; 10+ messages in thread
From: Christoph Hellwig @ 2026-01-21 15:08 UTC (permalink / raw)
To: Darrick J. Wong; +Cc: cem, stable, linux-xfs
On Tue, Jan 20, 2026 at 10:39:15PM -0800, Darrick J. Wong wrote:
> From: Darrick J. Wong <djwong@kernel.org>
>
> In debugging other problems with generic/753, it turns out that it's
> possible for the system go to down in the middle of a remote xattr set
> operation such that the leaf block entry is marked incomplete and
> valueblk is set to zero. Make this no longer a failure.
Looks good:
Reviewed-by: Christoph Hellwig <hch@lst.de>
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH 5/6] xfs: fix the xattr scrub to detect freemap/entries array collisions
2026-01-23 7:00 [PATCHSET 1/3] xfs: fix problems in the attr leaf freemap code Darrick J. Wong
@ 2026-01-23 7:02 ` Darrick J. Wong
0 siblings, 0 replies; 10+ messages in thread
From: Darrick J. Wong @ 2026-01-23 7:02 UTC (permalink / raw)
To: djwong, cem; +Cc: hch, stable, linux-xfs
From: Darrick J. Wong <djwong@kernel.org>
In the previous patches, we observed that it's possible for there to be
freemap entries with zero size but a nonzero base. This isn't an
inconsistency per se, but older kernels can get confused by this and
corrupt the block, leading to corruption.
If we see this, flag the xattr structure for optimization so that it
gets rebuilt.
Cc: <stable@vger.kernel.org> # v4.15
Fixes: 13791d3b833428 ("xfs: scrub extended attribute leaf space")
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
---
fs/xfs/scrub/attr.c | 54 ++++++++++++++++++++++++++-------------------------
1 file changed, 27 insertions(+), 27 deletions(-)
diff --git a/fs/xfs/scrub/attr.c b/fs/xfs/scrub/attr.c
index 708334f9b2bd13..ef299be01de5ea 100644
--- a/fs/xfs/scrub/attr.c
+++ b/fs/xfs/scrub/attr.c
@@ -287,32 +287,6 @@ xchk_xattr_set_map(
return ret;
}
-/*
- * Check the leaf freemap from the usage bitmap. Returns false if the
- * attr freemap has problems or points to used space.
- */
-STATIC bool
-xchk_xattr_check_freemap(
- struct xfs_scrub *sc,
- struct xfs_attr3_icleaf_hdr *leafhdr)
-{
- struct xchk_xattr_buf *ab = sc->buf;
- unsigned int mapsize = sc->mp->m_attr_geo->blksize;
- int i;
-
- /* Construct bitmap of freemap contents. */
- bitmap_zero(ab->freemap, mapsize);
- for (i = 0; i < XFS_ATTR_LEAF_MAPSIZE; i++) {
- if (!xchk_xattr_set_map(sc, ab->freemap,
- leafhdr->freemap[i].base,
- leafhdr->freemap[i].size))
- return false;
- }
-
- /* Look for bits that are set in freemap and are marked in use. */
- return !bitmap_intersects(ab->freemap, ab->usedmap, mapsize);
-}
-
/*
* Check this leaf entry's relations to everything else.
* Returns the number of bytes used for the name/value data.
@@ -403,6 +377,7 @@ xchk_xattr_block(
*last_checked = blk->blkno;
bitmap_zero(ab->usedmap, mp->m_attr_geo->blksize);
+ bitmap_zero(ab->freemap, mp->m_attr_geo->blksize);
/* Check all the padding. */
if (xfs_has_crc(ds->sc->mp)) {
@@ -449,6 +424,9 @@ xchk_xattr_block(
if ((char *)&entries[leafhdr.count] > (char *)leaf + leafhdr.firstused)
xchk_da_set_corrupt(ds, level);
+ if (ds->sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
+ goto out;
+
buf_end = (char *)bp->b_addr + mp->m_attr_geo->blksize;
for (i = 0, ent = entries; i < leafhdr.count; ent++, i++) {
/* Mark the leaf entry itself. */
@@ -467,7 +445,29 @@ xchk_xattr_block(
goto out;
}
- if (!xchk_xattr_check_freemap(ds->sc, &leafhdr))
+ /* Construct bitmap of freemap contents. */
+ for (i = 0; i < XFS_ATTR_LEAF_MAPSIZE; i++) {
+ if (!xchk_xattr_set_map(ds->sc, ab->freemap,
+ leafhdr.freemap[i].base,
+ leafhdr.freemap[i].size))
+ xchk_da_set_corrupt(ds, level);
+
+ /*
+ * freemap entries with zero length and nonzero base can cause
+ * problems with older kernels, so we mark these for preening
+ * even though there's no inconsistency.
+ */
+ if (leafhdr.freemap[i].size == 0 &&
+ leafhdr.freemap[i].base > 0)
+ xchk_da_set_preen(ds, level);
+
+ if (ds->sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
+ goto out;
+ }
+
+ /* Look for bits that are set in freemap and are marked in use. */
+ if (bitmap_intersects(ab->freemap, ab->usedmap,
+ mp->m_attr_geo->blksize))
xchk_da_set_corrupt(ds, level);
if (leafhdr.usedbytes != usedbytes)
^ permalink raw reply related [flat|nested] 10+ messages in thread