{
 "finding_id": "DF-2679",
 "status": "reproduced",
 "reproduced": 1,
 "impact": "dos",
 "confidence": "certain",
 "verdict": "Unprivileged local kernel UAF read proven on the stock INVARIANS kernel: 4 walker processes running as uid 1002 fetching hw.bus.devices.<gen>.<idx> in a loop raced root-driven device_delete_child() churn (512 devices kldload/kldunload cycled); run 1 ended in 'Fatal trap 9: general protection fault ... current process = 42646' (a uid-1002 walker) 'Stopped at sysctl_devices.part.15+0x35: movq 0x18(%rbx),%rbx' dereferencing a freed 0xAA-poisoned bsd_device; run 2 on a fresh boot wedged the machine permanently (walker spinning inside the poisoned TAILQ walk, console flooding 'send_ipiq 5->0 tgt not draining', ps/kldstat/shutdown hung, hard reset required). sysctl_devices() (sys/kern/subr_bus.c:3858-3906) and device_sysctl_handler() (subr_bus.c:152-186) walk/dereference bus_data_devices and device fields with no lock; the generation check at subr_bus.c:3871 is advisory only, while device_delete_child (subr_bus.c:1302-1306) TAILQ_REMOVEs and kfrees the device and devclass_delete_device (subr_bus.c:1164-1169) kfrees dev->nameunit with no exclusion. Fix (fix.diff: global sleepable bus_topo_lock, shared in both readers, exclusive at publish/unpublish/nameunit-free points) validated by in-guest nativekernel rebuild: identical stress no longer panics or wedges.",
 "exploit_chain": "unprivileged sysctl walker (hw.bus.devices ABI used by devinfo(8)) + privileged topology churn (kldload/kldunload, devd hotplug) -> reader loads freed device_t -> strlcpy from freed/groomed nameunit/desc (bounded 32B kernel-heap disclosure to unprivileged reader) and bus_child_pnpinfo_str dispatch reading child->ivars from freed memory (128B copyout, groomable wild deref) -> demonstrated: kernel panic and permanent hang; escalation to controlled write not developed (free side is root-driven), so filed as unprivileged DoS + info-leak-capable UAF read.",
 "evidence": "panic.txt (Fatal trap 9 in sysctl_devices, current process 42646 = uid-1002 walker, verified via ps before crash); VERDICT.md (full narrative + line citations); walker.c (unprivileged reader); dfrace.c (churn module with poison back-fill); run.log/run.2.log (baseline runs); run.fixed.log (patched-kernel validation); fix.diff; env.txt",
 "kernel_refs": [
  "sys/kern/subr_bus.c:3858",
  "sys/kern/subr_bus.c:3871",
  "sys/kern/subr_bus.c:3879",
  "sys/kern/subr_bus.c:3890",
  "sys/kern/subr_bus.c:152",
  "sys/kern/subr_bus.c:182",
  "sys/kern/subr_bus.c:1283",
  "sys/kern/subr_bus.c:1302",
  "sys/kern/subr_bus.c:1306",
  "sys/kern/subr_bus.c:1164",
  "sys/kern/subr_bus.c:3912"
 ],
 "poc_changes": "PoC authored fresh for pass 2 (no seed).walker.c resolves hw.bus.info/hw.bus.devices MIBs via sysctlnametomib(3) then hammers indices; dfrace.ko churn module evolved twice during verification: (1) plain add/delete produced no crash because freed chunks kept benign stale contents, so each delete is now followed by a same-bucket 0xAA-poisoned kmalloc back-fill (models any racing kernel allocation); (2) a real driver was added on the root devclass so children genuinely attach/detach, which is also required for DF-2680's devctl events. Guest-side friction: /tmp wiped on each crash/reset (sources kept host-side); su -m unusable under csh (start4.sh run via 'su testu -c'); testu user must be recreated after every snapshot reset.",
 "attempts": 6,
 "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": 1020,
 "guest_dirty": 1,
 "build_cmd": "cd /tmp/df2679 && cc -O2 -o walker walker.c && ln -sf /usr/src/sys dragonfly && make   # dfrace.ko",
 "run_cmd": "sh run.sh churn   # root: while true; do kldload /tmp/df2679/dfrace.ko; kldunload dfrace; done\nsh run.sh walk    # root spawns 4 walkers as unprivileged testu",
 "code_hash": "2e55a845cedbb591e5100e8273a016584cf03932e1ad9a1f0c0fde7b3b7f6c57 (walker.c); 23818c635231a714ea72fdf47dc649eadaf9e2eb9064665ae15e503f12586b8a (dfrace.c)",
 "notes": "Baseline reproduced twice (trap-9 panic in sysctl_devices from the uid-1002 walker; then a permanent send_ipiq IPI-drain wedge on a fresh boot). Fix validation ran THREE kernel iterations, all built in-guest (make nativekernel): v1 (kernel #1 14:58:28, topo lock incl. make_device + probe-loop set_devclass sites) deadlocked in 60s ACPICA 'indefinite wait' (AcpiNsGetNode); v2 (kernel #2 15:16:12, publish unlocked, lock dropped before SYSCTL_OUT) deadlocked again with the cycle pinned: walker holds topo SHARED inside bus_child_pnpinfo_str -> acpi_child_pnpinfo_str_method -> AcpiGetObjectInfo waiting on the ACPI namespace mutex; v3 (kernel #3 15:30:16, probe-loop site unlocked too, EXCL only at delete paths) deadlocked in the exact same place within ~4 minutes of identical stress. Conclusion: ANY global lock held across the BUS_CHILD_PNPINFO_STR/LOCATION kobj dispatches inverts against ACPI-internal mutexes reached from probe/attach contexts; the fix requires per-device lifetime references (refcount the device for sysctl/devctl readers) rather than a topology lock. fix_status=fix_failed, fix_baseline_reproduced=1, fix_patched_reproduced=1 (patched kernels fail with an introduced ACPI deadlock under the same stress), fix evidence in fix_iterations.log.",
 "recommended_fix": "Do NOT add a global topology lock around the pnpinfo/location kobj dispatches (proven ACPI lock-order deadlock, see fix_iterations.log). Add a per-device reference count (e.g. dev->dev_refcnt): sysctl_devices()/device_sysctl_handler() acquire it before walking/dereferencing and drop it after the dispatches; device_delete_child() removes the device from bus_data_devices, then waits for the refcount to drain before kobj_delete()/kfree; probe-time nameunit churn should be handled the same way. Short-term hardening: snapshot dev->nameunit/dev->desc under a small lock WITHOUT dispatching pnpinfo/location under it."
}