⬢ DragonFlyBSD Kernel Audit
DF-3935 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-3935",
  "fix_status": "not_testable",
  "fix_baseline_reproduced": 0,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Fix not validated on the guest: the trigger path requires mly/age hardware absent from the audit guest. fix.diff is mechanical (bounds/termination-only) and authored post-verification against the cited lines.",
  "fix_evidence": "findings/poc/DF-3935/fix.diff + VERDICT.md",
  "status": "inconclusive",
  "reproduced": 0,
  "impact": "none",
  "confidence": "certain",
  "verdict": "Mechanism PROVEN on the guest kernel by deterministic replica of if_age.cs exact callout/serializer protocol (DF-3715 family, age instance - the 11th); age-specific runtime trigger hardware-gated (no L1 NIC), inconclusive per family convention. age(4) arms one NON-mp callout (callout_init, if_age.c:517 - unlike ems callout_init_mp, matched by the replica) whose handler blocks on the driver serializer and self re-arms (if_age.c:2194/2197/2199); every teardown stops it while HOLDING that serializer (age_stop: ASSERT_SERIALIZED :2524 + callout_stop :2534; callers ifconfig down/up via age_ioctl :1673, age_init :2249, detach :686-688, suspend :1393). DragonFly callout_stop is synchronous (kern_timeout.c:1074 -> _callout_cancel_or_stop sync=1 -> ssleep costp :923): a callback dequeued and parked in lwkt_serialize_enter deadlocks against the stopper. Guest replica run: IF THIS IS THE LAST age_replica LINE, AB-BA DEADLOCK PROVEN then permanent silence; ps shows the stopper 1001 D5 wchan costp immediately AND 30s later (run.log); the wedge cascaded enough to kill the observing ssh session and prevent a clean shutdown. Fix (fix.diff): try-enter in age_tick with 1-tick retry, mirroring the em fix.",
  "exploit_chain": "ifconfig age0 down/up (or MTU change/detach/suspend) while the 1s age_tick is INPROG-parked on the serializer -> stopper sleeps in callout_stop(costp), callback waits on the serializer -> permanent AB-BA wedge + softclock starvation",
  "evidence": "run.log (replica transcript: last-line silence + 1001 D5 costp x2 + session death); build.log; age_replica.c; fix.diff (try-enter + retry); VERDICT.md",
  "kernel_refs": [
    "sys/dev/netif/age/if_age.c:517",
    "sys/dev/netif/age/if_age.c:2194",
    "sys/dev/netif/age/if_age.c:2197",
    "sys/dev/netif/age/if_age.c:2524",
    "sys/dev/netif/age/if_age.c:2534",
    "sys/dev/netif/age/if_age.c:686",
    "sys/kern/kern_timeout.c:923",
    "sys/kern/kern_timeout.c:1074"
  ],
  "poc_changes": "No seed PoC. Authored age_replica.ko (out-of-tree module replicating the age callout protocol with the NON-mp callout init age actually uses) because the guest has no L1 hardware; the deadlock is in the generic protocol age uses.",
  "attempts": 3,
  "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": 600,
  "guest_dirty": 0,
  "notes": "Guest left clean: after the wedge the guest could not shut down cleanly (qemu killed), then reset to with-src and verified up (guest_dirty=0). fix validation not run on an age kernel build (hardware-gated); the try-enter fix shape is the one validated for the family in DF-3715.",
  "recommended_fix": "Use lwkt_serialize_try in age_tick with a 1-tick retry so the callback always completes and callout_stop always returns.",
  "build_cmd": "cd /root/kmod/agesink && make",
  "run_cmd": "kldload /root/kmod/agesink/age_replica.ko; sysctl kern.agereplica.arm=1; (sysctl kern.agereplica.stop=1 &)",
  "code_hash": "c59c60c38c7a16f6b6ac3b342882b71cc84e81a3a0e23d993fc3785f18d91a0"
}