Re: [PATCH v2] xfs/554: xfs add illegal bestfree array size inject for leaf dir

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Thu, Sep 29, 2022 at 02:04:19PM -0700, Darrick J. Wong wrote:
> On Thu, Sep 29, 2022 at 07:32:13PM +0800, Zorro Lang wrote:
> > On Wed, Sep 28, 2022 at 05:53:55PM +0800, Guo Xuenan wrote:
> > > Test leaf dir allocting new block when bestfree array size
> > > less than data blocks count, which may lead to UAF.
> > > 
> > > Signed-off-by: Guo Xuenan <guoxuenan@xxxxxxxxxx>
> > > ---
> > >  tests/xfs/554     | 96 +++++++++++++++++++++++++++++++++++++++++++++++
> > >  tests/xfs/554.out |  7 ++++
> > >  2 files changed, 103 insertions(+)
> > >  create mode 100755 tests/xfs/554
> > >  create mode 100644 tests/xfs/554.out
> > > 
> > > diff --git a/tests/xfs/554 b/tests/xfs/554
> > > new file mode 100755
> > > index 00000000..dba6aefa
> > > --- /dev/null
> > > +++ b/tests/xfs/554
> > > @@ -0,0 +1,96 @@
> > > +#! /bin/bash
> > > +# SPDX-License-Identifier: GPL-2.0
> > > +# Copyright (c) 2022 Huawei Limited.  All Rights Reserved.
> > > +#
> > > +# FS QA Test No. 554
> > > +#
> > > +# Check the running state of the XFS under illegal bestfree count
> > > +# for leaf directory format.
> > > +
> > > +. ./common/preamble
> > > +_begin_fstest auto quick
> > > +
> > > +# Import common functions.
> > > +. ./common/populate
> > > +
> > > +# real QA test starts here
> > > +_supported_fs xfs
> > > +_require_scratch
> > > +
> > > +# Get last dirblock entries
> > > +__lastdb_entry_list()
> > 
> > I think you don't need to use "__" for a case internal function.
> > 
> > > +{
> > > +	local dir_ino=$1
> > > +	local entry_list=$2
> > > +	local nblocks=`_scratch_xfs_db -c "inode $dir_ino" -c "p core.nblocks" |
> > > +			sed -e 's/^.*core.nblocks = //g' -e 's/\([0-9]*\).*$/\1/g'`
> > 
> > _scratch_xfs_get_metadata_field ...
> > 
> > > +	local last_db=$((nblocks / 2))
> > 
> > I'm not a xfs expert, what's this mean? Why nblocks/2 is the last data block?
> > For example, if we get a directory inode as this:
> >   u3.bmx[0-3] = [startoff,startblock,blockcount,extentflag]
> >   0:[0,14,2,0]
> >   1:[2,10,2,0]
> >   2:[4,72,6,0]
> >   3:[8388608,12,2,0]
> > 
> > do you want to get the "2:[4,72,6,0]" ?
> 
> I think they want 9 (offset (4) + blockcount (6) - 1)?
> 
> I'm also not sure what this computation does...
> 
> > > +	_scratch_xfs_db -c "inode $dir_ino" -c "dblock ${last_db}" -c 'p du' |\
> > > +		grep ".name =" | sed -e 's/^.*.name = //g' \
> > > +		-e 's/\"//g' > ${entry_list} ||\
> > > +		_fail "get last dir block entries failed"
> 
> ...though from the 'print du' command, I'm fairly sure they want to
> extract the dirents from the last directory data block.  So yes, 9.

OK, so he wants the last logic block of the last extent which store dir entries.

> 
> This is sorta ugly, I think this means that you'd have to 'print u3.bmx'
> and then walk the bmbt entries to return the fileoff of the last block
> before $leaf_lblk.

Yeah, it will be ugly, might be something like this:

remove_lastdb_entries()
{
	local dino=$1

	local leaf_bmx=$(_scratch_xfs_db -c "inode $dino" -c "p u3.bmx" | \
				sed -ne "/\[$leaf_lblk,/s/\(.*\)\:\[.*\]/\1/p")
	local lastdb_bmx=$((leaf_bmx - 1))
	local startoff=$(_scratch_xfs_db -c "inode $dino" -c "p u3.bmx" | \
				sed -ne "s/$lastdb_bmx:\[\(.*\),.*,.*,.*\]/\1/p")
	local blockcount=$(_scratch_xfs_db -c "inode $dino" -c "p u3.bmx" | \
				sed -ne "s/$lastdb_bmx:\[.*,.*,\(.*\),.*\]/\1/p")
	local lastdb=$((startoff + blockcount - 1))

	# remove all entries in the last logic data block
	_scratch_xfs_db -c "inode $dino" -c "dblock $lastdb" -c "p du" | \
		sed -ne "s/du.*\.name = \"\(.*\)\"/\1/p" | xargs rm -f
}

Or some better implements. Anyway these code looks like easy to broken :)

So I tried to ask if this step is necessary to reproduce the bug? The v1 patch
didn't have this step, is it can reproduce this bug ?
I think we don't need to make sure it's reproducible 100%, if we can simplify
a test case very much and keep high probability to reproduce the bug, that's
fine for me.

Thanks,
Zorro

> 
> > > +}
> > > +
> > > +echo "Format and mount"
> > > +_scratch_mkfs > $seqres.full 2>&1
> > > +_scratch_mount
> > > +
> > > +echo "Create and check leaf dir"
> > > +blksz="$(stat -f -c '%s' "${SCRATCH_MNT}")"
> > > +dirblksz=`$XFS_INFO_PROG "${SCRATCH_DEV}" | grep naming.*bsize |
> > > +	sed -e 's/^.*bsize=//g' -e 's/\([0-9]*\).*$/\1/g'`
> > > +# Usually, following routine will create a directory with one leaf block
> > > +# and three data block, meanwhile, the last data block is not full.
> 
> 
> 
> > > +__populate_create_dir "${SCRATCH_MNT}/S_IFDIR.FMT_LEAF" "$((dirblksz / 12))"
> > > +leaf_dir="$(__populate_find_inode "${SCRATCH_MNT}/S_IFDIR.FMT_LEAF")"
> > > +_scratch_unmount
> > > +
> > > +# Delete directory entries in the last directory block,
> > > +last_db_entries=$tmp.ldb_ents
> > > +__lastdb_entry_list ${leaf_dir} ${last_db_entries}
> > 
> > Hmm... I don't like to give a tmp file to a function to get a name list,
> > how about:
> > 
> >   lastdb_entry_list ${leaf_dir} > $tmp.ldb_ents
> > 
> > Or ...(below)
> > 
> > > +_scratch_mount
> > > +cat ${last_db_entries} >> $seqres.full
> > > +cat ${last_db_entries} | while read f
> > > +do
> > > +	rm -rf ${SCRATCH_MNT}/S_IFDIR.FMT_LEAF/$f
> > > +done
> > > +_scratch_unmount
> > 
> > ... you even can make all above code into a function named
> > remove_lastdb_entries() or other name you like.
> > 
> > This new part (not in v1) makes this case more complicated than v1, is it
> > necessary to reproduce the bug? Do we have a simple way?
> > 
> > > +
> > > +# Check leaf directory
> > > +leaf_lblk="$((32 * 1073741824 / blksz))"
> > > +node_lblk="$((64 * 1073741824 / blksz))"
> > > +__populate_check_xfs_dir "${leaf_dir}" "leaf"
> > > +
> > > +# Inject abnormal bestfree count
> > > +echo "Inject bad bestfree count."
> > > +_scratch_xfs_db -x -c "inode ${leaf_dir}" -c "dblock ${leaf_lblk}" \
> > > +	-c "write ltail.bestcount 0"
> > > +# Adding new entries to S_IFDIR.FMT_LEAF. Since we delete the files
> 
> It would be nice to have a blank line before the comments so that the
> test doesn't look quite so much like a wall of text.
> 
> > > +# in last direcotry block, current dir block have no spare space for new
> 
> s/direcotry/directory/
> 
> > > +# entry. With ltail.bestcount setting illegally (eg. bestcount=0), then
> > > +# creating new directories, which will trigger xfs to allocate new dir
> > > +# block, meanwhile, exception will be triggered.
> > > +# Root cause is that xfs don't examin the number bestfree slots, when the
> > > +# slots number less than dir data blocks, if we need to allocate new dir
> > > +# data block and update the bestfree array, we will use the dir block number
> > > +# as index to assign bestfree array, while we did not check the leaf buf
> > > +# boundary which may cause UAF or other memory access problem.
> 
> I suppose this implies that the test should be tagged dangerous for now?
> 
> > > +echo "Add directory entries to trigger exception."
> > > +_scratch_mount
> > > +seq 1 $((dirblksz / 24)) | while read d
> > > +do
> > > +mkdir "${SCRATCH_MNT}/S_IFDIR.FMT_LEAF/TEST$(printf "%.04d" "$d")" >> $seqres.full 2>&1
> > 
> > Indentation
> > 
> > > +done
> > > +_scratch_unmount
> > > +
> > > +# Bad bestfree count should be found and fixed by xfs_repair
> > > +_scratch_xfs_repair -n >> $seqres.full 2>&1
> > > +egrep -q 'leaf block.*bad tail' $seqres.full && echo "Repair found problems."
> > 
> > If you don't care about the xfs_repair output, you can do it simply as:
> >   _scratch_xfs_repair -n >> $seqres.full 2>&1 && \
> >           echo "repair didn't find corruption?"
> > 
> > Or you can filter the output of `_scratch_xfs_repair -n` to be golden image.
> 
> I think they're trying to look specifically for xfs_repair complaining
> about the bad leaf1 block tail, not just any error.
> 
> --D
> 
> > > +_repair_scratch_fs >> $seqres.full 2>&1 || _fail "Repair failed!"
> > 
> > How about:
> > 
> >   _repair_scratch_fs >> $seqres.full 2>&1
> >   _scratch_xfs_repair -n >> $seqres.full 2>&1 || _fail "xfs_repair should not fail"
> > 
> > (not sure, welcome more suggestions)
> > 
> > > +
> > > +# Check demsg error
> > > +_check_dmesg
> > 
> > You don't need to do that manually, except your dmesg error isn't in the
> > default checking list of _check_dmesg.
> > 
> > > +
> > > +# Success, all done
> > > +status=0
> > > +exit
> > > diff --git a/tests/xfs/554.out b/tests/xfs/554.out
> > > new file mode 100644
> > > index 00000000..d966ab0a
> > > --- /dev/null
> > > +++ b/tests/xfs/554.out
> > > @@ -0,0 +1,7 @@
> > > +QA output created by 554
> > > +Format and mount
> > > +Create and check leaf dir
> > > +Inject bad bestfree count.
> > > +ltail.bestcount = 0
> > > +Add a directory entry to trigger exception.
> > > +Repair found problems.
> > > -- 
> > > 2.25.1
> > > 
> > 
> 




[Index of Archives]     [XFS Filesystem Development (older mail)]     [Linux Filesystem Development]     [Linux Audio Users]     [Yosemite Trails]     [Linux Kernel]     [Linux RAID]     [Linux SCSI]


  Powered by Linux