Network input paths X_ipip_input and pim_input dereference viftable/last_encap_vif without mroute_token racing vif teardown to NULL deref or 5.5MB OOB read
Summary
X_ipip_input and pim_input PIM-REGISTER branch both walk viftable/numvifs/last_encap_vif/reg_vif_num WITHOUT acquiring mroute_token while del_vif and X_ip_mrouter_done mutate exactly those fields under token. Concurrent teardown (mrouted restart config reload MRT_DONE) on netisr0 can bzero cached vif or rewrite reg_vif_num=VIFI_INVALID(65535) between validation and use producing NULL deref on v_ifp panic OR viftable[65535] read ~5.5MB past 32-entry static array used as struct ifnet pointer garbage deref. X_ipip_input last_encap_vif deref after vif zeroed v_ifp NULL ip_input.c:544 deref panic. pim_input reg_vif_num VIFI_INVALID viftable[65535] garbage ifp if_simloop deref. tbf_reprocess_q saved vifp also tokenless.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2541 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| poc.c | trigger-source | root-configured MRT_INIT + tunnel vif + ipip flood + concurrent del_vif race -> panic | 6.9 KB | view raw |
| build.sh | build-script | cc -O0 -o poc poc.c | 151 B | view raw |
| run.sh | run-script | kldload ip_mroute; ./poc (root) | 485 B | view raw |
| build.log | build-log | successful build output | 92 B | view raw |
| run.log | run-log | unpatched run 1 -> panic | 217 B | view raw |
| run.2.log | run-log | unpatched run 2 (reproducibility) -> panic | 380 B | view raw |
| panic.txt | panic-signature | Fatal trap 12 IP=0x0 Idle | 432 B | view raw |
| env.txt | environment | uname + cc version | 192 B | view raw |
| fix.diff | suggested-fix | add mroute_token to X_ipip_input (entry+4 returns) β partial; insufficient alone | 1.3 KB | view raw |
| fix_results.txt | fix-validation | patched module objdump-verified + re-run still panics; root cause of insufficiency | 1.2 KB | view raw |
| VERDICT.md | verdict | full narrative | 5.4 KB | β raw |
DF-2541 β ip_mroute X_ipip_input / pim_input race (NULL-deref / panic)
Verdict: REPRODUCED (panic); fix_status = fix_failed (minimal token fix insufficient)
What the finding claimed
X_ipip_input (sys/net/ip_mroute/ip_mroute.c:1710) and the PIM-REGISTER branch of
pim_input (:3020) read viftable/numvifs/last_encap_vif/reg_vif_num without
holding mroute_token, while del_vif (:873) and X_ip_mrouter_done (:605) mutate exactly
those fields under mroute_token (including bzero() of viftable slots at :909 and
:634). A concurrent teardown can therefore zero a vif between validation and use.
Reachability established on this guest (6.5-DEVELOPMENT #0, X86_64_GENERIC)
pim_inputis DEAD CODE here. It is entirely inside#ifdef PIM(sys/net/ip_mroute/ip_mroute.c:3250#endif /* PIM */). The GENERIC kernel has nooptions PIM(sys/config/X86_64_GENERIC), and theip_mroute.komodule Makefile defines only-DMROUTING, not-DPIM(sys/net/ip_mroute/Makefile). Confirmed at runtime:sysctl net.inet.pimβ unknown oid;nm /boot/kernel/kernel | grep pim_inputβ empty. The PIM-REGISTER NULL-deref at :3230 is unreachable on this guest.X_ipip_inputis reachable via theencap4_input()backward-compat hook (sys/netinet/ip_encap.c:231if (proto == IPPROTO_IPV4 && ipip_input)) once the module iskldload'd (MOD_LOAD setsipip_input = X_ipip_input, :3266). But its first guard (:1716if (!have_encap_tunnel)) returns early unless root has added aVIFF_TUNNELvif (set inadd_vif:785-786).MRT_INITrequiresSOCK_RAW/IPPROTO_IGMPand root (ip_mrouter_init:571-572). So this is a root-configured race β the realistic threat is an admin running mrouted while an attacker floods during MRT_DONE/restart.
Reproduction (root-configured race β panic)
poc.c (run as root): opens the IGMP mrouter socket, MRT_INIT, adds a VIFF_TUNNEL
vif (rmt=10.0.2.99) β have_encap_tunnel=1, primes the last_encap_vif cache by flooding
IP-in-IP packets (outer proto=4, inner dst=multicast), then races MRT_DEL_VIF(0)+re-add
against the packet flood. del_vif bzero()s viftable[0] (v_ifpβ0) while a packet reuses
the stale cached last_encap_vif; the decapsulated multicast packet is then forwarded into
torn-down state.
Observed (unpatched #0, twice): Fatal trap 12: page fault while in kernel mode,
instruction pointer = 0x8:0x0 (NULL function-pointer call), current process = Idle
(netisr context). Reproducible in <2 s of the race loop. See panic.txt, run.log,
run.2.log.
Mechanism (cited hops)
X_ipip_input:1734-1747 β walksviftable/numvifsand cacheslast_encap_src/last_encap_vif` with no token.del_vif:883 acquiresmroute_token; :888-891 resets the cache (only ifvifp == last_encap_vif); :909bzero((caddr_t)vifp, sizeof(*vifp))βv_ifp= 0.X_ipip_input:1763m->m_pkthdr.rcvif = last_encap_vif->v_ifp;(stale cache β NULL), :1765netisr_queue(NETISR_IP, m)β ip_input runs with a NULL/torn-down rcvif β NULL function-pointer call β panic.- Downstream: the decapsulated multicast packet is forwarded by
X_ip_mforward(:1154) /ip_mdq, which accessviftable/numvifswithout the token at entry, so a concurrent teardown also corrupts the forwarding path.
Fix attempt + validation
fix.diff adds lwkt_gettoken(&mroute_token) to X_ipip_input (entry + all 4 return
paths). lwkt_gettoken does serialize cross-CPU on contention
(sys/kern/lwkt_token.c:686-716: _lwkt_trytokref_spin then lwkt_switch()).
Built the patched ip_mroute.ko, objdump-verified lwkt_gettoken at offset 0x14e +
4 lwkt_reltoken in X_ipip_input, installed (sha256 de5fc1β¦ vs original 7a4a68β¦),
reloaded, re-ran the SAME PoC. Result: still panics, identical signature.
Why the minimal fix is insufficient (fix_failed): protecting only X_ipip_input's
v_ifp read does not close the race, because the decapsulated multicast packet is
subsequently forwarded by X_ip_mforward/ip_mdq (:1154+), which read viftable/
numvifs without the token at entry; a concurrent del_vif/X_ip_mrouter_done
bzero()s viftable under the token, so the forwarding path still derefs zeroed vif
state β same NULL-pointer panic.
Concrete next iteration for a complete fix: the teardown paths (del_vif and
X_ip_mrouter_done) must synchronize against in-flight netisr packets before bzero() β
e.g. a netisr drain/barrier (analogous to netisr_wait) on teardown β and/or the
entry of X_ip_mforward/ip_mdq must acquire mroute_token for the full viftable walk.
This is a broader locking change beyond a single-function patch.
PoC changes
- Wrote
poc.cfrom scratch (no prior scaffold existed). Fixedvifi_t(was u_int8_t; the kernel's isu_short, sys/net/ip_mroute/ip_mroute.h:82 β first build returned EINVAL), and used the correctSOCK_RAW/IPPROTO_IGMPmrouter socket required byip_mrouter_init(:571).
Impact
Kernel panic / DoS. Reachable only with root-configured multicast routing (MRT_INIT is privileged) plus concurrent reconfiguration; an unprivileged user cannot trigger it. Not a privilege escalation β no memory-corruption primitive is attacker-shaped here, it is a torn-down-state deref. Medium severity is appropriate (admin-config DoS; pim_input half is dead code on GENERIC).
Fix verification
fix_failedfix_failed. Built patched ip_mroute.ko (objdump-confirmed lwkt_gettoken at X_ipip_input+0x14e plus 4 lwkt_reltoken for 4 return paths), installed (sha de5fc1... vs original 7a4a68...), reloaded, re-ran SAME PoC. STILL PANICS with identical signature (Fatal trap 12, IP=0x0, Idle). Minimal token fix in X_ipip_input insufficient because decapsulated multicast packet is subsequently forwarded downstream by X_ip_mforward/ip_mdq (ip_mroute.c:1154+), which read viftable/numvifs WITHOUT token at entry; concurrent del_vif/X_ip_mrouter_done bzeros viftable under token, so forwarding path still derefs zeroed vif state => same NULL-pointer panic.
baseline (unpatched #0): PoC -> panic 'Fatal trap 12 IP=0x0 Idle'. patched ip_mroute.ko (objdump-verified token added): SAME PoC -> STILL panic 'Fatal trap 12 IP=0x0 Idle' (identical signature). Conclusion: token in X_ipip_input alone does not close race; complete fix needs to protect full X_ip_mforward/ip_mdq entry paths with mroute_token OR drain netisr in del_vif/X_ip_mrouter_done before bzero.
Confirmed kernel references
Detail
Exploit chain
none β not memory corruption; race-induced torn-down-state NULL/func-ptr deref (DoS panic). No attacker-shaped write primitive, no escalation chain.
Evidence (decisive lines)
Fatal trap 12: page fault while in kernel mode / fault virtual address=0x0 / instruction pointer=0x8:0x0 / current process=Idle / kernel: type 12 trap, code=10 / Stopped at 0: (run 1 and run 2 identical). Mechanism: X_ipip_input :1734-1763 (no token) vs del_vif :883,:909 (bzero under token).
PoC changes
Wrote poc.c from scratch. Fixed vifi_t (was u_int8_t; kernel's is u_short, ip_mroute.h:82 β first build returned EINVAL) and used SOCK_RAW/IPPROTO_IGMP mrouter socket required by ip_mrouter_init (:571). fix.diff adds lwkt_gettoken(&mroute_token) to X_ipip_input (entry+4 returns).
Verified recommended fix
Partial: add lwkt_gettoken(&mroute_token) to X_ipip_input so viftable walk + last_encap_vif->v_ifp read is atomic w.r.t. del_vif (fix.diff). Supersedes finding's 'NULL check before deref' framing by targeting cited root cause (missing token). NOTE: this minimal fix alone does NOT close the race β see fix_status. Complete fix needs netisr drain/barrier in del_vif & X_ip_mrouter_done before bzero, OR acquire mroute_token across X_ip_mforward/ip_mdq entry paths.
Verdict
REPRODUCED (root-configured race panic). pim_input is DEAD CODE on this guest: entirely inside #ifdef PIM (ip_mroute.c:3250), and neither X86_64_GENERIC (no 'options PIM') nor ip_mroute.ko Makefile (defines only -DMROUTING, not -DPIM) compile it β sysctl net.inet.pim 'unknown oid', nm shows no pim_input symbol. So PIM-REGISTER NULL-deref half of claim unreachable. X_ipip_input IS reachable via encap4_input (ip_encap.c:231) after kldload, but only after root adds a VIFF_TUNNEL vif (have_encap_tunnel guard, :1716). A root PoC (MRT_INIT + tunnel vif + IP-in-IP flood racing MRT_DEL_VIF) panics deterministically in <2s: 'Fatal trap 12 page fault, IP=0x0 (NULL func-ptr call), current process=Idle' (netisr), reproduced twice. Root cause: X_ipip_input reads viftable/last_encap_vif WITHOUT mroute_token while del_vif bzeros the cached vif under the token; the stale cached last_encap_vif->v_ifp becomes 0 -> rcvif=NULL -> ip_input NULL func-ptr call. Requires root (MRT_INIT privileged) so admin-config DoS, not unpriv->root.
No comments yet.