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
next prev 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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox