Thread (2 messages) 2 messages, 1 author, 3d ago

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
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help