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)
PoC verification
Evidence pack
findings/poc/DF-3302 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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
inconclusiveFix (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
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.
No comments yet.