[PATCH net] amt: fix use-after-free in amt_group_work
From: Zhenghang Xiao <hidden>
Date: 2026-05-26 10:58:44
Subsystem:
amt (automatic multicast tunneling), networking drivers, the rest · Maintainers:
Taehee Yoo, Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
amt_group_work() dereferences gnode->tunnel_list and gnode->amt before
acquiring tunnel->lock or entering an RCU read-side critical section.
When amt_del_group() runs concurrently (e.g. from amt_clear_groups()
during interface teardown), it calls cancel_delayed_work() which cannot
stop an already-executing handler, then proceeds to kfree_rcu(gnode).
Under PREEMPT_RCU the grace period can complete while amt_group_work()
is between the container_of() and spin_lock_bh(), freeing gnode and
causing a use-after-free.
Fix by wrapping the early gnode dereferences in rcu_read_lock() to
prevent the grace period from completing while the handler accesses the
freed object. Additionally, change hlist_del_rcu() to hlist_del_init_rcu()
in amt_del_group() so amt_group_work() can detect an already-deleted gnode
via hlist_unhashed() after acquiring the lock, avoiding double-processing.
Fixes: bc54e49c140b ("amt: add multicast(IGMP) report message handler")
Signed-off-by: Zhenghang Xiao <redacted>
---
drivers/net/amt.c | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)
diff --git a/drivers/net/amt.c b/drivers/net/amt.c
index f2f3139e38a5..f937957dbd61 100644
--- a/drivers/net/amt.c
+++ b/drivers/net/amt.c@@ -260,7 +260,7 @@ static void amt_del_group(struct amt_dev *amt, struct amt_group_node *gnode) if (cancel_delayed_work(&gnode->group_timer)) dev_put(amt->dev); - hlist_del_rcu(&gnode->node); + hlist_del_init_rcu(&gnode->node); gnode->tunnel_list->nr_groups--; if (!gnode->v6)
@@ -412,23 +412,31 @@ static void amt_group_work(struct work_struct *work) struct amt_group_node *gnode = container_of(to_delayed_work(work), struct amt_group_node, group_timer); - struct amt_tunnel_list *tunnel = gnode->tunnel_list; - struct amt_dev *amt = gnode->amt; + struct amt_tunnel_list *tunnel; struct amt_source_node *snode; bool delete_group = true; + struct amt_dev *amt; struct hlist_node *t; int i, buckets; + rcu_read_lock(); + tunnel = gnode->tunnel_list; + amt = gnode->amt; buckets = amt->hash_buckets; spin_lock_bh(&tunnel->lock); + if (hlist_unhashed(&gnode->node)) { + spin_unlock_bh(&tunnel->lock); + rcu_read_unlock(); + goto out; + } if (gnode->filter_mode == MCAST_INCLUDE) { /* Not Used */ spin_unlock_bh(&tunnel->lock); + rcu_read_unlock(); goto out; } - rcu_read_lock(); for (i = 0; i < buckets; i++) { hlist_for_each_entry_safe(snode, t, &gnode->sources[i], node) {
--
2.50.1 (Apple Git-155)