[PATCH v2 experimental-tests] erofs-utils: tests: test FUSE error handling on corrupted inodes

Gao Xiang hsiangkao at linux.alibaba.com
Tue Mar 31 13:33:21 AEDT 2026



On 2026/3/30 18:30, Nithurshen wrote:
> This patch introduces a regression test (erofs/099) to verify that
> the FUSE daemon gracefully handles corrupted inodes without crashing
> or violating the FUSE protocol.
> 
> Recently, a bug was identified where erofs_read_inode_from_disk()
> would fail, but erofsfuse_getattr() lacked a return statement
> after sending an error reply. This caused a fall-through, sending
> a second reply via fuse_reply_attr() and triggering a libfuse
> segmentation fault.
> 
> To prevent future regressions, this test:
> 1. Creates a valid EROFS image.
> 2. Surgically corrupts the root inode (injecting random data at
>     offset 1152) while leaving the superblock intact so it mounts.
> 3. Mounts the image in the foreground to capture daemon stderr.
> 4. Runs 'stat' to trigger the inode read failure.
> 5. Evaluates the stderr log to ensure no segfaults, aborts, or
>     "multiple replies" warnings are emitted by libfuse.
> 
> Signed-off-by: Nithurshen <nithurshen.dev at gmail.com>
> ---
> Changes in v2:
> - Added $FSTYP check to ensure the test is skipped when testing the
>    kernel driver (Gao Xiang).
> - Renamed cleanup() to _cleanup() to align with standard rc teardown.
> ---
>   tests/Makefile.am   |  3 ++
>   tests/erofs/099     | 90 +++++++++++++++++++++++++++++++++++++++++++++
>   tests/erofs/099.out |  2 +
>   3 files changed, 95 insertions(+)
>   create mode 100755 tests/erofs/099
>   create mode 100644 tests/erofs/099.out
> 
> diff --git a/tests/Makefile.am b/tests/Makefile.am
> index e376d6a..c0f117c 100644
> --- a/tests/Makefile.am
> +++ b/tests/Makefile.am
> @@ -122,6 +122,9 @@ TESTS += erofs/027
>   # 028 - test inode page cache sharing functionality
>   TESTS += erofs/028
>   
> +# 099 - test fuse error handling on truncated images
> +TESTS += erofs/099
> +
>   EXTRA_DIST = common/rc erofs
>   
>   clean-local: clean-local-check
> diff --git a/tests/erofs/099 b/tests/erofs/099
> new file mode 100755
> index 0000000..11dab4d
> --- /dev/null
> +++ b/tests/erofs/099
> @@ -0,0 +1,90 @@
> +#!/bin/sh
> +# SPDX-License-Identifier: GPL-2.0+
> +#
> +# Test FUSE daemon and kernel error handling on corrupted inodes
> +#
> +seq=`basename $0`
> +seqres=$RESULT_DIR/$(echo $0 | awk '{print $((NF-1))"/"$NF}' FS="/")
> +
> +# get standard environment, filters and checks
> +. "${srcdir}/common/rc"
> +
> +_cleanup()
> +{
> +	cd /
> +	rm -rf $tmp.*
> +	# Ensure we kill our background daemon if it's still alive
> +	[ -n "$fuse_pid" ] && kill -9 $fuse_pid 2>/dev/null
> +}
> +
> +# remove previous $seqres.full before test
> +rm -f $seqres.full
> +
> +# real QA test starts here
> +echo "QA output created by $seq"
> +
> +# Default to erofs (kernel) if FSTYP is not set
> +[ -z "$FSTYP" ] && FSTYP="erofs"
> +
> +if [ -z "$SCRATCH_DEV" ]; then
> +	SCRATCH_DEV=$tmp/erofs_$seq.img
> +	rm -f $SCRATCH_DEV
> +fi
> +
> +localdir="$tmp/$seq"
> +rm -rf $localdir
> +mkdir -p $localdir
> +
> +echo "test data" > $localdir/testfile
> +
> +_scratch_mkfs $localdir >> $seqres.full 2>&1 || _fail "failed to mkfs"

You need to disable superblock checksum using:

_scratch_mkfs -Enosbcrc $localdir >> $seqres.full 2>&1 || _fail "failed to mkfs"


> +
> +# Corrupt the root inode to force erofs_read_inode_from_disk to fail.
> +# The EROFS superblock is at offset 1024 and is 128 bytes long.
> +# The metadata (including the root inode) starts immediately after (offset 1152).

I think a better way is to use `dump.erofs` to get the NID, and use
meta_blkaddr * block_size + NID * 32 to calculate the inode offset:
for example:

$EROFSDUMP_PROG --path=/testfile $SCRATCH_DEV
...

> +# We inject 1024 bytes of random garbage starting at offset 1152. This leaves
> +# the SB intact so the mount succeeds, but guarantees the inode read will fail.
> +dd if=/dev/urandom of=$SCRATCH_DEV bs=1 seek=1152 count=1024 conv=notrunc >> $seqres.full 2>&1
> +
> +if [ "$FSTYP" = "erofsfuse" ]; then
> +	[ -z "$EROFSFUSE_PROG" ] && _notrun "erofsfuse is not available"
> +	# Run erofsfuse in the foreground to capture libfuse's internal stderr
> +	$EROFSFUSE_PROG -f $SCRATCH_DEV $SCRATCH_MNT > $tmp/fuse_err.log 2>&1 &
> +	fuse_pid=$!
> +	# Wait for the mount to establish
> +	sleep 1
> +else
> +	_require_erofs
> +	_scratch_mount >> $seqres.full 2>&1
> +fi
> +
> +# Attempt to stat the root directory. We expect this to fail with an error.
> +timeout 5 stat $SCRATCH_MNT >> $seqres.full 2>&1
> +res=$?
> +
> +if [ "$FSTYP" = "erofsfuse" ]; then
> +	# Clean up the mount
> +	umount $SCRATCH_MNT >> $seqres.full 2>&1

Can you use `_scratch_unmount`? Does that work?

> +	# Wait for the daemon to cleanly exit, or kill it if stuck
> +	kill $fuse_pid 2>/dev/null
> +	wait $fuse_pid 2>/dev/null
> +	cat $tmp/fuse_err.log >> $seqres.full
> +
> +	# Evaluate results based on captured stderr and timeout
> +	if [ $res -eq 124 ]; then
> +		_fail "stat command timed out (FUSE daemon likely hung due to double reply)"
> +	elif grep -q -i "multiple replies" $tmp/fuse_err.log; then
> +		_fail "Bug detected: libfuse reported multiple replies to request"
> +	elif grep -q -i "segmentation fault\|aborted" $tmp/fuse_err.log; then
> +		_fail "Bug detected: FUSE daemon crashed"
> +	fi
> +else
> +	# Kernel check: ensure no hang and error is returned
> +	[ $res -eq 124 ] && _fail "stat command timed out (kernel hung?)"
> +	[ $res -eq 0 ] && _fail "stat unexpectedly succeeded on a corrupted image"
> +	_scratch_unmount >> $seqres.full 2>&1
> +fi
> +
> +echo Silence is golden
> +status=0
> +exit 0
> \ No newline at end of file

Need a new line here.

Thanks,
Gao Xiang


More information about the Linux-erofs mailing list