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
  2026-05-28 19:37 ` Simon Horman
  0 siblings, 1 reply; 2+ messages 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] 2+ messages in thread

* Re: [PATCH net] appletalk: fix use-after-free in atalk_sendmsg due to route lookup race
  2026-05-26  8:57 [PATCH net] appletalk: fix use-after-free in atalk_sendmsg due to route lookup race Zhenghang Xiao
@ 2026-05-28 19:37 ` Simon Horman
  0 siblings, 0 replies; 2+ messages in thread
From: Simon Horman @ 2026-05-28 19:37 UTC (permalink / raw)
  To: kipreyyy; +Cc: 'Simon Horman', davem, edumazet, kuba, pabeni, netdev

From: 'Simon Horman' <horms@kernel.org>

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://sashiko.dev
---
Subject: appletalk: fix use-after-free in atalk_sendmsg due to route lookup race

This patch attempts to fix a use-after-free in atalk_sendmsg() by deferring 
route deletion with kfree_rcu() and caching required route fields under 
rcu_read_lock(). It also takes a reference on the route devices using 
dev_hold() to prevent them from disappearing during the sleeping allocation.

> diff --git a/net/appletalk/ddp.c b/net/appletalk/ddp.c
> index 30a6dc06291c3..2c754378fa088 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);

Simon says: while the issue below is flagged as not being introduced by
            this patch, I think that if it is problem then it prevents
	    this patch from working as intended.

	    So if it is an issue I suggest addressing it in the context of
	    this patch, perhaps by adding another patch to this patch-set.

[Severity: High]
This isn't a bug introduced by this patch, but does the synchronous
dev_put() allow the network device reference count to drop to zero while
RCU readers are active?

While kfree_rcu() defers freeing the route, dev_put(tmp->dev) is still
called synchronously here. If a device is being unregistered and this
synchronous dev_put() removes the last reference, the device's refcount
could drop to zero before the RCU grace period ends.

When a concurrent atalk_sendmsg() holding rcu_read_lock() then calls
dev_hold() on this device, could it increment a zero refcount and cause a
refcount saturation warning or use-after-free?

[ ... ]

> @@ -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);

Simon says: likewise for the issue below.

[Severity: High]
This is a pre-existing issue, but this appears to have the same synchronous
dev_put() problem as atrtr_delete() above.

[ ... ]

> @@ -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);

Simon says: and the issue below too.

[Severity: High]
This isn't a bug introduced by this patch, but are there other callers
of atrtr_find() that are now missing rcu_read_lock() protection?

atrtr_get_dev() calls atrtr_find(), which temporarily takes
atalk_routes_lock and then returns a raw pointer:

net/appletalk/ddp.c:atrtr_get_dev() {
	struct atalk_route *atr = atrtr_find(sa);
	return atr ? atr->dev : NULL;
}

Since atrtr_get_dev() does not hold rcu_read_lock() when dereferencing
atr->dev, and this patch uses kfree_rcu() to defer route deletion, could
a concurrent route deletion free the route before the read occurs?

>  	} 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);

Simon says: on the other hand, this one looks like it is orthogonal to
            this patch and should be considered in the context of possible
	    follow-up.

[Severity: Critical]
This is a pre-existing issue, but is there a data race on atrtr_default.dev
that causes a NULL pointer dereference here?

atrtr_device_down() sets the static atrtr_default.dev locklessly to NULL:

net/appletalk/ddp.c:atrtr_device_down() {
...
	if (atrtr_default.dev == dev)
		atrtr_set_default(NULL);
}

If atrtr_find() locklessly returns &atrtr_default, and atrtr_device_down()
clears the default route between the lookup and dev = rt->dev, could this
read a NULL pointer?

Since dev_hold(dev) does not check for NULL, wouldn't it attempt to
increment the refcount at a NULL offset and cause a kernel crash?

Simon says: While this one seems to warrant further investigation in
            the context of this patch.

[Severity: High]
This isn't a bug introduced by this patch, but does the lack of reference
counting on the default route break the newly added RCU assumptions?

The static atrtr_default route does not hold a reference count on its
network device (no dev_hold() is performed when atrtr_set_default()
sets it). Consequently, when the device is unregistered, its refcount
can drop to zero immediately.

If atalk_sendmsg() retrieves this route and calls dev_hold() inside its
RCU critical section, could it increment a zero refcount? This would trigger
the same refcount saturation and use-after-free issues as the synchronous
dev_put() described earlier.

> +	rt_flags = rt->flags;
> +	rt_gateway = rt->gateway;
>  
>  	hard_header_len = dev->hard_header_len;

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-05-28 19:42 UTC | newest]

Thread overview: 2+ messages (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
2026-05-28 19:37 ` Simon Horman

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