public inbox for netdev@vger.kernel.org
 help / color / mirror / Atom feed
From: Hillf Danton <hdanton@sina.com>
To: Deepanshu Kartikey <kartikey406@gmail.com>
Cc: edumazet@google.com, netdev@vger.kernel.org,
	linux-kernel@vger.kernel.org,
	syzbot+f50072212ab792c86925@syzkaller.appspotmail.com
Subject: Re: [PATCH v2] atm: lec: fix use-after-free in sock_def_readable()
Date: Tue, 10 Mar 2026 11:18:04 +0800	[thread overview]
Message-ID: <20260310031805.752-1-hdanton@sina.com> (raw)
In-Reply-To: <20260309155908.508768-1-kartikey406@gmail.com>

On Mon,  9 Mar 2026 21:29:08 +0530 Deepanshu Kartikey wrote:
> A race condition exists between lec_atm_close() setting priv->lecd
> to NULL and concurrent access to priv->lecd in send_to_lecd(),
> lec_handle_bridge(), and lec_atm_send(). When the socket is freed
> via RCU while another thread is still using it, a use-after-free
> occurs in sock_def_readable() when accessing the socket's wait queue.
> 
> The root cause is that lec_atm_close() clears priv->lecd without
> any synchronization, while callers dereference priv->lecd without
> any protection against concurrent teardown.
> 
> Fix this by converting priv->lecd to an RCU-protected pointer:
> - Mark priv->lecd as __rcu in lec.h
> - Use rcu_assign_pointer() in lec_atm_close() and lecd_attach()
>   for safe pointer assignment
> - Use rcu_access_pointer() for NULL checks that do not dereference
>   the pointer in lec_start_xmit(), lec_push(), send_to_lecd() and
>   lecd_attach()
> - Use rcu_read_lock/rcu_dereference/rcu_read_unlock in send_to_lecd(),
>   lec_handle_bridge() and lec_atm_send() to safely access lecd
> - Use rcu_assign_pointer() followed by synchronize_rcu() in
>   lec_atm_close() to ensure all readers have completed before
>   proceeding. This is safe since lec_atm_close() is called from
>   vcc_release() which holds lock_sock(), a sleeping lock.
> - Remove the manual sk_receive_queue drain from lec_atm_close()
>   since vcc_destroy_socket() already drains it after lec_atm_close()
>   returns.
> 
> v2: Switch from spinlock + sock_hold/put approach to RCU to properly
>     fix the race. The v1 spinlock approach had two issues pointed out
>     by Eric Dumazet:
>     1. priv->lecd was still accessed directly after releasing the
>        lock instead of using a local copy.
>     2. The spinlock did not prevent packets being queued after
>        lec_atm_close() drains sk_receive_queue since timer and
>        workqueue paths bypass netif_stop_queue().
> 
> Note: Syzbot patch testing was attempted but the test VM terminated
>     unexpectedly with "Connection to localhost closed by remote host",
>     likely due to a QEMU AHCI emulation issue unrelated to this fix.
>     Compile testing with "make W=1 net/atm/lec.o" passes cleanly.
> 
> Reported-by: syzbot+f50072212ab792c86925@syzkaller.appspotmail.com
> Closes: https://syzkaller.appspot.com/bug?extid=f50072212ab792c86925
> Link: https://lore.kernel.org/all/20260309093614.502094-1-kartikey406@gmail.com/T/ [v1]
> Signed-off-by: Deepanshu Kartikey <kartikey406@gmail.com>
> ---
>  net/atm/lec.c | 70 +++++++++++++++++++++++++++++++++------------------
>  net/atm/lec.h |  2 +-
>  2 files changed, 46 insertions(+), 26 deletions(-)
> 
> diff --git a/net/atm/lec.c b/net/atm/lec.c
> index fb93c6e1c329..32adf28663a0 100644
> --- a/net/atm/lec.c
> +++ b/net/atm/lec.c
> @@ -154,10 +154,19 @@ static void lec_handle_bridge(struct sk_buff *skb, struct net_device *dev)
>  					/* 0x01 is topology change */
>  
>  		priv = netdev_priv(dev);
> -		atm_force_charge(priv->lecd, skb2->truesize);
> -		sk = sk_atm(priv->lecd);
> -		skb_queue_tail(&sk->sk_receive_queue, skb2);
> -		sk->sk_data_ready(sk);
> +		struct atm_vcc *vcc;
> +
> +		rcu_read_lock();
> +		vcc = rcu_dereference(priv->lecd);
> +		if (vcc) {
> +			atm_force_charge(vcc, skb2->truesize);
> +			sk = sk_atm(vcc);
> +			skb_queue_tail(&sk->sk_receive_queue, skb2);
> +			sk->sk_data_ready(sk);
> +		} else {
> +			dev_kfree_skb(skb2);
> +		}
> +		rcu_read_unlock();
>  	}
>  }
>  #endif /* IS_ENABLED(CONFIG_BRIDGE) */
> @@ -216,7 +225,7 @@ static netdev_tx_t lec_start_xmit(struct sk_buff *skb,
>  	int is_rdesc;
>  
>  	pr_debug("called\n");
> -	if (!priv->lecd) {
> +	if (!rcu_access_pointer(priv->lecd)) {
>  		pr_info("%s:No lecd attached\n", dev->name);
>  		dev->stats.tx_errors++;
>  		netif_stop_queue(dev);
> @@ -449,10 +459,19 @@ static int lec_atm_send(struct atm_vcc *vcc, struct sk_buff *skb)
>  				break;
>  			skb2->len = sizeof(struct atmlec_msg);
>  			skb_copy_to_linear_data(skb2, mesg, sizeof(*mesg));
> -			atm_force_charge(priv->lecd, skb2->truesize);
> -			sk = sk_atm(priv->lecd);
> -			skb_queue_tail(&sk->sk_receive_queue, skb2);
> -			sk->sk_data_ready(sk);
> +			struct atm_vcc *vcc;
> +
> +			rcu_read_lock();
> +			vcc = rcu_dereference(priv->lecd);
> +			if (vcc) {
> +				atm_force_charge(vcc, skb2->truesize);
> +				sk = sk_atm(vcc);
> +				skb_queue_tail(&sk->sk_receive_queue, skb2);
> +				sk->sk_data_ready(sk);
> +			} else {
> +				dev_kfree_skb(skb2);
> +			}
> +			rcu_read_unlock();
>  		}
>  	}
>  #endif /* IS_ENABLED(CONFIG_BRIDGE) */
> @@ -468,23 +486,16 @@ static int lec_atm_send(struct atm_vcc *vcc, struct sk_buff *skb)
>  
>  static void lec_atm_close(struct atm_vcc *vcc)
>  {
> -	struct sk_buff *skb;
>  	struct net_device *dev = (struct net_device *)vcc->proto_data;
>  	struct lec_priv *priv = netdev_priv(dev);
>  
> -	priv->lecd = NULL;
> +	rcu_assign_pointer(priv->lecd, NULL);
> +	synchronize_rcu();
>  	/* Do something needful? */
>  
At this point priv->lecd is no longer used, so why not make lecd valid
throughout the lifespan of priv and free it after stopping dev queue,
instead of the tedious rcu trick?

>  	netif_stop_queue(dev);
>  	lec_arp_destroy(priv);
>  
> -	if (skb_peek(&sk_atm(vcc)->sk_receive_queue))
> -		pr_info("%s closing with messages pending\n", dev->name);
> -	while ((skb = skb_dequeue(&sk_atm(vcc)->sk_receive_queue))) {
> -		atm_return(vcc, skb->truesize);
> -		dev_kfree_skb(skb);
> -	}
> -
>  	pr_info("%s: Shut down!\n", dev->name);
>  	module_put(THIS_MODULE);
>  }
> @@ -510,12 +521,14 @@ send_to_lecd(struct lec_priv *priv, atmlec_msg_type type,
>  	     const unsigned char *mac_addr, const unsigned char *atm_addr,
>  	     struct sk_buff *data)
>  {
> +	struct atm_vcc *vcc;
>  	struct sock *sk;
>  	struct sk_buff *skb;
>  	struct atmlec_msg *mesg;
>  
> -	if (!priv || !priv->lecd)
> +	if (!priv || !rcu_access_pointer(priv->lecd))
>  		return -1;
> +
>  	skb = alloc_skb(sizeof(struct atmlec_msg), GFP_ATOMIC);
>  	if (!skb)
>  		return -1;
> @@ -532,18 +545,27 @@ send_to_lecd(struct lec_priv *priv, atmlec_msg_type type,
>  	if (atm_addr)
>  		memcpy(&mesg->content.normal.atm_addr, atm_addr, ATM_ESA_LEN);
>  
> -	atm_force_charge(priv->lecd, skb->truesize);
> -	sk = sk_atm(priv->lecd);
> +	rcu_read_lock();
> +	vcc = rcu_dereference(priv->lecd);
> +	if (!vcc) {
> +		rcu_read_unlock();
> +		kfree_skb(skb);
> +		return -1;
> +	}
> +
> +	atm_force_charge(vcc, skb->truesize);
> +	sk = sk_atm(vcc);
>  	skb_queue_tail(&sk->sk_receive_queue, skb);
>  	sk->sk_data_ready(sk);
>  
>  	if (data != NULL) {
>  		pr_debug("about to send %d bytes of data\n", data->len);
> -		atm_force_charge(priv->lecd, data->truesize);
> +		atm_force_charge(vcc, data->truesize);
>  		skb_queue_tail(&sk->sk_receive_queue, data);
>  		sk->sk_data_ready(sk);
>  	}
>  
> +	rcu_read_unlock();
>  	return 0;
>  }
>  
> @@ -618,7 +640,7 @@ static void lec_push(struct atm_vcc *vcc, struct sk_buff *skb)
>  
>  		atm_return(vcc, skb->truesize);
>  		if (*(__be16 *) skb->data == htons(priv->lecid) ||
> -		    !priv->lecd || !(dev->flags & IFF_UP)) {
> +		    !rcu_access_pointer(priv->lecd) || !(dev->flags & IFF_UP)) {
>  			/*
>  			 * Probably looping back, or if lecd is missing,
>  			 * lecd has gone down
> @@ -753,12 +775,12 @@ static int lecd_attach(struct atm_vcc *vcc, int arg)
>  		priv = netdev_priv(dev_lec[i]);
>  	} else {
>  		priv = netdev_priv(dev_lec[i]);
> -		if (priv->lecd)
> +		if (rcu_access_pointer(priv->lecd))
>  			return -EADDRINUSE;
>  	}
>  	lec_arp_init(priv);
>  	priv->itfnum = i;	/* LANE2 addition */
> -	priv->lecd = vcc;
> +	rcu_assign_pointer(priv->lecd, vcc);
>  	vcc->dev = &lecatm_dev;
>  	vcc_insert_socket(sk_atm(vcc));
>  
> diff --git a/net/atm/lec.h b/net/atm/lec.h
> index be0e2667bd8c..ec85709bf818 100644
> --- a/net/atm/lec.h
> +++ b/net/atm/lec.h
> @@ -91,7 +91,7 @@ struct lec_priv {
>  						 */
>  	spinlock_t lec_arp_lock;
>  	struct atm_vcc *mcast_vcc;		/* Default Multicast Send VCC */
> -	struct atm_vcc *lecd;
> +	struct atm_vcc __rcu *lecd;
>  	struct delayed_work lec_arp_work;	/* C10 */
>  	unsigned int maximum_unknown_frame_count;
>  						/*
> -- 
> 2.43.0

  reply	other threads:[~2026-03-10  3:27 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-03-09 15:59 [PATCH v2] atm: lec: fix use-after-free in sock_def_readable() Deepanshu Kartikey
2026-03-10  3:18 ` Hillf Danton [this message]
2026-03-12  1:03   ` Deepanshu Kartikey
2026-03-14  2:53     ` Hillf Danton
2026-03-14 12:00 ` Deepanshu Kartikey
2026-03-14 12:10   ` Eric Dumazet
2026-03-14 15:40 ` patchwork-bot+netdevbpf

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260310031805.752-1-hdanton@sina.com \
    --to=hdanton@sina.com \
    --cc=edumazet@google.com \
    --cc=kartikey406@gmail.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=syzbot+f50072212ab792c86925@syzkaller.appspotmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox