Re: [PATCH net v2 1/1] net/sched: cls_route: fix fastmap use-after-free and preserve netns teardown ordering
From: Jamal Hadi Salim <jhs@mojatatu.com>
Date: 2026-07-27 13:01:56
Also in:
stable
On Fri, Jul 24, 2026 at 3:24 PM Jamal Hadi Salim [off-list ref] wrote:
The route4 classifier maintains a 16-slot fastmap cache that stores raw
struct route4_filter pointers indexed by (id, iif). The reader
(route4_classify) populates this cache via route4_set_fastmap() for every
classified packet that hits a filter. The writer (route4_delete,
route4_change) clears the cache via route4_reset_fastmap() before
RCU-deferred kfree of the filter.
This creates a UAF race:
1. Reader walks the RCU-protected bucket chain, finds filter f
2. Writer unlinks f, calls route4_reset_fastmap(), then tcf_queue_work()
3. Reader calls route4_set_fastmap() and writes f into the cache
*after* the writer's reset, caching a pointer about to be freed
4. After the RCU grace period, kfree(f) executes
5. Next classified packet on the same (id, iif) tuple hits the stale
fastmap entry and reads f->res from freed memory
Reproduced with an mdelay(100) accelerator in route4_set_fastmap() and a
concurrent add/delete stress test (provided by both zdi and Santosh).
Both triggered KASAN slab-use-after-free reports in the route4 fastmap
paths.
Fix:
Move the fastmap flush to the writer side under RTNL, ordered after
synchronize_rcu(). This ensures stale fastmap entries written by
in-flight readers are visible (readers have finished) and no new readers
can find the unlinked filter, so the subsequent route4_reset_fastmap(head)
flushes them safely while head is valid under RTNL.
In route4_destroy() the synchronous __route4_delete_filter() fallback is
retained for the case where tcf_exts_get_net() returns false (netns is
being torn down in cleanup_net()). The trailing synchronize_rcu() before
kfree_rcu(head) drains any in-flight readers first, so the synchronous
free is safe. Removing the fallback and unconditionally deferring via
route4_queue_work() would race with tc_action_net_exit(): cleanup_net()
does not flush the tc_filter_wq workqueue, so the deferred work could run
after the action idrinfo has been freed, causing a use-after-free on
idrinfo->lock in __tcf_action_put().Well... AI reads commits and comments and correlates to code ;-> Both Sashiko's (legitimately) dont like this commit log's accuracy.
Note: This fix will slow down deletes and filter add/replace, since synchronize_rcu() is called under RTNL in route4_delete() and route4_change(). An alternative fix would have been to introduce refcnt to the head struct but that is a much bigger patch. We justify this as a reasonable fix.
[..]
quoted hunk ↗ jump to hunk
diff --git a/net/sched/cls_route.c b/net/sched/cls_route.c index bd6f945bd388..c7d0164c1e9f 100644 --- a/net/sched/cls_route.c +++ b/net/sched/cls_route.c@@ -307,6 +307,19 @@ static void route4_destroy(struct tcf_proto *tp, bool rtnl_held, kfree_rcu(b, rcu); } } + + /* All filters are unlinked; no new reader can find them on the + * chain. Wait for in-flight readers that may still hold a filter + * pointer and have published it into the fastmap after we unlinked. + * Then flush the stale entries while head is still valid under + * RTNL, matching the route4_delete() pattern. This also protects + * the synchronous __route4_delete_filter() path above: when the + * netns is dying, tcf_exts_get_net() returns false and the filter + * is freed directly, but only after all readers have drained. + */
And the above related comment. sashiko-claude is over-reaching with some manufactured UAF and performance comment (Which i address in the commit log). Let me know if i should send v3 making the commit and comment more accurate as well as re-emphasize the performance issue. (with hope next review sashiko wont bring it up). We can ignore the UAF claim made. cheers, jamal