Commit 27a0c41f authored by Darrick J. Wong's avatar Darrick J. Wong
Browse files

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: default avatar"Darrick J. Wong" <djwong@kernel.org>
Reviewed-by: default avatarChristoph Hellwig <hch@lst.de>
parent a165f7e7
Loading
Loading
Loading
Loading
+55 −0
Original line number Diff line number Diff line
@@ -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);