Unprivileged NULL-pointer dereference via IP_MULTICAST_VIF selecting a PIM REGISTER vif (ip_mdq rt=NULL -> pim_register_send -> rt->mfc_rp)
Summary
IP_MULTICAST_VIF has no privilege check (ip_output.c:1479-1494, reachable from plain UDP sockets), so any local user can pin a multicast send to any live vif - including the PIM register vif. X_ip_mforward's imo fast path then calls ip_mdq(m, ifp, NULL, vifi) with rt=NULL; for a REGISTER vif ip_mdq routes the packet to pim_register_send(..., rt), which dereferences rt->mfc_rp (offset 0x90) whenever mrt_api_config has MRT_MFC_RP. VERIFIED on guest (PIM kernel): unprivileged uid-32767 child sendto -> Fatal trap 12, fault VA 0x90, 'Stopped at pim_register_send+0x1de'. Precondition: kernel with MROUTING+PIM (module builds without PIM) running pimd + rsvpd. The only unprivileged-reachable path in this file (one UDP sendto, no packet injection). Fix validated (rt!=NULL guard): fix kernel runs clean. Fixed-offset read from NULL - DoS ceiling.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3304 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| regvifnull.c | β | 3.9 KB | view raw | |
| ip_mroute_common.h | β | 2.2 KB | view raw | |
| build.sh | β | 94 B | view raw | |
| run.sh | β | 134 B | view raw | |
| build.log | β | 367 B | view raw | |
| run.log | β | 845 B | view raw | |
| panic.txt | β | 667 B | view raw | |
| fix_run.log | β | 350 B | view raw | |
| env.txt | β | 181 B | view raw | |
| VERDICT.md | β | 4.0 KB | β raw | |
| fix.diff | β | 2.7 KB | view raw | |
| verdict.json | β | 3.2 KB | view raw |
DF-3304 VERDICT β REPRODUCED (unprivileged NULL deref β kernel panic)
Bottom line
An unprivileged local user crashes the kernel (Fatal trap 12, NULL dereference) by sending one UDP datagram, whenever the machine is acting as a PIM-SM rendezvous-point router with the advanced MFC API enabled and an RSVP vif registered. Reproduced deterministically on the guest, twice (same signature), from a clean boot.
Chain (all citations sys/net/ip_mroute/ip_mroute.c unless noted)
sys/netinet/ip_output.c:1479-1494βIP_MULTICAST_VIFsetsockopt has no privilege check (legal_vif_num()only), and is reachable from ordinary UDP sockets (sys/netinet/udp_usrreq.c:268).sys/netinet/ip_output.c:407-435β for a locally-originated multicast datagram, withip_mrouterset,rsvp_on != 0(elseimo = NULL), and the destination group not joined on the outgoing ifp, ip_output callsip_mforward(ip, ifp, m, imo).ip_mroute.c:1186-1198β imo fast path:vifi = imo->imo_multicast_vifis valid (register vif installed by the daemon), soreturn ip_mdq(m, ifp, NULL, vifi)β rt = NULL.ip_mroute.c:1468-1474βxmt_vifis the REGISTER vif βpim_register_send(ip, viftable + xmt_vif, m, rt)with rt = NULL.ip_mroute.c:2826-2827βif ((mrt_api_config & MRT_MFC_RP) && (rt->mfc_rp.s_addr != INADDR_ANY))β NULL+0x90 read β panic. (Andpim_register_send_rpwould also readrt->mfc_parent,ip_mroute.c:2950.)
Preconditions (set up by root in the PoC; realistic for a pimd+rsvpd box)
- kernel built with
options MROUTING+options PIM(note: the stockip_mroute.komodule is built WITHOUT PIM βsys/net/ip_mroute/Makefilepasses only-DMROUTINGβ so the module path cannot reach this bug; a PIM kernel is required, which is exactly what a PIM-SM RP runs) - mrouter active:
MRT_INIT+MRT_ADD_VIF(VIFF_REGISTER)(pimd) MRT_API_CONFIG(MRT_MFC_RP)(pimd advanced API)rsvp_onset by an RSVP socketIP_RSVP_VIF_ON(rsvpd)
Reproduction (guest, PIMTEST kernel #0 built from stock sources)
# ./regvifnull (root; drops to uid 32767 for the trigger) MRT_INIT ok MRT_API_CONFIG(MRT_MFC_RP) rc=0 api=0x100 MRT_ADD_VIF(REGISTER) rc=0 IP_RSVP_VIF_ON rc=0 trigger: uid=32767 euid=32767 trigger: IP_MULTICAST_VIF(0) rc=0 <-- no privilege required trigger: sending UDP to 224.9.9.9 (expect panic) trigger: sendto rc=7 errno=0 (no panic?) <-- datagram queued to netisr Fatal trap 12: page fault while in kernel mode fault virtual address = 0x90 <- offsetof(mfc, mfc_rp) Stopped at pim_register_send+0x1de: movl 0x90(%r13),%r10d <- r13 = rt = NULL
Panic is in the netisr context processing the unprivileged child's
datagram. Full log: run.log / panic.txt.
Exploitability
Impact ceiling here is denial of service (NULL deref read, panic). No controllable write. It is nonetheless the only ip_mroute path in this file reachable by a fully unprivileged user without any packet injection β one UDP sendto after the daemon configures the box.
Fix validation (kernel #1, all five fixes applied)
./regvifnull on the patched kernel: full run, child status 0x0,
no panic observed (fix_run.log). Baseline panics on the first trigger.
Recommended fix
pim_register_send(): take the upcall path when rt == NULL (see
fix.diff):
- if ((mrt_api_config & MRT_MFC_RP) &&
+ if (rt != NULL && (mrt_api_config & MRT_MFC_RP) &&
(rt->mfc_rp.s_addr != INADDR_ANY)) {
(Defense in depth: also consider a priv_check for IP_MULTICAST_VIF, as
FreeBSD documents the option as "used by multicast routing daemons only".)
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
fixedRebuilt kernel with fix.diff (rt!=NULL guard): regvifnull completes, child exits 0, no panic (fix_run.log). Baseline panicked deterministically.
fix_run.log; fix_build.log (kernel #1 built 10:37:50, installed 10:38:26, uname #1 10:33:47)
Confirmed kernel references
Detail
Exploit chain
unpriv UDP socket -> setsockopt(IP_MULTICAST_VIF=reg_vif, no priv check) -> sendto(224.9.9.9) -> ip_output (ip_mrouter set, rsvp_on, group not joined) -> X_ip_mforward(imo) -> ip_mdq(rt=NULL) -> pim_register_send -> rt->mfc_rp NULL deref -> panic. Ceiling: kernel panic/DoS only (NULL read, no controlled write).
Evidence (decisive lines)
['run.log: trigger uid=32767, IP_MULTICAST_VIF rc=0, then Fatal trap 12', 'panic.txt: fault virtual address 0x90, Stopped at pim_register_send+0x1de movl 0x90(%r13),%r10d', 'fix_run.log: patched kernel child status 0x0, no panic observed']
PoC changes
Fixed nested / / in header comment; added sys/param.h; trigger child setuid(32767); needed kernel with options MROUTING+options PIM (built PIMTEST config, nativekernel) because ip_mroute.ko is built without PIM.
Verified recommended fix
pim_register_send(): take the upcall branch when rt==NULL: 'if (rt != NULL && (mrt_api_config & MRT_MFC_RP) && ...)'; consider priv_check for IP_MULTICAST_VIF.
Verdict
Unprivileged local user (uid 32767 child) crashes the kernel with one UDP datagram after setsockopt(IP_MULTICAST_VIF) on a plain UDP socket: ip_output keeps imo (rsvp_on!=0), X_ip_mforward's imo fast path calls ip_mdq(m,ifp,NULL,vifi), the REGISTER vif routes it to pim_register_send(...,rt=NULL), which reads rt->mfc_rp at NULL+0x90 when mrt_api_config has MRT_MFC_RP. Fatal trap 12 at 0x90 in pim_register_send+0x1de, reproduced twice from clean boot on a PIM kernel (options MROUTING+PIM; the stock ip_mroute.ko module builds without PIM so module-only systems are not reachable). Fix (rt!=NULL guard) verified on rebuilt kernel #1: trigger runs clean, no panic.
No comments yet.