⬢ DragonFlyBSD Kernel Audit
DF-4773 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-4773",
  "status": "inconclusive",
  "reproduced": 0,
  "impact": "leak",
  "confidence": "likely",
  "verdict": "Driver-specific execution is impossible on the audit guest (the Apple SMC cannot attach under QEMU: the SMC handshake needs real hardware answering the ISA/MMIO protocol, and apple_smc_init fails before any oid is registered), so the finding could not be exercised through smc_sysctl.c itself. The teardown CONTRACT was replicated on the stock guest kernel with generic primitives and the drivers own dynamic-oid API: registering a sensor oid in a device-style sysctl ctx whose handler dereferences backing chunks (== smc_sysctl.c:179), then running the apple_smc_detach order (free the sensor chunks at detach, oid ctx freed only at unload == device_delete, smc.c:507-512 vs subr_bus.c:2129-2149/device_sysctl_fini) yields a DETERMINISTIC post-detach UAF read: the still-registered oid returns 0xdeadbeef \u2014 recycled kernel heap \u2014 to an unprivileged reader (run.log). Applying the fix.diff ordering (ctx freed at the top of detach, from detach context) eliminates the window: the read returns unknown oid (run.fixed.log) \u2014 mechanism-level A/B. The in-flight-handler race prong (dead oid_running drain, kern_sysctl.c:391-398 \u2014 nothing increments oid_running, no wakeup exists) is the already-reproduced DF-4647 mechanism on this same kernel; two hammer+kldunload attempts here did not win the race (212/212 reads clean then READ-FAIL, race.attempts.log). Expected driver-path outcomes, source-traced: UAF read of freed sensor-key heap as the SMC key, then SMC_LOCK on the lockuninitd sc_lock (smc.c:544) and bus_write_1(sc->sc_ioport==NULL) (smc.h:110) -> NULL-page write -> panic in the detach..delete window of any kldunload apple_smc.",
  "exploit_chain": "unprivileged user loops reads of dev.acpi_APP0001.N.temperature.* (world-readable; light.control is even CTLFLAG_ANYBODY-writable) while root runs kldunload apple_smc -> driver detach frees the sensor-key heap chunks (smc.c:507-512) while every oid remains registered until device_delete -> read lands in the window: freed-heap pointer dereferenced as the 4-byte SMC key (heap info leak into the SMC protocol / handler) and, on the ISA backend, NULL-ioport write -> kernel panic; alternatively an in-flight handler races ctx_frees oid kfree with no drain (DF-4647 dead code)",
  "evidence": "run.log (buggy order: 0x0a5a5a5a -> 0x0 (freed) -> 0xdeadbeef recycled heap through the still-registered oid; dmesg: oid removal happens NOW \u2014 after backing free); run.fixed.log (fix.diff order: unknown oid after detach \u2014 window closed); race.attempts.log (2 unload-race attempts, not won); df4773.c (teardown-contract replica incl. the verified handler-side sysctl_ctx_free self-deadlock on the per-cpu sysctllock, kern_sysctl.c:1572-1574); fix.diff (git-apply clean vs sys/)",
  "kernel_refs": [
    "sys/dev/apple/smc/smc.c:503-512",
    "sys/dev/apple/smc/smc.c:544",
    "sys/dev/apple/smc/smc.c:188-190",
    "sys/dev/apple/smc/smc_sysctl.c:169-184",
    "sys/dev/apple/smc/smc_sysctl.c:207-249",
    "sys/dev/apple/smc/smc.h:109-113",
    "sys/kern/subr_bus.c:2128-2149",
    "sys/kern/kern_sysctl.c:391-398"
  ],
  "poc_changes": "Built the teardown-contract replica from scratch: dynamic SYSCTL_ADD_PROC oid in a sysctl_ctx (static SYSCTL_PROC oids cannot be removed on DFly \u2014 cant remove non-dynamic nodes!); the fix-order branch must free the ctx from a kthread because a sysctl handler calling sysctl_ctx_free/sysctl_remove_oid self-deadlocks (userland_sysctl holds the per-cpu sysctllock SHARED across handler invocation, kern_sysctl.c:1572-1574, and removal needs it EXCLUSIVE \u2014 wedge verified deterministically twice); kmalloc sizeof(int) chunks mirror smc.c:725-727s KEYLEN+1 allocs; values are logged as ints surfaced through sysctl.",
  "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": 5700,
  "guest_dirty": 0,
  "build_cmd": "cd /root/poc/DF-4773 && make -m /usr/share/mk KMODDIR=/root/poc/DF-4773",
  "run_cmd": "cd /root/poc/DF-4773 && kldload ./df4773.ko && V1=$(sysctl -n kern.df4773_sensor0); sysctl kern.df4773_detach=1 >/dev/null; V2=$(sysctl -n kern.df4773_sensor0); sysctl kern.df4773_spray=1 >/dev/null; V3=$(sysctl -n kern.df4773_sensor0); printf %s %s %s\\n $V1 $V2 $V3; kldunload df4773   # fixed order: sysctl kern.df4773_fixmode=1 first",
  "code_hash": "297f980f177e47cb8f40921732ddc67775e356280812f8b499249dcf93ff91f2",
  "notes": "Status inconclusive is deliberate: the deterministic UAF window and the fix ordering were reproduced at contract level on the stock guest kernel, but smc_sysctl.c itself cannot execute without Apple SMC hardware. The race prong is DF-4647s already-reproduced mechanism (same kernel); not re-won in 2 attempts here. Side-discovery for the record: handler-context oid removal self-deadlocks on the per-cpu sysctllock \u2014 worth documenting API-wide. sysctl write permission model verified: non-ANYBODY writes require SYSCAP_NOSYSCTL_WR (kern_sysctl.c:1446-1447), so fan duty writes are root-only and correctly gated.",
  "recommended_fix": "free the device sysctl ctx at the TOP of apple_smc_detach (before freeing sensor strings / releasing resources) \u2014 sysctl_ctx_free(device_get_sysctl_ctx(dev)); the later newbus device_sysctl_fini is a no-op on the drained ctx",
  "fix_status": "not_testable",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Cannot validate on a kernel running the actual smc.c patch (the driver cannot attach on this guest \u2014 no Apple SMC hardware). Validated at mechanism level with the contract replica A/B instead: buggy order leaks recycled heap through the still-registered oid (run.log: 0xdeadbeef); fix.diff order (ctx freed at top of detach, from detach context) returns unknown oid and closes the window (run.fixed.log). fix.diff applies clean against sys/ (git apply --check).",
  "fix_evidence": "run.log vs run.fixed.log (A/B); fix.diff; race.attempts.log"
}