nge(4) callout AB-BA deadlock: nge_tick blocks on the driver serializer while nge_stop synchronously stops the timer under it (DF-3715 family)
Summary
nge_stat_timer is a plain 1 Hz callout whose callback does a BLOCKING serialize_enter and re-arms inside the serializer; nge_stop calls the synchronous callout_stop under that same serializer - INCLUDING nge_intr's unwanted-interrupt path, i.e. reachable from interrupt context. Parked-callback-vs-stopper AB-BA: permanent wedge of caller, softclock CPU, serializer, sysctl subsystem; wedged systems cannot be shut down. Mechanism PROVEN on guest via exact-protocol replica (stopper D5/costp; fix-shape control validated). try-enter fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4112 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.8 KB | β raw | |
| VERDICT.md | β | 3.1 KB | β raw | |
| tlnge_replica.c | β | 9.6 KB | view raw | |
| Makefile | β | 66 B | β download | |
| build.sh | β | 130 B | view raw | |
| run.sh | β | 308 B | view raw | |
| build.log | β | 1.3 KB | view raw | |
| run.log | β | 2.7 KB | view raw | |
| env.txt | β | 347 B | view raw | |
| fix.diff | β | 852 B | view raw | |
| manifest.json | β | 1.2 KB | view raw | |
| verdict.json | β | 4.0 KB | view raw |
DF-4112 β nge(4): callout AB-BA deadlock β nge_tick blocks on the serializer nge_stop stops the callout under
What this is
sys/dev/netif/nge/if_nge.c arms sc->nge_stat_timer with a plain
callout_init() (if_nge.c:731) β MP-safe on this kernel
(sys/kern/kern_timeout.c:770) β and re-arms it every hz from
nge_init (if_nge.c:1717). The callback, nge_tick()
(if_nge.c:1366-1406), takes the driver serializer by blocking
(lwkt_serialize_enter(ifp->if_serializer), if_nge.c:1372 β parks in
tsleep "slize", sys/kern/lwkt_serialize.c:227-237) and re-arms itself
inside the serializer (if_nge.c:1403).
nge_stop() (if_nge.c:2094) calls callout_stop(&sc->nge_stat_timer)
while its caller holds that same serializer (nge_stop is reached
under the serializer from nge_ioctl if_nge.c:2031, nge_init :1716,
nge_watchdog :2067, nge_detach :934, and nge_intr :1491 β the
latter is the interrupt thread itself).
DragonFly's callout_stop() synchronously waits for an in-progress
callback (ssleep "costp", sys/kern/kern_timeout.c:920,1091). A
callback parked on the stopper's serializer deadlocks against it: both
threads wedge permanently, the interface serializer is never released,
and the parked softclock thread stops delivering all callouts for its
CPU. Same mechanism as DF-3715 (em(4)); re-proven here with the exact
nge shape.
Build / run (verification on the audit guest)
# guest has no nge(4)/DP83820 hardware (vtnet0 lo0 only), so the driver
# protocol is replicated verbatim by an out-of-tree module.
scp tlnge_replica.c Makefile root@guest:/root/kmod/
vm.sh run_root 'cd /root/kmod && make'
vm.sh run_root 'kldload /root/kmod/tlnge_replica.ko; sysctl kern.ngereplica.arm=1; sleep 3; \
(sysctl kern.ngereplica.stop_blk=1 >/dev/null 2>&1 &); sleep 6; \
dmesg | grep "replica\[nge\]" | tail -3; \
ps -o pid,stat,wchan,command -ax | grep stop_blk | grep -v grep'
Expected / observed
- dmesg ends with
replica[nge]: handler PARKED on our serializer (fired=8 ran=7) - entering callout_stop()then the marker β and nothing after (permanent). - stopper thread
D5, wchancostp(kern_timeout.c:920). - Fix-shape control (
stop_fix: try-enter + 1-tick retry, the proposed fix):callout_stopunder the serializer returns,pending=0, firing fully stalled β fix mechanism validated on this kernel. - On real nge(4) hardware the trigger is
ifconfig nge0 down/ MTU change / detach / link-watchdog reinit racing the 1-second tick.
Trigger on hardware
ifconfig nge0 down (SIOCSIFFLAGS), SIOCSIFMTU reinit, kldunload
if_nge, nge_watchdog recovery, or an unwanted interrupt while the
interface is down (nge_intr β nge_stop directly, if_nge.c:1490-1493)
β racing the once-per-second nge_tick.
DF-4112 β VERDICT
Finding: nge(4) nge_tick callout callback blocks on the driver
serializer (lwkt_serialize_enter, sys/dev/netif/nge/if_nge.c:1372)
while nge_stop() calls the synchronous callout_stop(&sc->
nge_stat_timer) (if_nge.c:2094) holding that same serializer β AB-BA
deadlock, the DF-3715 (em(4)) family.
Status: inconclusive per the family convention (target hardware
absent β guest has vtnet0 lo0 only, no NS DP83820/83821), with the
mechanism PROVEN deterministically on the guest kernel by a replica
module reproducing the exact nge protocol:
- callout created with plain
callout_init()(as if_nge.c:731; MPSAFE on this kernel per sys/kern/kern_timeout.c:770), - period
hz(as if_nge.c:1717), - callback does a blocking
lwkt_serialize_enter(if_nge.c:1372 βtsleep "slize", sys/kern/lwkt_serialize.c:227-237) and re-arms itself inside the serializer (if_nge.c:1403), - stopper takes the serializer (as every nge_stop caller does: nge_ioctl
if_nge.c:2031, nge_init :1716, nge_watchdog :2067, nge_detach :934,
and nge_intr :1491 β interrupt-thread context included), waits for the
handler to park, then
callout_stop()(if_nge.c:2094 β kern_timeout.c:1091 sync wait,ssleep "costp"at :920).
Observed on the guest (DragonFly 6.5-DEVELOPMENT x86_64):
replica[nge]: handler PARKED on our serializer (fired=8 ran=7) - entering callout_stop() replica[nge]: IF THIS IS THE LAST replica LINE, AB-BA DEADLOCK IS PROVEN 989 D5 costp sysctl kern.ngereplica.stop_blk=1 <- never returns
callout_stop()never returned; follow-up session hung because even sysctl reads block against the wedged writer's sysctl lock.- Fix-shape control: the proposed fix (
lwkt_serialize_try+ 1-tick retry) is fully stopped bycallout_stop()under a held serializer on this exact kernel (pending=0, firing stalled) β fix mechanism validated. - Post-wedge guests could not be shut down cleanly (force-kill required) β on hardware this is an un-shutdown-able system.
Why hardware-gated: nge(4) attaches only to PCI_VENDOR_NS /
PCI_PRODUCT_NS_DP83820 (if_nge.c:140-144); this guest has none.
Exploit chain / impact: not a corruption primitive. A privileged local user (ifconfig down/up, MTU change, kldunload if_nge) racing the 1-second tick β or simply an interrupt arriving while the interface is admin-down (nge_intr β nge_stop, if_nge.c:1490-1493) β permanently wedges threads, a CPU's callout delivery, the interface serializer, and the sysctl subsystem.
Fix: nge_tick() must not block: lwkt_serialize_try() with a
1-tick callout_reset retry (fix.diff). Fix mechanism validated on the
guest; patched driver itself not exercisable without hardware β
fix_status not_tested.
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
not_testablefix.diff (try-enter + 1-tick retry) authored against the read-only sys/ tree, patch --dry-run clean. Fix MECHANISM validated on this kernel by the module's fix-shape control (callout_stop under a held serializer returns and fully unloads the try-based handler: pending=0, firing stalled). The patched nge driver itself cannot be exercised without DP83820 hardware.
findings/poc/DF-4112/fix.diff; findings/poc/DF-4112/run.log fix-shape section
Confirmed kernel references
- sys/dev/netif/nge/if_nge.c:731
- sys/dev/netif/nge/if_nge.c:1372
- sys/dev/netif/nge/if_nge.c:1403
- sys/dev/netif/nge/if_nge.c:1717
- sys/dev/netif/nge/if_nge.c:2094
- sys/dev/netif/nge/if_nge.c:2031
- sys/dev/netif/nge/if_nge.c:1716
- sys/dev/netif/nge/if_nge.c:2067
- sys/dev/netif/nge/if_nge.c:934
- sys/dev/netif/nge/if_nge.c:1491
- sys/kern/kern_timeout.c:920
- sys/kern/kern_timeout.c:1091
- sys/kern/lwkt_serialize.c:234
Detail
Evidence (decisive lines)
findings/poc/DF-4112/run.log (decisive lines: 'handler PARKED on our serializer (fired=8 ran=7) - entering callout_stop()' then marker then silence; ps '989 D5 costp'; fix-shape 'callout_stop RETURNED ... pending=0 fired-stalled=YES'); build.log (BUILD_RC=0); VERDICT.md; fix.diff (try-enter + 1-tick retry)
PoC changes
No seed PoC. Authored tlnge_replica.ko (out-of-tree module) replicating the exact nge protocol because the guest has no DP83820 hardware.
Verified recommended fix
Make nge_tick() non-blocking: lwkt_serialize_try() with a 1-tick callout_reset retry.
Verdict
Mechanism PROVEN on the guest kernel by deterministic replica of if_nge.c's exact callout/serializer protocol; nge-specific runtime hardware-gated (inconclusive per family convention, as DF-3715). nge_tick (sys/dev/netif/nge/if_nge.c:1366-1406) is the callback of a plain-callout_init (if_nge.c:731) 1 Hz timer (if_nge.c:1717) that BLOCKS in lwkt_serialize_enter (if_nge.c:1372) and re-arms itself inside the serializer (if_nge.c:1403); nge_stop() (if_nge.c:2094) tears it down with callout_stop() while the caller holds that same serializer (nge_ioctl :2031, nge_init :1716, nge_watchdog :2067, nge_detach :934, and nge_intr :1491 in the interrupt thread). DragonFly callout_stop synchronously waits for an in-progress callback (kern_timeout.c:1091 -> ssleep 'costp' at :920), so a parked callback deadlocks against the stopper (AB-BA): both threads wedge permanently, the interface serializer is never released, the affected CPU's softclock thread stops delivering all callouts, and (observed) the sysctl subsystem blocks. Kernel-module replica on the guest: stop under a held serializer with the nge-shaped handler parked -> callout_stop NEVER returned (thread D5 wchan 'costp'); fix-shape control (lwkt_serialize_try + 1-tick retry, the proposed fix) is fully unloaded by callout_stop under the serializer on this kernel (pending=0, firing stalled) - fix mechanism validated. Post-wedge guests cannot be shut down cleanly.
No comments yet.