ng_fec_rmnode infinite loop when member interface vanished: dangling ptr + unkillable loop
Summary
ng_fec_rmnode(:1224-1229): while(!TAILQ_EMPTY) p=TAILQ_FIRST ksprintf(ifname,%s,p->fec_if->if_xname) ng_fec_delport(priv,ifname). delport re-resolves ifunit(:433) returns ENOENT(:434-439) WITHOUT removing port from TAILQ if interface destroyed. Loop never terminates. p->fec_if dangling -> if_xname UAF. Same as DF-0502(ng7). Fix: unlink+free directly in rmnode dont re-resolve.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0528 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| trigger.sh | trigger-source | create node, add tap, destroy tap, shutdown -> loop | 1.5 KB | view raw |
| build.sh | build-script | 228 B | view raw | |
| run.sh | run-script | 216 B | view raw | |
| fix.diff | suggested-fix | unlink+free directly in rmnode, skip ifunit re-resolution | 999 B | view raw |
| VERDICT.md | verdict | source confirmation + DF-0529 blocker | 2.6 KB | β raw |
| build.log | build-log | fix compile-validated RC=0 | 63 B | view raw |
| env.txt | environment | 829 B | view raw | |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-0528 β VERDICT
Verdict: SOURCE-CONFIRMED, NOT REACHABLE AT RUNTIME (blocked by DF-0529)
The bug (confirmed in source)
ng_fec_rmnode() (sys/netgraph/fec/ng_fec.c:1213) tears down the bundle by
looping over member ports and calling ng_fec_delport() by interface name,
which re-resolves the interface via ifunit():
// :1224-1229
while (!TAILQ_EMPTY(&b->ng_fec_ports)) {
p = TAILQ_FIRST(&b->ng_fec_ports);
ksprintf(ifname, "%s", p->fec_if->if_xname); // UAF if iface already freed
ng_fec_delport(priv, ifname);
}
ng_fec_delport() (:417) does:
// :433-439
bifp = ifunit(iface);
if (bifp == NULL) {
kprintf("... doesn't seem to exist\n");
return (ENOENT); // returns WITHOUT removing the port from the TAILQ
}
If a member interface has already been destroyed (e.g. the admin destroyed a
tap member, or the underlying NIC was detached), ifunit() returns NULL and
delport returns ENOENT without unlinking the port. The while loop in
rmnode therefore never makes progress β infinite loop (kernel hang /
soft-lockup). Additionally p->fec_if->if_xname is a use-after-free once
the member ifnet has been freed. This is the same defect as the netgraph7
twin DF-0502.
Why it cannot be triggered at runtime on this guest
ng_fec_rmnode() runs when an ng_fec node is shut down β which requires a
node to have been created first. But ng_fec_constructor() panics on every
node creation (DF-0529), so no node is ever available to shut down. Verified
empirically; the node is uncreatable even after DF-0529's documented fix.
Impact ceiling (latent)
Kernel hang / infinite loop in ng_fec_rmnode (local DoS), plus a
use-after-free read on p->fec_if->if_xname. The UAF is a read (string
format), not a controllable write.
Exploit chain
none β infinite-loop DoS + UAF read; not a write-capable primitive.
Fix validation
fix.diff replaces the delport-by-name loop with a direct unlink+free
(avoiding the ifunit() re-resolution entirely):
while (!TAILQ_EMPTY(&b->ng_fec_ports)) {
p = TAILQ_FIRST(&b->ng_fec_ports);
TAILQ_REMOVE(&b->ng_fec_ports, p, fec_list);
kfree(p, M_NETGRAPH);
}
b->fec_ifcnt = 0;
Compiles cleanly (RC=0). fix_status = not_testable β the DF-0529 constructor
panic prevents creating a node to shut down; compile-validated only, traced to
close the cited path.
PoC changes
Wrote trigger.sh (create node, add tap, destroy tap, shutdown node β needs a
DF-0529-fixed kernel), build.sh/run.sh, fix.diff. No upstream PoC.
Fix verification
not_testablecompile validated
see evidence pack
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
Source-confirmed. ng_fec_rmnode infinite loop + UAF when member iface destroyed. Blocked by DF-0529.
No comments yet.