β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-5663

Unsynchronized cam_extend growth frees ea->ps under lockless readers: use-after-free read returned to CAM drivers as struct cam_periph * (root cause of the DF-5462 class; first conclusive reproduction β€” consumption panics the kernel)

Summary

No lock exists in struct extend_array: every growth executes kmalloc->bcopy->kfree(ea->ps) while cam_extend_get loads and dereferences ps[index] with zero synchronization - a reader straddling the kfree gets a pointer read from freed memory returned as struct cam_periph *, which consumers immediately refcount++/deref/lock. Reproduced with the REAL production functions on the live kernel: 620k growth frees / 34.4M gets in 60s -> 204 conclusive UAF hits (0x4141414141414141 returned) + 191,555 anomalies (foreign live kernel pointers handed back - the type-confusion primitive); consuming one bad pointer exactly as ptopen does -> Fatal trap 9 GPF in the refcount increment, kernel to DDB. Root/physical-hotplug-gated (growth every 8th periph unit, root:operator nodes); this is the primitive-level ROOT CAUSE owner and first conclusive repro of the DF-5462 class (whose real-flow rig hit ~90 windows without a conclusive hit). spinlock + alloc-outside-lock fix validated (same budget -> hits=0).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5663 Β· 9 files
FileTypeDescriptionSize
race5663.c β€” 8.3 KB view raw
build.sh β€” 210 B view raw
run.sh β€” 292 B view raw
build.log β€” 466 B view raw
run.log β€” 1.6 KB view raw
panic.txt β€” 923 B view raw
fix_run.log β€” 653 B view raw
fix.diff β€” 4.0 KB view raw
env.txt β€” 812 B view raw

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

fix.diff (spinlock in struct extend_array; set/get/release serialized; growth kmalloc outside lock + re-check under lock) applied to guest /usr/src, full nativekernel rebuild + installkernel + reboot into #1 Oct 9 14:34:16. Rig re-run with identical budget: 620,000 growth frees / 7.2M gets -> hits=0 anoms=0 (baseline: 204/191555). Guest healthy, threads exited cleanly.

fix_run.log, fix.diff, fix_build.log (DF-5662 pack, shared build)
↓ fix.diffper-fix-DF-5663

Confirmed kernel references

Detail

Exploit chain

kernel rig (root KLD) racing the real cam_extend_set growth kfree against real cam_extend_get readers -> reader obtains pointer read from freed+reused kernel memory -> consumed like ptopen's cam_periph_acquire (refcount++ through 0x4141414141414141) -> Fatal trap 9, kernel in DDB. Real-flow chain (unpriv user) does not exist: growth requires periph registration churn (root camcontrol rescan / physical hotplug) and the racing readers' nodes are root-gated; DF-5462 documents the real-flow analysis.

Evidence (decisive lines)

run.log (204 hits + 191555 anomalies, stats line), panic.txt (UAF HIT -> consuming bad periph -> Fatal trap 9, Stopped at r5663_reader+0x14c), fix_run.log (hits=0 anoms=0 on fixed kernel, same 620k-free budget)

PoC changes

Authored from scratch (no seed). v1 (6 spinning LWKT threads incl. cross-CPU stampers) starved userland on the 6-CPU guest and misunderstood DFBSD per-CPU slab zones; redesigned as v2: 3 threads total, grower self-stamps via same-CPU LIFO reclaim of the just-freed chunk (kern_slaballoc.c:1576-1585), which made the UAF deterministic enough to hit 204 times in 60s. DF-5462's ptsim real-flow rig (~90 windows) never hit; the primitive-level rig generates ~10k windows/s.

Verified recommended fix

Add a spinlock to struct extend_array serializing set/get/release (growth kmalloc outside the lock, install under lock with re-check) β€” see fix.diff, shared with DF-5662.

Verdict

struct extend_array (cam_extend.c:33-37) has no lock; cam_extend_set growth executes kfree(ea->ps) at cam_extend.c:64 while lockless cam_extend_get readers (cam_extend.c:84-86) load ea->ps and dereference ps[index]. Reproduced with the real production functions on the live kernel: 620k growth frees / 34.4M gets in 60s produced 204 conclusive UAF reads (cam_extend_get returned 0x4141414141414141, a freed-then-reused chunk) and 191,555 anomalies (foreign live kernel pointers handed back as the stored object pointer = the type-confusion primitive). Consuming one bad pointer exactly as ptopen does (cam_periph_acquire refcount++) caused Fatal trap 9 GPF 'lock addl $0x1,(%r14)' and dropped the kernel to DDB. First successful reproduction of the DF-5462 class (filed against scsi_pt, not_reproduced); this finding owns the root cause at the primitive and covers all 7 consumers (pt/pass/ses/da/sa/ch/cd). Real-flow reachability remains root/physical-hotplug-gated (growth only at every 8th periph unit; device nodes root-gated) -> Medium, consistent with DF-5462. Fix (spinlock serializing set/get/release, growth kmalloc outside the lock) validated by nativekernel rebuild: same rig budget -> hits=0 anoms=0.