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:
parent
a27ba2607e
commit
6915ef35c0
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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).
|
||||
*/
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue