DF-3228 / verdict.json
{ "finding_id": "DF-3228", "status": "reproduced", "reproduced": 1, "impact": "dos", "confidence": "certain", "verdict": "REPRODUCED on the stock guest: fuse_cdevpriv_dtor (fuse_device.c:38-45) drops the open-time reference only when fuse_cdevpriv_close() returns 0; for a never-mounted fd it returns ENODEV (fuse_device.c:84-88) and the dtor skips fuse_mount_free() \u2014 every open(/dev/fuse)+close() without a mount leaks sizeof(struct fuse_mount). Measured: vmstat -m temp grew 472 allocs/335K -> 46.1K allocs/20.3MB within ~5 min of a 100k-cycle loop (still running when sampled), linear and unbounded. Semi-privileged primitive: /dev/fuse is 0660 root:operator (fuse_device.c:313-4), so group operator / root; slow kernel-heap exhaustion (CWE-401). devfs_set_cdevpriv()s return is also ignored at fuse_device.c:57.", "exploit_chain": "operator/root account: loop open+close on /dev/fuse without mounting -> ~256B M_TEMP leak per cycle -> unbounded kernel heap exhaustion -> memory-pressure DoS for all kernel subsystems.", "evidence": [ "findings/poc/DF-3228/run.log \u2014 baseline temp 472/335K -> 46.1K/20.3MB after 100k cycles", "findings/poc/DF-3228/fix_run2.log \u2014 patched: 20000 cycles, temp identical 472/335K before/after, no panic", "findings/poc/DF-3228/fix_run.log \u2014 fix v1 regression proof: plain fuse_mount_free() on never-mounted fmp panics in crfree(NULL) (Fatal trap 12 crfree+0xc) \u2014 why fix v2 uses plain kfree" ], "kernel_refs": [ "sys/vfs/fuse/fuse_device.c:38-45", "sys/vfs/fuse/fuse_device.c:53-57", "sys/vfs/fuse/fuse_device.c:82-97", "sys/vfs/fuse/fuse_vfsops.c:78-93", "sys/vfs/fuse/fuse_device.c:313-314" ], "poc_changes": "none \u2014 trivial loop written fresh; format warnings (int vs long) in the printf summary are cosmetic and do not affect the leak measurement.", "attempts": 2, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "runtime_sec": 600, "guest_dirty": 0, "build_cmd": "cc -O2 -Wall -o /tmp/df3228 /tmp/poc-src/DF-3228/df3228.c", "run_cmd": "kldload fuse && vmstat -m | grep temp && /tmp/df3228 100000 && vmstat -m | grep temp", "code_hash": "fee0405af21196a5a069dbaab79553c81051e603dcf92c960127da86732dee36", "notes": "Fix iteration documented honestly: v1 (always fuse_mount_free) introduced a crfree(NULL) panic because fmp->cred is only assigned in fuse_mount() (fuse_vfsops.c:185) \u2014 caught by the fix-validation run; v2 kfree()s the never-mounted object (refcnt==1, no mtx_inited locks, no cred) and checks devfs_set_cdevpriv() in open. v2 validated: zero growth over 20000 cycles, no panic.", "recommended_fix": "fuse_cdevpriv_dtor: if (fmp->devvp) { fuse_cdevpriv_close(fmp); fuse_mount_free(fmp); } else kfree(fmp, M_TEMP); plus check devfs_set_cdevpriv() return in fuse_device_open (see fix.diff).", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 (fuse.ko module rebuilt in-guest with fix.diff v2)", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Patched fuse.ko: 20000 open+close cycles leave vmstat -m temp byte-identical to baseline (472 allocs/335K) with no panic; baseline leaked ~256B/cycle.", "fix_evidence": [ "findings/poc/DF-3228/fix_run2.log", "findings/poc/DF-3228/fix.diff" ] } |