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

mld6_stop_listening mutates the shared MLD link-local templates without mld6_token β€” torn if_index embedding mis-addresses MLD DONE and mis-classifies the all-nodes comparison

Summary

mld6_stop_listening writes the shared globals mld6_all_nodes_linklocal/mld6_all_routers_linklocal (s6_addr16[1] = htons(if_index)) and calls mld6_sendpkt without taking mld6_token, while every other path touching those globals holds it (start_listening, mld6_input including the rewrites, fasttimeo). On SMP, an unprivileged group-leave on interface A races query/timeout processing on interface B: the reader picks up a torn embedding, the QUERY walk's ff02::1 comparison mis-classifies, and the DONE is addressed ff02::2 with a foreign interface id so routers drop it - lost leave notification, group traffic persists for the robustness interval. No memory-safety impact (fixed-size globals) - hardening/consistency. Source-level proof only (Info; Phase V skipped per contract); fix diff authored+validated in pack. Fix: wrap the body in mld6_token, mirroring start_listening.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3451 Β· 5 files
FileTypeDescriptionSize
README.md β€” 2.1 KB ↓ raw
VERDICT.md β€” 1.1 KB ↓ raw
fix.diff β€” 823 B view raw
verdict.json β€” 2.0 KB view raw
manifest.json β€” 608 B view raw

DF-3451 β€” mld6_stop_listening mutates shared MLD link-local templates without mld6_token

Severity: Info (hardening / locking-consistency). No runtime PoC required per contract (Info findings are skipped in Phase V); this pack documents the source-level proof and the fix.

Defect

sys/netinet6/mld6.c:163-176 β€” mld6_stop_listening() writes the two shared global link-local templates:

mld6_all_nodes_linklocal.s6_addr16[1]   = htons(in6m->in6m_ifp->if_index);
mld6_all_routers_linklocal.s6_addr16[1] = htons(in6m->in6m_ifp->if_index);

and then (conditionally) calls mld6_sendpkt(in6m, MLD_LISTENER_DONE, &mld6_all_routers_linklocal) β€” all without holding mld6_token.

Every other path that touches these globals holds the token: - mld6_start_listening() mld6.c:146-160 (lwkt_gettoken(&mld6_token)), - mld6_input() mld6.c:201-369 (token held across the whole handler, incl. the :269-270 / :327-328 template rewrites), - mld6_fasttimeo() mld6.c:382-403.

Effect

On an SMP system, in6_delmulti() (leaving a group, e.g. unprivileged IPV6_DROP_MEMBERSHIP on interface A) races mld6_input()/fasttimeo() on another CPU (interface B): the reader picks up a half-updated template β€” specifically s6_addr16[1] embedding the other interface's if_index β€” and

  1. the QUERY-path comparisons at mld6.c:292-293 test membership records against ff02::1 with a foreign if_index embedded (mis-classification),
  2. the DONE being sent is addressed to ff02::2 with the wrong interface id (routers ignore it β‡’ lost leave β‡’ group traffic keeps flowing to the segment for the MLD listener-robustness interval).

No memory-safety violation (the globals are plain fixed-size structs), hence Info; but it is a provable divergence from the file's own locking discipline and the same family of shared-state races that produced DF-0691/DF-0692.

Fix

fix.diff β€” wrap the whole body of mld6_stop_listening() in lwkt_gettoken(&mld6_token) / lwkt_reltoken(&mld6_token), mirroring mld6_start_listening() (no new lock order: token-across-mld6_sendpkt is exactly what start_listening already does).

VERDICT.md
↓ download raw

DF-3451 VERDICT

Status: untested (Info-severity finding - Phase V intentionally skipped per contract). Source-level proof complete; fix authored and diff-validated.

The defect, effect analysis, and the fix are in README.md. Summary: mld6_stop_listening (mld6.c:163-176) is the only MLD path mutating the shared mld6_all_{nodes,routers}_linklocal templates and driving mld6_sendpkt without mld6_token. Impact ceiling: lost/mis-addressed MLD DONE notifications and mis-classified all-nodes comparisons under SMP concurrency (interface leave on one CPU racing query/timeout processing on another). No memory unsafety. fix.diff adds the token, mirroring start_listening (mld6.c:146).

Not re-reported (same root cause as DF-0691, covered by its fix): the MLD_LISTENER_REPORT path's post-lookup in6m write at mld6.c:354-357.

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

not_testable
↓ fix.diffper-fix-DF-3451

Confirmed kernel references

Detail

Evidence (decisive lines)

['README.md (source citations mld6.c:146-160 vs :163-176 vs :201/:269/:327/:382)', 'fix.diff']

PoC changes

none - no runnable PoC produced for an Info finding per contract

Verified recommended fix

Wrap mld6_stop_listening's body in lwkt_gettoken(&mld6_token)/lwkt_reltoken(&mld6_token), mirroring mld6_start_listening.

Verdict

Info-severity locking-consistency defect, source-proven, deliberately not exercised at runtime per contract (Info findings skip Phase V): mld6_stop_listening (sys/netinet6/mld6.c:163-176) writes the shared globals mld6_all_nodes_linklocal / mld6_all_routers_linklocal and sends the MLD DONE without mld6_token, while every other path touching those globals (start_listening mld6.c:146, mld6_input mld6.c:201, fasttimeo mld6.c:382) holds it. Concurrent leave vs query/timeout on an SMP box yields torn if_index embeddings: mis-classified ff02::1 comparisons in the QUERY walk and DONE packets addressed with a foreign interface id (lost leave notification, group traffic persists for the robustness interval). No memory-safety impact - fixed-size globals - hence Info. fix.diff mirrors start_listening's token-across-send pattern; no new lock order introduced.