Post-unmount fstatfs(2) on a still-open fd NULL-derefs in fuse_statfs: unguarded VFSTOFUSE(mp) after fuse_unmount clears mnt_data feeds fuse_ipc_get(NULL) -> fetch-add at 0x160 -> fatal trap 12 (deterministic panic)
Summary
fuse_unmount() stores mp->mnt_data = NULL as its last action (fuse_vfsops.c:294). kern_fstatfs takes the mount from fp->f_nchandle.mount with no vnode ops, so a dead-vnode guard cannot intercept; fuse_statfs (:388-393) has no NULL/dead entry guard and passes fmp=NULL into fuse_ipc_get(), whose atomic_fetchadd_long(&fmp->unique,1) faults at NULL+0x160. VERIFIED 3/3 on stock guest ('Stopped at fuse_ipc_get+0x63: lock xaddq %rax,0x160(%r13)'), twice incidentally during DF-3229 staging and once with the minimal race PoC (mount -> dir fd -> umount thread racing a fstatfs loop; no timing precision needed - the NULL window is inside the umount syscall). Standard admin-unmounts-under-user-traffic DoS: umount side needs mount owner/root, fstatfs side is any process holding an fd. Distinct from DF-3193 (freed-fmp walk on the WAKE path); this is the ENTRY path. Fix validated (NULL/dead entry guard in fuse_statfs/statvfs): identical run returns ENOTCONN, guest up.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3230 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df3230.c | β | 5.9 KB | view raw | |
| build.sh | β | 230 B | view raw | |
| run.sh | β | 156 B | view raw | |
| panic.txt | β | 748 B | view raw | |
| serial_full.txt | β | 19.8 KB | view raw | |
| fix.diff | β | 744 B | view raw | |
| verdict.json | β | 3.7 KB | view raw | |
| VERDICT.md | β | 3.1 KB | β raw | |
| env.txt | β | 188 B | view raw |
DF-3230 β post-unmount fstatfs(2) NULL-deref panic in fuse_statfs β fuse_ipc_get
Status: REPRODUCED (deterministic, minimal PoC + 2 prior in-staging occurrences, identical signature) β panic β fix VALIDATED in-guest.
Root cause
fuse_unmount()storesmp->mnt_data = NULLat fuse_vfsops.c:294 as its last substantive action before returning to userspace.- An
fstatfs(2)on a still-open fd whose file was on the fuse mount reacheskern_fstatfs(kern/vfs_syscalls.c:1495-1496), which takes the mount fromfp->f_nchandle.mount(or the vnode'sv_mount) β thestruct mountis still valid and referenced; no vnode ops run, so no dead-vnode guard can intercept. fuse_statfs()(fuse_vfsops.c:386-414) doesfmp = VFSTOFUSE(mp)β now NULL β with no NULL / dead guard, and callsfuse_ipc_get(fmp, 0).fuse_ipc_get()(fuse_ipc.c:100) executesatomic_fetchadd_long(&fmp->unique, 1)β fetch-add at NULL+0x160 β
Fatal user address access from kernel mode from df3230 at ffffffff826094b3 Fatal trap 12: page fault while in kernel mode fault virtual address = 0x160 fault code = supervisor write data, page not present instruction pointer = 0x8:0xffffffff826094b3 current process = 1058 (df3230) Stopped at fuse_ipc_get+0x63: lock xaddq %rax,0x160(%r13)
Distinct from DF-3193 (freed-fmp walk on the wake path of an
in-flight statfs): this is the entry path with a NULL fmp after a
completed unmount, and it is deterministic β any fstatfs on an fd that
outlives its fuse mount.
Reproducibility
- 2 incidental occurrences during DF-3229 staging (full-rate fstatfs
hammer crossing
mnt_data=NULL; identicalfuse_ipc_get+0x63/ 0x160 signature both times), plus - the minimal
df3230.c(mount β open dir fd β umount thread racing a fstatfs loop): panic on the first run β the NULL window is inside the umount syscall, so no timing precision is needed (panic.txt, serial_full.txt).
Trust boundary: the umount side needs the mount owner/root (or usermount policy); the fstatfs side is any process holding an fd on the mount β a root-triggered unprivileged-user-DoS-able panic in the standard "user has the fs mounted/opens files while admin unmounts" scenario.
Fix (fix.diff, validated)
Guard fuse_statfs() and fuse_statvfs() entries:
if (fmp == NULL || fuse_test_dead(fmp))
return ENOTCONN;
Validation: fix (+ DF-3229/3227 fixes) built into the in-guest fuse.ko, module reloaded, identical PoC rerun β no panic, guest up (the race loop completes; fstatfs now returns ENOTCONN). Baseline: 3/3 panics on stock.
Files
df3230.cβ minimal reproducerpanic.txt/serial_full.txtβ captured serial console (this boot)fix.diffβ validated guardverdict.json/manifest.json
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
fixedBaseline: 3/3 stock panics (identical signature). Patched module (fuse_vfsops.c guards): identical PoC rerun -> race loop completes with ENOTCONN returns, no panic, guest up.
fix.diff; validation session output: patched run 'mount fd opened; umount thread + fstatfs race loop' then clean completion, guest up (vs panic.txt baseline)
Confirmed kernel references
Detail
Evidence (decisive lines)
['panic.txt / serial_full.txt: full captured panic on the serial console from the minimal df3230.c run (current-boot capture, not reconstructed)', "run.log: 'mount fd opened; umount thread + fstatfs race loop' followed by the panic on stock", 'fuse_vfsops.c:294 (mnt_data=NULL) + :393 (unguarded VFSTOFUSE->fuse_ipc_get) + fuse_ipc.c:100 (fetchadd on fmp->unique)']
PoC changes
Original discovery was incidental (two DF-3229 staging panics, signature fuse_ipc_get+0x63 fault 0x160). Minimal PoC written from scratch; the naive sequential form (umount then fstatfs) wedged in vflush, so the final form races a fstatfs loop against a umount thread, which catches the mnt_data==NULL window inside the umount syscall deterministically.
Verified recommended fix
Guard fuse_statfs() and fuse_statvfs() entries: if (fmp == NULL || fuse_test_dead(fmp)) return ENOTCONN; (fix.diff)
Verdict
Deterministic kernel NULL-deref panic, reproduced 3x on the stock INVARIANTS guest (2 incidental during DF-3229 staging + 1 with the minimal PoC), identical signature every time: fuse_unmount() clears mp->mnt_data (fuse_vfsops.c:294); an fstatfs(2) on a still-open fd reaches fuse_statfs() via kern_fstatfs's fp->f_nchandle.mount with no vnode ops, where the unguarded VFSTOFUSE(mp) NULL feeds fuse_ipc_get() (fuse_ipc.c:100) -> atomic_fetchadd at NULL+0x160 -> 'Fatal trap 12, supervisor write, page not present / Stopped at fuse_ipc_get+0x63'. The NULL window lives inside the umount syscall, so a plain fstatfs race loop catches it with no timing precision. Distinct from DF-3193 (freed-fmp walk on the wake path of an in-flight statfs); this is the entry path with a NULL fmp. Fix validated in-guest: NULL/dead guard on fuse_statfs+fuse_statvfs entries, module rebuilt, identical PoC rerun -> no panic, guest up.
No comments yet.