]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
xfs: strengthen attr leaf block freemap checking
authorDarrick J. Wong <djwong@kernel.org>
Fri, 23 Jan 2026 17:27:32 +0000 (09:27 -0800)
committerDarrick J. Wong <djwong@kernel.org>
Fri, 23 Jan 2026 17:27:32 +0000 (09:27 -0800)
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>
fs/xfs/libxfs/xfs_attr_leaf.c

index 75d481427bfea40273f9322dfc1ecb5177e37dec..c3327b10709c8af03b79b7c7fb19811125038fca 100644 (file)
@@ -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);