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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3304 Β· 12 files
FileTypeDescriptionSize
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
VERDICT.md
↓ download 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)

  1. sys/netinet/ip_output.c:1479-1494 β€” IP_MULTICAST_VIF setsockopt has no privilege check (legal_vif_num() only), and is reachable from ordinary UDP sockets (sys/netinet/udp_usrreq.c:268).
  2. sys/netinet/ip_output.c:407-435 β€” for a locally-originated multicast datagram, with ip_mrouter set, rsvp_on != 0 (else imo = NULL), and the destination group not joined on the outgoing ifp, ip_output calls ip_mforward(ip, ifp, m, imo).
  3. ip_mroute.c:1186-1198 β€” imo fast path: vifi = imo->imo_multicast_vif is valid (register vif installed by the daemon), so return ip_mdq(m, ifp, NULL, vifi) β€” rt = NULL.
  4. ip_mroute.c:1468-1474 β€” xmt_vif is the REGISTER vif β†’ pim_register_send(ip, viftable + xmt_vif, m, rt) with rt = NULL.
  5. ip_mroute.c:2826-2827 β€” if ((mrt_api_config & MRT_MFC_RP) && (rt->mfc_rp.s_addr != INADDR_ANY)) β†’ NULL+0x90 read β†’ panic. (And pim_register_send_rp would also read rt->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 stock ip_mroute.ko module is built WITHOUT PIM β€” sys/net/ip_mroute/Makefile passes 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_on set by an RSVP socket IP_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.

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

fixed
baseline reproduced→ patch + rebuild →patched clean

Rebuilt 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)
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Thu Sep 10 10:33:47 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/PIMTEST x86_64

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.