Netdev List
 help / color / mirror / Atom feed
* [PATCH net] appletalk: fix use-after-free in atalk_sendmsg due to route lookup race
@ 2026-05-26  8:57 Zhenghang Xiao
  0 siblings, 0 replies; only message in thread
From: Zhenghang Xiao @ 2026-05-26  8:57 UTC (permalink / raw)
  To: davem, edumazet, kuba, pabeni; +Cc: netdev, Zhenghang Xiao

atrtr_find() returns a raw pointer after releasing atalk_routes_lock.
In atalk_sendmsg(), both the primary route (rt) and loopback route
(rt_lo) pointers are held across sock_alloc_send_skb() which can sleep.
Concurrent atrtr_delete() or atrtr_device_down() can kfree() the route
during this window, causing UAF when rt->flags, rt->gateway, or
rt_lo->dev are accessed.

Fix by deferring route frees with kfree_rcu() and caching all needed
route fields under rcu_read_lock() before the sleeping allocation.
Take dev_hold() on both route devices to prevent disappearance after
the route's dev_put() in the delete path.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Zhenghang Xiao <kipreyyy@gmail.com>
---
 include/linux/atalk.h |  1 +
 net/appletalk/ddp.c   | 52 +++++++++++++++++++++++++++----------------
 2 files changed, 34 insertions(+), 19 deletions(-)

diff --git a/include/linux/atalk.h b/include/linux/atalk.h
index a55bfc6567d0..932cb72379c2 100644
--- a/include/linux/atalk.h
+++ b/include/linux/atalk.h
@@ -12,6 +12,7 @@ struct atalk_route {
 	struct atalk_addr  gateway;
 	int		   flags;
 	struct atalk_route *next;
+	struct rcu_head	   rcu;
 };
 
 /**
diff --git a/net/appletalk/ddp.c b/net/appletalk/ddp.c
index 30a6dc06291c..2c754378fa08 100644
--- a/net/appletalk/ddp.c
+++ b/net/appletalk/ddp.c
@@ -603,7 +603,7 @@ static int atrtr_delete(struct atalk_addr *addr)
 		     tmp->target.s_node == addr->s_node)) {
 			*r = tmp->next;
 			dev_put(tmp->dev);
-			kfree(tmp);
+			kfree_rcu(tmp, rcu);
 			goto out;
 		}
 		r = &tmp->next;
@@ -628,7 +628,7 @@ static void atrtr_device_down(struct net_device *dev)
 		if (tmp->dev == dev) {
 			*r = tmp->next;
 			dev_put(dev);
-			kfree(tmp);
+			kfree_rcu(tmp, rcu);
 		} else
 			r = &tmp->next;
 	}
@@ -1553,10 +1553,13 @@ static int atalk_sendmsg(struct socket *sock, struct msghdr *msg, size_t len)
 	int loopback = 0;
 	struct sockaddr_at local_satalk, gsat;
 	struct sk_buff *skb;
-	struct net_device *dev;
+	struct net_device *dev = NULL;
+	struct net_device *dev_lo = NULL;
 	struct ddpehdr *ddp;
 	int size, hard_header_len;
 	struct atalk_route *rt, *rt_lo = NULL;
+	int rt_flags;
+	struct atalk_addr rt_gateway;
 	int err;
 
 	if (flags & ~(MSG_DONTWAIT|MSG_CMSG_COMPAT))
@@ -1600,6 +1603,7 @@ static int atalk_sendmsg(struct socket *sock, struct msghdr *msg, size_t len)
 	/* For headers */
 	size = sizeof(struct ddpehdr) + len + ddp_dl->header_length;
 
+	rcu_read_lock();
 	if (usat->sat_addr.s_net || usat->sat_addr.s_node == ATADDR_ANYNODE) {
 		rt = atrtr_find(&usat->sat_addr);
 	} else {
@@ -1610,29 +1614,38 @@ static int atalk_sendmsg(struct socket *sock, struct msghdr *msg, size_t len)
 
 		rt = atrtr_find(&at_hint);
 	}
