DORMANTno replies

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