DF-2233 / verdict.json
{ "finding_id": "DF-2233", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "The lockless traversal UAF is real and reproduces with stock modules: two independent boots, each panicking in under 45 seconds with Stopped at iconv_lookupconv+0x30 movq 0x800(%rsi),%rdi (fault VA 0x800) while a plain kern.iconv.add caller walked the converter list and kldunload libiconv concurrently freed and NULLed the class ops tables. A third rig (in-kernel iconv_open walkers vs unload) killed the guest outright with no trap text. The cslist-walk variant against register/free churn performs stale reads of freed but still-mapped chunks and does not fault. Reachability is root-on-both-sides on default config: sysctl writes are gated by SYSCAP_NOSYSCTL_WR and kldunload needs root, with an unprivileged mount-side angle only under vfs.usermount=1. Impact is therefore local DoS / kernel UAF read, severity Medium as filed. The fix (lock held across traversal and dispatch, plus an in-flight refcount drain guard on unload) was validated on rebuilt modules: the baseline panic signature never reappears, 316M-walk soaks and 240 s unload storms are clean; a nanosecond-scale kldunload-vs-inflight residual remains and is documented (needs linker-level drain support).", "exploit_chain": "", "evidence": "panic.txt and panic.2.txt: serial captures of both panics showing Stopped at iconv_lookupconv+0x30 with fault virtual address 0x800 from process churnadd. run.log: full rig narrative including the 300M-walk churn run (no fault, stale-read analysis), the refcount-leak side observation (pair ref=12685 never drains) and the silent hard death of the bundled-module rig. fixvalidation.log: fixed-build soaks A, B2 (316798072 walks, clean), C3 (240 s unload storm, clean) and the documented C4 residual.", "kernel_refs": [ "sys/libiconv/iconv.c:145", "sys/libiconv/iconv.c:150", "sys/libiconv/iconv.c:162", "sys/libiconv/iconv.c:167", "sys/libiconv/iconv.c:243", "sys/libiconv/iconv.c:251", "sys/libiconv/iconv.c:418", "sys/libiconv/iconv.c:133", "sys/libiconv/iconv.c:218", "sys/libiconv/iconv.c:80", "sys/kern/subr_kobj.c:225", "sys/kern/kern_sysctl.c:1447" ], "poc_changes": "No seed pack existed; built from scratch. Rig 1 needed no custom module at all (stock libiconv.ko + stock kern.iconv.add sysctl path driven by churnadd walk mode). The bundled race2233.ko compiles the pristine libiconv sources plus an in-kernel walk harness replicating the mount-path iconv_open call. CTLFLAG_NOLOCK was required on the harness sysctl so several hammer processes run concurrently.", "attempts": 6, "guest_uname": "DragonFly dfbsd 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": 2100, "guest_dirty": 0, "build_cmd": "sh build.sh (guest root): cd /root/rig2233 && make; cc -O -o churnadd churnadd.c", "run_cmd": "sh run.sh (guest root): kldload libiconv; 4x /root/rig2233/churnadd walk 200000000 & loop { kldunload libiconv; kldload libiconv }", "code_hash": "cd8f3075935550cd438e0618c0b011569527e6d98229140b4b53101035da0b36", "notes": "Side observation for upstream: after heavy successful open/close churn the UTF-16BE<->UTF-8 pair cp_refcount leaks monotonically (observed 12685) making the module permanently unloadable. Fix validation A/B: baseline panics in iconv_lookupconv+0x30 twice; with fix.diff applied and modules rebuilt, identical and harder soaks never reproduce that signature; residual teardown micro-race documented in VERDICT.md.", "fix_status": "fixed", "fix_notes": "fix.diff: rename lookups to _locked variants, hold iconv_lock (shared or exclusive) across traversal AND dispatch in iconv_open, iconv_add and iconv_sysctl_add (lookup moved under the exclusive section), and add an iconv_refs in-flight counter with EBUSY drain guard in iconv_mod_unload. Validated on rebuilt modules; the walk UAF is gone. Residual: an in-flight caller whose ref lands after the final unload check can still race the unmap (generic DragonFly kldunload limitation, one silent reboot observed after a max-aggression supervised storm); airtight closure needs linker-level drain support.", "recommended_fix": "Hold iconv_lock across all list traversals and result dispatch; add an in-flight refcount drain guard refusing unload (EBUSY) while callers are inside the module." } |