All of lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Marc Kleine-Budde" <mkl@pengutronix.de>
Cc: linux-can@vger.kernel.org,
	"Oliver Hartkopp" <socketcan@hartkopp.net>,
	"Vincent Mailhol" <mailhol@kernel.org>,
	"Oleksij Rempel" <o.rempel@pengutronix.de>
Subject: Re: [PATCH net-next 11/11] can: protect the receive lists with a per-device lock
Date: Thu, 22 Oct 2026 10:28:13 +0000	[thread overview]
Message-ID: <sashiko-outbox-181335@kernel.org> (raw)
In-Reply-To: <20261021102756.1900679-12-mkl@pengutronix.de>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] can: raw/isotp: race in bind() overwrites ENODEV with ENETDOWN on unbind
- [Medium] can: isotp: premature so->dev publication in isotp_bind() races with NETDEV_DOWN
--

commit 8449957dc1b032324bb9194614a09818da738dd0
Author: Oliver Hartkopp <socketcan@hartkopp.net>

can: protect the receive lists with a per-device lock

This commit adds a per-device spinlock to struct can_dev_rcv_lists so that
CAN receive list registrations and unregistrations are protected
independently of network namespace moves. This allows CAN_RAW, CAN_BCM, and
CAN_ISOTP to manage their receive filters under lock_sock() without holding
rtnl_lock().

Link: https://lore.kernel.org/linux-can/CEA6A38A-2646-4ADA-95B4-CBAE2F301A8E@doyensec.com/
Link: https://patch.msgid.link/20261020203257.230955-6-socketcan@hartkopp.net

> diff --git a/net/can/isotp.c b/net/can/isotp.c
> index 5e1c3971ebfce..8ec37d252557e 100644
> --- a/net/can/isotp.c
> +++ b/net/can/isotp.c

[ ... ]

> @@ -1614,7 +1612,17 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l
>  		goto out;
>  	}
>  
> -	dev = dev_get_by_index(net, addr->can_ifindex);
> +	/* Publish the device inside the RCU section of the lookup: When it
> +	 * is unlisted concurrently, either isotp_notify() sees so->dev after
> +	 * synchronize_net() or the lookup fails.
> +	 */
> +	rcu_read_lock();
> +	dev = dev_get_by_index_rcu(net, addr->can_ifindex);
> +	if (dev) {
> +		dev_hold(dev);
> +		WRITE_ONCE(so->dev, dev);
> +	}
> +	rcu_read_unlock();
>  	if (!dev) {
>  		err = -ENODEV;
>  		goto out;
> @@ -1627,7 +1635,12 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l
>  		err = -EINVAL;
>  		goto out_put_dev;
>  	}
> -	if (!(dev->flags & IFF_UP))
> +
> +	/* Either isotp_notify() sees so->dev on NETDEV_DOWN or we see the
> +	 * cleared IFF_UP, pairs with isotp_notify()
> +	 */
> +	smp_mb();
> +	if (!(READ_ONCE(dev->flags) & IFF_UP))
>  		notify_enetdown = 1;
>  
>  	ifindex = dev->ifindex;
> @@ -1653,15 +1666,17 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l
>  	 * can safely reach the device later, even if a concurrent
>  	 * NETDEV_UNREGISTER has already unlisted it by ifindex.
>  	 */
> -	so->dev = dev;
>  	netdev_hold(so->dev, &so->dev_tracker, GFP_KERNEL);
>  
>  out_put_dev:
> -	/* remove potential reference from dev_get_by_index() */
> +	/* the published so->dev is only valid for a successful bind() */
> +	if (err)
> +		WRITE_ONCE(so->dev, NULL);

[Severity: Medium]
In net/can/isotp.c, isotp_bind() publishes so->dev inside the RCU read-side
critical section before validating can_get_ml_priv(dev) and
READ_ONCE(dev->mtu) < so->ll.mtu:

	rcu_read_lock();
	dev = dev_get_by_index_rcu(net, addr->can_ifindex);
	if (dev) {
		dev_hold(dev);
		WRITE_ONCE(so->dev, dev);
	}
	rcu_read_unlock();
	...
	if (READ_ONCE(dev->mtu) < so->ll.mtu) {
		err = -EINVAL;
		goto out_put_dev;
	}

