Daemon LOOKUP reply with S_IFMT-clear mode yields vtyp=VBAD β fuse_node_vn KKASSERT(0) panic (INVARIANTS force-enabled module-wide)
| Field | Value |
|---|---|
| ID | DF-3028 |
| Status | new |
| Severity | Medium |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:N/I:N/A:H |
| CWE | CWE-20 / CWE-617 |
| File | sys/vfs/fuse/fuse_vnops.c |
| Lines | 554-569 (sinks: fuse_node.c:103-104, :210-211; fuse.h:31-33) |
| Area | vfs/fuse |
| Confidence | certain |
| Discovered | 2026-09-02 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | base:vfs |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
fuse_vnops.c maps a daemon-supplied mode with no S_IFMT bits to vtyp=VBAD with no validation; fuse_alloc_node rejects only VBLK/VCHR/VFIFO, so the VBAD node reaches fuse_node_vn whose switch default is KKASSERT(0). fuse.h force-defines INVARIANTS for the entire module, so the panic fires on ANY kernel configuration. One crafted reply + any name resolution = guaranteed panic. Malicious/compromised FUSE daemon (or any process that received the /dev/fuse fd via SCM_RIGHTS) β the stated trust boundary; one reply permanently DoSes the host, and the resolving syscall (stat/open/ls) can be issued by any unprivileged user. Distinct from DF-0926 (existing-nodeid type confusion β fuse_set_attr KKASSERT): this is a new nodeid with garbage S_IFMT hitting a different sink. VERIFIED live (panic.txt with the exact predicted backtrace); patched module returns EINVAL.
Timeline
- 2026-09-02 Discovered during pass-2 audit of fuse_vnops.c (GLM 5.3); daemon-crafted-reply panic reproduced + fix validated.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3028 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fuse_daemon.c | β | 16.5 KB | view raw | |
| vbad_trigger.c | β | 993 B | view raw | |
| build.sh | β | 132 B | view raw | |
| run.sh | β | 576 B | view raw | |
| panic.txt | β | 634 B | view raw | |
| fix.diff | β | 808 B | view raw | |
| fix_run.log | β | 705 B | view raw | |
| VERDICT.md | β | 1.6 KB | β raw | |
| env.txt | β | 203 B | view raw |
DF-3028 verdict β REPRODUCED (deterministic panic, malicious-daemon DoS)
Root cause (path:line)
sys/vfs/fuse/fuse_vnops.c:568-569β S_IFMT-less mode falls through tovtyp = VBADwith no validation.sys/vfs/fuse/fuse_node.c:103-104βfuse_alloc_noderejects only VBLK/VCHR/VFIFO; VBAD (and VNON) pass.sys/vfs/fuse/fuse_node.c:210-211β switch defaultKKASSERT(0).sys/vfs/fuse/fuse.h:31-33β INVARIANTS force-enabled module-wide, so the KKASSERT panics production kernels too.
Reproduction (guest DragonFly 6.5-DEVELOPMENT #0)
Daemon mode vbad-lookup (single LOOKUP reply with mode bits = 0644, i.e.
S_IFMT clear). Trigger: unprivileged stat /mnt/fuse/target. Result
(panic.txt):
panic: assertion "0" failed in fuse_node_vn at /usr/src/sys/vfs/fuse/fuse_node.c:211 fuse_node_vn() at fuse_node_vn+0x3b8 fuse_alloc_node() at fuse_alloc_node+0x110 fuse_vop_nresolve() at fuse_vop_nresolve+0x246 vop_nresolve() ... cache_resolve()
Guest dropped into DDB; ssh died. One daemon reply = one guaranteed panic.
Trust model
The FUSE daemon is a trust boundary (a compromised/malicious userspace FS, or any process that obtained the /dev/fuse fd via SCM_RIGHTS). This is a single-reply, persistent-mount remote-equivalent DoS from that boundary; the trigger itself runs unprivileged.
Fix validation
fix.diff (reject VBAD/VNON in nresolve before alloc + harden
fuse_alloc_node) applied in-guest, module rebuilt: identical trigger returns
EINVAL (stat fails cleanly), guest stays up (fix_run.log). fix_status=fixed.
Fix verification
fixedPatched module: identical trigger returns EINVAL (stat fails cleanly), no panic, guest healthy.
fix_run.log, fix.diff
Confirmed kernel references
Detail
Exploit chain
malicious/compromised daemon (or any process holding the /dev/fuse fd) sends one crafted LOOKUP reply -> any name resolution on the mount (stat/open/ls by any user) panics the kernel; repeatable after reboot, persistent DoS of the host
Evidence (decisive lines)
['panic.txt: assertion "0" failed in fuse_node_vn at fuse_node.c:211 via fuse_alloc_node <- fuse_vop_nresolve <- cache_resolve', 'fix_run.log: patched stat failed: Invalid argument (no panic), guest alive']
PoC changes
derived from DF-0780 daemon harness; added vbad-lookup mode (LOOKUP reply mode=0644) and namespace support so mount/stat flow works
Verified recommended fix
Reject VBAD/VNON vtyp in fuse_vop_nresolve (return EINVAL) and harden fuse_alloc_node to reject them as well.
Verdict
A daemon LOOKUP reply with S_IFMT-clear attr.mode yields vtyp=VBAD in fuse_vop_nresolve (fuse_vnops.c:568); fuse_alloc_node passes VBAD through (fuse_node.c:103 rejects only VBLK/VCHR/VFIFO) and fuse_node_vn's switch default KKASSERT(0) (fuse_node.c:211) panics. fuse.h:31-33 force-enables INVARIANTS module-wide so this fires on any kernel. Verified: unprivileged stat() on a mount served by a vbad-lookup daemon panicked the guest instantly with the exact predicted backtrace; patched module returns EINVAL and survives.
No comments yet.