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

View file

@ -932,10 +932,51 @@ xfs_dir2_data_make_free(
*needscanp = needscan; *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. * Take a byte range out of an existing unused space and make it un-free.
*/ */
void int
xfs_dir2_data_use_free( xfs_dir2_data_use_free(
struct xfs_da_args *args, struct xfs_da_args *args,
struct xfs_buf *bp, 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_hdr_t *hdr; /* data block header */
xfs_dir2_data_free_t *dfp; /* bestfree pointer */ 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 matchback; /* matches end of freespace */
int matchfront; /* matches start of freespace */ int matchfront; /* matches start of freespace */
int needscan; /* need to regen bestfree */ 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 */ int oldlen; /* old unused entry's length */
struct xfs_dir2_data_free *bf;
hdr = bp->b_addr; hdr = bp->b_addr;
ASSERT(hdr->magic == cpu_to_be32(XFS_DIR2_DATA_MAGIC) || fa = xfs_dir2_data_check_free(hdr, dup, offset, len);
hdr->magic == cpu_to_be32(XFS_DIR3_DATA_MAGIC) || if (fa)
hdr->magic == cpu_to_be32(XFS_DIR2_BLOCK_MAGIC) || goto corrupt;
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)));
/* /*
* Look up the entry in the bestfree table. * 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); xfs_dir2_data_freeremove(hdr, bf, dfp, needlogp);
dfp = xfs_dir2_data_freeinsert(hdr, bf, newdup, dfp = xfs_dir2_data_freeinsert(hdr, bf, newdup,
needlogp); needlogp);
ASSERT(dfp != NULL); fa = xfs_dir2_data_check_new_free(hdr, dfp, newdup);
ASSERT(dfp->length == newdup->length); if (fa)
ASSERT(be16_to_cpu(dfp->offset) == (char *)newdup - (char *)hdr); goto corrupt;
/* /*
* If we got inserted at the last slot, * If we got inserted at the last slot,
* that means we don't know if there was a better * 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); xfs_dir2_data_freeremove(hdr, bf, dfp, needlogp);
dfp = xfs_dir2_data_freeinsert(hdr, bf, newdup, dfp = xfs_dir2_data_freeinsert(hdr, bf, newdup,
needlogp); needlogp);
ASSERT(dfp != NULL); fa = xfs_dir2_data_check_new_free(hdr, dfp, newdup);
ASSERT(dfp->length == newdup->length); if (fa)
ASSERT(be16_to_cpu(dfp->offset) == (char *)newdup - (char *)hdr); goto corrupt;
/* /*
* If we got inserted at the last slot, * If we got inserted at the last slot,
* that means we don't know if there was a better * that means we don't know if there was a better
@ -1084,6 +1121,11 @@ xfs_dir2_data_use_free(
} }
} }
*needscanp = needscan; *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. */ /* 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. * Mark the initial part of our freespace in use for the new entry.
*/ */
xfs_dir2_data_use_free(args, dbp, dup, error = xfs_dir2_data_use_free(args, dbp, dup,
(xfs_dir2_data_aoff_t)((char *)dup - (char *)hdr), length, (xfs_dir2_data_aoff_t)((char *)dup - (char *)hdr),
&needlog, &needscan); length, &needlog, &needscan);
if (error) {
xfs_trans_brelse(tp, lbp);
return error;
}
/* /*
* Initialize our new entry (at last). * Initialize our new entry (at last).
*/ */

View file

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