xfs: sanity-check the unused space before trying to use it

In xfs_dir2_data_use_free, we examine on-disk metadata and ASSERT if
it doesn't make sense.  Since a carefully crafted fuzzed image can cause
the kernel to crash after blowing a bunch of assertions, let's move
those checks into a validator function and rig everything up to return
EFSCORRUPTED to userspace.  Found by lastbit fuzzing ltail.bestcount via
xfs/391.

Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
Reviewed-by: Brian Foster <bfoster@redhat.com>
This commit is contained in:
Darrick J. Wong 2018-03-23 10:06:51 -07:00
parent a27ba2607e
commit 6915ef35c0
5 changed files with 111 additions and 49 deletions

View File

@ -173,7 +173,7 @@ extern void xfs_dir2_data_log_unused(struct xfs_da_args *args,
extern void xfs_dir2_data_make_free(struct xfs_da_args *args,
struct xfs_buf *bp, xfs_dir2_data_aoff_t offset,
xfs_dir2_data_aoff_t len, int *needlogp, int *needscanp);
extern void xfs_dir2_data_use_free(struct xfs_da_args *args,
extern int xfs_dir2_data_use_free(struct xfs_da_args *args,
struct xfs_buf *bp, struct xfs_dir2_data_unused *dup,
xfs_dir2_data_aoff_t offset, xfs_dir2_data_aoff_t len,
int *needlogp, int *needscanp);

View File

@ -451,15 +451,19 @@ xfs_dir2_block_addname(
* No stale entries, will use enddup space to hold new leaf.
*/
if (!btp->stale) {
xfs_dir2_data_aoff_t aoff;
/*
* Mark the space needed for the new leaf entry, now in use.
*/
xfs_dir2_data_use_free(args, bp, enddup,
(xfs_dir2_data_aoff_t)
((char *)enddup - (char *)hdr + be16_to_cpu(enddup->length) -
sizeof(*blp)),
(xfs_dir2_data_aoff_t)sizeof(*blp),
&needlog, &needscan);
aoff = (xfs_dir2_data_aoff_t)((char *)enddup - (char *)hdr +
be16_to_cpu(enddup->length) - sizeof(*blp));
error = xfs_dir2_data_use_free(args, bp, enddup, aoff,
(xfs_dir2_data_aoff_t)sizeof(*blp), &needlog,
&needscan);
if (error)
return error;
/*
* Update the tail (entry count).
*/
@ -541,9 +545,11 @@ xfs_dir2_block_addname(
/*
* Mark space for the data entry used.
*/
xfs_dir2_data_use_free(args, bp, dup,
(xfs_dir2_data_aoff_t)((char *)dup - (char *)hdr),
(xfs_dir2_data_aoff_t)len, &needlog, &needscan);
error = xfs_dir2_data_use_free(args, bp, dup,
(xfs_dir2_data_aoff_t)((char *)dup - (char *)hdr),
(xfs_dir2_data_aoff_t)len, &needlog, &needscan);
if (error)
return error;
/*
* Create the new data entry.
*/
@ -997,8 +1003,10 @@ xfs_dir2_leaf_to_block(
/*
* Use up the space at the end of the block (blp/btp).
*/
xfs_dir2_data_use_free(args, dbp, dup, args->geo->blksize - size, size,
&needlog, &needscan);
error = xfs_dir2_data_use_free(args, dbp, dup,
args->geo->blksize - size, size, &needlog, &needscan);
if (error)
return error;
/*
* Initialize the block tail.
*/
@ -1110,18 +1118,14 @@ xfs_dir2_sf_to_block(
* Add block 0 to the inode.
*/
error = xfs_dir2_grow_inode(args, XFS_DIR2_DATA_SPACE, &blkno);
if (error) {
kmem_free(sfp);
return error;
}
if (error)
goto out_free;
/*
* Initialize the data block, then convert it to block format.
*/
error = xfs_dir3_data_init(args, blkno, &bp);
if (error) {
kmem_free(sfp);
return error;
}
if (error)
goto out_free;
xfs_dir3_block_init(mp, tp, bp, dp);
hdr = bp->b_addr;
@ -1136,8 +1140,10 @@ xfs_dir2_sf_to_block(
*/
dup = dp->d_ops->data_unused_p(hdr);
needlog = needscan = 0;
xfs_dir2_data_use_free(args, bp, dup, args->geo->blksize - i,
i, &needlog, &needscan);
error = xfs_dir2_data_use_free(args, bp, dup, args->geo->blksize - i,
i, &needlog, &needscan);
if (error)
goto out_free;
ASSERT(needscan == 0);
/*
* Fill in the tail.
@ -1150,9 +1156,11 @@ xfs_dir2_sf_to_block(
/*
* Remove the freespace, we'll manage it.
*/
xfs_dir2_data_use_free(args, bp, dup,
(xfs_dir2_data_aoff_t)((char *)dup - (char *)hdr),
be16_to_cpu(dup->length), &needlog, &needscan);
error = xfs_dir2_data_use_free(args, bp, dup,
(xfs_dir2_data_aoff_t)((char *)dup - (char *)hdr),
be16_to_cpu(dup->length), &needlog, &needscan);
if (error)
goto out_free;
/*
* Create entry for .
*/
@ -1256,4 +1264,7 @@ xfs_dir2_sf_to_block(
xfs_dir2_block_log_tail(tp, bp);
xfs_dir3_data_check(dp, bp);
return 0;
out_free:
kmem_free(sfp);
return error;
}

View File

@ -932,10 +932,51 @@ xfs_dir2_data_make_free(
*needscanp = needscan;
}
/* Check our free data for obvious signs of corruption. */
static inline xfs_failaddr_t
xfs_dir2_data_check_free(
struct xfs_dir2_data_hdr *hdr,
struct xfs_dir2_data_unused *dup,
xfs_dir2_data_aoff_t offset,
xfs_dir2_data_aoff_t len)
{
if (hdr->magic != cpu_to_be32(XFS_DIR2_DATA_MAGIC) &&
hdr->magic != cpu_to_be32(XFS_DIR3_DATA_MAGIC) &&
hdr->magic != cpu_to_be32(XFS_DIR2_BLOCK_MAGIC) &&
hdr->magic != cpu_to_be32(XFS_DIR3_BLOCK_MAGIC))
return __this_address;
if (be16_to_cpu(dup->freetag) != XFS_DIR2_DATA_FREE_TAG)
return __this_address;
if (offset < (char *)dup - (char *)hdr)
return __this_address;
if (offset + len > (char *)dup + be16_to_cpu(dup->length) - (char *)hdr)
return __this_address;
if ((char *)dup - (char *)hdr !=
be16_to_cpu(*xfs_dir2_data_unused_tag_p(dup)))
return __this_address;
return NULL;
}
/* Sanity-check a new bestfree entry. */
static inline xfs_failaddr_t
xfs_dir2_data_check_new_free(
struct xfs_dir2_data_hdr *hdr,
struct xfs_dir2_data_free *dfp,
struct xfs_dir2_data_unused *newdup)
{
if (dfp == NULL)
return __this_address;
if (dfp->length != newdup->length)
return __this_address;
if (be16_to_cpu(dfp->offset) != (char *)newdup - (char *)hdr)
return __this_address;
return NULL;
}
/*
* Take a byte range out of an existing unused space and make it un-free.
*/
void
int
xfs_dir2_data_use_free(
struct xfs_da_args *args,
struct xfs_buf *bp,
@ -947,23 +988,19 @@ xfs_dir2_data_use_free(
{
xfs_dir2_data_hdr_t *hdr; /* data block header */
xfs_dir2_data_free_t *dfp; /* bestfree pointer */
xfs_dir2_data_unused_t *newdup; /* new unused entry */
xfs_dir2_data_unused_t *newdup2; /* another new unused entry */
struct xfs_dir2_data_free *bf;
xfs_failaddr_t fa;
int matchback; /* matches end of freespace */
int matchfront; /* matches start of freespace */
int needscan; /* need to regen bestfree */
xfs_dir2_data_unused_t *newdup; /* new unused entry */
xfs_dir2_data_unused_t *newdup2; /* another new unused entry */
int oldlen; /* old unused entry's length */
struct xfs_dir2_data_free *bf;
hdr = bp->b_addr;
ASSERT(hdr->magic == cpu_to_be32(XFS_DIR2_DATA_MAGIC) ||
hdr->magic == cpu_to_be32(XFS_DIR3_DATA_MAGIC) ||
hdr->magic == cpu_to_be32(XFS_DIR2_BLOCK_MAGIC) ||
hdr->magic == cpu_to_be32(XFS_DIR3_BLOCK_MAGIC));
ASSERT(be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG);
ASSERT(offset >= (char *)dup - (char *)hdr);
ASSERT(offset + len <= (char *)dup + be16_to_cpu(dup->length) - (char *)hdr);
ASSERT((char *)dup - (char *)hdr == be16_to_cpu(*xfs_dir2_data_unused_tag_p(dup)));
fa = xfs_dir2_data_check_free(hdr, dup, offset, len);
if (fa)
goto corrupt;
/*
* Look up the entry in the bestfree table.
*/
@ -1008,9 +1045,9 @@ xfs_dir2_data_use_free(
xfs_dir2_data_freeremove(hdr, bf, dfp, needlogp);
dfp = xfs_dir2_data_freeinsert(hdr, bf, newdup,
needlogp);
ASSERT(dfp != NULL);
ASSERT(dfp->length == newdup->length);
ASSERT(be16_to_cpu(dfp->offset) == (char *)newdup - (char *)hdr);
fa = xfs_dir2_data_check_new_free(hdr, dfp, newdup);
if (fa)
goto corrupt;
/*
* If we got inserted at the last slot,
* that means we don't know if there was a better
@ -1036,9 +1073,9 @@ xfs_dir2_data_use_free(
xfs_dir2_data_freeremove(hdr, bf, dfp, needlogp);
dfp = xfs_dir2_data_freeinsert(hdr, bf, newdup,
needlogp);
ASSERT(dfp != NULL);
ASSERT(dfp->length == newdup->length);
ASSERT(be16_to_cpu(dfp->offset) == (char *)newdup - (char *)hdr);
fa = xfs_dir2_data_check_new_free(hdr, dfp, newdup);
if (fa)
goto corrupt;
/*
* If we got inserted at the last slot,
* that means we don't know if there was a better
@ -1084,6 +1121,11 @@ xfs_dir2_data_use_free(
}
}
*needscanp = needscan;
return 0;
corrupt:
xfs_corruption_error(__func__, XFS_ERRLEVEL_LOW, args->dp->i_mount,
hdr, __FILE__, __LINE__, fa);
return -EFSCORRUPTED;
}
/* Find the end of the entry data in a data/block format dir block. */

View File

@ -877,9 +877,13 @@ xfs_dir2_leaf_addname(
/*
* Mark the initial part of our freespace in use for the new entry.
*/
xfs_dir2_data_use_free(args, dbp, dup,
(xfs_dir2_data_aoff_t)((char *)dup - (char *)hdr), length,
&needlog, &needscan);
error = xfs_dir2_data_use_free(args, dbp, dup,
(xfs_dir2_data_aoff_t)((char *)dup - (char *)hdr),
length, &needlog, &needscan);
if (error) {
xfs_trans_brelse(tp, lbp);
return error;
}
/*
* Initialize our new entry (at last).
*/

View File

@ -1729,6 +1729,7 @@ xfs_dir2_node_addname_int(
__be16 *bests;
struct xfs_dir3_icfree_hdr freehdr;
struct xfs_dir2_data_free *bf;
xfs_dir2_data_aoff_t aoff;
dp = args->dp;
mp = dp->i_mount;
@ -2023,9 +2024,13 @@ xfs_dir2_node_addname_int(
/*
* Mark the first part of the unused space, inuse for us.
*/
xfs_dir2_data_use_free(args, dbp, dup,
(xfs_dir2_data_aoff_t)((char *)dup - (char *)hdr), length,
&needlog, &needscan);
aoff = (xfs_dir2_data_aoff_t)((char *)dup - (char *)hdr);
error = xfs_dir2_data_use_free(args, dbp, dup, aoff, length,
&needlog, &needscan);
if (error) {
xfs_trans_brelse(tp, dbp);
return error;
}
/*
* Fill in the new entry and log it.
*/