[PATCH v6 experimental-tests] erofs-utils: tests: test FUSE error handling on corrupted inodes
Gao Xiang <[email protected]>
| Newsgroups | org.ozlabs.lists.linux-erofs |
|---|---|
| Message-ID | <[email protected]> |
From: Nithurshen <[email protected]> This patch introduces a regression test 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. Uses dump.erofs to dynamically determine the root directory's inode NID and metadata block address. 3. Deterministically corrupts the root inode by injecting 32 bytes of 0xFF, invalidating its layout while leaving the superblock intact. 4. Mounts the image in the foreground to capture daemon stderr. 5. Runs `stat` on the root directory to trigger the inode read failure. 6. Evaluates the stderr log to ensure no segfaults, aborts, or "multiple replies" warnings are emitted by libfuse. Signed-off-by: Nithurshen <[email protected]> Signed-off-by: Gao Xiang <[email protected]> --- I will apply this refined version. tests/Makefile.am | 4 ++ tests/erofs/029 | 93 +++++++++++++++++++++++++++++++++++++++++++++ tests/erofs/029.out | 2 + 3 files changed, 99 insertions(+) create mode 100755 tests/erofs/029 create mode 100644 tests/erofs/029.out diff --git a/tests/Makefile.am b/tests/Makefile.am index 226955cdccbd..d8ac067805e8 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -22,6 +22,7 @@ TESTS_ENVIRONMENT = \ fi; \ [ -z $$MKFS_EROFS_PROG ] && export MKFS_EROFS_PROG=../mkfs/mkfs.erofs; \ [ -z $$FSCK_EROFS_PROG ] && export FSCK_EROFS_PROG=../fsck/fsck.erofs; \ + [ -z $$DUMP_EROFS_PROG ] && export DUMP_EROFS_PROG=../dump/dump.erofs; \ [ -z $$EROFSFUSE_PROG ] && export EROFSFUSE_PROG=../fuse/erofsfuse; if ENABLE_LZ4 @@ -122,6 +123,9 @@ TESTS += erofs/027 # 028 - test inode page cache sharing functionality TESTS += erofs/028 +# 029 - test FUSE daemon and kernel error handling on corrupted inodes +TESTS += erofs/029 + # NEW TEST CASE HERE # TESTS += erofs/999 diff --git a/tests/erofs/029 b/tests/erofs/029 new file mode 100755 index 000000000000..82fdd5d01892 --- /dev/null +++ b/tests/erofs/029 @@ -0,0 +1,93 @@ +#!/bin/sh +# SPDX-License-Identifier: MIT +# +# 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 +} + +_require_erofs + +# remove previous $seqres.full before test +rm -f $seqres.full + +# real QA test starts here +echo "QA output created by $seq" + +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 -T0 --all-time -Enosbcrc $localdir >> $seqres.full 2>&1 || _fail "failed to mkfs" + +sbdump=$($DUMP_EROFS_PROG $SCRATCH_DEV) +meta_blkaddr=$(printf "$sbdump" | grep -i "meta_blkaddr" | grep -oE '[0-9]+' | head -n 1) +[ -n "$meta_blkaddr" ] && _fail "failed get get meta_blkaddr" +blocksize=$(printf "$sbdump" | grep -i "block size" | grep -oE '[0-9]+' | head -n 1) +[ -n "$blocksize" ] && _fail "failed to get block size" + +victim=$($DUMP_EROFS_PROG --path=/ $SCRATCH_DEV | grep -iE 'nid\s*[:=]?\s*[0-9]+' -o | grep -oE '[0-9]+' | head -n 1) +[ -z "$victim" ] && _fail "failed to find root NID" + +seeko=$((meta_blkaddr*blocksize+victim*32)) + +# Deterministically corrupt the root inode's layout by writing 32 bytes of 0xFF +awk 'BEGIN { for(i=0;i<32;i++) printf "\377" }' | \ + dd of=$SCRATCH_DEV bs=1 seek=$seeko count=32 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/erofsfuse.log 2>&1 & + fuse_pid=$! + # Wait for the mount to establish + sleep 1 +elif ! _try_scratch_mount >> $seqres.full 2>&1; then + echo Silence is golden + status=0 + exit 0 +fi + +# Attempt to stat the root directory to directly trigger getattr without a lookup +timeout 5 stat $SCRATCH_MNT >> $seqres.full 2>&1 +res=$? +[ $res -eq 124 ] && _fail "stat command timed out (kernel hung?)" +[ $res -eq 0 ] && _fail "stat unexpectedly succeeded on a corrupted image" + +if [ "$FSTYP" = "erofsfuse" ]; then + _scratch_unmount >> $seqres.full 2>&1 + # 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/erofsfuse.log >> $seqres.full + + if grep -q -i "multiple replies" $tmp/erofsfuse.log; then + _fail "Bug detected: libfuse reported multiple replies to request" + elif grep -q -i "segmentation fault\|aborted" $tmp/erofsfuse.log; then + _fail "Bug detected: FUSE daemon crashed" + fi +else + _scratch_unmount >> $seqres.full 2>&1 +fi + +echo Silence is golden +status=0 +exit 0 diff --git a/tests/erofs/029.out b/tests/erofs/029.out new file mode 100644 index 000000000000..8ee6db49fd4d --- /dev/null +++ b/tests/erofs/029.out @@ -0,0 +1,2 @@ +QA output created by 029 +Silence is golden -- 2.43.5