Unlike raw_bind(), which publishes ro->bind_dev early (used only for
NETDEV_UNREGISTER under lock_sock(sk)) and only sets ro->dev once validation
and filter registration succeed, isotp_notify() checks READ_ONCE(so->dev)
locklessly for NETDEV_DOWN:

	if (READ_ONCE(so->dev) != dev)
		return;

	switch (msg) {
	...
	case NETDEV_DOWN:
		sk->sk_err = ENETDOWN;
		if (!sock_flag(sk, SOCK_DEAD))
			sk_error_report(sk);
		break;
	}

If isotp_bind() fails the MTU check (for example when CAN_ISOTP_LL_OPTS sets
ll.mtu = CANFD_MTU and the target device has dev->mtu = CAN_MTU) while a
concurrent NETDEV_DOWN notifier runs on that device before out_put_dev clears
so->dev, won't isotp_notify() observe READ_ONCE(so->dev) == dev and set
sk->sk_err = ENETDOWN on the still-unbound socket?

Would checking can_get_ml_priv(dev) and READ_ONCE(dev->mtu) < so->ll.mtu
inside rcu_read_lock() before writing so->dev avoid this window?

> +
> +	/* remove reference from the device lookup */
>  	dev_put(dev);
>  out:
>  	release_sock(sk);
> -	rtnl_unlock();
>  
>  	if (notify_enetdown) {
>  		sk->sk_err = ENETDOWN;
> @@ -1864,12 +1879,21 @@ static void isotp_notify(struct isotp_sock *so, unsigned long msg,
>  	if (!net_eq(dev_net(dev), sock_net(sk)))
>  		return;
>  
> -	if (so->dev != dev)
> +	if (msg == NETDEV_DOWN)
> +		smp_mb(); /* pairs with isotp_bind(), see raw_notify() */
> +
> +	/* lockless fast path, see raw_notify() */
> +	if (READ_ONCE(so->dev) != dev)
>  		return;
>  
>  	switch (msg) {
>  	case NETDEV_UNREGISTER:
>  		lock_sock(sk);
> +		if (so->dev != dev) {
> +			release_sock(sk);
> +			break;
> +		}
> +
>  		/* remove current filters & unregister */
>  		if (so->bound) {
>  			if (isotp_register_rxid(so))
> @@ -1885,7 +1909,7 @@ static void isotp_notify(struct isotp_sock *so, unsigned long msg,
>  
>  		so->ifindex = 0;
>  		so->bound  = 0;
> -		so->dev = NULL;
> +		WRITE_ONCE(so->dev, NULL);
>  		release_sock(sk);
>  
>  		sk->sk_err = ENODEV;
> diff --git a/net/can/raw.c b/net/can/raw.c
> index dfea48768b231..d07f3bb68cd5c 100644
> --- a/net/can/raw.c
> +++ b/net/can/raw.c

[ ... ]

> @@ -305,16 +309,31 @@ static void raw_notify(struct raw_sock *ro, unsigned long msg,
>  		       struct net_device *dev)
>  {
>  	struct sock *sk = &ro->sk;
> +	struct net_device *bind_dev;
>  
>  	if (!net_eq(dev_net(dev), sock_net(sk)))
>  		return;
>  
> -	if (ro->dev != dev)
> +	if (msg == NETDEV_DOWN)
> +		smp_mb(); /* IFF_UP is cleared before, pairs with raw_bind() */
> +
> +	/* Lockless fast path, ro->bind_dev covers a concurrent bind().
> +	 * Read it before ro->dev, pairs with smp_store_release() in
> +	 * raw_bind(). A stale ro->bind_dev only costs a lock_sock().
> +	 */
> +	bind_dev = smp_load_acquire(&ro->bind_dev);
> +	if (READ_ONCE(ro->dev) != dev &&
> +	    (msg != NETDEV_UNREGISTER || bind_dev != dev))
>  		return;
>  
>  	switch (msg) {
>  	case NETDEV_UNREGISTER:
>  		lock_sock(sk);
> +		if (ro->dev != dev) {
> +			release_sock(sk);
> +			break;
> +		}
> +
>  		/* remove current filters & unregister */
>  		if (ro->bound) {
>  			raw_disable_allfilters(dev_net(dev), dev, sk);
> @@ -326,7 +345,7 @@ static void raw_notify(struct raw_sock *ro, unsigned long msg,
>  
>  		ro->ifindex = 0;
>  		ro->bound = 0;
> -		ro->dev = NULL;
> +		WRITE_ONCE(ro->dev, NULL);
>  		ro->count = 0;
>  		release_sock(sk);
>  

[ ... ]

> @@ -528,16 +557,24 @@ static int raw_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int len
>  		ro->ifindex = ifindex;
>  		ro->bound = 1;
>  		/* bind() ok -> hold a reference for new ro->dev */
> -		ro->dev = dev;
> +		WRITE_ONCE(ro->dev, dev);
>  		netdev_hold(ro->dev, &ro->dev_tracker, GFP_KERNEL);
> +
> +		if (dev) {
> +			/* Either raw_notify() sees ro->dev on NETDEV_DOWN or
> +			 * we see the cleared IFF_UP, pairs with raw_notify()
> +			 */
> +			smp_mb();
> +			if (!(READ_ONCE(dev->flags) & IFF_UP))
> +				notify_enetdown = 1;
> +		}
>  	}
>  
>  out_put_dev:
> -	/* remove potential reference from dev_get_by_index() */
> +	/* remove reference from the device lookup */
>  	dev_put(dev);
>  out:
>  	release_sock(sk);
> -	rtnl_unlock();
>  
>  	if (notify_enetdown) {
>  		sk->sk_err = ENETDOWN;

[Severity: Medium]
Can raw_bind() and isotp_bind() overwrite sk->sk_err = ENODEV with ENETDOWN
when racing against a concurrent NETDEV_UNREGISTER?

During unregister_netdevice_many_notify() or __dev_change_net_namespace(),
netif_close() clears IFF_UP before unlist_netdevice(dev), synchronize_net(),
and call_netdevice_notifiers(NETDEV_UNREGISTER, dev).

If raw_bind() or isotp_bind() looks up and publishes the device just before
unlist_netdevice(dev), the following sequence can occur:

1. Under lock_sock(sk), raw_bind() or isotp_bind() sees !(READ_ONCE(dev->flags) & IFF_UP)
   and sets notify_enetdown = 1.
2. Concurrent raw_notify() or isotp_notify() for NETDEV_UNREGISTER sees the
   published ro->bind_dev or so->dev and waits on lock_sock(sk).
3. raw_bind() or isotp_bind() calls release_sock(sk).
4. raw_notify() or isotp_notify() acquires lock_sock(sk), removes the
   filters, resets bound = 0 and dev = NULL, calls release_sock(sk), and sets
   sk->sk_err = ENODEV.
5. raw_bind() or isotp_bind() runs its post-unlock notify_enetdown block:

	release_sock(sk);

	if (notify_enetdown) {
		sk->sk_err = ENETDOWN;
		if (!sock_flag(sk, SOCK_DEAD))
			sk_error_report(sk);
	}

This clobbers sk->sk_err = ENODEV with ENETDOWN on a socket that is now
unbound. For CAN_RAW sockets in particular, raw_recvmsg() does not check
ro->bound and relies on sk->sk_err = ENODEV to report that the device went
away, so overwriting ENODEV with ENETDOWN leaves a subsequent blocking
recvmsg() waiting on an unbound socket with no filters.

Should sk->sk_err and sk_error_report(sk) be updated while still holding
lock_sock(sk) in raw_bind(), isotp_bind(), raw_notify(), and isotp_notify()?

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261021102756.1900679-1-mkl@pengutronix.de?part=11

  parent reply	other threads:[~2026-10-22 10:28 UTC|newest]

Thread overview: 30+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-21 10:24 [PATCH net-next 0/11] pull-request: can-next 2026-10-21 Marc Kleine-Budde
2026-10-21 10:24 ` [PATCH net-next 01/11] can: dev: can_dropped_invalid_skb: drop CAN XL frames on non-CAN XL devices Marc Kleine-Budde
2026-10-21 10:33   ` netdev-bot+sinfo
2026-10-21 10:24 ` [PATCH net-next 02/11] can: skb: make echo skb freeing safe in any IRQ context Marc Kleine-Budde
2026-10-22 11:14   ` netdev-bot+sashiko
2026-10-21 10:24 ` [PATCH net-next 03/11] can: raw: remove redundant NULL check before netdev_hold() Marc Kleine-Budde
2026-10-21 10:24 ` [PATCH net-next 04/11] can: skb: make CAN skb allocation failure paths IRQ-safe Marc Kleine-Budde
2026-10-21 10:24 ` [PATCH net-next 05/11] can: isotp: check the frame type, not just the length Marc Kleine-Budde
2026-10-21 10:24 ` [PATCH net-next 06/11] can: proc: reset pkg_stats atomics individually Marc Kleine-Budde
2026-10-21 10:24 ` [PATCH net-next 07/11] can: proc: skip stats calculation after reset in can_stat_update() Marc Kleine-Budde
2026-10-21 10:24 ` [PATCH net-next 08/11] can: proc: remove pointers from CAN specific proc output Marc Kleine-Budde
2026-10-21 10:24 ` [PATCH net-next 09/11] can: replace ARPHRD_CAN type checks with can_get_ml_priv() Marc Kleine-Budde
2026-10-21 10:24 ` [PATCH net-next 10/11] can: dev: can_put_echo_skb(): free skb on invalid echo index Marc Kleine-Budde
2026-10-21 10:24 ` [PATCH net-next 11/11] can: protect the receive lists with a per-device lock Marc Kleine-Budde
2026-10-21 20:55   ` Jakub Kicinski
2026-10-21 21:59     ` Oliver Hartkopp
2026-10-21 23:05       ` Marc Kleine-Budde
2026-10-21 23:22         ` Oliver Hartkopp
2026-10-21 23:13       ` Marc Kleine-Budde
2026-10-22 10:28   ` sashiko-bot [this message]
2026-10-22 11:14   ` netdev-bot+sashiko
2026-10-21 11:19 ` [PATCH net-next 0/11] pull-request: can-next 2026-10-21 Paolo Abeni
2026-10-21 11:33   ` Oliver Hartkopp
2026-10-21 20:56     ` Jakub Kicinski
2026-10-21 21:32       ` Oliver Hartkopp
  -- strict thread matches above, loose matches on Subject: below --
2026-10-21 23:07 [PATCH net-next 0/11] pull-request: can-next 2026-10-22 Marc Kleine-Budde
2026-10-21 23:07 ` [PATCH net-next 11/11] can: protect the receive lists with a per-device lock Marc Kleine-Budde
2026-10-19 17:01 [PATCH net-next 0/11] pull-request: can-next 2026-10-19 Marc Kleine-Budde
2026-10-19 17:01 ` [PATCH net-next 11/11] can: protect the receive lists with a per-device lock Marc Kleine-Budde
2026-10-20 17:11   ` netdev-bot+sashiko
2026-10-20 19:00     ` Oliver Hartkopp
2026-10-20 20:48       ` Oliver Hartkopp

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=sashiko-outbox-181335@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=linux-can@vger.kernel.org \
    --cc=mailhol@kernel.org \
    --cc=mkl@pengutronix.de \
    --cc=o.rempel@pengutronix.de \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=socketcan@hartkopp.net \
    /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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.