-	err = -ENETUNREACH;
-	if (!rt)
+	if (!rt) {
+		rcu_read_unlock();
+		err = -ENETUNREACH;
 		goto out;
+	}
 
 	dev = rt->dev;
-
-	net_dbg_ratelimited("SK %p: Size needed %d, device %s\n",
-			sk, size, dev->name);
+	dev_hold(dev);
+	rt_flags = rt->flags;
+	rt_gateway = rt->gateway;
 
 	hard_header_len = dev->hard_header_len;
 	/* Leave room for loopback hardware header if necessary */
 	if (usat->sat_addr.s_node == ATADDR_BCAST &&
-	    (dev->flags & IFF_LOOPBACK || !(rt->flags & RTF_GATEWAY))) {
+	    (dev->flags & IFF_LOOPBACK || !(rt_flags & RTF_GATEWAY))) {
 		struct atalk_addr at_lo;
 
 		at_lo.s_node = 0;
 		at_lo.s_net  = 0;
 
 		rt_lo = atrtr_find(&at_lo);
-
-		if (rt_lo && rt_lo->dev->hard_header_len > hard_header_len)
-			hard_header_len = rt_lo->dev->hard_header_len;
+		if (rt_lo) {
+			dev_lo = rt_lo->dev;
+			dev_hold(dev_lo);
+			if (dev_lo->hard_header_len > hard_header_len)
+				hard_header_len = dev_lo->hard_header_len;
+		}
 	}
+	rcu_read_unlock();
+
+	net_dbg_ratelimited("SK %p: Size needed %d, device %s\n",
+			sk, size, dev->name);
 
 	size += hard_header_len;
 	release_sock(sk);
@@ -1675,7 +1688,7 @@ static int atalk_sendmsg(struct socket *sock, struct msghdr *msg, size_t len)
 	 * to group we are in)
 	 */
 	if (ddp->deh_dnode == ATADDR_BCAST &&
-	    !(rt->flags & RTF_GATEWAY) && !(dev->flags & IFF_LOOPBACK)) {
+	    !(rt_flags & RTF_GATEWAY) && !(dev->flags & IFF_LOOPBACK)) {
 		struct sk_buff *skb2 = skb_copy(skb, GFP_KERNEL);
 
 		if (skb2) {
@@ -1693,19 +1706,18 @@ static int atalk_sendmsg(struct socket *sock, struct msghdr *msg, size_t len)
 		/* loop back */
 		skb_orphan(skb);
 		if (ddp->deh_dnode == ATADDR_BCAST) {
-			if (!rt_lo) {
+			if (!dev_lo) {
 				kfree_skb(skb);
 				err = -ENETUNREACH;
 				goto out;
 			}
-			dev = rt_lo->dev;
-			skb->dev = dev;
+			skb->dev = dev_lo;
 		}
-		ddp_dl->request(ddp_dl, skb, dev->dev_addr);
+		ddp_dl->request(ddp_dl, skb, skb->dev->dev_addr);
 	} else {
 		net_dbg_ratelimited("SK %p: send out.\n", sk);
-		if (rt->flags & RTF_GATEWAY) {
-		    gsat.sat_addr = rt->gateway;
+		if (rt_flags & RTF_GATEWAY) {
+		    gsat.sat_addr = rt_gateway;
 		    usat = &gsat;
 		}
 
@@ -1718,6 +1730,8 @@ static int atalk_sendmsg(struct socket *sock, struct msghdr *msg, size_t len)
 
 out:
 	release_sock(sk);
+	dev_put(dev);
+	dev_put(dev_lo);
 	return err ? : len;
 }
 
-- 
2.50.1 (Apple Git-155)


^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2026-05-26  8:57 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-26  8:57 [PATCH net] appletalk: fix use-after-free in atalk_sendmsg due to route lookup race Zhenghang Xiao

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox