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

MRT6_INIT length gate uses sizeof(int *) (8) instead of sizeof(int) (4): IPv6 multicast routing cannot be enabled by any conforming daemon on 64-bit DragonFly

Summary

ip6_mrouter_init() validates the MRT6_INIT option mbuf with m->m_len != sizeof(int*) (8) - so the documented KAME/pim6sd API ('int v = 1; setsockopt(..., MRT6_INIT, &v, sizeof(v))') always fails ENOPROTOOPT on amd64. DETERMINISTICALLY VERIFIED on stock guest (errno 42 for 4 bytes, success for 8). The v4 twin was modernized to sopt_to_kbuf; v6 never was. Root-only subsystem made entirely unusable (functional availability bug, no memory-safety impact); also a triage gate: it proves the file's other attack-surface paths (DF-0423/24/26 family) are unreachable via stock daemons on amd64 while custom 8-byte callers (and any fixed kernel) reach them. Fix validated (m->m_len < sizeof(int)): 4-byte call returns rc=0, follow-on 8-byte correctly EADDRINUSE.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3378 Β· 13 files
FileTypeDescriptionSize
README.md β€” 1.4 KB ↓ raw
VERDICT.md β€” 2.4 KB ↓ raw
trigger_mrt6_init.c β€” 1.7 KB view raw
build.sh β€” 111 B view raw
run.sh β€” 104 B view raw
build.log β€” 159 B view raw
run.log β€” 144 B view raw
fix_run.log β€” 137 B view raw
fix_build.log β€” 108 B view raw
env.txt β€” 261 B view raw
fix.diff β€” 285 B view raw
manifest.json β€” 1.0 KB view raw
verdict.json β€” 2.8 KB view raw

DF-3378 β€” MRT6_INIT rejects the standard 4-byte KAME API argument on 64-bit (sizeof(int *) vs sizeof(int))

What

ip6_mrouter_init() validates the MRT6_INIT option with

if (!m || (m->m_len != sizeof(int *)))   /* sys/netinet6/ip6_mroute.c:426 */
        return ENOPROTOOPT;

m is built by soopt_getm()/soopt_to_mbuf() so m->m_len equals the setsockopt value size. Every conforming routing daemon (pim6sd/pim6dd and the documented KAME API) passes int v = 1; setsockopt(s, IPPROTO_IPV6, MRT6_INIT, &v, sizeof(v)) β€” 4 bytes. On amd64 sizeof(int *) == 8, so 4 != 8 β†’ ENOPROTOOPT: IPv6 multicast routing cannot be enabled at all on 64-bit DragonFly by any stock daemon. The subsystem becomes reachable only for a caller that happens to pass 8 bytes (first int = 1).

Side effect for triage: this gate also blocked the whole file's attack surface (DF-0423/0424-style paths) behind an API no stock daemon uses β€” while custom / 8-byte callers get full access.

