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

ng_fec_choose_port dereferences ether/IP headers without mbuf length validation: OOB read

Summary

ng_fec_choose_port: eh=mtod(m)(:897) ip=mtod+sizeof(ether_header)(:899-900) ip6=same(:902-903). No m_len/m_pkthdr.len check no m_pullup. INET reads ntohl(ip->ip_dst)(:930) INET6 reads ip6_dst.s6_addr[15](:935). Short/fragmented mbuf -> OOB read past m_data. Same as DF-0504(ng7). Fix: m_pullup before deref.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0526 Β· 9 files
FileTypeDescriptionSize
trigger.sh trigger-source harness; documents DF-0529 blocker 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 m_pullup in ng_fec_start caller 651 B view raw
VERDICT.md verdict source confirmation + DF-0529 blocker 3.2 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-0526 β€” VERDICT

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

The bug (confirmed in source)

ng_fec_choose_port() (sys/netgraph/fec/ng_fec.c:876) dereferences the ethernet/IP/IP6 headers of the head mbuf without any m_pullup() and without any length check:

// :897
eh  = mtod(m, struct ether_header *);
// :899-900
ip  = (struct ip *)(mtod(m, char *) + sizeof(struct ether_header));
// :902-903
ip6 = (struct ip6_hdr *)(mtod(m, char *) + sizeof(struct ether_header));
...
// :930  (case M_FEC_INET)
port = (ntohl(ip->ip_dst.s_addr) ^ ntohl(ip->ip_src.s_addr)) & mask;   // DEREF
// :935  (case M_FEC_INET6)
port = (ip6->ip6_dst.s6_addr[15] ^ ip6->ip6_src.s6_addr[15]) & mask;   // DEREF

The pointers are computed unconditionally from mtod(m); the actual dereferences happen in the switch on m_flags. A short or fragmented mbuf whose head mbuf does not contain a full ether_header + ip (or ip6) header causes an out-of-bounds read past m_data β€” an info leak of adjacent slab / stack bytes. This is the same defect as the netgraph7 twin DF-0504.

This is an OOB READ (info leak), not a write.

Why it cannot be triggered at runtime on this guest

ng_fec_choose_port() is called only from ng_fec_start() (:1013), which runs when packets are dequeued from a live fec interface's send queue. A live fec interface requires a successfully-constructed ng_fec node. But:

ng_fec_constructor() panics on EVERY node creation (DF-0529). See DF-0529/VERDICT.md. No ng_fec node can ever be brought up, so no packet ever reaches choose_port.

This was confirmed empirically: every ngctl mkpeer .: fec ... panics the guest in the constructor before any interface exists. Even after applying DF-0529's documented fix, the constructor still panics (residual kfree cascade), so the node remains uncreatable. Therefore DF-0526 is genuinely unreachable on this kernel β€” not because the code is dead, but because a separate, earlier bug (DF-0529) panics first.

Impact ceiling (latent, if the node were creatable)

Local information leak of kernel memory adjacent to the head mbuf's m_data. On a system where an admin had configured a working fec bundle and user traffic was routed through it, a short/malformed packet could leak slab bytes. No write primitive is derivable from this read alone.

Exploit chain

none β€” this is an OOB read, not memory corruption. No escalation chain.

Fix validation

fix.diff adds an m_pullup() in the caller ng_fec_start() (the correct location, since choose_port takes the mbuf by value and cannot propagate a reallocated pointer), dropping short mbufs cleanly:

m0 = m_pullup(m0, sizeof(struct ether_header) + sizeof(struct ip));
if (m0 == NULL) { IFNET_STAT_INC(ifp,ierrors,1); priv->if_error=ENOBUFS; return; }
  • compiles cleanly (RC=0, ng_fec.ko produced with the fix in source);
  • fix_status = not_testable: the panic path (DF-0529) prevents creating a node to exercise choose_port, so the fix cannot be runtime-validated on this guest. Compile-validated only; traced to close the cited code path.

PoC changes

Wrote trigger.sh (harness entry; notes the DF-0529 blocker), build.sh/ run.sh, fix.diff. No upstream PoC existed.

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 no m_pullup before ether/IP deref -> OOB read. Blocked by DF-0529 constructor panic.