ip_setmoptions IP_ADD_MEMBERSHIP: route reference dropped before rt_ifp is read β use-after-release race and permanent rtentry leak when the lookup held the last reference
Summary
In the no-interface/no-index ADD_MEMBERSHIP path, ip_setmoptions does 'rt = rtlookup(...); --rt->rt_refcnt; ifp = rt->rt_ifp;'. rtlookup returns the rtentry with a reference held, and the correct release idiom is RTFREE(). The bare '--' (a) leaks the rtentry permanently when our reference was the last (e.g. a fresh protocol-cloned route, which RTL_DOCLONE creates refcounted), and (b) dereferences rt->rt_ifp AFTER giving up the reference, so a concurrent RTFREE from another CPU can free the rtentry inside the window and in_addmulti() then runs on a garbage ifp. Unpriv user racing route-table churn during IP_ADD_MEMBERSHIP: UAF read -> garbage interface pointer (tiny window, Low/likely); with cloning routes, repeated ADD_MEMBERSHIP leaks cloned rtentries (kmem exhaustion). Analysis-only (no pack). Fix: copy rt_ifp first, then RTFREE(rt). [VERIFIED pass2: about 370k join attempts raced against route churn no hit - two-instruction window line-confirmed; capture-ifp-before-rtfree fix compile-validated]
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3371 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 973 B | β raw | |
| VERDICT.md | β | 2.1 KB | β raw | |
| manifest.json | β | 666 B | view raw | |
| race_mreq.c | β | 3.5 KB | view raw | |
| verdict.json | β | 1.6 KB | view raw |
DF-3371 -- verification pack
Method: Code-line analysis + attempted live reproduction Status: NOT REPRODUCED | impact: none | confidence: likely
What this is
IP_ADD_MEMBERSHIP with imr_address=INADDR_ANY does --rt->rt_refcnt then reads rt->rt_ifp (ip_output.c:1655-1659): a manual decrement + use-after-drop - if a concurrent RTM_DELETE freed the entry in between, both the decrement and the read touch freed memory. Stress attempted: ~370k joins raced against route(8) add/delete churn of 239.1/16 (and an in-process RTM socket writer; its RTM_DELETE formatting hit ESRCH, so the external route(8) churn carried the delete side). Window is 2 instructions - not hit; mechanism line-confirmed.
Build
cc -O -o <prog> <prog>.c (on the guest)
Run
25 (root)
(success criterion: see VERDICT.md)
Evidence
VERDICT.md run logs: joins=159670/173573/216187 across three runs, no crash
Kernel references: sys/netinet/ip_output.c:1655-1659
DF-3371 -- VERDICT
NOT REPRODUCED (impact=none, confidence=likely) Guest: DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64
Summary
IP_ADD_MEMBERSHIP with imr_address=INADDR_ANY does --rt->rt_refcnt then reads rt->rt_ifp (ip_output.c:1655-1659): a manual decrement + use-after-drop - if a concurrent RTM_DELETE freed the entry in between, both the decrement and the read touch freed memory. Stress attempted: ~370k joins raced against route(8) add/delete churn of 239.1/16 (and an in-process RTM socket writer; its RTM_DELETE formatting hit ESRCH, so the external route(8) churn carried the delete side). Window is 2 instructions - not hit; mechanism line-confirmed.
How it was verified
- Method: Code-line analysis + attempted live reproduction
- Kernel references: sys/netinet/ip_output.c:1655-1659
- Evidence: VERDICT.md run logs: joins=159670/173573/216187 across three runs, no crash
- Reachability: local-unpriv where noted in the summary; root-config for tun/lagg/carp/ipfw3/gre items; remote-facing for the packet parsers (wlan harnesses model the RX/TX path verbatim because the guest has no 802.11 hardware; precedent DF-0393/DF-0594/DF-0621/DF-0350).
- Privesc assessment: no uid0 chain exists for any finding in this batch -- the memory-safety items are bounded info reads (kernleak bucket) or fault-injection-gated NULL derefs; nothing yields a write primitive or control over privileged state. The one panic-class live item (DF-3414) is root-only (SIOCIFDESTROY).
Fix
fix_status: not_testable -- Fix: rtfree(rt) AFTER capturing ifp (two-line reorder); needs fault injection to A/B.
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.
(No fix.diff for this item: documented recommendation only - see Fix above.)
Fix verification
not_testableFix: rtfree(rt) AFTER capturing ifp (two-line reorder); needs fault injection to A/B.
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md run logs: joins=159670/173573/216187 across three runs, no crash
Verdict
IP_ADD_MEMBERSHIP with imr_address=INADDR_ANY does --rt->rt_refcnt then reads rt->rt_ifp (ip_output.c:1655-1659): a manual decrement + use-after-drop - if a concurrent RTM_DELETE freed the entry in between, both the decrement and the read touch freed memory. Stress attempted: ~370k joins raced against route(8) add/delete churn of 239.1/16 (and an in-process RTM socket writer; its RTM_DELETE formatting hit ESRCH, so the external route(8) churn carried the delete side). Window is 2 instructions - not hit; mechanism line-confirmed.
No comments yet.