Reproduce (deterministic, root, stock kernel #0)

# cc -O -Wall -o /tmp/df3378 /root/poc/trigger_mrt6_init.c && /tmp/df3378
MRT6_INIT(4-byte int arg): rc=-1 errno=42 (Protocol not available)   <- the bug
MRT6_INIT(8-byte arg):     rc=0  errno=0 (Success)

Fixed kernel (m->m_len < sizeof(int)): first line becomes rc=0.

Files

  • trigger_mrt6_init.c, build.sh, run.sh, run.log, fix.diff
  • VERDICT.md, manifest.json, verdict.json
VERDICT.md
↓ download raw

DF-3378 VERDICT β€” REPRODUCED (deterministic; MRT6_INIT gate uses sizeof(int*) so IPv6 multicast routing cannot be enabled by conforming daemons on amd64)

Bottom line

ip6_mrouter_init() gates MRT6_INIT on m->m_len != sizeof(int *) (sys/netinet6/ip6_mroute.c:426). m comes from soopt_getm() / soopt_to_mbuf() (sys/kern/uipc_socket.c:2438-2492), which sets m->m_len = sopt_valsize. The documented KAME/pim6sd API passes a 4-byte int β€” on amd64 sizeof(int *) == 8 so every conforming daemon gets ENOPROTOOPT and IPv6 multicast routing cannot be enabled at all. Only a caller passing 8 bytes (first int = 1) initializes the subsystem. Verified deterministically on the guest stock kernel #0 and after the one-line fix on kernel #1. Functional availability bug of a root-only subsystem (no memory safety impact); also a reachability gate for every other path in the file (stock daemons never reach them; custom 8-byte callers do).

Reproduction (stock kernel #0, root)

$ cc -O -Wall -o /tmp/df3378 trigger_mrt6_init.c && /tmp/df3378
MRT6_INIT(4-byte int arg): rc=-1 errno=42 (Protocol not available)   <- bug
MRT6_INIT(8-byte arg):     rc=0  errno=0 (Success)

(run.log). errno 42 = ENOPROTOOPT exactly at ip6_mroute.c:426-427.

Fix validation (kernel #1)

m->m_len != sizeof(int *) β†’ m->m_len < sizeof(int) (fix.diff; same line FreeBSD uses). On kernel #1:

MRT6_INIT(4-byte int arg): rc=0 errno=0 (Success)          <- fixed
MRT6_INIT(8-byte arg):     rc=-1 errno=48 (Address already in use)

(fix_run.log). The second line's EADDRINUSE is correct behavior: the first call now succeeds and claims ip6_mrouter, so the second INIT is refused β€” proof the gate opened. fix_status=fixed.

Severity rationale

Low: requires root (raw ICMPv6 socket) to even call; impact is "feature unusable / wrong errno", no crash, no leak, no bypass. Triage value is high though: it proves all mroute6 attack-surface findings (incl. DF-0423) are unreachable via stock daemons on amd64, while custom callers (or a fixed kernel) reach them.

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

One-line fix validated on the same rebuilt kernel as DF-3377: MRT6_INIT(4-byte int) now returns rc=0; the follow-on 8-byte call correctly returns EADDRINUSE because the first call now claims ip6_mrouter.

fix_run.log
↓ fix.diffDragonFly 6.5-DEVELOPMENT #1: Fri Sep 11 20:41:14 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC

Confirmed kernel references

Detail

Evidence (decisive lines)

['run.log (stock #0: 4-byte errno=42, 8-byte rc=0)', 'fix_run.log (kernel #1: 4-byte rc=0; 8-byte EADDRINUSE because the first call now succeeds - correct)', 'VERDICT.md']

PoC changes

Needed sys/param.h include for NBBY (ip6_mroute.h uses it); option level IPPROTO_IPV6 on the IPPROTO_ICMPV6 raw socket (rip6_ctloutput requires level IPPROTO_IPV6 for MRT6_*).

Verified recommended fix

Change the length gate to m->m_len < sizeof(int) (accept the 4-byte KAME API arg; ideally port the v4 file's sopt_to_kbuf pattern to all MRT6_* handlers).

Verdict

ip6_mrouter_init() gates MRT6_INIT on m->m_len != sizeof(int ) (ip6_mroute.c:426); soopt_getm/soopt_to_mbuf set m_len = sopt_valsize, so the documented KAME API (int arg, 4 bytes) always fails ENOPROTOOPT on amd64 where sizeof(int)=8. Deterministically verified on stock kernel #0: 4-byte -> errno 42, 8-byte -> rc=0. Consequence: no conforming daemon (pim6sd/pim6dd) can enable IPv6 multicast routing on 64-bit DragonFly; the subsystem's entire attack surface is reachable only by custom 8-byte callers. Functional availability bug of a root-only feature - no memory-safety impact (impact=none for corruption purposes; availability/correctness defect). One-line fix (m->m_len < sizeof(int), matching FreeBSD) validated on rebuilt kernel #1: 4-byte MRT6_INIT returns rc=0.