DF-3367 / verdict.json
{ "finding_id": "DF-3367", "status": "reproduced", "reproduced": 1, "impact": "dos", "confidence": "certain", "verdict": "RTM_CHANGE whose new gateway equals the routes own destination takes rt_setgates EADDRNOTAVAIL path (route.c:1193-1205), which deletes the route from the current CPUs tree, while rtsearch_msghandlers temporary reference (route.c:1726-1729) is dropped to zero without a finalizing rtfree: the rtentry, its key chunk and its ifa reference leak permanently on that CPU, and the per-CPU routing tables diverge (CPUs 1..N keep the route). Verified empirically as root with plain route(8): 200 add+change cycles grew the routetbl malloc type from 1.48K/257K to 3.82K/669K in-use allocations with zero routes visible on cpu0, and `route delete -host 10.99.2.1` (a route cpu0 does not have) succeeded via the surviving CPU1..N copies. A subsequent RTM_ADD of the same destination deterministically panics via route.c:763-768 (cpu0 adds, cpu1 EEXIST) \u2014 traced, deliberately not triggered during verification. Privileged trigger only (routing socket write), hence Low severity: robustness/lifetime bug, memory-exhaustion and panic DoS by root or a compromised routing daemon.", "exploit_chain": "root (or compromised routing daemon): route add -host A GW; route change -host A A -> leaked rtentry+ifa on cpu0, table divergence; re-adding A later panics the kernel (rtrequest1_msghandler route.c:763-768). No unprivileged trigger exists (route_output requires privilege for RTM_CHANGE, rtsock.c:738-740).", "evidence": [ "findings/poc/DF-3367/leakcheck2.log \u2014 vmstat -m routetbl BEFORE 1.48K/257K -> AFTER-200-CYCLES 3.82K/669K; routes visible on cpu0: 0; `route delete -host 10.99.2.1` returns rc=0 (route exists only on cpus 1..N = divergence proven)", "findings/poc/DF-3367/leakcheck.log \u2014 first 100-cycle confirmation", "findings/poc/DF-3367/README.md \u2014 full refcount trace route.c:1726-1729 + 1193-1205" ], "kernel_refs": [ "sys/net/route.c:1193-1205", "sys/net/route.c:1726-1729", "sys/net/route.c:813-868", "sys/net/route.c:763-768", "sys/net/rtsock.c:928-936" ], "poc_changes": "no C PoC needed \u2014 plain route(8) add/change cycles reproduce; measurement via vmstat -m (M_RTABLE = routetbl)", "attempts": 2, "guest_uname": "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", "runtime_sec": 95, "guest_dirty": 0, "build_cmd": "(none; route(8))", "run_cmd": "for i in 1..200: route add -host 10.99.2.$i 10.0.2.15 && route change -host 10.99.2.$i 10.99.2.$i ; vmstat -m | grep routetbl", "code_hash": "", "notes": "panic leg (re-ADD after divergence -> rtrequest1_msghandler panic route.c:763-768) traced but not triggered to avoid aborting the concurrent DF-3366 fix-validation kernel build. Cleanup: leftover phantom 10.99.2.x routes on cpus 1..N removed; guest reset planned before returning.", "recommended_fix": "Make rtsearch_msghandler drop its temp ref with RTFREE()-style finalization (route.c:1729), or stop rt_setgates EADDRNOTAVAIL path from mutating the tree and fail the outer RTM_CHANGE atomically on all CPUs." } |