linux/fs/xfs/scrub/dabtree.c
Darrick J. Wong e581c9397a xfs: check dabtree node hash values when loading child blocks
When xchk_da_btree_block is loading a non-root dabtree block, we know
that the parent block had to have a (hashval, address) pointer to the
block that we just loaded.  Check that the hashval in the parent matches
the block we just loaded.

This was found by fuzzing nbtree[3].hashval = ones in xfs/394.

Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
2020-09-23 08:58:51 -07:00

593 lines
14 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2017 Oracle. All Rights Reserved.
* Author: Darrick J. Wong <darrick.wong@oracle.com>
*/
#include "xfs.h"
#include "xfs_fs.h"
#include "xfs_shared.h"
#include "xfs_format.h"
#include "xfs_trans_resv.h"
#include "xfs_mount.h"
#include "xfs_log_format.h"
#include "xfs_trans.h"
#include "xfs_inode.h"
#include "xfs_dir2.h"
#include "xfs_dir2_priv.h"
#include "xfs_attr_leaf.h"
#include "scrub/scrub.h"
#include "scrub/common.h"
#include "scrub/trace.h"
#include "scrub/dabtree.h"
/* Directory/Attribute Btree */
/*
* Check for da btree operation errors. See the section about handling
* operational errors in common.c.
*/
bool
xchk_da_process_error(
struct xchk_da_btree *ds,
int level,
int *error)
{
struct xfs_scrub *sc = ds->sc;
if (*error == 0)
return true;
switch (*error) {
case -EDEADLOCK:
/* Used to restart an op with deadlock avoidance. */
trace_xchk_deadlock_retry(sc->ip, sc->sm, *error);
break;
case -EFSBADCRC:
case -EFSCORRUPTED:
/* Note the badness but don't abort. */
sc->sm->sm_flags |= XFS_SCRUB_OFLAG_CORRUPT;
*error = 0;
/* fall through */
default:
trace_xchk_file_op_error(sc, ds->dargs.whichfork,
xfs_dir2_da_to_db(ds->dargs.geo,
ds->state->path.blk[level].blkno),
*error, __return_address);
break;
}
return false;
}
/*
* Check for da btree corruption. See the section about handling
* operational errors in common.c.
*/
void
xchk_da_set_corrupt(
struct xchk_da_btree *ds,
int level)
{
struct xfs_scrub *sc = ds->sc;
sc->sm->sm_flags |= XFS_SCRUB_OFLAG_CORRUPT;
trace_xchk_fblock_error(sc, ds->dargs.whichfork,
xfs_dir2_da_to_db(ds->dargs.geo,
ds->state->path.blk[level].blkno),
__return_address);
}
static struct xfs_da_node_entry *
xchk_da_btree_node_entry(
struct xchk_da_btree *ds,
int level)
{
struct xfs_da_state_blk *blk = &ds->state->path.blk[level];
struct xfs_da3_icnode_hdr hdr;
ASSERT(blk->magic == XFS_DA_NODE_MAGIC);
xfs_da3_node_hdr_from_disk(ds->sc->mp, &hdr, blk->bp->b_addr);
return hdr.btree + blk->index;
}
/* Scrub a da btree hash (key). */
int
xchk_da_btree_hash(
struct xchk_da_btree *ds,
int level,
__be32 *hashp)
{
struct xfs_da_node_entry *entry;
xfs_dahash_t hash;
xfs_dahash_t parent_hash;
/* Is this hash in order? */
hash = be32_to_cpu(*hashp);
if (hash < ds->hashes[level])
xchk_da_set_corrupt(ds, level);
ds->hashes[level] = hash;
if (level == 0)
return 0;
/* Is this hash no larger than the parent hash? */
entry = xchk_da_btree_node_entry(ds, level - 1);
parent_hash = be32_to_cpu(entry->hashval);
if (parent_hash < hash)
xchk_da_set_corrupt(ds, level);
return 0;
}
/*
* Check a da btree pointer. Returns true if it's ok to use this
* pointer.
*/
STATIC bool
xchk_da_btree_ptr_ok(
struct xchk_da_btree *ds,
int level,
xfs_dablk_t blkno)
{
if (blkno < ds->lowest || (ds->highest != 0 && blkno >= ds->highest)) {
xchk_da_set_corrupt(ds, level);
return false;
}
return true;
}
/*
* The da btree scrubber can handle leaf1 blocks as a degenerate
* form of leafn blocks. Since the regular da code doesn't handle
* leaf1, we must multiplex the verifiers.
*/
static void
xchk_da_btree_read_verify(
struct xfs_buf *bp)
{
struct xfs_da_blkinfo *info = bp->b_addr;
switch (be16_to_cpu(info->magic)) {
case XFS_DIR2_LEAF1_MAGIC:
case XFS_DIR3_LEAF1_MAGIC:
bp->b_ops = &xfs_dir3_leaf1_buf_ops;
bp->b_ops->verify_read(bp);
return;
default:
/*
* xfs_da3_node_buf_ops already know how to handle
* DA*_NODE, ATTR*_LEAF, and DIR*_LEAFN blocks.
*/
bp->b_ops = &xfs_da3_node_buf_ops;
bp->b_ops->verify_read(bp);
return;
}
}
static void
xchk_da_btree_write_verify(
struct xfs_buf *bp)
{
struct xfs_da_blkinfo *info = bp->b_addr;
switch (be16_to_cpu(info->magic)) {
case XFS_DIR2_LEAF1_MAGIC:
case XFS_DIR3_LEAF1_MAGIC:
bp->b_ops = &xfs_dir3_leaf1_buf_ops;
bp->b_ops->verify_write(bp);
return;
default:
/*
* xfs_da3_node_buf_ops already know how to handle
* DA*_NODE, ATTR*_LEAF, and DIR*_LEAFN blocks.
*/
bp->b_ops = &xfs_da3_node_buf_ops;
bp->b_ops->verify_write(bp);
return;
}
}
static void *
xchk_da_btree_verify(
struct xfs_buf *bp)
{
struct xfs_da_blkinfo *info = bp->b_addr;
switch (be16_to_cpu(info->magic)) {
case XFS_DIR2_LEAF1_MAGIC:
case XFS_DIR3_LEAF1_MAGIC:
bp->b_ops = &xfs_dir3_leaf1_buf_ops;
return bp->b_ops->verify_struct(bp);
default:
bp->b_ops = &xfs_da3_node_buf_ops;
return bp->b_ops->verify_struct(bp);
}
}
static const struct xfs_buf_ops xchk_da_btree_buf_ops = {
.name = "xchk_da_btree",
.verify_read = xchk_da_btree_read_verify,
.verify_write = xchk_da_btree_write_verify,
.verify_struct = xchk_da_btree_verify,
};
/* Check a block's sibling. */
STATIC int
xchk_da_btree_block_check_sibling(
struct xchk_da_btree *ds,
int level,
int direction,
xfs_dablk_t sibling)
{
struct xfs_da_state_path *path = &ds->state->path;
struct xfs_da_state_path *altpath = &ds->state->altpath;
int retval;
int plevel;
int error;
memcpy(altpath, path, sizeof(ds->state->altpath));
/*
* If the pointer is null, we shouldn't be able to move the upper
* level pointer anywhere.
*/
if (sibling == 0) {
error = xfs_da3_path_shift(ds->state, altpath, direction,
false, &retval);
if (error == 0 && retval == 0)
xchk_da_set_corrupt(ds, level);
error = 0;
goto out;
}
/* Move the alternate cursor one block in the direction given. */
error = xfs_da3_path_shift(ds->state, altpath, direction, false,
&retval);
if (!xchk_da_process_error(ds, level, &error))
goto out;
if (retval) {
xchk_da_set_corrupt(ds, level);
goto out;
}
if (altpath->blk[level].bp)
xchk_buffer_recheck(ds->sc, altpath->blk[level].bp);
/* Compare upper level pointer to sibling pointer. */
if (altpath->blk[level].blkno != sibling)
xchk_da_set_corrupt(ds, level);
out:
/* Free all buffers in the altpath that aren't referenced from path. */
for (plevel = 0; plevel < altpath->active; plevel++) {
if (altpath->blk[plevel].bp == NULL ||
(plevel < path->active &&
altpath->blk[plevel].bp == path->blk[plevel].bp))
continue;
xfs_trans_brelse(ds->dargs.trans, altpath->blk[plevel].bp);
altpath->blk[plevel].bp = NULL;
}
return error;
}
/* Check a block's sibling pointers. */
STATIC int
xchk_da_btree_block_check_siblings(
struct xchk_da_btree *ds,
int level,
struct xfs_da_blkinfo *hdr)
{
xfs_dablk_t forw;
xfs_dablk_t back;
int error = 0;
forw = be32_to_cpu(hdr->forw);
back = be32_to_cpu(hdr->back);
/* Top level blocks should not have sibling pointers. */
if (level == 0) {
if (forw != 0 || back != 0)
xchk_da_set_corrupt(ds, level);
return 0;
}
/*
* Check back (left) and forw (right) pointers. These functions
* absorb error codes for us.
*/
error = xchk_da_btree_block_check_sibling(ds, level, 0, back);
if (error)
goto out;
error = xchk_da_btree_block_check_sibling(ds, level, 1, forw);
out:
memset(&ds->state->altpath, 0, sizeof(ds->state->altpath));
return error;
}
/* Load a dir/attribute block from a btree. */
STATIC int
xchk_da_btree_block(
struct xchk_da_btree *ds,
int level,
xfs_dablk_t blkno)
{
struct xfs_da_state_blk *blk;
struct xfs_da_intnode *node;
struct xfs_da_node_entry *btree;
struct xfs_da3_blkinfo *hdr3;
struct xfs_da_args *dargs = &ds->dargs;
struct xfs_inode *ip = ds->dargs.dp;
xfs_ino_t owner;
int *pmaxrecs;
struct xfs_da3_icnode_hdr nodehdr;
int error = 0;
blk = &ds->state->path.blk[level];
ds->state->path.active = level + 1;
/* Release old block. */
if (blk->bp) {
xfs_trans_brelse(dargs->trans, blk->bp);
blk->bp = NULL;
}
/* Check the pointer. */
blk->blkno = blkno;
if (!xchk_da_btree_ptr_ok(ds, level, blkno))
goto out_nobuf;
/* Read the buffer. */
error = xfs_da_read_buf(dargs->trans, dargs->dp, blk->blkno,
XFS_DABUF_MAP_HOLE_OK, &blk->bp, dargs->whichfork,
&xchk_da_btree_buf_ops);
if (!xchk_da_process_error(ds, level, &error))
goto out_nobuf;
if (blk->bp)
xchk_buffer_recheck(ds->sc, blk->bp);
/*
* We didn't find a dir btree root block, which means that
* there's no LEAF1/LEAFN tree (at least not where it's supposed
* to be), so jump out now.
*/
if (ds->dargs.whichfork == XFS_DATA_FORK && level == 0 &&
blk->bp == NULL)
goto out_nobuf;
/* It's /not/ ok for attr trees not to have a da btree. */
if (blk->bp == NULL) {
xchk_da_set_corrupt(ds, level);
goto out_nobuf;
}
hdr3 = blk->bp->b_addr;
blk->magic = be16_to_cpu(hdr3->hdr.magic);
pmaxrecs = &ds->maxrecs[level];
/* We only started zeroing the header on v5 filesystems. */
if (xfs_sb_version_hascrc(&ds->sc->mp->m_sb) && hdr3->hdr.pad)
xchk_da_set_corrupt(ds, level);
/* Check the owner. */
if (xfs_sb_version_hascrc(&ip->i_mount->m_sb)) {
owner = be64_to_cpu(hdr3->owner);
if (owner != ip->i_ino)
xchk_da_set_corrupt(ds, level);
}
/* Check the siblings. */
error = xchk_da_btree_block_check_siblings(ds, level, &hdr3->hdr);
if (error)
goto out;
/* Interpret the buffer. */
switch (blk->magic) {
case XFS_ATTR_LEAF_MAGIC:
case XFS_ATTR3_LEAF_MAGIC:
xfs_trans_buf_set_type(dargs->trans, blk->bp,
XFS_BLFT_ATTR_LEAF_BUF);
blk->magic = XFS_ATTR_LEAF_MAGIC;
blk->hashval = xfs_attr_leaf_lasthash(blk->bp, pmaxrecs);
if (ds->tree_level != 0)
xchk_da_set_corrupt(ds, level);
break;
case XFS_DIR2_LEAFN_MAGIC:
case XFS_DIR3_LEAFN_MAGIC:
xfs_trans_buf_set_type(dargs->trans, blk->bp,
XFS_BLFT_DIR_LEAFN_BUF);
blk->magic = XFS_DIR2_LEAFN_MAGIC;
blk->hashval = xfs_dir2_leaf_lasthash(ip, blk->bp, pmaxrecs);
if (ds->tree_level != 0)
xchk_da_set_corrupt(ds, level);
break;
case XFS_DIR2_LEAF1_MAGIC:
case XFS_DIR3_LEAF1_MAGIC:
xfs_trans_buf_set_type(dargs->trans, blk->bp,
XFS_BLFT_DIR_LEAF1_BUF);
blk->magic = XFS_DIR2_LEAF1_MAGIC;
blk->hashval = xfs_dir2_leaf_lasthash(ip, blk->bp, pmaxrecs);
if (ds->tree_level != 0)
xchk_da_set_corrupt(ds, level);
break;
case XFS_DA_NODE_MAGIC:
case XFS_DA3_NODE_MAGIC:
xfs_trans_buf_set_type(dargs->trans, blk->bp,
XFS_BLFT_DA_NODE_BUF);
blk->magic = XFS_DA_NODE_MAGIC;
node = blk->bp->b_addr;
xfs_da3_node_hdr_from_disk(ip->i_mount, &nodehdr, node);
btree = nodehdr.btree;
*pmaxrecs = nodehdr.count;
blk->hashval = be32_to_cpu(btree[*pmaxrecs - 1].hashval);
if (level == 0) {
if (nodehdr.level >= XFS_DA_NODE_MAXDEPTH) {
xchk_da_set_corrupt(ds, level);
goto out_freebp;
}
ds->tree_level = nodehdr.level;
} else {
if (ds->tree_level != nodehdr.level) {
xchk_da_set_corrupt(ds, level);
goto out_freebp;
}
}
/* XXX: Check hdr3.pad32 once we know how to fix it. */
break;
default:
xchk_da_set_corrupt(ds, level);
goto out_freebp;
}
/*
* If we've been handed a block that is below the dabtree root, does
* its hashval match what the parent block expected to see?
*/
if (level > 0) {
struct xfs_da_node_entry *key;
key = xchk_da_btree_node_entry(ds, level - 1);
if (be32_to_cpu(key->hashval) != blk->hashval) {
xchk_da_set_corrupt(ds, level);
goto out_freebp;
}
}
out:
return error;
out_freebp:
xfs_trans_brelse(dargs->trans, blk->bp);
blk->bp = NULL;
out_nobuf:
blk->blkno = 0;
return error;
}
/* Visit all nodes and leaves of a da btree. */
int
xchk_da_btree(
struct xfs_scrub *sc,
int whichfork,
xchk_da_btree_rec_fn scrub_fn,
void *private)
{
struct xchk_da_btree ds = {};
struct xfs_mount *mp = sc->mp;
struct xfs_da_state_blk *blks;
struct xfs_da_node_entry *key;
xfs_dablk_t blkno;
int level;
int error;
/* Skip short format data structures; no btree to scan. */
if (!xfs_ifork_has_extents(XFS_IFORK_PTR(sc->ip, whichfork)))
return 0;
/* Set up initial da state. */
ds.dargs.dp = sc->ip;
ds.dargs.whichfork = whichfork;
ds.dargs.trans = sc->tp;
ds.dargs.op_flags = XFS_DA_OP_OKNOENT;
ds.state = xfs_da_state_alloc(&ds.dargs);
ds.sc = sc;
ds.private = private;
if (whichfork == XFS_ATTR_FORK) {
ds.dargs.geo = mp->m_attr_geo;
ds.lowest = 0;
ds.highest = 0;
} else {
ds.dargs.geo = mp->m_dir_geo;
ds.lowest = ds.dargs.geo->leafblk;
ds.highest = ds.dargs.geo->freeblk;
}
blkno = ds.lowest;
level = 0;
/* Find the root of the da tree, if present. */
blks = ds.state->path.blk;
error = xchk_da_btree_block(&ds, level, blkno);
if (error)
goto out_state;
/*
* We didn't find a block at ds.lowest, which means that there's
* no LEAF1/LEAFN tree (at least not where it's supposed to be),
* so jump out now.
*/
if (blks[level].bp == NULL)
goto out_state;
blks[level].index = 0;
while (level >= 0 && level < XFS_DA_NODE_MAXDEPTH) {
/* Handle leaf block. */
if (blks[level].magic != XFS_DA_NODE_MAGIC) {
/* End of leaf, pop back towards the root. */
if (blks[level].index >= ds.maxrecs[level]) {
if (level > 0)
blks[level - 1].index++;
ds.tree_level++;
level--;
continue;
}
/* Dispatch record scrubbing. */
error = scrub_fn(&ds, level);
if (error)
break;
if (xchk_should_terminate(sc, &error) ||
(sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT))
break;
blks[level].index++;
continue;
}
/* End of node, pop back towards the root. */
if (blks[level].index >= ds.maxrecs[level]) {
if (level > 0)
blks[level - 1].index++;
ds.tree_level++;
level--;
continue;
}
/* Hashes in order for scrub? */
key = xchk_da_btree_node_entry(&ds, level);
error = xchk_da_btree_hash(&ds, level, &key->hashval);
if (error)
goto out;
/* Drill another level deeper. */
blkno = be32_to_cpu(key->before);
level++;
if (level >= XFS_DA_NODE_MAXDEPTH) {
/* Too deep! */
xchk_da_set_corrupt(&ds, level - 1);
break;
}
ds.tree_level--;
error = xchk_da_btree_block(&ds, level, blkno);
if (error)
goto out;
if (blks[level].bp == NULL)
goto out;
blks[level].index = 0;
}
out:
/* Release all the buffers we're tracking. */
for (level = 0; level < XFS_DA_NODE_MAXDEPTH; level++) {
if (blks[level].bp == NULL)
continue;
xfs_trans_brelse(sc->tp, blks[level].bp);
blks[level].bp = NULL;
}
out_state:
xfs_da_state_free(ds.state);
return error;
}