From: Gao Xiang <hsiangkao@linux.alibaba.com>
To: linux-erofs@lists.ozlabs.org
Cc: Nithurshen <nithurshen.dev@gmail.com>, Gao Xiang <xiang@kernel.org>
Subject: [PATCH v6 experimental-tests] erofs-utils: tests: test FUSE error handling on corrupted inodes
Date: Tue, 7 Apr 2026 11:01:32 +0800 [thread overview]
Message-ID: <20260407030132.2606768-1-hsiangkao@linux.alibaba.com> (raw)
In-Reply-To: <20260403003452.4626-1-nithurshen.dev@gmail.com>
From: Nithurshen <nithurshen.dev@gmail.com>
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 <nithurshen.dev@gmail.com>
Signed-off-by: Gao Xiang <xiang@kernel.org>
---
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
prev parent reply other threads:[~2026-04-07 3:01 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-30 4:28 [PATCH experimental-tests] erofs-utils: tests: test FUSE error handling on corrupted inodes Nithurshen
2026-03-30 5:43 ` Gao Xiang
2026-03-30 10:30 ` [PATCH v2 " Nithurshen
2026-03-31 2:33 ` Gao Xiang
2026-04-01 7:10 ` [PATCH v3 " Nithurshen
2026-04-01 7:19 ` Gao Xiang
2026-04-01 7:55 ` [PATCH v4 " Nithurshen
2026-04-01 8:05 ` Gao Xiang
2026-04-01 8:09 ` Gao Xiang
2026-04-03 0:34 ` [PATCH v5 " Nithurshen
2026-04-07 3:01 ` Gao Xiang [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260407030132.2606768-1-hsiangkao@linux.alibaba.com \
--to=hsiangkao@linux.alibaba.com \
--cc=linux-erofs@lists.ozlabs.org \
--cc=nithurshen.dev@gmail.com \
--cc=xiang@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox