summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDarrick J. Wong <djwong@kernel.org>2026-01-23 09:27:32 -0800
committerDarrick J. Wong <djwong@kernel.org>2026-01-23 09:27:32 -0800
commit27a0c41f33d8d31558d334b07eb58701aab0b3dd (patch)
tree12f2ff569dfcd8082ca1c0039e2250de1642a104
parenta165f7e7633ee0d83926d29e7909fdd8dd4dfadc (diff)
xfs: strengthen attr leaf block freemap checking
Check for erroneous overlapping freemap regions and collisions between freemap regions and the xattr leaf entry array. Note that we must explicitly zero out the extra freemaps in xfs_attr3_leaf_compact so that the in-memory buffer has a correctly initialized freemap array to satisfy the new verification code, even if subsequent code changes the contents before unlocking the buffer. Signed-off-by: "Darrick J. Wong" <djwong@kernel.org> Reviewed-by: Christoph Hellwig <hch@lst.de>
-rw-r--r--fs/xfs/libxfs/xfs_attr_leaf.c55
1 files changed, 55 insertions, 0 deletions
diff --git a/fs/xfs/libxfs/xfs_attr_leaf.c b/fs/xfs/libxfs/xfs_attr_leaf.c
index 75d481427bfe..c3327b10709c 100644
--- a/fs/xfs/libxfs/xfs_attr_leaf.c
+++ b/fs/xfs/libxfs/xfs_attr_leaf.c
@@ -85,6 +85,49 @@ xfs_attr_leaf_entries_end(
xfs_attr3_leaf_hdr_size(leaf);
}
+static inline bool
+ichdr_freemaps_overlap(
+ const struct xfs_attr3_icleaf_hdr *ichdr,
+ unsigned int x,
+ unsigned int y)
+{
+ const unsigned int xend =
+ ichdr->freemap[x].base + ichdr->freemap[x].size;
+ const unsigned int yend =
+ ichdr->freemap[y].base + ichdr->freemap[y].size;
+
+ /* empty slots do not overlap */
+ if (!ichdr->freemap[x].size || !ichdr->freemap[y].size)
+ return false;
+
+ return ichdr->freemap[x].base < yend && xend > ichdr->freemap[y].base;
+}
+
+static inline xfs_failaddr_t
+xfs_attr_leaf_ichdr_freemaps_verify(
+ const struct xfs_attr3_icleaf_hdr *ichdr,
+ const struct xfs_attr_leafblock *leaf)
+{
+ unsigned int entries_end =
+ xfs_attr_leaf_entries_end(ichdr->count, leaf);
+ int i;
+
+ if (ichdr_freemaps_overlap(ichdr, 0, 1))
+ return __this_address;
+ if (ichdr_freemaps_overlap(ichdr, 0, 2))
+ return __this_address;
+ if (ichdr_freemaps_overlap(ichdr, 1, 2))
+ return __this_address;
+
+ for (i = 0; i < XFS_ATTR_LEAF_MAPSIZE; i++) {
+ if (ichdr->freemap[i].size > 0 &&
+ ichdr->freemap[i].base < entries_end)
+ return __this_address;
+ }
+
+ return NULL;
+}
+
/*
* attr3 block 'firstused' conversion helpers.
*
@@ -228,6 +271,8 @@ xfs_attr3_leaf_hdr_to_disk(
hdr3->freemap[i].base = cpu_to_be16(from->freemap[i].base);
hdr3->freemap[i].size = cpu_to_be16(from->freemap[i].size);
}
+
+ ASSERT(xfs_attr_leaf_ichdr_freemaps_verify(from, to) == NULL);
return;
}
to->hdr.info.forw = cpu_to_be32(from->forw);
@@ -243,6 +288,8 @@ xfs_attr3_leaf_hdr_to_disk(
to->hdr.freemap[i].base = cpu_to_be16(from->freemap[i].base);
to->hdr.freemap[i].size = cpu_to_be16(from->freemap[i].size);
}
+
+ ASSERT(xfs_attr_leaf_ichdr_freemaps_verify(from, to) == NULL);
}
static xfs_failaddr_t
@@ -395,6 +442,10 @@ xfs_attr3_leaf_verify(
return __this_address;
}
+ fa = xfs_attr_leaf_ichdr_freemaps_verify(&ichdr, leaf);
+ if (fa)
+ return fa;
+
return NULL;
}
@@ -1664,6 +1715,10 @@ xfs_attr3_leaf_compact(
ichdr_dst->freemap[0].base = xfs_attr3_leaf_hdr_size(leaf_src);
ichdr_dst->freemap[0].size = ichdr_dst->firstused -
ichdr_dst->freemap[0].base;
+ ichdr_dst->freemap[1].base = 0;
+ ichdr_dst->freemap[2].base = 0;
+ ichdr_dst->freemap[1].size = 0;
+ ichdr_dst->freemap[2].size = 0;
/* write the header back to initialise the underlying buffer */
xfs_attr3_leaf_hdr_to_disk(args->geo, leaf_dst, ichdr_dst);