DF-2680 / verdict.json
{ "finding_id": "DF-2680", "status": "reproduced", "reproduced": 1, "impact": "dos", "confidence": "certain", "verdict": "Use-after-free of devsoftc.async_proc reproduced on the stock kernel with a visible, benign manifestation: a root process opened /dev/devctl, issued FIOASYNC=1 (devioctl stores raw curproc, subr_bus.c:412-417, no reference) and exited; devclose (subr_bus.c:352-361) left async_proc dangling after the struct proc was reaped and kfree'd (kern_exit.c:1336). The next fork recycled the freed chunk, and every subsequent devctl event (device attach/detach -> devctl_queue_data, subr_bus.c:521-523) executed ksignal() on the recycled memory: lwpsignal() ran PHOLD(p) and lwkt_gettoken(&p->p_token) (kernel writes through the dangling pointer) and delivered SIGIO to a process that never opened /dev/devctl. 5/5 rounds reproduced (catcher pid = victim pid + 1 each time); a live-path control (victim stays alive, handler installed) confirmed the FIOASYNC->ksignal delivery path first. Trigger requires root to open the 0600 RESTRICTEDROOT-gated device, hence Low severity despite the kernel-memory-write primitive.", "exploit_chain": "root opens /dev/devctl + FIOASYNC -> exits without clearing -> async_proc dangles -> any later devctl event ksignal()s freed memory -> PHOLD/token writes into whatever recycled the chunk (cross-process SIGIO demonstrated; foreign-object corruption if recycled by non-proc data). No uid0 chain (root-gated setup).", "evidence": "run.log (control GOT_SIGIO pid=1961 live path; stale rounds GOT_SIGIO pid=1994/2032/2048/2075/2091 'never opened devctl'); victim.c/victim2.c/catcher.c; VERDICT.md; fix.diff", "kernel_refs": [ "sys/kern/subr_bus.c:405", "sys/kern/subr_bus.c:412", "sys/kern/subr_bus.c:352", "sys/kern/subr_bus.c:521", "sys/kern/subr_bus.c:336", "sys/kern/kern_exit.c:1336" ], "poc_changes": "PoC authored fresh. Iterations: initial runs showed no SIGIO because (a) SIGIO's default disposition is ignore, so both the control and the catcher need handlers installed, and (b) the DF-2679 churn module's children never attached, so no devadded/devremoved devctl events fired at all - fixed by adding a real driver to the churn module; devd must be stopped because /dev/devctl allows a single reader.", "attempts": 4, "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": 240, "guest_dirty": 0, "build_cmd": "cd /tmp/df2680 && cc -O2 -o victim victim.c && cc -O2 -o victim2 victim2.c && cc -O2 -o catcher catcher.c", "run_cmd": "sh run_seq.sh (root; needs dfrace.ko from DF-2679 as devctl event source)", "code_hash": "c03a16480fd02c67be4f0c192e98fe92fd1118777da16f24d6c5128b13a06377 (victim.c)", "notes": "Not fix-validated by rebuild (Low severity): fix is the two-line close-path clear in fix.diff plus optional PHOLD/PRELE reference handling; reasoned correctness covered in VERDICT.md. fix_status=inconclusive for the record.", "recommended_fix": "devclose(): devsoftc.async_proc = NULL under devsoftc.lock (and hold a PHOLD/PRELE reference on the stored proc in devioctl if SIGIO-after-exit is ever desired)." } |