From 2e8868fc63609323e4293ccb66325fb089bae8a4 Mon Sep 17 00:00:00 2001 From: pancake Date: Mon, 26 Oct 2026 21:12:56 +0100 Subject: [PATCH] Reject unexpected filesystem errors in the test suite --- FSTODO.md | 19 ++++++++++--------- Makefile | 1 + README.md | 7 +++++-- meson.build | 2 ++ test/harness.sh | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ test/images.txt | 4 ++-- test/run.sh | 14 +++++++++++--- 7 files changed, 79 insertions(+), 16 deletions(-) create mode 100644 test/harness.sh diff --git a/FSTODO.md b/FSTODO.md index 43c3877..76a7624 100644 --- a/FSTODO.md +++ b/FSTODO.md @@ -62,8 +62,9 @@ Status legend: **[api]** core r_fs API, **[plugin]** a specific plugin, "symlinked" variants (`R`, `D`, ...). In practice: - grub plugins use `'f'` for regular files, which is not a defined constant. - ubifs uses `'l'` for symlinks, also undefined. - - reiserfs and xfs report symlinks as `'f'`, and opening them fails - (`reiserfs-link-root.img`, `xfs-symlink-size.img`). + - reiserfs and xfs now preserve symlinks as `'l'` through the grub wrapper + (`reiserfs-link-root.img`, `xfs-symlink-size.img`); reading their targets + still needs the readlink API below. - no plugin uses the upper-case variants. - **Proposal:** - define `R_FS_FILE_TYPE_SYMLINK 'l'`, `R_FS_FILE_TYPE_FIFO 'p'` and `R_FS_FILE_TYPE_SOCKET 'S'`; @@ -157,14 +158,14 @@ Status legend: **[api]** core r_fs API, **[plugin]** a specific plugin, | Plugin | Image | Symptom | Notes | |---|---|---|---| -| bfs | `haiku.bfs` | `/radare2_test/gvpr`: reads stop at 2048 of 5736 bytes, and later reads return 0 | `fs_bfs_read` only looks at the block run that contains `addr` ("read from direct blocks only"). Indirect and double-indirect runs are needed | -| ext2 (grub) | `ext4_advanced.img` | mount fails | incompat flags 0x22c2: extents, 64bit, flex_bg and **metadata_csum_seed** (0x2000). grub rejects unknown incompat bits. Allow it read-only | -| ntfs | `ntfs.img` | `$Extend/$ObjId`, `$Quota` and `$Reparse` are listed but cannot be opened | these metafiles have no unnamed `$DATA` stream. Report them as special, or expose their named streams | -| ntfs | `ntfs-resident-oob.img` | `/file` cannot be opened | crafted image. Make sure the failure is a clean error | -| reiserfs | `reiserfs-link-root.img`, `reiserfs-corrupt.img` | `link` is reported as `'f'` and cannot be opened | symlink type missing (see 1.5, 1.6) | -| xfs | `xfs-symlink-size.img` | `link` is reported as `'f'` | same as above | +| bfs | `haiku.bfs` | fixed: multi-block and cross-run reads return the full file | direct runs use the same reader as directories; indirect and double-indirect runs remain unsupported | +| ext2 (grub) | `ext4_advanced.img` | fixed: the image mounts and passes read stress | 64-bit group descriptor stride and inode table addresses are supported; metadata checksum seeds are allowed for read-only access | +| ntfs | `ntfs.img` | fixed: index-view metadata is reported as special entries | named stream access remains a separate feature | +| ntfs | `ntfs-resident-oob.img` | `/file` is correctly refused | passing negative test checks the exact open failure; a crash or unrelated error fails | +| reiserfs | `reiserfs-link-root.img`, `reiserfs-corrupt.img` | fixed: `link` is reported as `'l'` | readlink support is still missing (see 1.6) | +| xfs | `xfs-symlink-size.img` | fixed: `link` is reported as `'l'` | readlink support is still missing | | ubifs | `ubifs.img` | 174 symlinks reported as `'l'` | readlink support (1.6) | -| ubifs | `ubifs-oversize.img` | `File size (11) exceeds filesystem size (0)`, then 0 bytes are read | `leb_cnt` is 0 here. The size clamp should not use the volume size when that size is unknown | +| ubifs | `ubifs-oversize.img` | fixed: the 11-byte file reads correctly | the volume-size clamp applies only when the volume size is known | | iso9660 | `iso.img` | level-1 names without an extension keep the dot: `LS.`, `SERVICES.` | strip the trailing `.` together with the `;1` version | | hfs | `hfs-oobread.img` | refuses to mount | crafted image, the expected result | | bfs | `bfs-sibling-cycle.img` | refuses to mount | crafted image, the expected result | diff --git a/Makefile b/Makefile index c16f035..0583813 100644 --- a/Makefile +++ b/Makefile @@ -61,6 +61,7 @@ static: ./dist/static.sh check test: $(BIN) + sh ./test/harness.sh ./test/run.sh $(TESTBINS) install: $(BIN) diff --git a/README.md b/README.md index 240dbd2..80bdfc6 100644 --- a/README.md +++ b/README.md @@ -170,8 +170,11 @@ f 19605 c960f1896b3e8434 /etc/services from the [radare2-testbins](https://github.com/radareorg/radare2-testbins) `fs/` directory (default `../radare2/test/bins/fs`, override with `make check TESTBINS=...`). Each image has an expected result. Plugin bugs show -up as `XFAIL`, and when one gets fixed it shows up as `XPASS` so the table can be -updated. Set `R2FUSE_MOUNT=1` to also mount every image and compare file sizes +up as `XFAIL`, and when one gets fixed it shows up as `XPASS` and fails the run +until its expectation is updated. Malformed images pass only when they are +rejected with the expected error; crashes and timeouts always fail. +The check/stress commands return 0 on success, 1 on a failed check, and 2 if +the image cannot be opened or mounted. Set `R2FUSE_MOUNT=1` to also mount every image and compare file sizes as seen through the kernel. ## How it works diff --git a/meson.build b/meson.build index 071740c..f4575fd 100644 --- a/meson.build +++ b/meson.build @@ -57,6 +57,8 @@ executable('r2fuse', link_args: link_args, install: true) +test('harness', find_program('sh'), args: [meson.current_source_dir() / 'test/harness.sh']) + test('check', find_program('test/run.sh'), args: [get_option('testbins')], depends: [], diff --git a/test/harness.sh b/test/harness.sh new file mode 100644 index 0000000..fa528ff --- /dev/null +++ b/test/harness.sh @@ -0,0 +1,48 @@ +#!/bin/sh +# Exercise the runner's exit-status handling without real filesystem plugins. +set -eu + +HERE=$(cd "$(dirname "$0")" && pwd) +TMP=$(mktemp -d "${TMPDIR:-/tmp}/r2fuse-harness.XXXXXX") +trap 'rm -rf "$TMP"' EXIT +mkdir "$TMP/images" +cat > "$TMP/mock" <<'EOF' +#!/bin/sh +case "$R2FUSE_TEST_STATUS" in +timeout) exec sleep 2 ;; +esac +printf '%s\n' "$R2FUSE_TEST_OUTPUT" +exit "$R2FUSE_TEST_STATUS" +EOF +chmod +x "$TMP/mock" + +check() { + image=$1 status=$2 output=$3 expected=$4 + : > "$TMP/images/$image" + result=0 + R2FUSE="$TMP/mock" R2FUSE_MOUNT= TIMEOUT=1 \ + R2FUSE_TEST_STATUS="$status" R2FUSE_TEST_OUTPUT="$output" \ + sh "$HERE/run.sh" "$TMP/images" > "$TMP/output" 2>&1 || result=$? + rm -f "$TMP/images/$image" + if [ "$result" -ne "$expected" ]; then + echo "Runner regression: $image, status $status: exit $result, expected $expected" + cat "$TMP/output" + exit 1 + fi +} + +check ext2.img 0 'valid image' 0 +check ext2.img 1 'unexpected read failure' 1 +check ext2.img 2 'unexpected mount rejection' 1 +check ext2.img 139 'crash' 1 +check ext2.img timeout '' 1 +check bfs-sibling-cycle.img 2 'malformed image rejected' 0 +check bfs-sibling-cycle.img 0 'unexpected acceptance' 1 +check bfs-sibling-cycle.img 1 'wrong kind of failure' 1 +check bfs-sibling-cycle.img 139 'crash' 1 +check ntfs-resident-oob.img 1 'FAIL /file: open failed' 0 +check ntfs-resident-oob.img 1 'FAIL /other: open failed' 1 +check ntfs-resident-oob.img 1 'FAIL /file: read failed' 1 +check ntfs-resident-oob.img 139 'crash' 1 +check ntfs-resident-oob.img 0 'unexpected acceptance' 1 +echo 'OK runner rejects unexpected errors, crashes, timeouts and unexpected passes' diff --git a/test/images.txt b/test/images.txt index dac2336..9418579 100644 --- a/test/images.txt +++ b/test/images.txt @@ -12,8 +12,8 @@ iso3.img - ok iso-joliet-rockridge.iso - ok udf.img udf ok autodetected as iso9660 haiku.bfs - ok -ntfs.img - fail $Extend metafiles cannot be opened -ntfs-resident-oob.img - fail crafted image +ntfs.img - ok index-view metadata is reported as special files +ntfs-resident-oob.img - reject-file:/file resident value outside the MFT record reiserfs-link-root.img - ok symlinks are reported as special files reiserfs-corrupt.img - ok crafted image reiserfs-name-oob.img - ok crafted image diff --git a/test/run.sh b/test/run.sh index 10ba31b..312db5f 100755 --- a/test/run.sh +++ b/test/run.sh @@ -70,12 +70,20 @@ while read -r img fstype expect notes; do 2) res=reject ;; *) res=crash ;; esac + case "$expect" in + reject-file:*) + if [ "$rc" -eq 1 ] && [ "$(grep -c '^FAIL ' "$TMP/out")" -eq 1 ] \ + && grep -Fxq "FAIL ${expect#reject-file:}: open failed" "$TMP/out"; then + res=$expect + fi + ;; + esac summary=$(tail -n 1 "$TMP/out") - if [ "$res" = "$expect" ]; then + if [ "$res" != crash ] && [ "$res" = "$expect" ]; then if [ "$res" = ok ]; then echo "OK $img: $summary"; pass=$((pass + 1)) - elif [ "$res" = reject ]; then - echo "OK $img: rejected malformed image"; pass=$((pass + 1)) + elif [ "$res" != fail ]; then + echo "OK $img: rejected malformed image or file"; pass=$((pass + 1)) else echo "XFAIL $img: $notes"; xfail=$((xfail + 1)) fi