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

add_mfc() uses mfc_find() result before taking mroute_token β€” UAF-write race against del_mfc() kfree

Summary

add_mfc() calls mfc_find() unlocked at :976, then takes mroute_token at :986 and writes through the found pointer in update_mfc_params(). A concurrent MRT_DEL_MFC takes the token, unlinks and kfrees the same entry; the adder's update then writes daemon-chosen ttls/flags into freed memory. Guest hammer (512 hash-colliding (S,G) pairs, 100s, two threads) did not land it - window is nanoseconds and objcache kfree does not poison - but the lock-discipline violation is certain from the code; same class as DF-0306. Root-only race (both need the mrouter raw socket): heap write into a freed M_MRTABLE chunk. not_reproduced (honest). Fix: take the token before the lookup and hold it through the update.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3302 Β· 9 files
FileTypeDescriptionSize
mfcrace.c β€” 3.4 KB view raw
ip_mroute_common.h β€” 2.2 KB view raw
build.sh β€” 97 B view raw
run.sh β€” 94 B view raw
build.log β€” 305 B view raw
run.log β€” 144 B view raw
VERDICT.md β€” 1.9 KB ↓ raw
fix.diff β€” 2.7 KB view raw
verdict.json β€” 2.1 KB view raw
VERDICT.md
↓ download raw

DF-3302 VERDICT β€” NOT REPRODUCED (race real, window nanoseconds)

Bottom line

add_mfc() calls mfc_find() at ip_mroute.c:976 before taking mroute_token; the returned rt is used under the token at :986-988 (update_mfc_params(rt, ...) writes rt->mfc_parent, rt->mfc_ttls[], rt->mfc_flags[]). A concurrent MRT_DEL_MFC on another CPU takes the token, unlinks and kfrees the same entry (:1095-1117) between the unlocked lookup and the update β†’ write into freed memory.

Guest run: 512 colliding (S,G) pairs (bucket 54) to lengthen the mfc_find walk, two threads hammering ADD_MFC vs DEL_MFC for 100 s: survived 100 seconds (no INVARIANTS trip / panic). The window between lookup-return and token acquisition is nanoseconds; kfree under DF's objcache does not poison freed chunks, so a silent UAF-write lands only with extreme luck. Race is certain from the code; manifestation not landed β†’ not_reproduced, impact none demonstrated.

Both racing operations are root-only sockopts (raw IGMP socket), so the practical severity is Low regardless.

Fix (in fix.diff; validated kernel builds and boots)

Take the token before the lookup and keep it across the update:

+    lwkt_gettoken(&mroute_token);
     rt = mfc_find(...);
     if (rt) { update_mfc_params(rt, mfccp);
-   lwkt_gettoken(&mroute_token);
-   update_mfc_params(rt, mfccp);
    lwkt_reltoken(&mroute_token); return 0; }

(the redundant second gettoken later in the function becomes a no-op for a recursive token but is removed in fix.diff for clarity).

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

inconclusive
baseline no→ patch + rebuild →patched clean

Fix (token across lookup+update) included in fix.diff; patched kernel built and boots; race itself never reproduced so no differential crash test possible.

fix_build.log
↓ 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

Evidence (decisive lines)

["run.log: 'crafted 512 (S,G) pairs colliding into bucket 54', 'survived 100 seconds'"]

PoC changes

None vs seed (self-authored); widened window with hash-colliding pairs.

Verified recommended fix

Take mroute_token before mfc_find() in add_mfc() and hold it through the update/insert paths.

Verdict

add_mfc() uses the result of an unlocked mfc_find() (ip_mroute.c:976) - token only taken at :986 for update_mfc_params() (:987-988), racing del_mfc's unlink+kfree (:1108-1117) -> write into freed memory. 100s hammer with 512 colliding (S,G) pairs (lengthened mfc_find walks) on two threads: no INVARIANTS trip, no panic - the window between lookup-return and token acquisition is nanoseconds and objcache kfree does not poison. Race is real by inspection; manifestation not landed. Both operations root-only => Low.