β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-0527

ng_fec_choose_port NULL dereference when computed port index removed: reachable panic after port delete

Summary

mask=fec_ifcnt==2?0x1:0x3(:896) so port may be 0..3. fec_idx assigned sequentially in addport(:395) NEVER renumbered when port removed in delport(:463-466 only TAILQ_REMOVE+kfree+ifcnt--). TAILQ_FOREACH(:945-948) exits p==NULL when hash selects removed index. Line :956 if(p->fec_ifstat!=1) derefs NULL. REACHABLE: 4-port bundle up, del one port (ifcnt=3, mask=0x3, only 3 of {0,1,2,3} remain), transmit -> ~25% flows hit missing slot -> NULL deref -> panic. Fix: if(p==NULL) return EINVAL or renumber fec_idx in delport or compute mask from actual indices.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0527 Β· 9 files
FileTypeDescriptionSize
trigger.sh trigger-source 4-port bundle, remove one, send traffic 2.3 KB view raw
build.sh build-script 228 B view raw
run.sh run-script 216 B view raw
fix.diff suggested-fix if(p==NULL) return EINVAL after TAILQ_FOREACH 558 B view raw
VERDICT.md verdict source confirmation + DF-0529 blocker 2.3 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
VERDICT.md verdict source confirmation + DF-0529 blocker
↓ download raw

DF-0527 β€” VERDICT

Verdict: SOURCE-CONFIRMED, NOT REACHABLE AT RUNTIME (blocked by DF-0529)

The bug (confirmed in source)

ng_fec_choose_port() computes the port hash mask from the current port count, but fec_idx is assigned sequentially at addport and never renumbered when a port is removed:

// :896
mask = b->fec_ifcnt == 2 ? 0x1 : 0x3;     // so 'port' may be 0..3
...
// :945-948
TAILQ_FOREACH(p, &b->ng_fec_ports, fec_list) {
    if (port == p->fec_idx)
        break;
}
// :956
if (p->fec_ifstat != 1) { ... }            // p may be NULL here -> DEREF

ng_fec_addport() (:395) sets new->fec_idx = b->fec_ifcnt; b->fec_ifcnt++;. ng_fec_delport() (:463-466) only does TAILQ_REMOVE + kfree(p) + b->fec_ifcnt-- β€” it does not renumber the surviving ports' fec_idx.

So after a 4-port bundle loses one member (fec_ifcnt 4 β†’ 3, mask stays 0x3), a packet whose XOR hash selects the removed index (e.g. index 3) causes the TAILQ_FOREACH to terminate with p == NULL, and :956 if (p->fec_ifstat != 1) dereferences NULL β†’ panic (page fault / NULL deref). With 4 ports and one removed, ~25 % of flows hit the missing slot.

Why it cannot be triggered at runtime on this guest

Same blocker as DF-0526: choose_port only runs on a live fec interface, and ng_fec_constructor() panics on every node creation (DF-0529), so no fec interface can ever be brought up. Verified empirically; the node is uncreatable even after DF-0529's documented fix (residual constructor panic).

Impact ceiling (latent)

NULL-dereference kernel panic (local DoS). This is a read deref of NULL (p->fec_ifstat), not a controllable write β€” no escalation primitive.

Exploit chain

none β€” NULL-deref panic (DoS), not memory corruption.

Fix validation

fix.diff adds if (p == NULL) return(EINVAL); immediately after the TAILQ_FOREACH (:948), so a hash that selects a removed slot bails out instead of dereferencing NULL. Compiles cleanly (RC=0). fix_status = not_testable β€” the DF-0529 constructor panic prevents creating a node to exercise the path; compile-validated only, traced to close the cited path.

PoC changes

Wrote trigger.sh (creates 4 tap ports, removes one, sends traffic β€” needs a DF-0529-fixed kernel), build.sh/run.sh, fix.diff. No upstream PoC.

Fix verification

not_testable

compile validated

see evidence pack

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source-confirmed. ng_fec_choose_port NULL deref when hash selects removed port index. Blocked by DF-0529.