DF-3935 / verdict.json
{ "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" } |