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)
PoC verification
Evidence pack
findings/poc/DF-3378 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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.diffVERDICT.md,manifest.json,verdict.json
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
fixedOne-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
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.
No comments yet.