Netdev List
 help / color / mirror / Atom feed
* [PATCH net 0/15] pull-request: can 2026-11-03
@ 2026-11-03 20:30 Marc Kleine-Budde
  2026-11-03 20:30 ` [PATCH net 01/15] can: dev: can_dropped_invalid_skb: drop CAN XL frames on non-CAN XL devices Marc Kleine-Budde
                   ` (14 more replies)
  0 siblings, 15 replies; 25+ messages in thread
From: Marc Kleine-Budde @ 2026-11-03 20:30 UTC (permalink / raw)
  To: netdev; +Cc: davem, kuba, linux-can, kernel

Hello netdev-team,

this is a pull request of 15 patches for net/main.

The first patch is by Vincent Mailhol and drops CAN-XL frames on non
CAN-XL devices.

A patch by Kaixuan Li for the CAN ISOTP protocol improves the check
for the correct CAN CC/FD frames.

The next 3 patches are by Cunhao Lu and make CAN skb freeing safe in
any context in several CAN helper functions.

A series by Oliver Hartkopp et al fixes several problems in the CAN
net-layer:

Oliver Hartkopp contributes a patch to convert the unreliable
ARPHRD_CAN type check to the robust can_get_ml_priv(). Further a patch
that adds missing error checking to the bind callback of the CAN
ISO-TP protocol. Followed by converting the CAN RAW, BCM and ISO-TP
protocol to protect the receive lists with a per-device lock. Then the
RAW and ISO-TP protocols are converted to copy the socket options
outside of lock_sock().

3 patches target the CAN procfs: A patch by Runyu Xiao resets the CAN
pkg_stats using atomics, a patch by Oliver Hartkopp fixes the stats
calculation after a statistics reset. Followed by a patch to remove
kernel pointers from the output.

ChunYuZhiQiang contributes 2 patches for the CAN netlink interfaces,
to prevent waking up the CAN device, if the interface is down.

The last patch is by Liu Changjie and fixes the decoding of bulk USB
URBs in the Peak USB CAN driver.

regards,
Marc

---

The following changes since commit 647b81ddefc6364290e67bf33c89d54971f8995e:

  tcp: wait for TIME_WAIT teardown before freeing per-netns ehash (2026-11-03 15:43:22 +0100)

are available in the Git repository at:

  git://git.kernel.org/pub/scm/linux/kernel/git/mkl/linux-can.git tags/linux-can-fixes-for-7.4-20261103

for you to fetch changes up to 2d4c272e816a5b784f1fc7899256f8826df34edb:

  can: peak_usb: pcan_usb_fd_decode_buf(): accept short uCAN end-of-list records (2026-11-03 19:54:30 +0100)

----------------------------------------------------------------
linux-can-fixes-for-7.4-20261103

----------------------------------------------------------------
ChunYuZhiQiang (2):
      can: dev: can_fill_info(): don't wake up controller if interface is down
      can: dev: can_tdc_fill_info(): don't read automatic TDCV if interface is down

Cunhao Lu (3):
      can: skb: make echo skb freeing safe in any IRQ context
      can: skb: make CAN skb allocation failure paths IRQ-safe
      can: dev: can_put_echo_skb(): free skb on invalid echo index

Kaixuan Li (1):
      can: isotp: check the frame type, not just the length

Liu Changjie (1):
      can: peak_usb: pcan_usb_fd_decode_buf(): accept short uCAN end-of-list records

Marc Kleine-Budde (3):
      Merge patch series "can: skb: make echo skb freeing safe in any IRQ context"
      Merge patch series "can: protect the receive lists with a per-device lock"
      Merge patch series "can: dev: can_fill_info(): don't wake up controller if interface is down"

Oliver Hartkopp (6):
      can: replace ARPHRD_CAN type checks with can_get_ml_priv()
      can: isotp: check the return values of can_rx_register() in isotp_bind()
      can: protect the receive lists with a per-device lock
      can: raw, isotp: copy socket options outside of lock_sock()
      can: proc: skip stats calculation after reset in can_stat_update()
      can: proc: remove pointers from CAN specific proc output

Runyu Xiao (1):
      can: proc: reset pkg_stats atomics individually

Vincent Mailhol (1):
      can: dev: can_dropped_invalid_skb: drop CAN XL frames on non-CAN XL devices

 Documentation/networking/can.rst           |  16 +-
 drivers/net/can/dev/netlink.c              |  21 ++-
 drivers/net/can/dev/skb.c                  |  16 +-
 drivers/net/can/usb/peak_usb/pcan_usb_fd.c |   7 +-
 include/linux/can/can-ml.h                 |   3 +
 include/linux/can/core.h                   |   2 +-
 include/linux/can/dev.h                    |   2 +-
 include/linux/can/skb.h                    |   4 +-
 include/net/netns/can.h                    |   2 +-
 net/can/af_can.c                           |  62 ++++---
 net/can/af_can.h                           |   2 +-
 net/can/bcm.c                              |  64 +++----
 net/can/gw.c                               |   9 +-
 net/can/isotp.c                            | 236 ++++++++++++++----------
 net/can/j1939/main.c                       |   2 +-
 net/can/proc.c                             |  95 ++++++----
 net/can/raw.c                              | 278 +++++++++++++++++------------
 17 files changed, 477 insertions(+), 344 deletions(-)

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

* [PATCH net 01/15] can: dev: can_dropped_invalid_skb: drop CAN XL frames on non-CAN XL devices
  2026-11-03 20:30 [PATCH net 0/15] pull-request: can 2026-11-03 Marc Kleine-Budde
@ 2026-11-03 20:30 ` Marc Kleine-Budde
  2026-11-03 20:48   ` netdev-bot+sinfo
  2026-11-05  9:10   ` patchwork-bot+netdevbpf
  2026-11-03 20:30 ` [PATCH net 02/15] can: isotp: check the frame type, not just the length Marc Kleine-Budde
                   ` (13 subsequent siblings)
  14 siblings, 2 replies; 25+ messages in thread
From: Marc Kleine-Budde @ 2026-11-03 20:30 UTC (permalink / raw)
  To: netdev
  Cc: davem, kuba, linux-can, kernel, Vincent Mailhol, stable,
	Marc Kleine-Budde

From: Vincent Mailhol <mailhol@kernel.org>

Sending a PF_PACKET bypasses the CAN framework logic and can directly
reach a CAN driver's xmit() function. The PF_PACKET framework only
checks that skb->len does not exceed the net_device MTU.

For a CAN device that is not CAN XL capable, anything above CANFD_MTU
(72 bytes) is therefore dropped before it reaches the driver. However,
CAN XL frames are variable length. can_is_canxl_skb() accepts lengths in
the range CANXL_HDR_SIZE + CANXL_MIN_DLEN up to CANXL_MTU, i.e. 13 to
2060 bytes.

As a result, an ETH_P_CANXL skb with a length between 13 and 72 bytes
can pass both the MTU and the can_dropped_invalid_skb() checks.

A driver that does not support CAN XL will interpret canxl_frame->flags
as a length because of the overlap with can_frame->len. And because
CANXL_XLF is set, the resulting length is between 128 and 255. For
drivers that do not check can_frame->len before copying can_frame->data,
as most drivers do, this results in a buffer overflow of up to 247
bytes.

Drop ETH_P_CANXL skbs if the device does not have the CAN_CAP_XL
capability. Keep can_is_canxl_skb() for the validation of CAN XL skbs.

Closes: https://sashiko.dev/#/patchset/20260731-master-v5-0-5b27029dee20@qq.com?part=1
Fixes: fb08cba12b52 ("can: canxl: update CAN infrastructure for CAN XL frames")
Signed-off-by: Vincent Mailhol <mailhol@kernel.org>
Link: https://patch.msgid.link/20260731-drop_canxl_frames-v1-1-7387b70353b3@kernel.org
Cc: stable@kernel.org
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
---
 drivers/net/can/dev/skb.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/net/can/dev/skb.c b/drivers/net/can/dev/skb.c
index 14edec5afb57..bc0787535b52 100644
--- a/drivers/net/can/dev/skb.c
+++ b/drivers/net/can/dev/skb.c
@@ -4,6 +4,7 @@
  * Copyright (C) 2008-2009 Wolfgang Grandegger <wg@grandegger.com>
  */
 
+#include <linux/can/can-ml.h>
 #include <linux/can/dev.h>
 #include <linux/module.h>
 #include <net/can.h>
@@ -388,7 +389,7 @@ bool can_dropped_invalid_skb(struct net_device *dev, struct sk_buff *skb)
 		break;
 
 	case ETH_P_CANXL:
-		if (!can_is_canxl_skb(skb))
+		if (!can_cap_enabled(dev, CAN_CAP_XL) || !can_is_canxl_skb(skb))
 			goto inval_skb;
 		break;
 

base-commit: 647b81ddefc6364290e67bf33c89d54971f8995e
-- 
2.53.0


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

* [PATCH net 02/15] can: isotp: check the frame type, not just the length
  2026-11-03 20:30 [PATCH net 0/15] pull-request: can 2026-11-03 Marc Kleine-Budde
  2026-11-03 20:30 ` [PATCH net 01/15] can: dev: can_dropped_invalid_skb: drop CAN XL frames on non-CAN XL devices Marc Kleine-Budde
@ 2026-11-03 20:30 ` Marc Kleine-Budde
  2026-11-03 20:30 ` [PATCH net 03/15] can: skb: make echo skb freeing safe in any IRQ context Marc Kleine-Budde
                   ` (12 subsequent siblings)
  14 siblings, 0 replies; 25+ messages in thread
From: Marc Kleine-Budde @ 2026-11-03 20:30 UTC (permalink / raw)
  To: netdev
  Cc: davem, kuba, linux-can, kernel, Kaixuan Li, Oliver Hartkopp,
	Quchaosheng, stable, Marc Kleine-Budde

From: Kaixuan Li <kaixuanli0131@gmail.com>

isotp_rcv() separates Classic CAN from CAN FD by skb->len alone:

	if (skb->len != so->ll.mtu)
		return;

	cf = (struct canfd_frame *)skb->data;

A CAN XL frame with cxl->len 4 is CAN_MTU bytes, so it passes, and is then
read as a canfd_frame whose len comes out of canxl_frame.flags: at least
0x80.

Of the paths that follow, only the flow control one uses that length
without bounding it first, so check_pad() walks to 255 over a 16-byte
frame and the caller reports EBADMSG on an unrelated socket.

bcm_rx_handler(), j1939_can_recv(), can_can_gw_rcv() and raw_rcv() check
the frame type here, and can_dropped_invalid_skb() switches on
skb->protocol on the transmit side. isotp_rcv() is the gap.

Fixes: fb08cba12b52 ("can: canxl: update CAN infrastructure for CAN XL frames")
Signed-off-by: Kaixuan Li <kaixuanli0131@gmail.com>
Reviewed-by: Oliver Hartkopp <socketcan@hartkopp.net>
Acked-by: Oliver Hartkopp <socketcan@hartkopp.net>
Reviewed-by: Quchaosheng <quchaosheng000406@163.com>
Link: https://patch.msgid.link/20260920035626.2581040-1-kaixuanli0131@gmail.com
Cc: stable@vger.kernel.org
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
---
 net/can/isotp.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/net/can/isotp.c b/net/can/isotp.c
index f5dc9d04bd68..d3e43ab48425 100644
--- a/net/can/isotp.c
+++ b/net/can/isotp.c
@@ -755,6 +755,14 @@ static void isotp_rcv(struct sk_buff *skb, void *data)
 	if (skb->len != so->ll.mtu)
 		return;
 
+	/* check for correct CAN CC/FD frame content */
+	if (so->ll.mtu == CAN_MTU) {
+		if (!can_is_can_skb(skb))
+			return;
+	} else if (!can_is_canfd_skb(skb)) {
+		return;
+	}
+
 	cf = (struct canfd_frame *)skb->data;
 
 	/* if enabled: check reception of my configured extended address */
-- 
2.53.0


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

* [PATCH net 03/15] can: skb: make echo skb freeing safe in any IRQ context
  2026-11-03 20:30 [PATCH net 0/15] pull-request: can 2026-11-03 Marc Kleine-Budde
  2026-11-03 20:30 ` [PATCH net 01/15] can: dev: can_dropped_invalid_skb: drop CAN XL frames on non-CAN XL devices Marc Kleine-Budde
  2026-11-03 20:30 ` [PATCH net 02/15] can: isotp: check the frame type, not just the length Marc Kleine-Budde
@ 2026-11-03 20:30 ` Marc Kleine-Budde
  2026-11-03 20:30 ` [PATCH net 04/15] can: skb: make CAN skb allocation failure paths IRQ-safe Marc Kleine-Budde
                   ` (11 subsequent siblings)
  14 siblings, 0 replies; 25+ messages in thread
From: Marc Kleine-Budde @ 2026-11-03 20:30 UTC (permalink / raw)
  To: netdev; +Cc: davem, kuba, linux-can, kernel, Cunhao Lu, stable,
	Marc Kleine-Budde

From: Cunhao Lu <1579567540@qq.com>

can_put_echo_skb() can be called with hardware interrupts disabled. Its
direct drop paths use kfree_skb(), while can_create_echo_skb() uses
kfree_skb() when cloning fails and consume_skb() after a successful clone.
None of these helpers is safe in every IRQ context.

Use dev_kfree_skb_any() for all drop paths and dev_consume_skb_any() when
consuming a successfully cloned skb. This preserves the respective skb drop
and consumed semantics regardless of the caller IRQ context.

Signed-off-by: Cunhao Lu <1579567540@qq.com>
Link: https://patch.msgid.link/tencent_E84809CF236D0137885E7E4E4D58340B3208@qq.com
Cc: stable@kernel.org
Fixes: ae64438be192 ("can: dev: fix skb drop check")
Fixes: 0ae89beb283a ("can: add destructor for self generated skbs")
Fixes: 3ccd4c6167d3 ("can: Unify droping of invalid tx skbs and netdev stats")
Fixes: 39549eef3587 ("can: CAN Network device driver and Netlink interface")
[mkl: also convert can_dropped_invalid_skb(), can_dev_dropped_skb()]
[mkl: add stable on Cc + add Fixes tags]
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>

---

FAQ:

Q: What about the comment in drivers/net/can/virtio_can.c
   virtio_can_start_xmit()? The error handling for virtqueue_add_sgs()
   mentions that can_put_echo_skb() consumes the skb via consume_skb().

A: The comment in virtio_can.c will be updated in a separate series, to
   ease backporting of this patch.

[mkl: add FAQ section]
---
 drivers/net/can/dev/skb.c | 6 +++---
 include/linux/can/dev.h   | 2 +-
 include/linux/can/skb.h   | 4 ++--
 3 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/drivers/net/can/dev/skb.c b/drivers/net/can/dev/skb.c
index bc0787535b52..46c151eece8f 100644
--- a/drivers/net/can/dev/skb.c
+++ b/drivers/net/can/dev/skb.c
@@ -63,7 +63,7 @@ int can_put_echo_skb(struct sk_buff *skb, struct net_device *dev,
 	    (skb->protocol != htons(ETH_P_CAN) &&
 	     skb->protocol != htons(ETH_P_CANFD) &&
 	     skb->protocol != htons(ETH_P_CANXL))) {
-		kfree_skb(skb);
+		dev_kfree_skb_any(skb);
 		return 0;
 	}
 
@@ -91,7 +91,7 @@ int can_put_echo_skb(struct sk_buff *skb, struct net_device *dev,
 	} else {
 		/* locking problem with netif_stop_queue() ?? */
 		netdev_err(dev, "%s: BUG! echo_skb %d is occupied!\n", __func__, idx);
-		kfree_skb(skb);
+		dev_kfree_skb_any(skb);
 		return -EBUSY;
 	}
 
@@ -403,7 +403,7 @@ bool can_dropped_invalid_skb(struct net_device *dev, struct sk_buff *skb)
 	return false;
 
 inval_skb:
-	kfree_skb(skb);
+	dev_kfree_skb_any(skb);
 	dev->stats.tx_dropped++;
 	return true;
 }
diff --git a/include/linux/can/dev.h b/include/linux/can/dev.h
index 6d0710d6f571..346faddee02e 100644
--- a/include/linux/can/dev.h
+++ b/include/linux/can/dev.h
@@ -176,7 +176,7 @@ static inline bool can_dev_dropped_skb(struct net_device *dev, struct sk_buff *s
 	return can_dropped_invalid_skb(dev, skb);
 
 invalid_skb:
-	kfree_skb(skb);
+	dev_kfree_skb_any(skb);
 	dev->stats.tx_dropped++;
 	return true;
 }
diff --git a/include/linux/can/skb.h b/include/linux/can/skb.h
index 5d27843862fc..87ace09c0d8b 100644
--- a/include/linux/can/skb.h
+++ b/include/linux/can/skb.h
@@ -78,12 +78,12 @@ static inline struct sk_buff *can_create_echo_skb(struct sk_buff *skb)
 
 	nskb = skb_clone(skb, GFP_ATOMIC);
 	if (unlikely(!nskb)) {
-		kfree_skb(skb);
+		dev_kfree_skb_any(skb);
 		return NULL;
 	}
 
 	can_skb_set_owner(nskb, skb->sk);
-	consume_skb(skb);
+	dev_consume_skb_any(skb);
 	return nskb;
 }
 
-- 
2.53.0


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

* [PATCH net 04/15] can: skb: make CAN skb allocation failure paths IRQ-safe
  2026-11-03 20:30 [PATCH net 0/15] pull-request: can 2026-11-03 Marc Kleine-Budde
                   ` (2 preceding siblings ...)
  2026-11-03 20:30 ` [PATCH net 03/15] can: skb: make echo skb freeing safe in any IRQ context Marc Kleine-Budde
@ 2026-11-03 20:30 ` Marc Kleine-Budde
  2026-11-03 20:30 ` [PATCH net 05/15] can: dev: can_put_echo_skb(): free skb on invalid echo index Marc Kleine-Budde
                   ` (10 subsequent siblings)
  14 siblings, 0 replies; 25+ messages in thread
From: Marc Kleine-Budde @ 2026-11-03 20:30 UTC (permalink / raw)
  To: netdev; +Cc: davem, kuba, linux-can, kernel, Cunhao Lu, Marc Kleine-Budde

From: Cunhao Lu <1579567540@qq.com>

The CAN skb allocation helpers are used from hardware interrupt receive
handlers. If can_skb_ext_add() fails, they release the newly allocated skb
with kfree_skb(), which is not safe in hardware interrupt context.

Use dev_kfree_skb_any() for the allocation failure paths in
alloc_can_skb(), alloc_canfd_skb(), and alloc_canxl_skb().

Fixes: 96ea3a1e2d31 ("can: add CAN skb extension infrastructure")
Signed-off-by: Cunhao Lu <1579567540@qq.com>
Link: https://patch.msgid.link/tencent_C825C17D442F801351CE2FBC4984064B4605@qq.com
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
---
 drivers/net/can/dev/skb.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/net/can/dev/skb.c b/drivers/net/can/dev/skb.c
index 46c151eece8f..3e36c0368bae 100644
--- a/drivers/net/can/dev/skb.c
+++ b/drivers/net/can/dev/skb.c
@@ -228,7 +228,7 @@ struct sk_buff *alloc_can_skb(struct net_device *dev, struct can_frame **cf)
 
 	csx = can_skb_ext_add(skb);
 	if (!csx) {
-		kfree_skb(skb);
+		dev_kfree_skb_any(skb);
 		goto out_error_cc;
 	}
 
@@ -259,7 +259,7 @@ struct sk_buff *alloc_canfd_skb(struct net_device *dev,
 
 	csx = can_skb_ext_add(skb);
 	if (!csx) {
-		kfree_skb(skb);
+		dev_kfree_skb_any(skb);
 		goto out_error_fd;
 	}
 
@@ -297,7 +297,7 @@ struct sk_buff *alloc_canxl_skb(struct net_device *dev,
 
 	csx = can_skb_ext_add(skb);
 	if (!csx) {
-		kfree_skb(skb);
+		dev_kfree_skb_any(skb);
 		goto out_error_xl;
 	}
 
-- 
2.53.0


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

* [PATCH net 05/15] can: dev: can_put_echo_skb(): free skb on invalid echo index
  2026-11-03 20:30 [PATCH net 0/15] pull-request: can 2026-11-03 Marc Kleine-Budde
                   ` (3 preceding siblings ...)
  2026-11-03 20:30 ` [PATCH net 04/15] can: skb: make CAN skb allocation failure paths IRQ-safe Marc Kleine-Budde
@ 2026-11-03 20:30 ` Marc Kleine-Budde
  2026-11-03 20:30 ` [PATCH net 06/15] can: replace ARPHRD_CAN type checks with can_get_ml_priv() Marc Kleine-Budde
                   ` (9 subsequent siblings)
  14 siblings, 0 replies; 25+ messages in thread
From: Marc Kleine-Budde @ 2026-11-03 20:30 UTC (permalink / raw)
  To: netdev
  Cc: davem, kuba, linux-can, kernel, Cunhao Lu, stable,
	Vincent Mailhol, Marc Kleine-Budde

From: Cunhao Lu <1579567540@qq.com>

can_put_echo_skb() consumes the skb on all paths except when the echo
index is out of bounds. This leaves ownership with the caller on -EINVAL,
unlike the other error paths, and can leak the skb if the caller expects
consistent semantics.

Free the skb before returning -EINVAL so that all return paths consume it.

Fixes: 6411959c10fe ("can: dev: can_put_echo_skb(): don't crash kernel if can_priv::echo_skb is accessed out of bounds")
Cc: stable@vger.kernel.org
Reviewed-by: Vincent Mailhol <mailhol@kernel.org>
Signed-off-by: Cunhao Lu <1579567540@qq.com>
Link: https://patch.msgid.link/tencent_683AA16E643DE00211CD2FB62991264DC605@qq.com
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>

---

FAQ:

Q: `can_skb_init_valid()` directly modifies `skb->data` without checking if
   the SKB is cloned or shared. Is this violating SKB shared buffer rules and
   causing data corruption?

A: The finding about can_skb_init_valid() modifying skb->data on a
   potentially shared buffer is not relevant for this (correct) patch.

   The FDF-flag write in can_skb_init_valid() only matters for PF_PACKET
   use: PF_CAN allocators already set CANFD_FDF. The write exists to
   normalize frames for PF_PACKET observers (tcpdump/Wireshark) so they can
   distinguish classic CAN from CAN FD at the netdev level, and to
   compensate for PF_PACKET senders that omit the bit. That normalization
   is functionally required.

   The shared-buffer concern only becomes realistic through a specific
   chain: a PF_PACKET sender injects a CANFD-sized frame, the CAN driver's
   loopback puts an echo skb back into can_rcv(), and cgw (without
   modfuncs) clones it for forwarding to another interface. Only on that
   second can_skb_init_valid() call is the skb cloned. By then the bit was
   already set on the exclusive skb during the initial xmit path, so the
   flags |= CANFD_FDF write is strictly idempotent for any parallel
   consumer of the shared buffer - no memory-safety or
   information-disclosure consequence. It's a formal violation of the skb
   sharing rules without an observable effect, so the current code can stay
   as-is.

Link: https://lore.kernel.org/all/54a3cc01-abcf-4a33-b932-39bb1f68cdd5@hartkopp.net/
[mkl: convert Oliver's mail to FAQ section]
---
 drivers/net/can/dev/skb.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/net/can/dev/skb.c b/drivers/net/can/dev/skb.c
index 3e36c0368bae..738d5968dfa6 100644
--- a/drivers/net/can/dev/skb.c
+++ b/drivers/net/can/dev/skb.c
@@ -55,6 +55,7 @@ int can_put_echo_skb(struct sk_buff *skb, struct net_device *dev,
 	if (idx >= priv->echo_skb_max) {
 		netdev_err(dev, "%s: BUG! Trying to access can_priv::echo_skb out of bounds (%u/max %u)\n",
 			   __func__, idx, priv->echo_skb_max);
+		dev_kfree_skb_any(skb);
 		return -EINVAL;
 	}
 
-- 
2.53.0


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

* [PATCH net 06/15] can: replace ARPHRD_CAN type checks with can_get_ml_priv()
  2026-11-03 20:30 [PATCH net 0/15] pull-request: can 2026-11-03 Marc Kleine-Budde
                   ` (4 preceding siblings ...)
  2026-11-03 20:30 ` [PATCH net 05/15] can: dev: can_put_echo_skb(): free skb on invalid echo index Marc Kleine-Budde
@ 2026-11-03 20:30 ` Marc Kleine-Budde
  2026-11-03 20:30 ` [PATCH net 07/15] can: isotp: check the return values of can_rx_register() in isotp_bind() Marc Kleine-Budde
                   ` (8 subsequent siblings)
  14 siblings, 0 replies; 25+ messages in thread
From: Marc Kleine-Budde @ 2026-11-03 20:30 UTC (permalink / raw)
  To: netdev
  Cc: davem, kuba, linux-can, kernel, Oliver Hartkopp, stable,
	Oleksij Rempel, Marc Kleine-Budde

From: Oliver Hartkopp <socketcan@hartkopp.net>

Commit 4e096a18867a ("net: introduce CAN specific pointer in the struct
net_device") introduced an explicit way to assign the midlayer private
pointer (dev->ml_priv) to named users like ML_PRIV_CAN. A valid pointer
returned by can_get_ml_priv() therefore identifies a real CAN device.

The referenced commit only converted the j1939 and proc code to this
check, while the other parts of the CAN subsystem still rely on
dev->type == ARPHRD_CAN.

The TUNSETLINK ioctl allows a down TUN/TAP device to set its hardware
type to ARPHRD_CAN while dev->ml_priv remains NULL. Such a repurposed
netdevice passes the ARPHRD_CAN checks.

Commit 8aa59e355949 ("can: af_can: fix NULL pointer dereference in
can_rx_register()") already made can_rx_register() reject such devices.
But can_rx_unregister() still only checks dev->type and dereferences
the NULL ml_priv when it walks the receive lists of the device.

CAN_ISOTP reaches this path: isotp_bind() ignores the return values of
can_rx_register() and binds the socket anyway. When the socket is
released or the device is unregistered, isotp_release() and
isotp_notify() remove the filters that have never been registered and
trigger the NULL pointer dereference in can_rx_unregister(). This only
requires owning a TUN/TAP device or CAP_NET_ADMIN in an unprivileged
user namespace.

Convert the remaining ARPHRD_CAN checks in af_can, bcm, gw, isotp and
raw to can_get_ml_priv().

Fixes: 4e096a18867a ("net: introduce CAN specific pointer in the struct net_device")
Cc: stable@vger.kernel.org
Cc: Oleksij Rempel <o.rempel@pengutronix.de>
Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
Link: https://patch.msgid.link/20261027-can-rx-lock-v12-1-eb5afc2d8b9b@hartkopp.net
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
---
 net/can/af_can.c | 15 ++++++---------
 net/can/bcm.c    |  7 ++++---
 net/can/gw.c     |  7 ++++---
 net/can/isotp.c  |  5 +++--
 net/can/raw.c    |  4 ++--
 5 files changed, 19 insertions(+), 19 deletions(-)

diff --git a/net/can/af_can.c b/net/can/af_can.c
index 34fe3b28d576..dc27ace43719 100644
--- a/net/can/af_can.c
+++ b/net/can/af_can.c
@@ -226,7 +226,7 @@ int can_send(struct sk_buff *skb, int loop)
 		goto inval_skb;
 	}
 
-	if (unlikely(skb->dev->type != ARPHRD_CAN)) {
+	if (unlikely(!can_get_ml_priv(skb->dev))) {
 		err = -EPERM;
 		goto inval_skb;
 	}
@@ -452,7 +452,7 @@ int can_rx_register(struct net *net, struct net_device *dev, canid_t can_id,
 
 	/* insert new receiver  (dev,canid,mask) -> (func,data) */
 
-	if (dev && (dev->type != ARPHRD_CAN || !can_get_ml_priv(dev)))
+	if (dev && !can_get_ml_priv(dev))
 		return -ENODEV;
 
 	if (dev && !net_eq(net, dev_net(dev)))
@@ -519,7 +519,7 @@ void can_rx_unregister(struct net *net, struct net_device *dev, canid_t can_id,
 	struct can_rcv_lists_stats *rcv_lists_stats = net->can.rcv_lists_stats;
 	struct can_dev_rcv_lists *dev_rcv_lists;
 
-	if (dev && dev->type != ARPHRD_CAN)
+	if (dev && !can_get_ml_priv(dev))
 		return;
 
 	if (dev && !net_eq(net, dev_net(dev)))
@@ -684,8 +684,7 @@ static int can_rcv(struct sk_buff *skb, struct net_device *dev,
 {
 	struct can_skb_ext *csx = can_skb_ext_find(skb);
 
-	if (unlikely(dev->type != ARPHRD_CAN || !can_get_ml_priv(dev) ||
-		     !csx || !can_is_can_skb(skb))) {
+	if (unlikely(!can_get_ml_priv(dev) || !csx || !can_is_can_skb(skb))) {
 		pr_warn_once("PF_CAN: dropped non conform CAN skbuff: dev type %d, len %d\n",
 			     dev->type, skb->len);
 
@@ -710,8 +709,7 @@ static int canfd_rcv(struct sk_buff *skb, struct net_device *dev,
 {
 	struct can_skb_ext *csx = can_skb_ext_find(skb);
 
-	if (unlikely(dev->type != ARPHRD_CAN || !can_get_ml_priv(dev) ||
-		     !csx || !can_is_canfd_skb(skb))) {
+	if (unlikely(!can_get_ml_priv(dev) || !csx || !can_is_canfd_skb(skb))) {
 		pr_warn_once("PF_CAN: dropped non conform CAN FD skbuff: dev type %d, len %d\n",
 			     dev->type, skb->len);
 
@@ -736,8 +734,7 @@ static int canxl_rcv(struct sk_buff *skb, struct net_device *dev,
 {
 	struct can_skb_ext *csx = can_skb_ext_find(skb);
 
-	if (unlikely(dev->type != ARPHRD_CAN || !can_get_ml_priv(dev) ||
-		     !csx || !can_is_canxl_skb(skb))) {
+	if (unlikely(!can_get_ml_priv(dev) || !csx || !can_is_canxl_skb(skb))) {
 		pr_warn_once("PF_CAN: dropped non conform CAN XL skbuff: dev type %d, len %d\n",
 			     dev->type, skb->len);
 
diff --git a/net/can/bcm.c b/net/can/bcm.c
index 3d637a1e0ac1..60406439a13f 100644
--- a/net/can/bcm.c
+++ b/net/can/bcm.c
@@ -54,6 +54,7 @@
 #include <linux/if_arp.h>
 #include <linux/skbuff.h>
 #include <linux/can.h>
+#include <linux/can/can-ml.h>
 #include <linux/can/core.h>
 #include <linux/can/skb.h>
 #include <linux/can/bcm.h>
@@ -1719,7 +1720,7 @@ static int bcm_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
 				goto out_release;
 			}
 
-			if (dev->type != ARPHRD_CAN) {
+			if (!can_get_ml_priv(dev)) {
 				dev_put(dev);
 				ret = -ENODEV;
 				goto out_release;
@@ -1867,7 +1868,7 @@ static int bcm_notifier(struct notifier_block *nb, unsigned long msg,
 {
 	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
 
-	if (dev->type != ARPHRD_CAN)
+	if (!can_get_ml_priv(dev))
 		return NOTIFY_DONE;
 	if (msg != NETDEV_UNREGISTER && msg != NETDEV_DOWN)
 		return NOTIFY_DONE;
@@ -2024,7 +2025,7 @@ static int bcm_connect(struct socket *sock, struct sockaddr_unsized *uaddr, int
 			ret = -ENODEV;
 			goto fail;
 		}
-		if (dev->type != ARPHRD_CAN) {
+		if (!can_get_ml_priv(dev)) {
 			dev_put(dev);
 			ret = -ENODEV;
 			goto fail;
diff --git a/net/can/gw.c b/net/can/gw.c
index 5793cb2420ed..54bb5bd3242a 100644
--- a/net/can/gw.c
+++ b/net/can/gw.c
@@ -52,6 +52,7 @@
 #include <linux/if_arp.h>
 #include <linux/skbuff.h>
 #include <linux/can.h>
+#include <linux/can/can-ml.h>
 #include <linux/can/core.h>
 #include <linux/can/skb.h>
 #include <linux/can/gw.h>
@@ -612,7 +613,7 @@ static int cgw_notifier(struct notifier_block *nb,
 	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
 	struct net *net = dev_net(dev);
 
-	if (dev->type != ARPHRD_CAN)
+	if (!can_get_ml_priv(dev))
 		return NOTIFY_DONE;
 
 	if (msg == NETDEV_UNREGISTER) {
@@ -1163,7 +1164,7 @@ static int cgw_create_job(struct sk_buff *skb,  struct nlmsghdr *nlh,
 	if (!gwj->src.dev)
 		goto out;
 
-	if (gwj->src.dev->type != ARPHRD_CAN)
+	if (!can_get_ml_priv(gwj->src.dev))
 		goto out;
 
 	gwj->dst.dev = __dev_get_by_index(net, gwj->ccgw.dst_idx);
@@ -1171,7 +1172,7 @@ static int cgw_create_job(struct sk_buff *skb,  struct nlmsghdr *nlh,
 	if (!gwj->dst.dev)
 		goto out;
 
-	if (gwj->dst.dev->type != ARPHRD_CAN)
+	if (!can_get_ml_priv(gwj->dst.dev))
 		goto out;
 
 	/* is sending the skb back to the incoming interface intended? */
diff --git a/net/can/isotp.c b/net/can/isotp.c
index d3e43ab48425..e4957855829e 100644
--- a/net/can/isotp.c
+++ b/net/can/isotp.c
@@ -65,6 +65,7 @@
 #include <linux/if_arp.h>
 #include <linux/skbuff.h>
 #include <linux/can.h>
+#include <linux/can/can-ml.h>
 #include <linux/can/core.h>
 #include <linux/can/skb.h>
 #include <linux/can/isotp.h>
@@ -1618,7 +1619,7 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l
 		err = -ENODEV;
 		goto out;
 	}
-	if (dev->type != ARPHRD_CAN) {
+	if (!can_get_ml_priv(dev)) {
 		err = -ENODEV;
 		goto out_put_dev;
 	}
@@ -1905,7 +1906,7 @@ static int isotp_notifier(struct notifier_block *nb, unsigned long msg,
 {
 	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
 
-	if (dev->type != ARPHRD_CAN)
+	if (!can_get_ml_priv(dev))
 		return NOTIFY_DONE;
 	if (msg != NETDEV_UNREGISTER && msg != NETDEV_DOWN)
 		return NOTIFY_DONE;
diff --git a/net/can/raw.c b/net/can/raw.c
index 0acd4f6c6dd6..e1d741a25be3 100644
--- a/net/can/raw.c
+++ b/net/can/raw.c
@@ -348,7 +348,7 @@ static int raw_notifier(struct notifier_block *nb, unsigned long msg,
 {
 	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
 
-	if (dev->type != ARPHRD_CAN)
+	if (!can_get_ml_priv(dev))
 		return NOTIFY_DONE;
 	if (msg != NETDEV_UNREGISTER && msg != NETDEV_DOWN)
 		return NOTIFY_DONE;
@@ -491,7 +491,7 @@ static int raw_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int len
 			err = -ENODEV;
 			goto out;
 		}
-		if (dev->type != ARPHRD_CAN) {
+		if (!can_get_ml_priv(dev)) {
 			err = -ENODEV;
 			goto out_put_dev;
 		}
-- 
2.53.0


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

* [PATCH net 07/15] can: isotp: check the return values of can_rx_register() in isotp_bind()
  2026-11-03 20:30 [PATCH net 0/15] pull-request: can 2026-11-03 Marc Kleine-Budde
                   ` (5 preceding siblings ...)
  2026-11-03 20:30 ` [PATCH net 06/15] can: replace ARPHRD_CAN type checks with can_get_ml_priv() Marc Kleine-Budde
@ 2026-11-03 20:30 ` Marc Kleine-Budde
  2026-11-03 20:30 ` [PATCH net 08/15] can: protect the receive lists with a per-device lock Marc Kleine-Budde
                   ` (7 subsequent siblings)
  14 siblings, 0 replies; 25+ messages in thread
From: Marc Kleine-Budde @ 2026-11-03 20:30 UTC (permalink / raw)
  To: netdev
  Cc: davem, kuba, linux-can, kernel, Oliver Hartkopp, stable,
	Marc Kleine-Budde

From: Oliver Hartkopp <socketcan@hartkopp.net>

isotp_bind() ignores the return values of can_rx_register() for the RX
and the echo receiver and binds the socket anyway. When a receiver can
not be allocated, bind() succeeds without this receiver, so the socket
does not receive frames or its transmissions time out. isotp_release()
and isotp_notify() later try to remove the missing receiver, which
triggers the "receive list entry not found" warning in
can_rx_unregister().

As the device has been checked before and RTNL prevents a namespace
change during bind(), only -ENOMEM can be returned here.

Check the return values and register the RX receiver as the last step.
The echo receiver is registered first, as it only handles the socket's
own echo frames and does nothing while no transmission is in progress.
So a failed RX registration only needs to remove the echo receiver, and
isotp_rcv() has never processed a frame for this socket.

Set so->ifindex before the registrations, so that a First Frame received
before so->bound is set can be answered with a Flow Control frame and
the RX timeout is started as usual. As the RX receiver is registered
last, this can only happen for a bind() that succeeds. Reset so->ifindex
when bind() fails.

Check IFF_UP only after the receivers have been registered, so that a
bind() that fails on a down device does not report ENETDOWN. As bind()
holds RTNL, the device state can not change in between.

Fixes: e057dd3fc20f ("can: add ISO 15765-2:2016 transport protocol")
Cc: stable@vger.kernel.org
Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
Link: https://patch.msgid.link/20261027-can-rx-lock-v12-2-eb5afc2d8b9b@hartkopp.net
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
---
 net/can/isotp.c | 42 +++++++++++++++++++++++++++---------------
 1 file changed, 27 insertions(+), 15 deletions(-)

diff --git a/net/can/isotp.c b/net/can/isotp.c
index e4957855829e..28e9d22772fa 100644
--- a/net/can/isotp.c
+++ b/net/can/isotp.c
@@ -1550,7 +1550,6 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l
 	struct sock *sk = sock->sk;
 	struct isotp_sock *so = isotp_sk(sk);
 	struct net *net = sock_net(sk);
-	int ifindex;
 	struct net_device *dev;
 	canid_t tx_id = addr->can_addr.tp.tx_id;
 	canid_t rx_id = addr->can_addr.tp.rx_id;
@@ -1627,26 +1626,36 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l
 		err = -EINVAL;
 		goto out_put_dev;
 	}
-	if (!(dev->flags & IFF_UP))
-		notify_enetdown = 1;
 
-	ifindex = dev->ifindex;
-
-	if (isotp_register_rxid(so))
-		can_rx_register(net, dev, rx_id, SINGLE_MASK(rx_id),
-				isotp_rcv, sk, "isotp", sk);
+	/* switch to new settings */
+	so->ifindex = dev->ifindex;
+	so->rxid = rx_id;
+	so->txid = tx_id;
 
 	/* no consecutive frame echo skb in flight */
 	WRITE_ONCE(so->cfecho, 0);
 
-	/* register for echo skb's */
-	can_rx_register(net, dev, tx_id, SINGLE_MASK(tx_id),
-			isotp_rcv_echo, sk, "isotpe", sk);
+	/* register for echo skb's first (no echo traffic at this point) */
+	err = can_rx_register(net, dev, tx_id, SINGLE_MASK(tx_id),
+			      isotp_rcv_echo, sk, "isotpe", sk);
+	if (err)
+		goto out_put_dev;
+
+	if (isotp_register_rxid(so)) {
+		err = can_rx_register(net, dev, rx_id, SINGLE_MASK(rx_id),
+				      isotp_rcv, sk, "isotp", sk);
+		if (err) {
+			/* unregister echo skb subscription */
+			can_rx_unregister(net, dev, tx_id, SINGLE_MASK(tx_id),
+					  isotp_rcv_echo, sk);
+			goto out_put_dev;
+		}
+	}
+
+	/* report a down device only for a successful bind() */
+	if (!(dev->flags & IFF_UP))
+		notify_enetdown = 1;
 
-	/* switch to new settings */
-	so->ifindex = ifindex;
-	so->rxid = rx_id;
-	so->txid = tx_id;
 	so->bound = 1;
 
 	/* bind() ok -> hold a reference for so->dev so that isotp_release()
@@ -1657,6 +1666,9 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l
 	netdev_hold(so->dev, &so->dev_tracker, GFP_KERNEL);
 
 out_put_dev:
+	if (err)
+		so->ifindex = 0; /* reset on failed bind() */
+
 	/* remove potential reference from dev_get_by_index() */
 	dev_put(dev);
 out:
-- 
2.53.0


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

* [PATCH net 08/15] can: protect the receive lists with a per-device lock
  2026-11-03 20:30 [PATCH net 0/15] pull-request: can 2026-11-03 Marc Kleine-Budde
                   ` (6 preceding siblings ...)
  2026-11-03 20:30 ` [PATCH net 07/15] can: isotp: check the return values of can_rx_register() in isotp_bind() Marc Kleine-Budde
@ 2026-11-03 20:30 ` Marc Kleine-Budde
  2026-11-04 20:46   ` netdev-bot+sashiko
  2026-11-03 20:30 ` [PATCH net 09/15] can: raw, isotp: copy socket options outside of lock_sock() Marc Kleine-Budde
                   ` (6 subsequent siblings)
  14 siblings, 1 reply; 25+ messages in thread
From: Marc Kleine-Budde @ 2026-11-03 20:30 UTC (permalink / raw)
  To: netdev
  Cc: davem, kuba, linux-can, kernel, Oliver Hartkopp, Norbert Szetei,
	stable, Marc Kleine-Budde

From: Oliver Hartkopp <socketcan@hartkopp.net>

The receive lists of a CAN device are located in the device's
can_ml_priv and travel with the device when it is moved to another
network namespace. But they are protected by the per-netns
rcvlists_lock. So can_rx_unregister() needs dev_net(dev) to pick the
right lock and silently returns when the passed namespace does not
match dev_net(dev) anymore.

isotp_release() and bcm_release() leave their notifier list before they
take any lock that serializes against the netdev notifier, which runs
under RTNL. When the bound device is moved to another namespace in this
window, the notifier skips the socket and the release path has to remove
the filters itself. isotp_release() passes the socket's namespace, which
does not match dev_net(dev) anymore. bcm_rx_unreg() passes dev_net(dev),
which can_rx_unregister() reads again for its check, so a namespace
change between both reads makes the check fail as well. In both cases
can_rx_unregister() silently returns. As can_rx_register() stores
rcv->sk without a reference, the remaining receivers point to the freed
socket (isotp) or the freed op (bcm):

  BUG: KASAN: use-after-free in isotp_rcv+0x1570/0x24d0
  Read of size 1 at addr ffff888118130552 by task isotp_ns_uaf/578
   can_rcv_filter+0x4af/0x8c0
   can_receive+0x28d/0x3c0
   can_rcv+0x2a9/0x310
   __netif_receive_skb_one_core+0x21a/0x260
   process_backlog+0x210/0x760

Besides this, the lists of a device are protected by the rcvlists_lock
of the namespace that is passed in. Callers which pass dev_net(dev) read
it at different times, so the same lists may be modified under the locks
of different namespaces while the device is moved.

Taking RTNL in these release paths is no option: raw_bind(),
raw_setsockopt() and raw_release() already hold RTNL while adding and
removing up to CAN_RAW_FILTER_MAX filters per socket, which unprivileged
users can trigger repeatedly and which makes RTNL a global bottleneck.

Instead fix the root cause and add a spinlock to struct
can_dev_rcv_lists, which protects the receive lists of a CAN device or
the 'any' CAN device lists of a namespace. It is initialized in
can_set_ml_priv(), which is used by all CAN network devices. With this,
can_rx_unregister() finds the subscription independently of the
current namespace of the device. The protocols keep the device valid
with their tracked references. The per-netns rcvlists_lock only protects
the receive list statistics anymore, and CAN_RAW, CAN_BCM and CAN_ISOTP
pass the namespace that has been used for can_rx_register().

So isotp_release() and bcm_release() now remove their filters correctly
without RTNL, and raw_release(), raw_setsockopt(), raw_bind() and
isotp_bind() do not need RTNL anymore and add and remove their filters
under lock_sock(). To serialize bind() with NETDEV_UNREGISTER, bind()
publishes the device inside the RCU section of its lookup, and
raw_notify() and isotp_notify() recheck the bound device under lock_sock().
As RTNL does not keep NETDEV_DOWN out of bind() anymore, bind() checks
IFF_UP after publishing the bound device, with a barrier pair against
raw_notify() and isotp_notify(), so a down transition during bind() is
still reported with ENETDOWN.

Fixes: 20bab8b88baa ("can: isotp: fix use-after-free race with concurrent NETDEV_UNREGISTER")
Fixes: 8e8cda6d737d ("can: initial support for network namespaces")
Reported-by: Norbert Szetei <norbert@doyensec.com>
Link: https://lore.kernel.org/linux-can/CEA6A38A-2646-4ADA-95B4-CBAE2F301A8E@doyensec.com/
Cc: stable@vger.kernel.org
Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
Link: https://patch.msgid.link/20261027-can-rx-lock-v12-3-eb5afc2d8b9b@hartkopp.net
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
---
 include/linux/can/can-ml.h |  3 ++
 include/net/netns/can.h    |  2 +-
 net/can/af_can.c           | 41 ++++++++++-------
 net/can/bcm.c              | 43 +++++-------------
 net/can/isotp.c            | 91 +++++++++++++++++++++++---------------
 net/can/raw.c              | 88 ++++++++++++++++++++++++++----------
 6 files changed, 162 insertions(+), 106 deletions(-)

diff --git a/include/linux/can/can-ml.h b/include/linux/can/can-ml.h
index 1e99fda2b380..cfefa4a385be 100644
--- a/include/linux/can/can-ml.h
+++ b/include/linux/can/can-ml.h
@@ -45,6 +45,7 @@
 #include <linux/can.h>
 #include <linux/list.h>
 #include <linux/netdevice.h>
+#include <linux/spinlock.h>
 
 /* exposed CAN device capabilities for network layer */
 #define CAN_CAP_CC BIT(0) /* CAN CC aka Classical CAN */
@@ -59,6 +60,7 @@
 enum { RX_ERR, RX_ALL, RX_FIL, RX_INV, RX_MAX };
 
 struct can_dev_rcv_lists {
+	spinlock_t lock; /* protects the receive lists and entries */
 	struct hlist_head rx[RX_MAX];
 	struct hlist_head rx_sff[CAN_SFF_RCV_ARRAY_SZ];
 	struct hlist_head rx_eff[CAN_EFF_RCV_ARRAY_SZ];
@@ -81,6 +83,7 @@ static inline struct can_ml_priv *can_get_ml_priv(struct net_device *dev)
 static inline void can_set_ml_priv(struct net_device *dev,
 				   struct can_ml_priv *ml_priv)
 {
+	spin_lock_init(&ml_priv->dev_rcv_lists.lock);
 	netdev_set_ml_priv(dev, ml_priv, ML_PRIV_CAN);
 }
 
diff --git a/include/net/netns/can.h b/include/net/netns/can.h
index 48b79f7e6236..54f23681e0d4 100644
--- a/include/net/netns/can.h
+++ b/include/net/netns/can.h
@@ -29,7 +29,7 @@ struct netns_can {
 
 	/* receive filters subscribed for 'all' CAN devices */
 	struct can_dev_rcv_lists *rx_alldev_list;
-	spinlock_t rcvlists_lock;
+	spinlock_t rcvlists_lock; /* protects the rcv_entries(_max) stats */
 	struct timer_list stattimer; /* timer for statistics update */
 	struct can_pkg_stats *pkg_stats;
 	struct can_rcv_lists_stats *rcv_lists_stats;
diff --git a/net/can/af_can.c b/net/can/af_can.c
index dc27ace43719..167b2dc40eeb 100644
--- a/net/can/af_can.c
+++ b/net/can/af_can.c
@@ -462,9 +462,10 @@ int can_rx_register(struct net *net, struct net_device *dev, canid_t can_id,
 	if (!rcv)
 		return -ENOMEM;
 
-	spin_lock_bh(&net->can.rcvlists_lock);
-
 	dev_rcv_lists = can_dev_rcv_lists_find(net, dev);
+
+	spin_lock_bh(&dev_rcv_lists->lock);
+
 	rcv_list = can_rcv_list_find(&can_id, &mask, dev_rcv_lists);
 
 	rcv->can_id = can_id;
@@ -478,6 +479,9 @@ int can_rx_register(struct net *net, struct net_device *dev, canid_t can_id,
 	hlist_add_head_rcu(&rcv->list, rcv_list);
 	dev_rcv_lists->entries++;
 
+	spin_unlock_bh(&dev_rcv_lists->lock);
+
+	spin_lock_bh(&net->can.rcvlists_lock);
 	rcv_lists_stats->rcv_entries++;
 	rcv_lists_stats->rcv_entries_max = max(rcv_lists_stats->rcv_entries_max,
 					       rcv_lists_stats->rcv_entries);
@@ -509,6 +513,14 @@ static void can_rx_delete_receiver(struct rcu_head *rp)
  *
  * Description:
  *  Removes subscription entry depending on given (subscription) values.
+ *  The receive lists of a CAN device are protected by a per-device lock,
+ *  so the subscription is found even when the device has been moved to
+ *  another network namespace after can_rx_register(). The caller has to
+ *  ensure that @dev stays valid, e.g. by holding a reference to @dev or
+ *  RTNL. @net should be the namespace that has been used for
+ *  can_rx_register(). It selects the 'any' CAN device lists for
+ *  @dev == NULL and is otherwise only used for the receive list
+ *  statistics, which become imprecise when another namespace is passed.
  */
 void can_rx_unregister(struct net *net, struct net_device *dev, canid_t can_id,
 		       canid_t mask, void (*func)(struct sk_buff *, void *),
@@ -522,12 +534,10 @@ void can_rx_unregister(struct net *net, struct net_device *dev, canid_t can_id,
 	if (dev && !can_get_ml_priv(dev))
 		return;
 
-	if (dev && !net_eq(net, dev_net(dev)))
-		return;
-
-	spin_lock_bh(&net->can.rcvlists_lock);
-
 	dev_rcv_lists = can_dev_rcv_lists_find(net, dev);
+
+	spin_lock_bh(&dev_rcv_lists->lock);
+
 	rcv_list = can_rcv_list_find(&can_id, &mask, dev_rcv_lists);
 
 	/* Search the receiver list for the item to delete.  This should
@@ -547,26 +557,26 @@ void can_rx_unregister(struct net *net, struct net_device *dev, canid_t can_id,
 	 * a warning here.
 	 */
 	if (!rcv) {
+		spin_unlock_bh(&dev_rcv_lists->lock);
 		pr_warn("can: receive list entry not found for dev %s, id %03X, mask %03X\n",
 			DNAME(dev), can_id, mask);
-		goto out;
+		return;
 	}
 
 	hlist_del_rcu(&rcv->list);
 	dev_rcv_lists->entries--;
 
+	spin_unlock_bh(&dev_rcv_lists->lock);
+
+	spin_lock_bh(&net->can.rcvlists_lock);
 	if (rcv_lists_stats->rcv_entries > 0)
 		rcv_lists_stats->rcv_entries--;
-
- out:
 	spin_unlock_bh(&net->can.rcvlists_lock);
 
 	/* schedule the receiver item for deletion */
-	if (rcv) {
-		if (rcv->sk)
-			sock_hold(rcv->sk);
-		call_rcu(&rcv->rcu, can_rx_delete_receiver);
-	}
+	if (rcv->sk)
+		sock_hold(rcv->sk);
+	call_rcu(&rcv->rcu, can_rx_delete_receiver);
 }
 EXPORT_SYMBOL(can_rx_unregister);
 
@@ -823,6 +833,7 @@ static int can_pernet_init(struct net *net)
 	net->can.rx_alldev_list = kzalloc_obj(*net->can.rx_alldev_list);
 	if (!net->can.rx_alldev_list)
 		goto out;
+	spin_lock_init(&net->can.rx_alldev_list->lock);
 	net->can.pkg_stats = kzalloc_obj(*net->can.pkg_stats);
 	if (!net->can.pkg_stats)
 		goto out_free_rx_alldev_list;
diff --git a/net/can/bcm.c b/net/can/bcm.c
index 60406439a13f..9288b96724da 100644
--- a/net/can/bcm.c
+++ b/net/can/bcm.c
@@ -964,7 +964,7 @@ static void bcm_remove_op(struct bcm_op *op)
 static void bcm_rx_unreg(struct net_device *dev, struct bcm_op *op)
 {
 	if (op->rx_reg_dev == dev) {
-		can_rx_unregister(dev_net(dev), dev, op->can_id,
+		can_rx_unregister(sock_net(op->sk), dev, op->can_id,
 				  REGMASK(op->can_id), bcm_rx_handler, op);
 
 		/* mark as removed subscription */
@@ -990,21 +990,10 @@ static int bcm_delete_rx_op(struct list_head *ops, struct bcm_msg_head *mh,
 			/* disable automatic timer on frame reception */
 			op->flags |= RX_NO_AUTOTIMER;
 
-			/*
-			 * Don't care if we're bound or not (due to netdev
-			 * problems) can_rx_unregister() is always a save
-			 * thing to do here.
-			 */
 			if (op->ifindex) {
-				/*
-				 * Only remove subscriptions that had not
-				 * been removed due to NETDEV_UNREGISTER
-				 * in bcm_notifier()
-				 *
-				 * op->rx_reg_dev is a tracked reference taken
-				 * when the subscription was registered, so it
-				 * stays valid here even if a concurrent
-				 * NETDEV_UNREGISTER already unlisted the dev.
+				/* Only remove subscriptions that had not been
+				 * removed in bcm_notify(). op->rx_reg_dev keeps
+				 * the device's receive lists accessible.
 				 */
 				if (op->rx_reg_dev)
 					bcm_rx_unreg(op->rx_reg_dev, op);
@@ -1936,6 +1925,7 @@ static int bcm_release(struct socket *sock)
 	list_del(&bo->notifier);
 	spin_unlock(&bcm_notifier_lock);
 
+	/* bcm_notify() can not access this socket anymore */
 	lock_sock(sk);
 
 #if IS_ENABLED(CONFIG_PROC_FS)
@@ -1949,29 +1939,20 @@ static int bcm_release(struct socket *sock)
 		bcm_remove_op(op);
 	}
 
-	list_for_each_entry_safe(op, next, &bo->rx_ops, list) {
-		/*
-		 * Don't care if we're bound or not (due to netdev problems)
-		 * can_rx_unregister() is always a save thing to do here.
-		 */
+	list_for_each_entry(op, &bo->rx_ops, list) {
 		if (op->ifindex) {
-			/*
-			 * Only remove subscriptions that had not
-			 * been removed due to NETDEV_UNREGISTER
-			 * in bcm_notifier()
-			 *
-			 * op->rx_reg_dev is a tracked reference taken
-			 * when the subscription was registered, so it
-			 * stays valid here even if a concurrent
-			 * NETDEV_UNREGISTER already unlisted the device.
+			/* Only remove subscriptions that had not been removed
+			 * in bcm_notify(). op->rx_reg_dev keeps the device's
+			 * receive lists accessible.
 			 */
 			if (op->rx_reg_dev)
 				bcm_rx_unreg(op->rx_reg_dev, op);
-		} else
+		} else {
 			can_rx_unregister(net, NULL, op->can_id,
 					  REGMASK(op->can_id),
 					  bcm_rx_handler, op);
-
+		}
+		cond_resched();
 	}
 
 	synchronize_rcu();
diff --git a/net/can/isotp.c b/net/can/isotp.c
index 28e9d22772fa..1e40825b58cb 100644
--- a/net/can/isotp.c
+++ b/net/can/isotp.c
@@ -1132,7 +1132,7 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
 	int off;
 	int err;
 
-	if (!so->bound || READ_ONCE(so->tx.state) == ISOTP_SHUTDOWN)
+	if (!READ_ONCE(so->bound) || READ_ONCE(so->tx.state) == ISOTP_SHUTDOWN)
 		return -EADDRNOTAVAIL;
 
 	/* claim the socket under so->rx_lock: this serializes the claim
@@ -1177,7 +1177,7 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
 	/* so->bound is only checked once above - a wakeup may have
 	 * unbound/rebound the socket meanwhile => recheck
 	 */
-	if (!so->bound) {
+	if (!READ_ONCE(so->bound)) {
 		err = -EADDRNOTAVAIL;
 		goto err_out_drop;
 	}
@@ -1423,7 +1423,7 @@ static int isotp_recvmsg(struct socket *sock, struct msghdr *msg, size_t size,
 	if (flags & ~(MSG_DONTWAIT | MSG_TRUNC | MSG_PEEK | MSG_CMSG_COMPAT))
 		return -EINVAL;
 
-	if (!so->bound)
+	if (!READ_ONCE(so->bound))
 		return -EADDRNOTAVAIL;
 
 	skb = skb_recv_datagram(sk, flags, &ret);
@@ -1497,11 +1497,11 @@ static int isotp_release(struct socket *sock)
 	list_del(&so->notifier);
 	spin_unlock(&isotp_notifier_lock);
 
-	rtnl_lock();
 	lock_sock(sk);
 
 	/* remove current filters & unregister
-	 * tracked reference so->dev is taken at bind() time with rtnl_lock
+	 * the tracked reference so->dev keeps the device's receive lists
+	 * accessible, even after a namespace change
 	 */
 	if (so->bound && so->dev) {
 		if (isotp_register_rxid(so))
@@ -1516,11 +1516,9 @@ static int isotp_release(struct socket *sock)
 	}
 
 	so->ifindex = 0;
-	so->bound = 0;
+	WRITE_ONCE(so->bound, 0);
 	so->dev = NULL;
 
-	rtnl_unlock();
-
 	/* Always wait for a grace period before touching the timers below.
 	 * A concurrent NETDEV_UNREGISTER may have already unregistered our
 	 * filters and cleared so->bound in isotp_notify() without waiting
@@ -1587,7 +1585,7 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l
 	if (!addr->can_ifindex)
 		return -ENODEV;
 
-	rtnl_lock();
+	/* lock_sock() serializes the binding with isotp_notify() */
 	lock_sock(sk);
 
 	if (so->bound) {
@@ -1613,19 +1611,24 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l
 		goto out;
 	}
 
-	dev = dev_get_by_index(net, addr->can_ifindex);
-	if (!dev) {
+	/* 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. Check the device before
+	 * publishing it.
+	 */
+	rcu_read_lock();
+	dev = dev_get_by_index_rcu(net, addr->can_ifindex);
+	if (!dev || !can_get_ml_priv(dev))
 		err = -ENODEV;
-		goto out;
-	}
-	if (!can_get_ml_priv(dev)) {
-		err = -ENODEV;
-		goto out_put_dev;
-	}
-	if (READ_ONCE(dev->mtu) < so->ll.mtu) {
+	else if (READ_ONCE(dev->mtu) < so->ll.mtu)
 		err = -EINVAL;
-		goto out_put_dev;
+	if (!err) {
+		dev_hold(dev);
+		WRITE_ONCE(so->dev, dev);
 	}
+	rcu_read_unlock();
+	if (err)
+		goto out;
 
 	/* switch to new settings */
 	so->ifindex = dev->ifindex;
@@ -1652,35 +1655,39 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l
 		}
 	}
 
-	/* report a down device only for a successful bind() */
-	if (!(dev->flags & IFF_UP))
-		notify_enetdown = 1;
-
-	so->bound = 1;
+	WRITE_ONCE(so->bound, 1);
 
 	/* bind() ok -> hold a reference for so->dev so that isotp_release()
 	 * 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:
-	if (err)
-		so->ifindex = 0; /* reset on failed bind() */
+	/* 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;
 
-	/* remove potential reference from dev_get_by_index() */
+out_put_dev:
+	/* revert so->ifindex and so->dev on failed bind() */
+	if (err) {
+		so->ifindex = 0;
+		WRITE_ONCE(so->dev, NULL);
+	}
+
+	/* remove reference from the device lookup */
 	dev_put(dev);
 out:
-	release_sock(sk);
-	rtnl_unlock();
-
 	if (notify_enetdown) {
 		sk->sk_err = ENETDOWN;
 		if (!sock_flag(sk, SOCK_DEAD))
 			sk_error_report(sk);
 	}
 
+	release_sock(sk);
+
 	return err;
 }
 
@@ -1876,12 +1883,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))
@@ -1896,16 +1912,19 @@ static void isotp_notify(struct isotp_sock *so, unsigned long msg,
 		}
 
 		so->ifindex = 0;
-		so->bound  = 0;
-		so->dev = NULL;
-		release_sock(sk);
+		WRITE_ONCE(so->bound, 0);
+		WRITE_ONCE(so->dev, NULL);
 
 		sk->sk_err = ENODEV;
 		if (!sock_flag(sk, SOCK_DEAD))
 			sk_error_report(sk);
+		release_sock(sk);
 		break;
 
 	case NETDEV_DOWN:
+		if (!READ_ONCE(so->bound))
+			break;
+
 		sk->sk_err = ENETDOWN;
 		if (!sock_flag(sk, SOCK_DEAD))
 			sk_error_report(sk);
diff --git a/net/can/raw.c b/net/can/raw.c
index e1d741a25be3..d6517f71cade 100644
--- a/net/can/raw.c
+++ b/net/can/raw.c
@@ -85,6 +85,7 @@ struct raw_sock {
 	struct sock sk;
 	struct net_device *dev;
 	netdevice_tracker dev_tracker;
+	struct net_device *bind_dev; /* hint for raw_notify(), no reference */
 	struct list_head notifier;
 	int ifindex;
 	unsigned int bound:1;
@@ -224,6 +225,7 @@ static int raw_enable_filters(struct net *net, struct net_device *dev,
 	int i;
 
 	for (i = 0; i < count; i++) {
+		cond_resched();
 		err = can_rx_register(net, dev, filter[i].can_id,
 				      filter[i].can_mask,
 				      raw_rcv, sk, "raw", sk);
@@ -258,9 +260,11 @@ static void raw_disable_filters(struct net *net, struct net_device *dev,
 {
 	int i;
 
-	for (i = 0; i < count; i++)
+	for (i = 0; i < count; i++) {
 		can_rx_unregister(net, dev, filter[i].can_id,
 				  filter[i].can_mask, raw_rcv, sk);
+		cond_resched();
+	}
 }
 
 static inline void raw_disable_errfilter(struct net *net,
@@ -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,13 +345,13 @@ 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);
 
 		sk->sk_err = ENODEV;
 		if (!sock_flag(sk, SOCK_DEAD))
 			sk_error_report(sk);
+		release_sock(sk);
 		break;
 
 	case NETDEV_DOWN:
@@ -381,6 +400,7 @@ static int raw_init(struct sock *sk)
 	ro->bound            = 0;
 	ro->ifindex          = 0;
 	ro->dev              = NULL;
+	ro->bind_dev         = NULL;
 
 	/* set default filter to single entry dfilter */
 	ro->dfilter.can_id   = 0;
@@ -431,13 +451,15 @@ static int raw_release(struct socket *sock)
 	list_del(&ro->notifier);
 	spin_unlock(&raw_notifier_lock);
 
-	rtnl_lock();
 	lock_sock(sk);
 
-	/* remove current filters & unregister */
+	/* remove current filters & unregister
+	 * the tracked reference ro->dev keeps the device's receive lists
+	 * accessible, even after a namespace change
+	 */
 	if (ro->bound) {
 		if (ro->dev) {
-			raw_disable_allfilters(dev_net(ro->dev), ro->dev, sk);
+			raw_disable_allfilters(net, ro->dev, sk);
 			netdev_put(ro->dev, &ro->dev_tracker);
 		} else {
 			raw_disable_allfilters(net, NULL, sk);
@@ -456,7 +478,6 @@ static int raw_release(struct socket *sock)
 	sock->sk = NULL;
 
 	release_sock(sk);
-	rtnl_unlock();
 
 	sock_prot_inuse_add(net, sk->sk_prot, -1);
 	sock_put(sk);
@@ -479,14 +500,25 @@ static int raw_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int len
 	if (addr->can_family != AF_CAN)
 		return -EINVAL;
 
-	rtnl_lock();
+	/* lock_sock() serializes the binding with raw_notify() */
 	lock_sock(sk);
 
 	if (ro->bound && addr->can_ifindex == ro->ifindex)
 		goto out;
 
 	if (addr->can_ifindex) {
-		dev = dev_get_by_index(sock_net(sk), addr->can_ifindex);
+		/* Publish the device inside the RCU section of the lookup:
+		 * When it is unlisted concurrently, either raw_notify() sees
+		 * ro->bind_dev after synchronize_net() or the lookup fails.
+		 */
+		rcu_read_lock();
+		dev = dev_get_by_index_rcu(sock_net(sk), addr->can_ifindex);
+		if (dev) {
+			dev_hold(dev);
+			/* pairs with smp_load_acquire() in raw_notify() */
+			smp_store_release(&ro->bind_dev, dev);
+		}
+		rcu_read_unlock();
 		if (!dev) {
 			err = -ENODEV;
 			goto out;
@@ -496,9 +528,6 @@ static int raw_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int len
 			goto out_put_dev;
 		}
 
-		if (!(dev->flags & IFF_UP))
-			notify_enetdown = 1;
-
 		ifindex = dev->ifindex;
 
 		/* filters set by default/setsockopt */
@@ -517,7 +546,7 @@ static int raw_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int len
 		if (ro->bound) {
 			/* unregister old filters */
 			if (ro->dev) {
-				raw_disable_allfilters(dev_net(ro->dev),
+				raw_disable_allfilters(sock_net(sk),
 						       ro->dev, sk);
 				/* drop reference to old ro->dev */
 				netdev_put(ro->dev, &ro->dev_tracker);
@@ -528,24 +557,32 @@ 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);
 		if (ro->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;
 		if (!sock_flag(sk, SOCK_DEAD))
 			sk_error_report(sk);
 	}
 
+	release_sock(sk);
+
 	return err;
 }
 
@@ -599,8 +636,13 @@ static int raw_setsockopt_locked(struct socket *sock, int optname,
 				return -EFAULT;
 		}
 
+		/* The reg_state check is only a shortcut: On a concurrent
+		 * unregistration or namespace change raw_notify() waits for
+		 * lock_sock() and removes the filters set here.
+		 */
 		dev = ro->dev;
-		if (ro->bound && dev && dev->reg_state != NETREG_REGISTERED) {
+		if (ro->bound && dev &&
+		    READ_ONCE(dev->reg_state) != NETREG_REGISTERED) {
 			if (count > 1)
 				kfree(filter);
 			return -ENODEV;
@@ -648,8 +690,10 @@ static int raw_setsockopt_locked(struct socket *sock, int optname,
 
 		err_mask &= CAN_ERR_MASK;
 
+		/* lockless shortcut, see CAN_RAW_FILTER */
 		dev = ro->dev;
-		if (ro->bound && dev && dev->reg_state != NETREG_REGISTERED)
+		if (ro->bound && dev &&
+		    READ_ONCE(dev->reg_state) != NETREG_REGISTERED)
 			return -ENODEV;
 
 		/* remove current error mask */
@@ -756,13 +800,11 @@ static int raw_setsockopt(struct socket *sock, int level, int optname,
 	if (level != SOL_CAN_RAW)
 		return -EINVAL;
 
-	rtnl_lock();
 	lock_sock(sk);
 
 	err = raw_setsockopt_locked(sock, optname, optval, optlen);
 
 	release_sock(sk);
-	rtnl_unlock();
 
 	return err;
 }
-- 
2.53.0


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

* [PATCH net 09/15] can: raw, isotp: copy socket options outside of lock_sock()
  2026-11-03 20:30 [PATCH net 0/15] pull-request: can 2026-11-03 Marc Kleine-Budde
                   ` (7 preceding siblings ...)
  2026-11-03 20:30 ` [PATCH net 08/15] can: protect the receive lists with a per-device lock Marc Kleine-Budde
@ 2026-11-03 20:30 ` Marc Kleine-Budde
  2026-11-04 20:46   ` netdev-bot+sashiko
  2026-11-03 20:30 ` [PATCH net 10/15] can: proc: reset pkg_stats atomics individually Marc Kleine-Budde
                   ` (5 subsequent siblings)
  14 siblings, 1 reply; 25+ messages in thread
From: Marc Kleine-Budde @ 2026-11-03 20:30 UTC (permalink / raw)
  To: netdev
  Cc: davem, kuba, linux-can, kernel, Oliver Hartkopp, stable,
	Marc Kleine-Budde

From: Oliver Hartkopp <socketcan@hartkopp.net>

The CAN_RAW and CAN_ISOTP specific socket options are copied from and
to user space while holding lock_sock(). A copy that faults or stalls,
e.g. on a userfaultfd or FUSE backed mapping, holds lock_sock() for its
whole duration.

raw_notify() and isotp_notify() take lock_sock() of a socket that is
bound to the vanishing device while holding RTNL. As the CAN_RAW
options can also be changed on a bound socket, a stalled copy can block
RTNL on an unregistration or namespace change of the bound device.
Before "can: protect the receive lists with a per-device lock",
raw_setsockopt() even held RTNL itself during the copy. CAN_ISOTP only
copies its options for unbound sockets, which isotp_notify() skips
without taking lock_sock().

Keep the user space copies out of the locked sections: Check the option
name and length and copy the option values, including the CAN_RAW_FILTER
filters, to kernel memory before lock_sock(). raw_setsockopt_locked()
and isotp_setsockopt_locked() get the copied values directly and drop
their now redundant length checks and copies. As a side effect, a
failing copy can not leave partially overwritten options behind
anymore. In raw_getsockopt() duplicate the filters under lock_sock() and
copy them to user space after release_sock().

As the option name and length are now checked before lock_sock(), the
order of the checks in isotp_setsockopt() and accordingly the order of
the returned error values changes. This is in line with POSIX, which
does not define an order in which multiple error conditions are
detected and reported.

The generic SOL_SOCKET options in sk_setsockopt() are not affected by
this patch.

Fixes: c18ce101f2e4 ("[CAN]: Add raw protocol")
Fixes: 1e5185c09058 ("can: raw: add locking for raw flags bitfield")
Cc: stable@vger.kernel.org # depends on "can: protect the receive lists with a per-device lock"
Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
Link: https://patch.msgid.link/20261027-can-rx-lock-v12-4-eb5afc2d8b9b@hartkopp.net
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
---
 net/can/isotp.c | 106 +++++++++++++++------------
 net/can/raw.c   | 186 +++++++++++++++++++++++++-----------------------
 2 files changed, 158 insertions(+), 134 deletions(-)

diff --git a/net/can/isotp.c b/net/can/isotp.c
index 1e40825b58cb..5e2de051fc5e 100644
--- a/net/can/isotp.c
+++ b/net/can/isotp.c
@@ -1709,8 +1709,8 @@ static int isotp_getname(struct socket *sock, struct sockaddr *uaddr, int peer)
 	return ISOTP_MIN_NAMELEN;
 }
 
-static int isotp_setsockopt_locked(struct socket *sock, int level, int optname,
-			    sockptr_t optval, unsigned int optlen)
+static int isotp_setsockopt_locked(struct socket *sock, int optname,
+				   const void *val)
 {
 	struct sock *sk = sock->sk;
 	struct isotp_sock *so = isotp_sk(sk);
@@ -1721,11 +1721,7 @@ static int isotp_setsockopt_locked(struct socket *sock, int level, int optname,
 
 	switch (optname) {
 	case CAN_ISOTP_OPTS:
-		if (optlen != sizeof(struct can_isotp_options))
-			return -EINVAL;
-
-		if (copy_from_sockptr(&so->opt, optval, optlen))
-			return -EFAULT;
+		memcpy(&so->opt, val, sizeof(so->opt));
 
 		/* no separate rx_ext_address is given => use ext_address */
 		if (!(so->opt.flags & CAN_ISOTP_RX_EXT_ADDR))
@@ -1750,55 +1746,37 @@ static int isotp_setsockopt_locked(struct socket *sock, int level, int optname,
 		break;
 
 	case CAN_ISOTP_RECV_FC:
-		if (optlen != sizeof(struct can_isotp_fc_options))
-			return -EINVAL;
-
-		if (copy_from_sockptr(&so->rxfc, optval, optlen))
-			return -EFAULT;
+		memcpy(&so->rxfc, val, sizeof(so->rxfc));
 		break;
 
 	case CAN_ISOTP_TX_STMIN:
-		if (optlen != sizeof(u32))
-			return -EINVAL;
-
-		if (copy_from_sockptr(&so->force_tx_stmin, optval, optlen))
-			return -EFAULT;
+		so->force_tx_stmin = *(const u32 *)val;
 		break;
 
 	case CAN_ISOTP_RX_STMIN:
-		if (optlen != sizeof(u32))
-			return -EINVAL;
-
-		if (copy_from_sockptr(&so->force_rx_stmin, optval, optlen))
-			return -EFAULT;
+		so->force_rx_stmin = *(const u32 *)val;
 		break;
 
-	case CAN_ISOTP_LL_OPTS:
-		if (optlen == sizeof(struct can_isotp_ll_options)) {
-			struct can_isotp_ll_options ll;
+	case CAN_ISOTP_LL_OPTS: {
+		const struct can_isotp_ll_options *ll = val;
 
-			if (copy_from_sockptr(&ll, optval, optlen))
-				return -EFAULT;
-
-			/* check for correct ISO 11898-1 DLC data length */
-			if (ll.tx_dl != padlen(ll.tx_dl))
-				return -EINVAL;
-
-			if (ll.mtu != CAN_MTU && ll.mtu != CANFD_MTU)
-				return -EINVAL;
-
-			if (ll.mtu == CAN_MTU &&
-			    (ll.tx_dl > CAN_MAX_DLEN || ll.tx_flags != 0))
-				return -EINVAL;
-
-			memcpy(&so->ll, &ll, sizeof(ll));
-
-			/* set ll_dl for tx path to similar place as for rx */
-			so->tx.ll_dl = ll.tx_dl;
-		} else {
+		/* check for correct ISO 11898-1 DLC data length */
+		if (ll->tx_dl != padlen(ll->tx_dl))
 			return -EINVAL;
-		}
+
+		if (ll->mtu != CAN_MTU && ll->mtu != CANFD_MTU)
+			return -EINVAL;
+
+		if (ll->mtu == CAN_MTU &&
+		    (ll->tx_dl > CAN_MAX_DLEN || ll->tx_flags != 0))
+			return -EINVAL;
+
+		memcpy(&so->ll, ll, sizeof(*ll));
+
+		/* set ll_dl for tx path to similar place as for rx */
+		so->tx.ll_dl = ll->tx_dl;
 		break;
+	}
 
 	default:
 		ret = -ENOPROTOOPT;
@@ -1812,13 +1790,49 @@ static int isotp_setsockopt(struct socket *sock, int level, int optname,
 
 {
 	struct sock *sk = sock->sk;
+	union {
+		struct can_isotp_options opt;
+		struct can_isotp_fc_options rxfc;
+		struct can_isotp_ll_options ll;
+		u32 stmin;
+	} val;
+	unsigned int size;
 	int ret;
 
 	if (level != SOL_CAN_ISOTP)
 		return -EINVAL;
 
+	/* copy the option values from user space before lock_sock() */
+	switch (optname) {
+	case CAN_ISOTP_OPTS:
+		size = sizeof(val.opt);
+		break;
+
+	case CAN_ISOTP_RECV_FC:
+		size = sizeof(val.rxfc);
+		break;
+
+	case CAN_ISOTP_TX_STMIN:
+	case CAN_ISOTP_RX_STMIN:
+		size = sizeof(val.stmin);
+		break;
+
+	case CAN_ISOTP_LL_OPTS:
+		size = sizeof(val.ll);
+		break;
+
+	default:
+		return -ENOPROTOOPT;
+	}
+
+	if (optlen != size)
+		return -EINVAL;
+
+	if (copy_from_sockptr(&val, optval, size))
+		return -EFAULT;
+
 	lock_sock(sk);
-	ret = isotp_setsockopt_locked(sock, level, optname, optval, optlen);
+	ret = isotp_setsockopt_locked(sock, optname, &val);
 	release_sock(sk);
 	return ret;
 }
diff --git a/net/can/raw.c b/net/can/raw.c
index d6517f71cade..0926d5686e41 100644
--- a/net/can/raw.c
+++ b/net/can/raw.c
@@ -604,63 +604,36 @@ static int raw_getname(struct socket *sock, struct sockaddr *uaddr,
 }
 
 static int raw_setsockopt_locked(struct socket *sock, int optname,
-				 sockptr_t optval, unsigned int optlen)
+				 const void *val, struct can_filter *filter,
+				 int count)
 {
 	struct sock *sk = sock->sk;
 	struct raw_sock *ro = raw_sk(sk);
-	struct can_filter *filter = NULL;  /* dyn. alloc'ed filters */
-	struct can_filter sfilter;         /* single filter */
 	struct net_device *dev = NULL;
-	can_err_mask_t err_mask = 0;
-	int count = 0;
+	can_err_mask_t err_mask;
 	int flag;
 	int err = 0;
 
 	switch (optname) {
 	case CAN_RAW_FILTER:
-		if (optlen % sizeof(struct can_filter) != 0)
-			return -EINVAL;
-
-		if (optlen > CAN_RAW_FILTER_MAX * sizeof(struct can_filter))
-			return -EINVAL;
-
-		count = optlen / sizeof(struct can_filter);
-
-		if (count > 1) {
-			/* filter does not fit into dfilter => alloc space */
-			filter = memdup_sockptr(optval, optlen);
-			if (IS_ERR(filter))
-				return PTR_ERR(filter);
-		} else if (count == 1) {
-			if (copy_from_sockptr(&sfilter, optval, sizeof(sfilter)))
-				return -EFAULT;
-		}
-
-		/* The reg_state check is only a shortcut: On a concurrent
+		/* @filter has been copied by raw_setsockopt(). It is linked
+		 * to the socket on success and freed by the caller on error.
+		 *
+		 * The reg_state check is only a shortcut: On a concurrent
 		 * unregistration or namespace change raw_notify() waits for
 		 * lock_sock() and removes the filters set here.
 		 */
 		dev = ro->dev;
 		if (ro->bound && dev &&
-		    READ_ONCE(dev->reg_state) != NETREG_REGISTERED) {
-			if (count > 1)
-				kfree(filter);
+		    READ_ONCE(dev->reg_state) != NETREG_REGISTERED)
 			return -ENODEV;
-		}
 
 		if (ro->bound) {
 			/* (try to) register the new filters */
-			if (count == 1)
-				err = raw_enable_filters(sock_net(sk), dev, sk,
-							 &sfilter, 1);
-			else
-				err = raw_enable_filters(sock_net(sk), dev, sk,
-							 filter, count);
-			if (err) {
-				if (count > 1)
-					kfree(filter);
+			err = raw_enable_filters(sock_net(sk), dev, sk, filter,
+						 count);
+			if (err)
 				return err;
-			}
 
 			/* remove old filter registrations */
 			raw_disable_filters(sock_net(sk), dev, sk, ro->filter,
@@ -674,7 +647,7 @@ static int raw_setsockopt_locked(struct socket *sock, int optname,
 		/* link new filters to the socket */
 		if (count == 1) {
 			/* copy filter data for single filter */
-			ro->dfilter = sfilter;
+			ro->dfilter = *filter;
 			filter = &ro->dfilter;
 		}
 		ro->filter = filter;
@@ -682,13 +655,7 @@ static int raw_setsockopt_locked(struct socket *sock, int optname,
 		break;
 
 	case CAN_RAW_ERR_FILTER:
-		if (optlen != sizeof(err_mask))
-			return -EINVAL;
-
-		if (copy_from_sockptr(&err_mask, optval, optlen))
-			return -EFAULT;
-
-		err_mask &= CAN_ERR_MASK;
+		err_mask = *(const can_err_mask_t *)val & CAN_ERR_MASK;
 
 		/* lockless shortcut, see CAN_RAW_FILTER */
 		dev = ro->dev;
@@ -715,31 +682,15 @@ static int raw_setsockopt_locked(struct socket *sock, int optname,
 		break;
 
 	case CAN_RAW_LOOPBACK:
-		if (optlen != sizeof(flag))
-			return -EINVAL;
-
-		if (copy_from_sockptr(&flag, optval, optlen))
-			return -EFAULT;
-
-		ro->loopback = !!flag;
+		ro->loopback = !!*(const int *)val;
 		break;
 
 	case CAN_RAW_RECV_OWN_MSGS:
-		if (optlen != sizeof(flag))
-			return -EINVAL;
-
-		if (copy_from_sockptr(&flag, optval, optlen))
-			return -EFAULT;
-
-		ro->recv_own_msgs = !!flag;
+		ro->recv_own_msgs = !!*(const int *)val;
 		break;
 
 	case CAN_RAW_FD_FRAMES:
-		if (optlen != sizeof(flag))
-			return -EINVAL;
-
-		if (copy_from_sockptr(&flag, optval, optlen))
-			return -EFAULT;
+		flag = *(const int *)val;
 
 		/* Enabling CAN XL includes CAN FD */
 		if (ro->xl_frames && !flag)
@@ -749,13 +700,7 @@ static int raw_setsockopt_locked(struct socket *sock, int optname,
 		break;
 
 	case CAN_RAW_XL_FRAMES:
-		if (optlen != sizeof(flag))
-			return -EINVAL;
-
-		if (copy_from_sockptr(&flag, optval, optlen))
-			return -EFAULT;
-
-		ro->xl_frames = !!flag;
+		ro->xl_frames = !!*(const int *)val;
 
 		/* Enabling CAN XL includes CAN FD */
 		if (ro->xl_frames)
@@ -763,11 +708,7 @@ static int raw_setsockopt_locked(struct socket *sock, int optname,
 		break;
 
 	case CAN_RAW_XL_VCID_OPTS:
-		if (optlen != sizeof(ro->raw_vcid_opts))
-			return -EINVAL;
-
-		if (copy_from_sockptr(&ro->raw_vcid_opts, optval, optlen))
-			return -EFAULT;
+		memcpy(&ro->raw_vcid_opts, val, sizeof(ro->raw_vcid_opts));
 
 		/* prepare 32 bit values for handling in hot path */
 		ro->tx_vcid_shifted = ro->raw_vcid_opts.tx_vcid << CANXL_VCID_OFFSET;
@@ -776,13 +717,7 @@ static int raw_setsockopt_locked(struct socket *sock, int optname,
 		break;
 
 	case CAN_RAW_JOIN_FILTERS:
-		if (optlen != sizeof(flag))
-			return -EINVAL;
-
-		if (copy_from_sockptr(&flag, optval, optlen))
-			return -EFAULT;
-
-		ro->join_filters = !!flag;
+		ro->join_filters = !!*(const int *)val;
 		break;
 
 	default:
@@ -795,17 +730,84 @@ static int raw_setsockopt(struct socket *sock, int level, int optname,
 			  sockptr_t optval, unsigned int optlen)
 {
 	struct sock *sk = sock->sk;
+	struct can_filter *filter = NULL;  /* dyn. alloc'ed filters */
+	struct can_filter sfilter;         /* single filter */
+	union {
+		can_err_mask_t err_mask;
+		int flag;
+		struct can_raw_vcid_options vcid_opts;
+	} val;
+	unsigned int size = 0;
+	int count = 0;
 	int err;
 
 	if (level != SOL_CAN_RAW)
 		return -EINVAL;
 
+	/* Copy the option values from user space before lock_sock(), so a
+	 * stalled copy can not block raw_notify() waiting for lock_sock()
+	 * under RTNL.
+	 */
+	switch (optname) {
+	case CAN_RAW_FILTER:
+		if (optlen % sizeof(struct can_filter) != 0)
+			return -EINVAL;
+
+		if (optlen > CAN_RAW_FILTER_MAX * sizeof(struct can_filter))
+			return -EINVAL;
+
+		count = optlen / sizeof(struct can_filter);
+
+		if (count > 1) {
+			/* filter does not fit into dfilter => alloc space */
+			filter = memdup_sockptr(optval, optlen);
+			if (IS_ERR(filter))
+				return PTR_ERR(filter);
+		} else if (count == 1) {
+			if (copy_from_sockptr(&sfilter, optval, sizeof(sfilter)))
+				return -EFAULT;
+			filter = &sfilter;
+		}
+		break;
+
+	case CAN_RAW_ERR_FILTER:
+		size = sizeof(val.err_mask);
+		break;
+
+	case CAN_RAW_XL_VCID_OPTS:
+		size = sizeof(val.vcid_opts);
+		break;
+
+	case CAN_RAW_LOOPBACK:
+	case CAN_RAW_RECV_OWN_MSGS:
+	case CAN_RAW_FD_FRAMES:
+	case CAN_RAW_XL_FRAMES:
+	case CAN_RAW_JOIN_FILTERS:
+		size = sizeof(val.flag);
+		break;
+
+	default:
+		return -ENOPROTOOPT;
+	}
+
+	if (size) {
+		if (optlen != size)
+			return -EINVAL;
+
+		if (copy_from_sockptr(&val, optval, size))
+			return -EFAULT;
+	}
+
 	lock_sock(sk);
 
-	err = raw_setsockopt_locked(sock, optname, optval, optlen);
+	err = raw_setsockopt_locked(sock, optname, &val, filter, count);
 
 	release_sock(sk);
 
+	/* on error free the filters allocated for CAN_RAW_FILTER above */
+	if (err && count > 1)
+		kfree(filter);
+
 	return err;
 }
 
@@ -826,6 +828,7 @@ static int raw_getsockopt(struct socket *sock, int level, int optname,
 
 	switch (optname) {
 	case CAN_RAW_FILTER: {
+		struct can_filter *filter = NULL;
 		int err = 0;
 
 		lock_sock(sk);
@@ -840,15 +843,22 @@ static int raw_getsockopt(struct socket *sock, int level, int optname,
 			} else {
 				if (len > fsize)
 					len = fsize;
-				if (copy_to_iter(ro->filter, len,
-						 &opt->iter_out) != len)
-					err = -EFAULT;
+				/* copy to user space after release_sock() */
+				filter = kmemdup(ro->filter, len, GFP_KERNEL);
+				if (!filter)
+					err = -ENOMEM;
 			}
 		} else {
 			len = 0;
 		}
 		release_sock(sk);
 
+		if (filter) {
+			if (copy_to_iter(filter, len, &opt->iter_out) != len)
+				err = -EFAULT;
+			kfree(filter);
+		}
+
 		if (!err)
 			opt->optlen = len;
 		return err;
-- 
2.53.0


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

* [PATCH net 10/15] can: proc: reset pkg_stats atomics individually
  2026-11-03 20:30 [PATCH net 0/15] pull-request: can 2026-11-03 Marc Kleine-Budde
                   ` (8 preceding siblings ...)
  2026-11-03 20:30 ` [PATCH net 09/15] can: raw, isotp: copy socket options outside of lock_sock() Marc Kleine-Budde
@ 2026-11-03 20:30 ` Marc Kleine-Budde
  2026-11-03 20:30 ` [PATCH net 11/15] can: proc: skip stats calculation after reset in can_stat_update() Marc Kleine-Budde
                   ` (4 subsequent siblings)
  14 siblings, 0 replies; 25+ messages in thread
From: Marc Kleine-Budde @ 2026-11-03 20:30 UTC (permalink / raw)
  To: netdev
  Cc: davem, kuba, linux-can, kernel, Runyu Xiao, stable,
	Oliver Hartkopp, Marc Kleine-Budde

From: Runyu Xiao <runyu.xiao@seu.edu.cn>

Commit 80b5f90158d1 ("can: statistics: use atomic access in hot path")
converted several members of struct can_pkg_stats to atomic_long_t and
updated the hot TX/RX and procfs read paths to use atomic_long_*()
helpers. However, can_init_stats() still clears the whole struct with
memset().

can_init_stats() is reached from can_stat_update() in timer context and
also from the procfs reset path. Those paths can run while the TX/RX hot
paths are concurrently updating rx_frames, tx_frames, matches and their
*_delta counters. Hitting the whole-struct memset() in that window
performs plain writes to fields that otherwise follow an atomic_long_t
access contract, which can lose or mix live statistics updates.

This issue was found by source-level API-misuse analysis looking for
whole-object resets left behind after atomic_long_t conversions, then
manually audited on Linux v6.18.21.

Replace the whole-struct memset() with a helper that resets the
atomic_long_t counters via atomic_long_set() and clears the derived
scalar statistics explicitly. This preserves the existing reset
semantics for scalar fields while restoring atomic access discipline for
the live counters.

Build-tested by compiling net/can/proc.o on x86_64 netdev/main.
Runtime-tested with a QEMU + vcan setup on Linux v6.18.21 by driving
concurrent traffic and reset_stats reads, which reproduced inconsistent
exported statistics before the fix.

[Hartkopp] can_stat_update() also read the *_delta counters at the
beginning and cleared them with atomic_long_set() at the end. Increments
from the hot path in between were lost for both the current and the next
interval. Use atomic_long_xchg() to fetch and clear the *_delta counters
in one atomic step instead.

On x86_64 atomic_long_set() is a plain store like memset(), so the
memset() is an actual problem only on architectures with lock-based
atomics (e.g. parisc). The inconsistencies seen on x86_64 come from the
*_delta handling and from the stale snapshots, which are fixed in "can:
proc: skip stats calculation after reset in can_stat_update()". As
before, the counters are reset one at a time and the derived scalar
statistics use plain stores.

Fixes: 80b5f90158d1 ("can: statistics: use atomic access in hot path")
Cc: stable@vger.kernel.org
Signed-off-by: Runyu Xiao <runyu.xiao@seu.edu.cn>
Acked-by: Oliver Hartkopp <socketcan@hartkopp.net>
Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
Link: https://patch.msgid.link/20261027-can-rx-lock-v12-5-eb5afc2d8b9b@hartkopp.net
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
---
 net/can/proc.c | 39 +++++++++++++++++++++++++++++----------
 1 file changed, 29 insertions(+), 10 deletions(-)

diff --git a/net/can/proc.c b/net/can/proc.c
index de4d05ae3459..70104f13077e 100644
--- a/net/can/proc.c
+++ b/net/can/proc.c
@@ -76,16 +76,39 @@ static const char rx_list_name[][8] = {
  * af_can statistics stuff
  */
 
+static void can_reset_pkg_stats(struct can_pkg_stats *pkg_stats)
+{
+	atomic_long_set(&pkg_stats->rx_frames, 0);
+	atomic_long_set(&pkg_stats->tx_frames, 0);
+	atomic_long_set(&pkg_stats->matches, 0);
+
+	pkg_stats->total_rx_rate = 0;
+	pkg_stats->total_tx_rate = 0;
+	pkg_stats->total_rx_match_ratio = 0;
+
+	pkg_stats->current_rx_rate = 0;
+	pkg_stats->current_tx_rate = 0;
+	pkg_stats->current_rx_match_ratio = 0;
+
+	pkg_stats->max_rx_rate = 0;
+	pkg_stats->max_tx_rate = 0;
+	pkg_stats->max_rx_match_ratio = 0;
+
+	atomic_long_set(&pkg_stats->rx_frames_delta, 0);
+	atomic_long_set(&pkg_stats->tx_frames_delta, 0);
+	atomic_long_set(&pkg_stats->matches_delta, 0);
+}
+
 static void can_init_stats(struct net *net)
 {
 	struct can_pkg_stats *pkg_stats = net->can.pkg_stats;
 	struct can_rcv_lists_stats *rcv_lists_stats = net->can.rcv_lists_stats;
 	/*
-	 * This memset function is called from a timer context (when
+	 * This stats reset is called from a timer context (when
 	 * can_stattimer is active which is the default) OR in a process
 	 * context (reading the proc_fs when can_stattimer is disabled).
 	 */
-	memset(pkg_stats, 0, sizeof(struct can_pkg_stats));
+	can_reset_pkg_stats(pkg_stats);
 	pkg_stats->jiffies_init = jiffies;
 
 	rcv_lists_stats->stats_reset++;
@@ -121,9 +144,10 @@ void can_stat_update(struct timer_list *t)
 	long rx_frames = atomic_long_read(&pkg_stats->rx_frames);
 	long tx_frames = atomic_long_read(&pkg_stats->tx_frames);
 	long matches = atomic_long_read(&pkg_stats->matches);
-	long rx_frames_delta = atomic_long_read(&pkg_stats->rx_frames_delta);
-	long tx_frames_delta = atomic_long_read(&pkg_stats->tx_frames_delta);
-	long matches_delta = atomic_long_read(&pkg_stats->matches_delta);
+	/* fetch and clear values for 'current rate' calculation */
+	long rx_frames_delta = atomic_long_xchg(&pkg_stats->rx_frames_delta, 0);
+	long tx_frames_delta = atomic_long_xchg(&pkg_stats->tx_frames_delta, 0);
+	long matches_delta = atomic_long_xchg(&pkg_stats->matches_delta, 0);
 
 	/* restart counting in timer context on user request */
 	if (user_reset)
@@ -172,11 +196,6 @@ void can_stat_update(struct timer_list *t)
 	if (pkg_stats->max_rx_match_ratio < pkg_stats->current_rx_match_ratio)
 		pkg_stats->max_rx_match_ratio = pkg_stats->current_rx_match_ratio;
 
-	/* clear values for 'current rate' calculation */
-	atomic_long_set(&pkg_stats->tx_frames_delta, 0);
-	atomic_long_set(&pkg_stats->rx_frames_delta, 0);
-	atomic_long_set(&pkg_stats->matches_delta, 0);
-
 	/* restart timer (one second) */
 	mod_timer(&net->can.stattimer, round_jiffies(jiffies + HZ));
 }
-- 
2.53.0


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

* [PATCH net 11/15] can: proc: skip stats calculation after reset in can_stat_update()
  2026-11-03 20:30 [PATCH net 0/15] pull-request: can 2026-11-03 Marc Kleine-Budde
                   ` (9 preceding siblings ...)
  2026-11-03 20:30 ` [PATCH net 10/15] can: proc: reset pkg_stats atomics individually Marc Kleine-Budde
@ 2026-11-03 20:30 ` Marc Kleine-Budde
  2026-11-03 20:30 ` [PATCH net 12/15] can: proc: remove pointers from CAN specific proc output Marc Kleine-Budde
                   ` (3 subsequent siblings)
  14 siblings, 0 replies; 25+ messages in thread
From: Marc Kleine-Budde @ 2026-11-03 20:30 UTC (permalink / raw)
  To: netdev
  Cc: davem, kuba, linux-can, kernel, Oliver Hartkopp, stable,
	Marc Kleine-Budde

From: Oliver Hartkopp <socketcan@hartkopp.net>

Commit 80b5f90158d1 ("can: statistics: use atomic access in hot path")
introduced local snapshots of the statistics counters at the beginning
of can_stat_update(). When can_init_stats() resets the statistics, these
stale pre-reset snapshots are still used for the following total/current
/max calculations and overwrite the freshly reset values. Additionally a
jiffies tick between the snapshot and the reset could trigger a second
can_init_stats() call.

Merge the reset conditions into a single check and skip the calculation
for the current interval when the statistics have been reset.

Fixes: 80b5f90158d1 ("can: statistics: use atomic access in hot path")
Cc: stable@vger.kernel.org
Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
Link: https://patch.msgid.link/20261027-can-rx-lock-v12-6-eb5afc2d8b9b@hartkopp.net
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
---
 net/can/proc.c | 30 ++++++++++++------------------
 1 file changed, 12 insertions(+), 18 deletions(-)

diff --git a/net/can/proc.c b/net/can/proc.c
index 70104f13077e..fb144d2adc35 100644
--- a/net/can/proc.c
+++ b/net/can/proc.c
@@ -149,25 +149,18 @@ void can_stat_update(struct timer_list *t)
 	long tx_frames_delta = atomic_long_xchg(&pkg_stats->tx_frames_delta, 0);
 	long matches_delta = atomic_long_xchg(&pkg_stats->matches_delta, 0);
 
-	/* restart counting in timer context on user request */
-	if (user_reset)
-		can_init_stats(net);
-
-	/* restart counting on jiffies overflow */
-	if (j < pkg_stats->jiffies_init)
-		can_init_stats(net);
-
-	/* prevent overflow in calc_rate() */
-	if (rx_frames > (LONG_MAX / HZ))
-		can_init_stats(net);
-
-	/* prevent overflow in calc_rate() */
-	if (tx_frames > (LONG_MAX / HZ))
-		can_init_stats(net);
-
-	/* matches overflow - very improbable */
-	if (matches > (LONG_MAX / 100))
+	/*
+	 * Restart counting in timer context on user request, on jiffies
+	 * overflow or to prevent overflows in calc_rate() and in the
+	 * match ratio calculation. The local snapshots are stale after
+	 * the reset, so skip the calculation for this interval.
+	 */
+	if (user_reset || j < pkg_stats->jiffies_init ||
+	    rx_frames > (LONG_MAX / HZ) || tx_frames > (LONG_MAX / HZ) ||
+	    matches > (LONG_MAX / 100)) {
 		can_init_stats(net);
+		goto out;
+	}
 
 	/* calc total values */
 	if (rx_frames)
@@ -196,6 +189,7 @@ void can_stat_update(struct timer_list *t)
 	if (pkg_stats->max_rx_match_ratio < pkg_stats->current_rx_match_ratio)
 		pkg_stats->max_rx_match_ratio = pkg_stats->current_rx_match_ratio;
 
+out:
 	/* restart timer (one second) */
 	mod_timer(&net->can.stattimer, round_jiffies(jiffies + HZ));
 }
-- 
2.53.0


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

* [PATCH net 12/15] can: proc: remove pointers from CAN specific proc output
  2026-11-03 20:30 [PATCH net 0/15] pull-request: can 2026-11-03 Marc Kleine-Budde
                   ` (10 preceding siblings ...)
  2026-11-03 20:30 ` [PATCH net 11/15] can: proc: skip stats calculation after reset in can_stat_update() Marc Kleine-Budde
@ 2026-11-03 20:30 ` Marc Kleine-Budde
  2026-11-03 20:30 ` [PATCH net 13/15] can: dev: can_fill_info(): don't wake up controller if interface is down Marc Kleine-Budde
                   ` (2 subsequent siblings)
  14 siblings, 0 replies; 25+ messages in thread
From: Marc Kleine-Budde @ 2026-11-03 20:30 UTC (permalink / raw)
  To: netdev
  Cc: davem, kuba, linux-can, kernel, Oliver Hartkopp,
	Sebastian Andrzej Siewior, Marc Kleine-Budde

From: Oliver Hartkopp <socketcan@hartkopp.net>

The proc content in /proc/net/can/ and /proc/net/can-bcm/ is intended to
check the internal filter lists (af_can, can_raw) and the efficiency and
functionality of can_bcm jobs. While exposing kernel addresses was
acceptable when this code was written the times have changed and multiple
attempts have been made to hash or remove such now sensitive data.

This patch removes the disclosure of pointers and instead provides the
function names and sock inode numbers when available. When the function
name can not be resolved (e.g. KALLSYMS=n or stripped module symbols)
%ps would fall back to the raw text address. Therefore (unknown) is
printed instead in this case. As there's no known tooling around the CAN
specific proc output breaking the ABI with this rework creates no issue.

Suggested-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
Link: https://patch.msgid.link/20261027-can-rx-lock-v12-7-eb5afc2d8b9b@hartkopp.net
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
---
 Documentation/networking/can.rst | 16 ++++++++++------
 include/linux/can/core.h         |  2 +-
 net/can/af_can.c                 |  6 +++---
 net/can/af_can.h                 |  2 +-
 net/can/bcm.c                    | 14 +++++++-------
 net/can/gw.c                     |  2 +-
 net/can/isotp.c                  |  4 ++--
 net/can/j1939/main.c             |  2 +-
 net/can/proc.c                   | 26 ++++++++++++++++----------
 net/can/raw.c                    |  4 ++--
 10 files changed, 44 insertions(+), 34 deletions(-)

diff --git a/Documentation/networking/can.rst b/Documentation/networking/can.rst
index 536ff411da1d..51bc70748f75 100644
--- a/Documentation/networking/can.rst
+++ b/Documentation/networking/can.rst
@@ -1040,17 +1040,21 @@ As described in :ref:`socketcan-receive-lists` the SocketCAN core uses several f
 lists to deliver received CAN frames to CAN protocol modules. These
 receive lists, their filters and the count of filter matches can be
 checked in the appropriate receive list. All entries contain the
-device and a protocol module identifier::
+CAN network device, the filter (can_id/can_mask), the number of filter
+matches, the inode number of the subscribing socket and the name of the
+receive function. A sock_inode of 0 denotes a kernel internal subscriber
+without a socket (e.g. CAN gateway or J1939). When the function name can
+not be resolved (e.g. with CONFIG_KALLSYMS=n) "(unknown)" is printed::
 
     foo@bar:~$ cat /proc/net/can/rcvlist_all
 
     receive list 'rx_all':
-      (vcan3: no entry)
-      (vcan2: no entry)
-      (vcan1: no entry)
-      device   can_id   can_mask  function  userdata   matches  ident
-       vcan0     000    00000000  f88e6370  f6c6f400         0  raw
       (any: no entry)
+      device   can_id   can_mask   matches  sock_inode  function
+       vcan0     000    00000000         0       63586  raw_rcv [can_raw]
+      (vcan1: no entry)
+      (vcan2: no entry)
+      (vcan3: no entry)
 
 In this example an application requests any CAN traffic from vcan0::
 
diff --git a/include/linux/can/core.h b/include/linux/can/core.h
index 2de74c2b78b6..effb5c31ef40 100644
--- a/include/linux/can/core.h
+++ b/include/linux/can/core.h
@@ -51,7 +51,7 @@ extern void can_proto_unregister(const struct can_proto *cp);
 int can_rx_register(struct net *net, struct net_device *dev,
 		    canid_t can_id, canid_t mask,
 		    void (*func)(struct sk_buff *, void *),
-		    void *data, char *ident, struct sock *sk);
+		    void *data, u64 ino, struct sock *sk);
 
 extern void can_rx_unregister(struct net *net, struct net_device *dev,
 			      canid_t can_id, canid_t mask,
diff --git a/net/can/af_can.c b/net/can/af_can.c
index 167b2dc40eeb..0f3051a61aab 100644
--- a/net/can/af_can.c
+++ b/net/can/af_can.c
@@ -418,7 +418,7 @@ static struct hlist_head *can_rcv_list_find(canid_t *can_id, canid_t *mask,
  * @mask: CAN mask (see description)
  * @func: callback function on filter match
  * @data: returned parameter for callback function
- * @ident: string for calling module identification
+ * @ino: inode number of sock (0 = unknown)
  * @sk: socket pointer (might be NULL)
  *
  * Description:
@@ -443,7 +443,7 @@ static struct hlist_head *can_rcv_list_find(canid_t *can_id, canid_t *mask,
  */
 int can_rx_register(struct net *net, struct net_device *dev, canid_t can_id,
 		    canid_t mask, void (*func)(struct sk_buff *, void *),
-		    void *data, char *ident, struct sock *sk)
+		    void *data, u64 ino, struct sock *sk)
 {
 	struct receiver *rcv;
 	struct hlist_head *rcv_list;
@@ -473,7 +473,7 @@ int can_rx_register(struct net *net, struct net_device *dev, canid_t can_id,
 	atomic_long_set(&rcv->matches, 0);
 	rcv->func = func;
 	rcv->data = data;
-	rcv->ident = ident;
+	rcv->ino = ino;
 	rcv->sk = sk;
 
 	hlist_add_head_rcu(&rcv->list, rcv_list);
diff --git a/net/can/af_can.h b/net/can/af_can.h
index 87887014f562..6e593ed5db5f 100644
--- a/net/can/af_can.h
+++ b/net/can/af_can.h
@@ -55,7 +55,7 @@ struct receiver {
 	atomic_long_t matches;
 	void (*func)(struct sk_buff *skb, void *data);
 	void *data;
-	char *ident;
+	u64 ino;
 	struct sock *sk;
 	struct rcu_head rcu;
 };
diff --git a/net/can/bcm.c b/net/can/bcm.c
index 9288b96724da..7bd2aa39777a 100644
--- a/net/can/bcm.c
+++ b/net/can/bcm.c
@@ -221,9 +221,7 @@ static int bcm_proc_show(struct seq_file *m, void *v)
 	struct bcm_sock *bo = bcm_sk(sk);
 	struct bcm_op *op;
 
-	seq_printf(m, ">>> socket %pK", sk->sk_socket);
-	seq_printf(m, " / sk %pK", sk);
-	seq_printf(m, " / bo %pK", bo);
+	seq_printf(m, ">>> sock inode %s", bo->procname);
 	seq_printf(m, " / dropped %lu", bo->dropped_usr_msgs);
 	seq_printf(m, " / bound %s", bcm_proc_getifname(net, ifname, bo->ifindex));
 	seq_printf(m, " <<<\n");
@@ -1526,7 +1524,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
 						      op->can_id,
 						      REGMASK(op->can_id),
 						      bcm_rx_handler, op,
-						      "bcm", sk);
+						      sock_i_ino(sk), sk);
 
 				/* keep a tracked reference so that a later
 				 * unregister can safely reach the device even
@@ -1550,7 +1548,8 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
 		} else {
 			err = can_rx_register(sock_net(sk), NULL, op->can_id,
 					      REGMASK(op->can_id),
-					      bcm_rx_handler, op, "bcm", sk);
+					      bcm_rx_handler, op,
+					      sock_i_ino(sk), sk);
 		}
 
 		if (err) {
@@ -2022,8 +2021,9 @@ static int bcm_connect(struct socket *sock, struct sockaddr_unsized *uaddr, int
 
 #if IS_ENABLED(CONFIG_PROC_FS)
 	if (net->can.bcmproc_dir) {
-		/* unique socket address as filename */
-		sprintf(bo->procname, "%llu", sock_i_ino(sk));
+		/* use unique socket inode number as filename */
+		snprintf(bo->procname, sizeof(bo->procname),
+			 "%llu", sock_i_ino(sk));
 		bo->bcm_proc_read = proc_create_net_single(bo->procname, 0644,
 						     net->can.bcmproc_dir,
 						     bcm_proc_show, sk);
diff --git a/net/can/gw.c b/net/can/gw.c
index 54bb5bd3242a..8a5327d8a00e 100644
--- a/net/can/gw.c
+++ b/net/can/gw.c
@@ -580,7 +580,7 @@ static inline int cgw_register_filter(struct net *net, struct cgw_job *gwj)
 {
 	return can_rx_register(net, gwj->src.dev, gwj->ccgw.filter.can_id,
 			       gwj->ccgw.filter.can_mask, can_can_gw_rcv,
-			       gwj, "gw", NULL);
+			       gwj, 0, NULL);
 }
 
 static inline void cgw_unregister_filter(struct net *net, struct cgw_job *gwj)
diff --git a/net/can/isotp.c b/net/can/isotp.c
index 5e2de051fc5e..7a90ba3f1cf1 100644
--- a/net/can/isotp.c
+++ b/net/can/isotp.c
@@ -1640,13 +1640,13 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l
 
 	/* register for echo skb's first (no echo traffic at this point) */
 	err = can_rx_register(net, dev, tx_id, SINGLE_MASK(tx_id),
-			      isotp_rcv_echo, sk, "isotpe", sk);
+			      isotp_rcv_echo, sk, sock_i_ino(sk), sk);
 	if (err)
 		goto out_put_dev;
 
 	if (isotp_register_rxid(so)) {
 		err = can_rx_register(net, dev, rx_id, SINGLE_MASK(rx_id),
-				      isotp_rcv, sk, "isotp", sk);
+				      isotp_rcv, sk, sock_i_ino(sk), sk);
 		if (err) {
 			/* unregister echo skb subscription */
 			can_rx_unregister(net, dev, tx_id, SINGLE_MASK(tx_id),
diff --git a/net/can/j1939/main.c b/net/can/j1939/main.c
index 5e5e6c228f22..d9384cf0e356 100644
--- a/net/can/j1939/main.c
+++ b/net/can/j1939/main.c
@@ -184,7 +184,7 @@ static int j1939_can_rx_register(struct j1939_priv *priv)
 
 	j1939_priv_get(priv);
 	ret = can_rx_register(dev_net(ndev), ndev, J1939_CAN_ID, J1939_CAN_MASK,
-			      j1939_can_recv, priv, "j1939", NULL);
+			      j1939_can_recv, priv, 0, NULL);
 	if (ret < 0) {
 		j1939_priv_put(priv);
 		return ret;
diff --git a/net/can/proc.c b/net/can/proc.c
index fb144d2adc35..b7496d2a37cd 100644
--- a/net/can/proc.c
+++ b/net/can/proc.c
@@ -204,26 +204,32 @@ static void can_print_rcvlist(struct seq_file *m, struct hlist_head *rx_list,
 	struct receiver *r;
 
 	hlist_for_each_entry_rcu(r, rx_list, list) {
+		char symcheck[4];
 		char *fmt = (r->can_id & CAN_EFF_FLAG)?
-			"   %-5s  %08x  %08x  %pK  %pK  %8ld  %s\n" :
-			"   %-5s     %03x    %08x  %pK  %pK  %8ld  %s\n";
+			"  %6s  %08x  %08x  %8ld  %10llu" :
+			"  %6s     %03x    %08x  %8ld  %10llu";
 
 		seq_printf(m, fmt, DNAME(dev), r->can_id, r->mask,
-			   r->func, r->data, atomic_long_read(&r->matches),
-			   r->ident);
+			   atomic_long_read(&r->matches), r->ino);
+
+		/* %ps falls back to the raw text address when the symbol
+		 * can not be resolved (KALLSYMS=n, stripped module symbols)
+		 */
+		snprintf(symcheck, sizeof(symcheck), "%ps", r->func);
+		if (strncmp(symcheck, "0x", 2))
+			seq_printf(m, "  %ps\n", r->func);
+		else
+			seq_puts(m, "  (unknown)\n");
 	}
 }
 
 static void can_print_recv_banner(struct seq_file *m)
 {
 	/*
-	 *                  can1.  00000000  00000000  00000000
-	 *                 .......          0  tp20
+	 *    device   can_id   can_mask   matches  sock_inode  function
+	 *     vcan0     000    00000000         0       43798  raw_rcv [can_raw]
 	 */
-	if (IS_ENABLED(CONFIG_64BIT))
-		seq_puts(m, "  device   can_id   can_mask      function          userdata       matches  ident\n");
-	else
-		seq_puts(m, "  device   can_id   can_mask  function  userdata   matches  ident\n");
+	seq_puts(m, "  device   can_id   can_mask   matches  sock_inode  function\n");
 }
 
 static int can_stats_proc_show(struct seq_file *m, void *v)
diff --git a/net/can/raw.c b/net/can/raw.c
index 0926d5686e41..f14270b56f52 100644
--- a/net/can/raw.c
+++ b/net/can/raw.c
@@ -228,7 +228,7 @@ static int raw_enable_filters(struct net *net, struct net_device *dev,
 		cond_resched();
 		err = can_rx_register(net, dev, filter[i].can_id,
 				      filter[i].can_mask,
-				      raw_rcv, sk, "raw", sk);
+				      raw_rcv, sk, sock_i_ino(sk), sk);
 		if (err) {
 			/* clean up successfully registered filters */
 			while (--i >= 0)
@@ -249,7 +249,7 @@ static int raw_enable_errfilter(struct net *net, struct net_device *dev,
 
 	if (err_mask)
 		err = can_rx_register(net, dev, 0, err_mask | CAN_ERR_FLAG,
-				      raw_rcv, sk, "raw", sk);
+				      raw_rcv, sk, sock_i_ino(sk), sk);
 
 	return err;
 }
-- 
2.53.0


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

* [PATCH net 13/15] can: dev: can_fill_info(): don't wake up controller if interface is down
  2026-11-03 20:30 [PATCH net 0/15] pull-request: can 2026-11-03 Marc Kleine-Budde
                   ` (11 preceding siblings ...)
  2026-11-03 20:30 ` [PATCH net 12/15] can: proc: remove pointers from CAN specific proc output Marc Kleine-Budde
@ 2026-11-03 20:30 ` Marc Kleine-Budde
  2026-11-04 20:46   ` netdev-bot+sashiko
  2026-11-03 20:30 ` [PATCH net 14/15] can: dev: can_tdc_fill_info(): don't read automatic TDCV " Marc Kleine-Budde
  2026-11-03 20:30 ` [PATCH net 15/15] can: peak_usb: pcan_usb_fd_decode_buf(): accept short uCAN end-of-list records Marc Kleine-Budde
  14 siblings, 1 reply; 25+ messages in thread
From: Marc Kleine-Budde @ 2026-11-03 20:30 UTC (permalink / raw)
  To: netdev
  Cc: davem, kuba, linux-can, kernel, ChunYuZhiQiang, stable,
	Vincent Mailhol, Marc Kleine-Budde

From: ChunYuZhiQiang <chunyuzhiqiang@yinhe.ht>

If the interface is down, the CAN controller might be powered down and
its clocks gated. Don't call do_get_berr_counter() to read the bus error
counters if the interface is down, and adjust can_get_size() to match.

This removes one trigger for a synchronous external abort seen on
i.MX8QXP when "ip link show" is run on a flexcan interface that was
never brought up. The underlying cause is platform-specific: on i.MX8QXP
the CAN clocks are gated through the SCU LPCG and the subsystem shares
the IMX_SC_R_CAN_0 power domain, and there is a window after
pm_runtime_resume_and_get() returns where the clock has not reached the
module yet. This patch avoids the netlink query path while the interface
is down, but does not root-cause the LPCG window itself.

As a result, IFLA_CAN_BERR_COUNTER is no longer reported for down
interfaces. Userspace must treat the attribute as optional.

The issue was triggered on an i.MX8QXP board by running "ip link show"
without ever bringing the interface up:

    Internal error: synchronous external abort: 0000000096000210

    pc : flexcan_read_le+0x0/0x18
    lr : flexcan_get_berr_counter+0x4c/0x8c
    Call trace:
     flexcan_read_le+0x0/0x18
     flexcan_get_berr_counter+0x4c/0x8c
     can_fill_info+0x1f8/0x434
     rtnl_fill_ifinfo+0x8fc/0xbb4
     rtnl_dump_ifinfo+0x364/0x448

This is the same class of problem fixed for m_can in
commit 91a55c72a821 ("can: m_can: m_can_get_berr_counter(): don't
wake up controller if interface is down").

Fixes: ec56acfef2af1 ("can: flexcan: flexcan_get_berr_counter(): switch on clocks before accessing ecr register")
Cc: stable@vger.kernel.org
Signed-off-by: ChunYuZhiQiang <chunyuzhiqiang@yinhe.ht>
Reviewed-by: Vincent Mailhol <mailhol@kernel.org>
Link: https://patch.msgid.link/20261027065012.191348-1-chunyuzhiqiang@yinhe.ht
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
---
 drivers/net/can/dev/netlink.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/net/can/dev/netlink.c b/drivers/net/can/dev/netlink.c
index 766d455950f5..b958c04d53db 100644
--- a/drivers/net/can/dev/netlink.c
+++ b/drivers/net/can/dev/netlink.c
@@ -812,7 +812,7 @@ static size_t can_get_size(const struct net_device *dev)
 	size += nla_total_size(sizeof(u32));			/* IFLA_CAN_STATE */
 	size += nla_total_size(sizeof(struct can_ctrlmode));	/* IFLA_CAN_CTRLMODE */
 	size += nla_total_size(sizeof(u32));			/* IFLA_CAN_RESTART_MS */
-	if (priv->do_get_berr_counter)				/* IFLA_CAN_BERR_COUNTER */
+	if (priv->do_get_berr_counter && netif_running(dev))	/* IFLA_CAN_BERR_COUNTER */
 		size += nla_total_size(sizeof(struct can_berr_counter));
 	if (priv->termination_const) {
 		size += nla_total_size(sizeof(priv->termination));		/* IFLA_CAN_TERMINATION */
@@ -1006,7 +1006,7 @@ static int can_fill_info(struct sk_buff *skb, const struct net_device *dev)
 	    nla_put(skb, IFLA_CAN_CTRLMODE, sizeof(cm), &cm) ||
 	    nla_put_u32(skb, IFLA_CAN_RESTART_MS, priv->restart_ms) ||
 
-	    (priv->do_get_berr_counter &&
+	    (priv->do_get_berr_counter && netif_running(dev) &&
 	     !priv->do_get_berr_counter(dev, &bec) &&
 	     nla_put(skb, IFLA_CAN_BERR_COUNTER, sizeof(bec), &bec)) ||
 
-- 
2.53.0


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

* [PATCH net 14/15] can: dev: can_tdc_fill_info(): don't read automatic TDCV if interface is down
  2026-11-03 20:30 [PATCH net 0/15] pull-request: can 2026-11-03 Marc Kleine-Budde
                   ` (12 preceding siblings ...)
  2026-11-03 20:30 ` [PATCH net 13/15] can: dev: can_fill_info(): don't wake up controller if interface is down Marc Kleine-Budde
@ 2026-11-03 20:30 ` Marc Kleine-Budde
  2026-11-03 20:30 ` [PATCH net 15/15] can: peak_usb: pcan_usb_fd_decode_buf(): accept short uCAN end-of-list records Marc Kleine-Budde
  14 siblings, 0 replies; 25+ messages in thread
From: Marc Kleine-Budde @ 2026-11-03 20:30 UTC (permalink / raw)
  To: netdev
  Cc: davem, kuba, linux-can, kernel, ChunYuZhiQiang, Vincent Mailhol,
	Marc Kleine-Budde

From: ChunYuZhiQiang <chunyuzhiqiang@yinhe.ht>

The automatic TDCV readback calls into the driver via do_get_auto_tdcv().
Some drivers, such as xcan_get_auto_tdcv(), read controller registers
without taking a runtime PM reference, so calling this while the
interface is down can touch registers whose clocks are gated.

Gate do_get_auto_tdcv() on netif_running() in can_tdc_fill_info() and
adjust can_tdc_get_size() and can_data_bittiming_get_size() so that only
the automatic TDCV size is skipped when the interface is down. The other
TDC parameters are still reported, as they are meaningful while the
interface is down.

This is a defensive hardening based on code inspection; the automatic
TDCV path has not been triggered on hardware.

Signed-off-by: ChunYuZhiQiang <chunyuzhiqiang@yinhe.ht>
Reviewed-by: Vincent Mailhol <mailhol@kernel.org>
Fixes: e8060f08cd69 ("can: netlink: add can_priv::do_get_auto_tdcv() to retrieve tdcv from device")
Link: https://patch.msgid.link/20261027065012.191348-2-chunyuzhiqiang@yinhe.ht
[mkl: fix open parenthesis alignment]
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
---
 drivers/net/can/dev/netlink.c | 17 ++++++++++-------
 1 file changed, 10 insertions(+), 7 deletions(-)

diff --git a/drivers/net/can/dev/netlink.c b/drivers/net/can/dev/netlink.c
index b958c04d53db..081dfddda000 100644
--- a/drivers/net/can/dev/netlink.c
+++ b/drivers/net/can/dev/netlink.c
@@ -720,7 +720,8 @@ static int can_changelink(struct net_device *dev, struct nlattr *tb[],
 	return 0;
 }
 
-static size_t can_tdc_get_size(struct data_bittiming_params *dbt_params,
+static size_t can_tdc_get_size(const struct net_device *dev,
+			       struct data_bittiming_params *dbt_params,
 			       u32 tdc_flags)
 {
 	bool tdc_manual = tdc_flags & CAN_CTRLMODE_TDC_MANUAL_MASK;
@@ -742,7 +743,8 @@ static size_t can_tdc_get_size(struct data_bittiming_params *dbt_params,
 	}
 
 	if (tdc_flags) {
-		if (tdc_manual || dbt_params->do_get_auto_tdcv)
+		if (tdc_manual ||
+		    (dbt_params->do_get_auto_tdcv && netif_running(dev)))
 			size += nla_total_size(sizeof(u32));	/* IFLA_CAN_TDCV */
 		size += nla_total_size(sizeof(u32));		/* IFLA_CAN_TDCO */
 		if (dbt_params->tdc_const->tdcf_max)
@@ -752,7 +754,8 @@ static size_t can_tdc_get_size(struct data_bittiming_params *dbt_params,
 	return size;
 }
 
-static size_t can_data_bittiming_get_size(struct data_bittiming_params *dbt_params,
+static size_t can_data_bittiming_get_size(const struct net_device *dev,
+					  struct data_bittiming_params *dbt_params,
 					  u32 tdc_flags)
 {
 	size_t size = 0;
@@ -764,7 +767,7 @@ static size_t can_data_bittiming_get_size(struct data_bittiming_params *dbt_para
 	if (dbt_params->data_bitrate_const)		/* IFLA_CAN_{,XL}_DATA_BITRATE_CONST */
 		size += nla_total_size(sizeof(*dbt_params->data_bitrate_const) *
 				       dbt_params->data_bitrate_const_cnt);
-	size += can_tdc_get_size(dbt_params, tdc_flags);/* IFLA_CAN_{,XL}_TDC */
+	size += can_tdc_get_size(dev, dbt_params, tdc_flags);/* IFLA_CAN_{,XL}_TDC */
 
 	return size;
 }
@@ -825,10 +828,10 @@ static size_t can_get_size(const struct net_device *dev)
 	size += sizeof(priv->bitrate_max);			/* IFLA_CAN_BITRATE_MAX */
 	size += can_ctrlmode_ext_get_size();			/* IFLA_CAN_CTRLMODE_EXT */
 
-	size += can_data_bittiming_get_size(&priv->fd,
+	size += can_data_bittiming_get_size(dev, &priv->fd,
 					    priv->ctrlmode & CAN_CTRLMODE_FD_TDC_MASK);
 
-	size += can_data_bittiming_get_size(&priv->xl,
+	size += can_data_bittiming_get_size(dev, &priv->xl,
 					    priv->ctrlmode & CAN_CTRLMODE_XL_TDC_MASK);
 	size += can_pwm_get_size(priv->xl.pwm_const,		/* IFLA_CAN_XL_PWM */
 				 priv->ctrlmode & CAN_CTRLMODE_XL_TMS);
@@ -910,7 +913,7 @@ static int can_tdc_fill_info(struct sk_buff *skb, const struct net_device *dev,
 		if (tdc_manual) {
 			tdcv = tdc->tdcv;
 			err = 0;
-		} else if (dbt_params->do_get_auto_tdcv) {
+		} else if (dbt_params->do_get_auto_tdcv && netif_running(dev)) {
 			err = dbt_params->do_get_auto_tdcv(dev, &tdcv);
 		}
 		if (!err && nla_put_u32(skb, IFLA_CAN_TDC_TDCV, tdcv))
-- 
2.53.0


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

* [PATCH net 15/15] can: peak_usb: pcan_usb_fd_decode_buf(): accept short uCAN end-of-list records
  2026-11-03 20:30 [PATCH net 0/15] pull-request: can 2026-11-03 Marc Kleine-Budde
                   ` (13 preceding siblings ...)
  2026-11-03 20:30 ` [PATCH net 14/15] can: dev: can_tdc_fill_info(): don't read automatic TDCV " Marc Kleine-Budde
@ 2026-11-03 20:30 ` Marc Kleine-Budde
  14 siblings, 0 replies; 25+ messages in thread
From: Marc Kleine-Budde @ 2026-11-03 20:30 UTC (permalink / raw)
  To: netdev
  Cc: davem, kuba, linux-can, kernel, Liu Changjie,
	Stéphane Grosjean, stable, Marc Kleine-Budde

From: Liu Changjie <liucj1228@outlook.com>

A PCAN-USB FD v2 running firmware 3.2.0 sends a 16-byte status record
followed by a four-byte zero-size end marker.

Since commit 93fcab2c6968 ("can: peak_usb: validate uCAN receive record
lengths") these transfers cause repeated receive-buffer dumps while the
interface is up.

This has been observed on Linux 7.2.11-arch1-1 with matching kernel
headers:
- Classic CAN, 500 kbit/s: 25 decoder errors in 5 s before the fix; none in
  15 s after.
- CAN FD, 500 kbit/s nominal and 2 Mbit/s data: 26 decoder errors in 5 s
  before the fix; none in 60 s after.
- Three additional down/up cycles: no decoder errors.

pcan_usb_fd_decode_buf() requires a complete 12-byte struct pucan_msg
before checking the size field, so it rejects the end marker with -EBADMSG.

Check that the size field is present before testing for an end marker.
Require the full header for nonzero-size records and keep the remaining
record and payload length checks.

This patch was created with LLM assistance.

Fixes: 93fcab2c6968 ("can: peak_usb: validate uCAN receive record lengths")
Signed-off-by: Liu Changjie <liucj1228@outlook.com>
Reviewed-by: Stéphane Grosjean <s.grosjean@peak-system.fr>
Link: https://patch.msgid.link/CH5PR19MB997156AB3BE253C3BE445223B0AC992@CH5PR19MB997156.namprd19.prod.outlook.com
Cc: stable@vger.kernel.org
[mkl: copy additional info to commit message]
[mkl: convert Assisted-by: tag to prose]
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
---
Built and tested the original and patched peak_usb modules on Arch
Linux 7.2.11-arch1-1 with matching kernel headers.

  - Classic CAN, 500 kbit/s: 25 decoder errors in 5 s before the fix;
    none in 15 s after.
  - CAN FD, 500 kbit/s nominal and 2 Mbit/s data: 26 decoder errors
    in 5 s before the fix; none in 60 s after.
  - Three additional down/up cycles: no decoder errors.

USB captures during the patched tests contained 406 packets with
short end markers. Neither receive-buffer dump recurred.

The CAN bus was disconnected; external TX/RX was not tested.
One startup "can_change_state: oops, state did not change" message and
an RX dropped count reaching 2 were observed during the patched tests.
Their causes remain undetermined.
---
 drivers/net/can/usb/peak_usb/pcan_usb_fd.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/drivers/net/can/usb/peak_usb/pcan_usb_fd.c b/drivers/net/can/usb/peak_usb/pcan_usb_fd.c
index 0d46f4ce5dca..39f0740421c3 100644
--- a/drivers/net/can/usb/peak_usb/pcan_usb_fd.c
+++ b/drivers/net/can/usb/peak_usb/pcan_usb_fd.c
@@ -755,7 +755,7 @@ static int pcan_usb_fd_decode_buf(struct peak_usb_device *dev, struct urb *urb)
 		u16 rx_msg_type, rx_msg_size;
 		size_t rx_msg_min_size;
 
-		if (msg_end - msg_ptr < sizeof(*rx_msg)) {
+		if (msg_end - msg_ptr < sizeof(rx_msg->size)) {
 			err = -EBADMSG;
 			break;
 		}
@@ -766,6 +766,11 @@ static int pcan_usb_fd_decode_buf(struct peak_usb_device *dev, struct urb *urb)
 			break;
 		}
 
+		if (msg_end - msg_ptr < sizeof(*rx_msg)) {
+			err = -EBADMSG;
+			break;
+		}
+
 		rx_msg_size = le16_to_cpu(rx_msg->size);
 		rx_msg_type = le16_to_cpu(rx_msg->type);
 
-- 
2.53.0


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

* Re: [PATCH net 01/15] can: dev: can_dropped_invalid_skb: drop CAN XL frames on non-CAN XL devices
  2026-11-03 20:30 ` [PATCH net 01/15] can: dev: can_dropped_invalid_skb: drop CAN XL frames on non-CAN XL devices Marc Kleine-Budde
@ 2026-11-03 20:48   ` netdev-bot+sinfo
  2026-11-05  9:10   ` patchwork-bot+netdevbpf
  1 sibling, 0 replies; 25+ messages in thread
From: netdev-bot+sinfo @ 2026-11-03 20:48 UTC (permalink / raw)
  To: Marc Kleine-Budde
  Cc: netdev, davem, kuba, linux-can, kernel, Vincent Mailhol, stable

Hi!

This is an automated message. This series looks like a fix, but its
commit messages seem to be missing some information:

 - How the issue was discovered, e.g. hit in production, hit during
   development, syzbot report, manual code inspection, LLM or static
   analysis tool scan.

 - Whether the issue was actually triggered, or is only theoretical
   (e.g. found by code inspection). If it was triggered please include
   the symptoms, like the stack trace or error messages.

Please do not repost the series just to address the above. Instead,
reply to this email with the missing information, so that reviewers
can take it into account. If the series needs another revision for
other reasons, please include the information in the commit messages
then.

The evaluation is done by an LLM so it may be wrong, if you think
that is the case please reply and explain.

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

* Re: [PATCH net 08/15] can: protect the receive lists with a per-device lock
  2026-11-03 20:30 ` [PATCH net 08/15] can: protect the receive lists with a per-device lock Marc Kleine-Budde
@ 2026-11-04 20:46   ` netdev-bot+sashiko
  2026-11-05  8:10     ` Oliver Hartkopp
  0 siblings, 1 reply; 25+ messages in thread
From: netdev-bot+sashiko @ 2026-11-04 20:46 UTC (permalink / raw)
  To: mkl; +Cc: netdev, davem, kuba, linux-can, kernel, liucj1228, s.grosjean,
	stable

Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.

Low priority / nit picks for your consideration:

- [Low] Since RTNL is no longer held across raw_bind() and isotp_bind(),
  a NETDEV_DOWN followed by NETDEV_UP inside the bind() window is never…

Pre-existing issues:
- [High] This patch did not introduce this problem, but CAN_BCM still
  lets an unprivileged task stall a user-memory copy while holding…

--

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

This adds a spinlock to struct can_dev_rcv_lists, so can_rx_unregister()
finds a subscription even after the device has moved to another network
namespace. The per-netns rcvlists_lock now only protects the statistics.
CAN_RAW and CAN_ISOTP bind()/release()/setsockopt() stop taking RTNL and
rely on lock_sock(), RCU publication of the bound device and a barrier
pair for NETDEV_DOWN.

Link: https://lore.kernel.org/linux-can/CEA6A38A-2646-4ADA-95B4-CBAE2F301A8E@doyensec.com/
Link: https://patch.msgid.link/20261027-can-rx-lock-v12-3-eb5afc2d8b9b@hartkopp.net

> diff --git a/net/can/bcm.c b/net/can/bcm.c
> index 60406439a13f7..9288b96724da2 100644
> --- a/net/can/bcm.c
> +++ b/net/can/bcm.c

[ ... ]

> @@ -1936,6 +1925,7 @@ static int bcm_release(struct socket *sock)
>  	list_del(&bo->notifier);
>  	spin_unlock(&bcm_notifier_lock);
>  
> +	/* bcm_notify() can not access this socket anymore */
>  	lock_sock(sk);
>  
>  #if IS_ENABLED(CONFIG_PROC_FS)

[Severity: High]
This is a pre-existing issue, not one introduced by this patch. The
commit message is about unprivileged users holding up RTNL, though, so
is CAN_BCM still open to the same kind of problem?

bcm_sendmsg() takes lock_sock(sk). While it holds the lock,
bcm_tx_setup(), bcm_rx_setup() and bcm_tx_send() copy frame data from
user memory with memcpy_from_msg():

net/can/bcm.c:bcm_tx_setup() {
    ...
			err = memcpy_from_msg((u8 *)cf, msg, op->cfsiz);
    ...
}

bcm_notify() runs from the netdev notifier with RTNL held. On
NETDEV_UNREGISTER it takes lock_sock() on every BCM socket in the
device's namespace, whether or not the socket is bound to that device:

net/can/bcm.c:bcm_notify() {
    ...
	case NETDEV_UNREGISTER:
		lock_sock(sk);
    ...
}

Suppose the sendmsg iovec points at a FUSE-backed mapping whose daemon
never answers. The copy then stalls with the socket lock held.

A CAN device unregister in that netns then blocks bcm_notify() in
lock_sock() with RTNL held. For example, the user can delete a vcan in
their own unprivileged user+net namespace. After that, every RTNL user
on the host hangs.

The CAN_RAW version of this is fixed later in this series by "can: raw,
isotp: copy socket options outside of lock_sock()". That covers
memdup_sockptr()/copy_from_sockptr() in raw_setsockopt_locked() and
copy_to_iter() in raw_getsockopt() under lock_sock(). Before this
patch, raw_setsockopt() even held RTNL directly across the copy.

CAN_ISOTP is not affected. The BCM paths look the same at the end of
the series.

Could the BCM frame data also be copied before lock_sock()?

[ ... ]

> diff --git a/net/can/raw.c b/net/can/raw.c
> index e1d741a25be3f..d6517f71cadeb 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;

[ ... ]

> @@ -479,14 +500,25 @@ static int raw_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int len

[ ... ]

> +		rcu_read_lock();
> +		dev = dev_get_by_index_rcu(sock_net(sk), addr->can_ifindex);
> +		if (dev) {
> +			dev_hold(dev);
> +			/* pairs with smp_load_acquire() in raw_notify() */
> +			smp_store_release(&ro->bind_dev, dev);
> +		}
> +		rcu_read_unlock();

[ ... ]

> @@ -496,9 +528,6 @@ static int raw_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int len
>  			goto out_put_dev;
>  		}
>  
> -		if (!(dev->flags & IFF_UP))
> -			notify_enetdown = 1;
> -
>  		ifindex = dev->ifindex;
>  
>  		/* filters set by default/setsockopt */

[ ... ]

> @@ -528,24 +557,32 @@ 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);
>  		if (ro->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;
> +		}
>  	}

[Severity: Low]
The commit message says:

  "As RTNL does not keep NETDEV_DOWN out of bind() anymore, bind() checks
  IFF_UP after publishing the bound device, with a barrier pair against
  raw_notify() and isotp_notify(), so a down transition during bind() is
  still reported with ENETDOWN."

Does this still hold when a full down/up cycle happens inside bind()?

raw_bind() registers the filters with raw_enable_allfilters() before it
publishes ro->dev. raw_rcv() can already queue frames for the socket at
that point. raw_enable_filters() can do up to CAN_RAW_FILTER_MAX
GFP_KERNEL allocations with cond_resched(), so this window can be long.

In raw_notify(), the bind_dev hint only covers NETDEV_UNREGISTER. A
NETDEV_DOWN in this window returns early:

raw_bind()                            RTNL holder
  smp_store_release(&ro->bind_dev)
  raw_enable_allfilters()
    /* frames get queued */
                                      dev_close()
                                        clears IFF_UP
                                        raw_notify(NETDEV_DOWN)
                                          ro->dev != dev -> return
                                      dev_open()
                                        sets IFF_UP
  WRITE_ONCE(ro->dev, dev)
  smp_mb()
  IFF_UP is set -> no ENETDOWN

isotp_bind() has the same window. so->dev is published early, but
isotp_notify() drops NETDEV_DOWN while so->bound is still 0:

net/can/isotp.c:isotp_notify() {
    ...
	case NETDEV_DOWN:
		if (!READ_ONCE(so->bound))
			break;
    ...
}

isotp_bind() only sets so->bound after both can_rx_register() calls,
and samples IFF_UP after that.

Before this patch, holding RTNL across bind() ruled out this
interleaving. Is it acceptable to miss ENETDOWN in this case? If so,
should the commit message only promise this for a down state that is
still in effect when bind() samples IFF_UP?

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261103204305.3211144-2-mkl%40pengutronix.de

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

* Re: [PATCH net 09/15] can: raw, isotp: copy socket options outside of lock_sock()
  2026-11-03 20:30 ` [PATCH net 09/15] can: raw, isotp: copy socket options outside of lock_sock() Marc Kleine-Budde
@ 2026-11-04 20:46   ` netdev-bot+sashiko
  2026-11-05  7:47     ` Oliver Hartkopp
  0 siblings, 1 reply; 25+ messages in thread
From: netdev-bot+sashiko @ 2026-11-04 20:46 UTC (permalink / raw)
  To: mkl; +Cc: netdev, davem, kuba, linux-can, kernel, liucj1228, s.grosjean,
	stable

Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.

Critical: 0 · High: 1 (1) · Medium: 0 · Low: 0

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [High] The patch does not remove the RTNL stall it describes.

Pre-existing issues:
- [High] This problem wasn't introduced by this patch, but CAN_BCM has
  the same RTNL-stall problem through a sibling notifier, and the series…

--

can: raw, isotp: copy socket options outside of lock_sock()

This commit moves the user space copies for the CAN_RAW and CAN_ISOTP
socket options, including the CAN_RAW_FILTER filters, so they happen
before lock_sock(). raw_getsockopt() now duplicates the filters under
the socket lock and copies them to user space after release_sock(). The
goal is to stop a stalled user copy from holding lock_sock() and blocking
raw_notify()/isotp_notify() under RTNL.

Link: https://patch.msgid.link/20261027-can-rx-lock-v12-4-eb5afc2d8b9b@hartkopp.net

> diff --git a/net/can/raw.c b/net/can/raw.c
> index d6517f71cadeb..0926d5686e419 100644
> --- a/net/can/raw.c
> +++ b/net/can/raw.c

[ ... ]

> @@ -795,17 +730,84 @@ static int raw_setsockopt(struct socket *sock, int level, int optname,
>  			  sockptr_t optval, unsigned int optlen)
>  {
>  	struct sock *sk = sock->sk;
> +	struct can_filter *filter = NULL;  /* dyn. alloc'ed filters */
> +	struct can_filter sfilter;         /* single filter */
> +	union {
> +		can_err_mask_t err_mask;
> +		int flag;
> +		struct can_raw_vcid_options vcid_opts;
> +	} val;
> +	unsigned int size = 0;
> +	int count = 0;
>  	int err;
>  
>  	if (level != SOL_CAN_RAW)
>  		return -EINVAL;
>  
> +	/* Copy the option values from user space before lock_sock(), so a
> +	 * stalled copy can not block raw_notify() waiting for lock_sock()
> +	 * under RTNL.
> +	 */

[Severity: High]
The comment says a stalled copy can not block raw_notify() waiting for
lock_sock() under RTNL. Is that true for the socket as a whole?

The same bound CAN_RAW socket can still hold the socket lock during a
user copy through the SOL_SOCKET path. sk_setsockopt() takes the lock
before it handles SO_ATTACH_FILTER:

net/core/sock.c:sk_setsockopt() {
    ...
	sockopt_lock_sock(sk);
    ...
			ret = sk_attach_filter(&fprog, sk);
    ...
}

and sk_attach_filter()->__get_filter() then copies the program from
user space:

	if (copy_from_user(prog->insns, fprog->filter, fsize)) {

SO_ATTACH_REUSEPORT_CBPF goes down the same path. On the getsockopt
side, sk_get_filter() calls copy_to_sockptr() under sockopt_lock_sock().
That is the SOL_SOCKET equivalent of the CAN_RAW_FILTER copy this patch
moves after release_sock().

Suppose fprog.filter points into a FUSE or userfaultfd backed mapping
that never resolves. On NETDEV_UNREGISTER or a netns change of the
bound device, raw_notify() then waits under RTNL:

net/can/raw.c:raw_notify() {
    ...
	case NETDEV_UNREGISTER:
		lock_sock(sk);
    ...
}

As far as I can tell, net/can/ has no capability checks. That would
make this reachable by any local user who can create and bind a CAN_RAW
socket.

The commit message says "The generic SOL_SOCKET options in
sk_setsockopt() are not affected by this patch". It does not say that
the RTNL stall is still reachable on the same socket. No later patch in
the series changes net/core/sock.c or net/core/filter.c.

Would it be better to stop raw_notify() and isotp_notify() from blocking
on lock_sock() under RTNL? The other option would be to also move the
generic filter copies out of the socket lock.

> +	switch (optname) {
> +	case CAN_RAW_FILTER:

[ ... ]

> +	if (size) {
> +		if (optlen != size)
> +			return -EINVAL;
> +
> +		if (copy_from_sockptr(&val, optval, size))
> +			return -EFAULT;
> +	}
> +
>  	lock_sock(sk);
>  
> -	err = raw_setsockopt_locked(sock, optname, optval, optlen);
> +	err = raw_setsockopt_locked(sock, optname, &val, filter, count);
>  
>  	release_sock(sk);

[Severity: High]
This is a pre-existing issue and was not introduced by this patch, but
CAN_BCM seems to have the same RTNL stall through its notifier. This
series does not address or mention it.

bcm_sendmsg() takes the socket lock and then dispatches to
bcm_tx_setup(), bcm_rx_setup() and bcm_tx_send(). These copy the CAN
frames from the user iovec while the lock is held:

net/can/bcm.c:bcm_sendmsg() {
    ...
	lock_sock(sk);
    ...
}

net/can/bcm.c:bcm_tx_setup() {
    ...
			err = memcpy_from_msg((u8 *)cf, msg, op->cfsiz);
    ...
}

bcm_notify() takes the lock for every bcm socket in the netns under
RTNL. It does this before checking whether the socket is bound to the
vanishing device:

net/can/bcm.c:bcm_notify() {
    ...
	if (!net_eq(dev_net(dev), sock_net(sk)))
		return;

	switch (msg) {

	case NETDEV_UNREGISTER:
		lock_sock(sk);
    ...
}

Say a BCM socket sends TX_SETUP, RX_SETUP or TX_SEND with the frame
data in a FUSE or userfaultfd backed mapping that stalls. Can any CAN
device unregistration in that netns then block RTNL indefinitely?

No later patch in the series changes these paths. The only bcm.c
changes are proc and can_rx_register naming.

>  
> +	/* on error free the filters allocated for CAN_RAW_FILTER above */
> +	if (err && count > 1)
> +		kfree(filter);
> +
>  	return err;
>  }

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261103204305.3211144-2-mkl%40pengutronix.de

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

* Re: [PATCH net 13/15] can: dev: can_fill_info(): don't wake up controller if interface is down
  2026-11-03 20:30 ` [PATCH net 13/15] can: dev: can_fill_info(): don't wake up controller if interface is down Marc Kleine-Budde
@ 2026-11-04 20:46   ` netdev-bot+sashiko
  2026-11-04 23:08     ` Marc Kleine-Budde
  0 siblings, 1 reply; 25+ messages in thread
From: netdev-bot+sashiko @ 2026-11-04 20:46 UTC (permalink / raw)
  To: mkl; +Cc: netdev, davem, kuba, linux-can, kernel, liucj1228, s.grosjean,
	stable

Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.

Critical: 0 · High: 0 · Medium: 1 · Low: 0

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [Medium] The commit message describes this as removing one trigger of a
  flexcan abort on i.MX8QXP.

--

can: dev: can_fill_info(): don't wake up controller if interface is down

This patch changes can_fill_info() and can_get_size() to skip
IFLA_CAN_BERR_COUNTER when the interface is not running. As a result,
do_get_berr_counter() is never called on a down interface. The goal is
to avoid a synchronous external abort in flexcan_get_berr_counter() on
i.MX8QXP when "ip link show" runs on an interface that was never brought
up.

Link: https://patch.msgid.link/20261027065012.191348-1-chunyuzhiqiang@yinhe.ht

> diff --git a/drivers/net/can/dev/netlink.c b/drivers/net/can/dev/netlink.c
> index 766d455950f55..b958c04d53db6 100644
> --- a/drivers/net/can/dev/netlink.c
> +++ b/drivers/net/can/dev/netlink.c

[ ... ]

> @@ -1006,7 +1006,7 @@ static int can_fill_info(struct sk_buff *skb, const struct net_device *dev)
>  	    nla_put(skb, IFLA_CAN_CTRLMODE, sizeof(cm), &cm) ||
>  	    nla_put_u32(skb, IFLA_CAN_RESTART_MS, priv->restart_ms) ||
>  
> -	    (priv->do_get_berr_counter &&
> +	    (priv->do_get_berr_counter && netif_running(dev) &&
>  	     !priv->do_get_berr_counter(dev, &bec) &&
>  	     nla_put(skb, IFLA_CAN_BERR_COUNTER, sizeof(bec), &bec)) ||

[Severity: Medium]
Is this meant to change the output for every CAN driver, and not only
flexcan?

can_fill_info() is the only core caller of priv->do_get_berr_counter.
With this check, no driver that sets the callback will report
IFLA_CAN_BERR_COUNTER on a down interface. can_get_size() gets the same
change.

Several drivers turn on their hardware inside the callback so that this
query works while the interface is down:

drivers/net/can/sun4i_can.c:sun4ican_get_berr_counter() {
    ...
	err = clk_prepare_enable(priv->clk);
    ...
}

drivers/net/can/c_can/c_can_main.c:c_can_get_berr_counter() {
    ...
	c_can_pm_runtime_get_sync(priv);
	err = __c_can_get_berr_counter(dev, bec);
	c_can_pm_runtime_put_sync(priv);
    ...
}

Other drivers do the same thing:

  bxcan_get_berr_counter() calls clk_prepare_enable()
  xcan_get_berr_counter() and ctucan_get_berr_counter() call
  pm_runtime_get_sync()
  rcar_can_get_berr_counter(), rkcanfd_get_berr_counter() and
  flexcan_get_berr_counter() call pm_runtime_resume_and_get()

The commit in the Fixes: tag, ec56acfef2af1, states this intent
directly: "flexcan_get_berr_counter() may be called from userspace even
if the interface is down ... This patch switches on the clocks".

After this change, the wake-up code in those drivers only runs when the
device is already active, so it no longer does anything useful. Should
that code be removed? Or would it be better to put the check in the
flexcan driver instead?

The commit message cites m_can as the precedent. Commit 91a55c72a821
fixed m_can inside the driver:

drivers/net/can/m_can/m_can.c:m_can_get_berr_counter() {
    ...
	/* Avoid waking up the controller if the interface is down */
	if (!(dev->flags & IFF_UP))
		return 0;
    ...
}

That version still sends the attribute, with zeroed counters. With this
patch, m_can sends no attribute at all for a down interface. Is that
change intended?

Because of the flexcan Fixes: tag and Cc: stable, this would be
backported to every stable tree since v3.16. After that, "ip -details
link show" would stop printing berr-counter for down interfaces on all
drivers, including ones where the query always worked.

The commit message does say that the attribute is no longer reported.
Could it also say that this applies to all CAN drivers, and that it
replaces the per-driver support for down interfaces?

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261103204305.3211144-2-mkl%40pengutronix.de

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

* Re: [PATCH net 13/15] can: dev: can_fill_info(): don't wake up controller if interface is down
  2026-11-04 20:46   ` netdev-bot+sashiko
@ 2026-11-04 23:08     ` Marc Kleine-Budde
  2026-11-05  8:20       ` Paolo Abeni
  0 siblings, 1 reply; 25+ messages in thread
From: Marc Kleine-Budde @ 2026-11-04 23:08 UTC (permalink / raw)
  To: netdev-bot+sashiko
  Cc: netdev, davem, kuba, linux-can, kernel, liucj1228, s.grosjean,
	stable

[-- Attachment #1: Type: text/plain, Size: 5195 bytes --]

On 04.11.2026 20:46:50, netdev-bot+sashiko@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential
> issue(s) to consider.
>
> Critical: 0 · High: 0 · Medium: 1 · Low: 0
>
> If you believe none of the issues are legitimate, please reply to each
> issue of Medium severity or higher and explain why the AI is wrong.
> If any of them are legitimate and you plan to respin, please let patchwork
> know by including "pw-bot: cr" as a separate line at the end of your reply
> (one such reply per series is enough).
>
> - [Medium] The commit message describes this as removing one trigger of a
>   flexcan abort on i.MX8QXP.
>
> --
>
> can: dev: can_fill_info(): don't wake up controller if interface is down
>
> This patch changes can_fill_info() and can_get_size() to skip
> IFLA_CAN_BERR_COUNTER when the interface is not running. As a result,
> do_get_berr_counter() is never called on a down interface. The goal is
> to avoid a synchronous external abort in flexcan_get_berr_counter() on
> i.MX8QXP when "ip link show" runs on an interface that was never brought
> up.
>
> Link: https://patch.msgid.link/20261027065012.191348-1-chunyuzhiqiang@yinhe.ht
>
> > diff --git a/drivers/net/can/dev/netlink.c b/drivers/net/can/dev/netlink.c
> > index 766d455950f55..b958c04d53db6 100644
> > --- a/drivers/net/can/dev/netlink.c
> > +++ b/drivers/net/can/dev/netlink.c
>
> [ ... ]
>
> > @@ -1006,7 +1006,7 @@ static int can_fill_info(struct sk_buff *skb, const struct net_device *dev)
> >  	    nla_put(skb, IFLA_CAN_CTRLMODE, sizeof(cm), &cm) ||
> >  	    nla_put_u32(skb, IFLA_CAN_RESTART_MS, priv->restart_ms) ||
> >
> > -	    (priv->do_get_berr_counter &&
> > +	    (priv->do_get_berr_counter && netif_running(dev) &&
> >  	     !priv->do_get_berr_counter(dev, &bec) &&
> >  	     nla_put(skb, IFLA_CAN_BERR_COUNTER, sizeof(bec), &bec)) ||
>
> [Severity: Medium]
> Is this meant to change the output for every CAN driver, and not only
> flexcan?

This is change is intended. Reading the CAN error counters of a down
interface makes no sense, as they must be read from hardware. The CAN
error counters are set to 0 by the hardware (as required by the CAN
specification) when the interface is (re)started.

> can_fill_info() is the only core caller of priv->do_get_berr_counter.
> With this check, no driver that sets the callback will report
> IFLA_CAN_BERR_COUNTER on a down interface. can_get_size() gets the same
> change.
>
> Several drivers turn on their hardware inside the callback so that this
> query works while the interface is down:

Yes - this can be removed in later patches.

> drivers/net/can/sun4i_can.c:sun4ican_get_berr_counter() {
>     ...
> 	err = clk_prepare_enable(priv->clk);
>     ...
> }
>
> drivers/net/can/c_can/c_can_main.c:c_can_get_berr_counter() {
>     ...
> 	c_can_pm_runtime_get_sync(priv);
> 	err = __c_can_get_berr_counter(dev, bec);
> 	c_can_pm_runtime_put_sync(priv);
>     ...
> }
>
> Other drivers do the same thing:
>
>   bxcan_get_berr_counter() calls clk_prepare_enable()
>   xcan_get_berr_counter() and ctucan_get_berr_counter() call
>   pm_runtime_get_sync()
>   rcar_can_get_berr_counter(), rkcanfd_get_berr_counter() and
>   flexcan_get_berr_counter() call pm_runtime_resume_and_get()
>
> The commit in the Fixes: tag, ec56acfef2af1, states this intent
> directly: "flexcan_get_berr_counter() may be called from userspace even
> if the interface is down ... This patch switches on the clocks".
>
> After this change, the wake-up code in those drivers only runs when the
> device is already active, so it no longer does anything useful. Should
> that code be removed?

ACK

> Or would it be better to put the check in the
> flexcan driver instead?

no - we want to fix this conceptually on the CAN netlink level.

> The commit message cites m_can as the precedent. Commit 91a55c72a821
> fixed m_can inside the driver:
>
> drivers/net/can/m_can/m_can.c:m_can_get_berr_counter() {
>     ...
> 	/* Avoid waking up the controller if the interface is down */
> 	if (!(dev->flags & IFF_UP))
> 		return 0;
>     ...
> }
>
> That version still sends the attribute, with zeroed counters. With this
> patch, m_can sends no attribute at all for a down interface. Is that
> change intended?
>
> Because of the flexcan Fixes: tag and Cc: stable, this would be
> backported to every stable tree since v3.16. After that, "ip -details
> link show" would stop printing berr-counter for down interfaces on all
> drivers, including ones where the query always worked.
>
> The commit message does say that the attribute is no longer reported.
> Could it also say that this applies to all CAN drivers, and that it
> replaces the per-driver support for down interfaces?

If we need to re-spin this series/PR we'll update the commit message.

Marc

--
Pengutronix e.K.                 | Marc Kleine-Budde          |
Embedded Linux                   | https://www.pengutronix.de |
Vertretung Nürnberg              | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax:   +49-5121-206917-9   |

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

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

* Re: [PATCH net 09/15] can: raw, isotp: copy socket options outside of lock_sock()
  2026-11-04 20:46   ` netdev-bot+sashiko
@ 2026-11-05  7:47     ` Oliver Hartkopp
  0 siblings, 0 replies; 25+ messages in thread
From: Oliver Hartkopp @ 2026-11-05  7:47 UTC (permalink / raw)
  To: netdev-bot+sashiko, mkl
  Cc: netdev, davem, kuba, linux-can, kernel, liucj1228, s.grosjean,
	stable



On 04.11.26 21:46, netdev-bot+sashiko@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential
> issue(s) to consider.
> 
> Critical: 0 · High: 1 (1) · Medium: 0 · Low: 0
> 
> If you believe none of the issues are legitimate, please reply to each
> issue of Medium severity or higher and explain why the AI is wrong.
> If any of them are legitimate and you plan to respin, please let patchwork
> know by including "pw-bot: cr" as a separate line at the end of your reply
> (one such reply per series is enough).
> 
> - [High] The patch does not remove the RTNL stall it describes.
> 
> Pre-existing issues:
> - [High] This problem wasn't introduced by this patch, but CAN_BCM has
>    the same RTNL-stall problem through a sibling notifier, and the series…
> 
> --
> 
> can: raw, isotp: copy socket options outside of lock_sock()
> 
> This commit moves the user space copies for the CAN_RAW and CAN_ISOTP
> socket options, including the CAN_RAW_FILTER filters, so they happen
> before lock_sock(). raw_getsockopt() now duplicates the filters under
> the socket lock and copies them to user space after release_sock(). The
> goal is to stop a stalled user copy from holding lock_sock() and blocking
> raw_notify()/isotp_notify() under RTNL.
> 
> Link: https://patch.msgid.link/20261027-can-rx-lock-v12-4-eb5afc2d8b9b@hartkopp.net
> 
>> diff --git a/net/can/raw.c b/net/can/raw.c
>> index d6517f71cadeb..0926d5686e419 100644
>> --- a/net/can/raw.c
>> +++ b/net/can/raw.c
> 
> [ ... ]
> 
>> @@ -795,17 +730,84 @@ static int raw_setsockopt(struct socket *sock, int level, int optname,
>>   			  sockptr_t optval, unsigned int optlen)
>>   {
>>   	struct sock *sk = sock->sk;
>> +	struct can_filter *filter = NULL;  /* dyn. alloc'ed filters */
>> +	struct can_filter sfilter;         /* single filter */
>> +	union {
>> +		can_err_mask_t err_mask;
>> +		int flag;
>> +		struct can_raw_vcid_options vcid_opts;
>> +	} val;
>> +	unsigned int size = 0;
>> +	int count = 0;
>>   	int err;
>>   
>>   	if (level != SOL_CAN_RAW)
>>   		return -EINVAL;
>>   
>> +	/* Copy the option values from user space before lock_sock(), so a
>> +	 * stalled copy can not block raw_notify() waiting for lock_sock()
>> +	 * under RTNL.
>> +	 */
> 
> [Severity: High]
> The comment says a stalled copy can not block raw_notify() waiting for
> lock_sock() under RTNL. Is that true for the socket as a whole?

No. The comment and the commit message refer to the CAN_RAW and
CAN_ISOTP specific socket options. The commit message explicitly states
that the generic SOL_SOCKET options in sk_setsockopt() are not affected
by this patch.

The SO_ATTACH_FILTER copy under sockopt_lock_sock() is generic socket
code and not specific to CAN. Before this series raw_setsockopt() even
held RTNL itself during the copy of the CAN_RAW options. This patch
removes the CAN specific part of the problem.

Avoiding lock_sock() in raw_notify() and isotp_notify() under RTNL
altogether would be a redesign of the notifier handling, which is not
suitable for this fix and can be considered separately. The code
comment can be made more precise ("a stalled copy of the CAN_RAW
options") in a follow-up for can-next, if required. But I think the 
commit message is enough.

> 
> The same bound CAN_RAW socket can still hold the socket lock during a
> user copy through the SOL_SOCKET path. sk_setsockopt() takes the lock
> before it handles SO_ATTACH_FILTER:
> 
> net/core/sock.c:sk_setsockopt() {
>      ...
> 	sockopt_lock_sock(sk);
>      ...
> 			ret = sk_attach_filter(&fprog, sk);
>      ...
> }
> 
> and sk_attach_filter()->__get_filter() then copies the program from
> user space:
> 
> 	if (copy_from_user(prog->insns, fprog->filter, fsize)) {
> 
> SO_ATTACH_REUSEPORT_CBPF goes down the same path. On the getsockopt
> side, sk_get_filter() calls copy_to_sockptr() under sockopt_lock_sock().
> That is the SOL_SOCKET equivalent of the CAN_RAW_FILTER copy this patch
> moves after release_sock().
> 
> Suppose fprog.filter points into a FUSE or userfaultfd backed mapping
> that never resolves. On NETDEV_UNREGISTER or a netns change of the
> bound device, raw_notify() then waits under RTNL:
> 
> net/can/raw.c:raw_notify() {
>      ...
> 	case NETDEV_UNREGISTER:
> 		lock_sock(sk);
>      ...
> }
> 
> As far as I can tell, net/can/ has no capability checks. That would
> make this reachable by any local user who can create and bind a CAN_RAW
> socket.
> 
> The commit message says "The generic SOL_SOCKET options in
> sk_setsockopt() are not affected by this patch". It does not say that
> the RTNL stall is still reachable on the same socket. No later patch in
> the series changes net/core/sock.c or net/core/filter.c.
> 
> Would it be better to stop raw_notify() and isotp_notify() from blocking
> on lock_sock() under RTNL? The other option would be to also move the
> generic filter copies out of the socket lock.
> 
>> +	switch (optname) {
>> +	case CAN_RAW_FILTER:
> 
> [ ... ]
> 
>> +	if (size) {
>> +		if (optlen != size)
>> +			return -EINVAL;
>> +
>> +		if (copy_from_sockptr(&val, optval, size))
>> +			return -EFAULT;
>> +	}
>> +
>>   	lock_sock(sk);
>>   
>> -	err = raw_setsockopt_locked(sock, optname, optval, optlen);
>> +	err = raw_setsockopt_locked(sock, optname, &val, filter, count);
>>   
>>   	release_sock(sk);
> 
> [Severity: High]
> This is a pre-existing issue and was not introduced by this patch, but
> CAN_BCM seems to have the same RTNL stall through its notifier.

Right, this is a pre-existing issue that is not introduced by this
series. CAN_BCM does not use socket options at all. CAN_BCM (and 
CAN_J1939) notifier handling will be looked at separately.

> This series does not address or mention it.
> 
> bcm_sendmsg() takes the socket lock and then dispatches to
> bcm_tx_setup(), bcm_rx_setup() and bcm_tx_send(). These copy the CAN
> frames from the user iovec while the lock is held:
> 
> net/can/bcm.c:bcm_sendmsg() {
>      ...
> 	lock_sock(sk);
>      ...
> }
> 
> net/can/bcm.c:bcm_tx_setup() {
>      ...
> 			err = memcpy_from_msg((u8 *)cf, msg, op->cfsiz);
>      ...
> }
> 
> bcm_notify() takes the lock for every bcm socket in the netns under
> RTNL. It does this before checking whether the socket is bound to the
> vanishing device:
> 
> net/can/bcm.c:bcm_notify() {
>      ...
> 	if (!net_eq(dev_net(dev), sock_net(sk)))
> 		return;
> 
> 	switch (msg) {
> 
> 	case NETDEV_UNREGISTER:
> 		lock_sock(sk);
>      ...
> }
> 
> Say a BCM socket sends TX_SETUP, RX_SETUP or TX_SEND with the frame
> data in a FUSE or userfaultfd backed mapping that stalls. Can any CAN
> device unregistration in that netns then block RTNL indefinitely?
> 
> No later patch in the series changes these paths. The only bcm.c
> changes are proc and can_rx_register naming.
> 
>>   
>> +	/* on error free the filters allocated for CAN_RAW_FILTER above */
>> +	if (err && count > 1)
>> +		kfree(filter);
>> +
>>   	return err;
>>   }
> 
> [ ... ]
> 


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

* Re: [PATCH net 08/15] can: protect the receive lists with a per-device lock
  2026-11-04 20:46   ` netdev-bot+sashiko
@ 2026-11-05  8:10     ` Oliver Hartkopp
  0 siblings, 0 replies; 25+ messages in thread
From: Oliver Hartkopp @ 2026-11-05  8:10 UTC (permalink / raw)
  To: netdev-bot+sashiko, mkl
  Cc: netdev, davem, kuba, linux-can, kernel, liucj1228, s.grosjean,
	stable



On 04.11.26 21:46, netdev-bot+sashiko@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found more severe
> issue(s) in other patches of this series. This mail is only an FYI, in case
> you want to address the nit picks below when you respin.
> 
> Low priority / nit picks for your consideration:
> 
> - [Low] Since RTNL is no longer held across raw_bind() and isotp_bind(),
>    a NETDEV_DOWN followed by NETDEV_UP inside the bind() window is never…
> 
> Pre-existing issues:
> - [High] This patch did not introduce this problem, but CAN_BCM still
>    lets an unprivileged task stall a user-memory copy while holding…
> 
> --
> 
> can: protect the receive lists with a per-device lock
> 
> This adds a spinlock to struct can_dev_rcv_lists, so can_rx_unregister()
> finds a subscription even after the device has moved to another network
> namespace. The per-netns rcvlists_lock now only protects the statistics.
> CAN_RAW and CAN_ISOTP bind()/release()/setsockopt() stop taking RTNL and
> rely on lock_sock(), RCU publication of the bound device and a barrier
> pair for NETDEV_DOWN.
> 
> Link: https://lore.kernel.org/linux-can/CEA6A38A-2646-4ADA-95B4-CBAE2F301A8E@doyensec.com/
> Link: https://patch.msgid.link/20261027-can-rx-lock-v12-3-eb5afc2d8b9b@hartkopp.net
> 
>> diff --git a/net/can/bcm.c b/net/can/bcm.c
>> index 60406439a13f7..9288b96724da2 100644
>> --- a/net/can/bcm.c
>> +++ b/net/can/bcm.c
> 
> [ ... ]
> 
>> @@ -1936,6 +1925,7 @@ static int bcm_release(struct socket *sock)
>>   	list_del(&bo->notifier);
>>   	spin_unlock(&bcm_notifier_lock);
>>   
>> +	/* bcm_notify() can not access this socket anymore */
>>   	lock_sock(sk);
>>   
>>   #if IS_ENABLED(CONFIG_PROC_FS)
> 
> [Severity: High]
> This is a pre-existing issue, not one introduced by this patch. The
> commit message is about unprivileged users holding up RTNL, though, so
> is CAN_BCM still open to the same kind of problem?
> 

Yes, this is a pre-existing issue that is not introduced by this series.
It has already been answered for this patch set here:

https://lore.kernel.org/linux-can/64f4dfcf-40ec-4838-85d3-a30c3912e242@hartkopp.net/

CAN_BCM (and CAN_J1939) notifier handling will be looked at separately.

> bcm_sendmsg() takes lock_sock(sk). While it holds the lock,
> bcm_tx_setup(), bcm_rx_setup() and bcm_tx_send() copy frame data from
> user memory with memcpy_from_msg():
> 
> net/can/bcm.c:bcm_tx_setup() {
>      ...
> 			err = memcpy_from_msg((u8 *)cf, msg, op->cfsiz);
>      ...
> }
> 
> bcm_notify() runs from the netdev notifier with RTNL held. On
> NETDEV_UNREGISTER it takes lock_sock() on every BCM socket in the
> device's namespace, whether or not the socket is bound to that device:
> 
> net/can/bcm.c:bcm_notify() {
>      ...
> 	case NETDEV_UNREGISTER:
> 		lock_sock(sk);
>      ...
> }
> 
> Suppose the sendmsg iovec points at a FUSE-backed mapping whose daemon
> never answers. The copy then stalls with the socket lock held.
> 
> A CAN device unregister in that netns then blocks bcm_notify() in
> lock_sock() with RTNL held. For example, the user can delete a vcan in
> their own unprivileged user+net namespace. After that, every RTNL user
> on the host hangs.
> 
> The CAN_RAW version of this is fixed later in this series by "can: raw,
> isotp: copy socket options outside of lock_sock()". That covers
> memdup_sockptr()/copy_from_sockptr() in raw_setsockopt_locked() and
> copy_to_iter() in raw_getsockopt() under lock_sock(). Before this
> patch, raw_setsockopt() even held RTNL directly across the copy.
> 
> CAN_ISOTP is not affected. The BCM paths look the same at the end of
> the series.
> 
> Could the BCM frame data also be copied before lock_sock()?
> 
> [ ... ]
> 
>> diff --git a/net/can/raw.c b/net/can/raw.c
>> index e1d741a25be3f..d6517f71cadeb 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;
> 
> [ ... ]
> 
>> @@ -479,14 +500,25 @@ static int raw_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int len
> 
> [ ... ]
> 
>> +		rcu_read_lock();
>> +		dev = dev_get_by_index_rcu(sock_net(sk), addr->can_ifindex);
>> +		if (dev) {
>> +			dev_hold(dev);
>> +			/* pairs with smp_load_acquire() in raw_notify() */
>> +			smp_store_release(&ro->bind_dev, dev);
>> +		}
>> +		rcu_read_unlock();
> 
> [ ... ]
> 
>> @@ -496,9 +528,6 @@ static int raw_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int len
>>   			goto out_put_dev;
>>   		}
>>   
>> -		if (!(dev->flags & IFF_UP))
>> -			notify_enetdown = 1;
>> -
>>   		ifindex = dev->ifindex;
>>   
>>   		/* filters set by default/setsockopt */
> 
> [ ... ]
> 
>> @@ -528,24 +557,32 @@ 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);
>>   		if (ro->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;
>> +		}
>>   	}
> 
> [Severity: Low]
> The commit message says:
> 
>    "As RTNL does not keep NETDEV_DOWN out of bind() anymore, bind() checks
>    IFF_UP after publishing the bound device, with a barrier pair against
>    raw_notify() and isotp_notify(), so a down transition during bind() is
>    still reported with ENETDOWN."
> 
> Does this still hold when a full down/up cycle happens inside bind()?
> 

No. As RTNL does not keep NETDEV_DOWN out of bind() anymore, a down/up
cycle that completes between the filter registration and the IFF_UP
check in bind() is not reported with ENETDOWN. Frames received in this
window before the down transition may then already be queued for the
socket.

I consider this acceptable: ENETDOWN is an asynchronous notification
and bind() has not returned at this point. When bind() returns, the
socket is bound to a device that is up, and every down transition
after the bound device has been published is reported, which is
ensured by the barrier pair. So no down transition is missed for a
bound socket.

The commit message could be more precise here, but I don't think this
justifies a respin.

> raw_bind() registers the filters with raw_enable_allfilters() before it
> publishes ro->dev. raw_rcv() can already queue frames for the socket at
> that point. raw_enable_filters() can do up to CAN_RAW_FILTER_MAX
> GFP_KERNEL allocations with cond_resched(), so this window can be long.
> 
> In raw_notify(), the bind_dev hint only covers NETDEV_UNREGISTER. A
> NETDEV_DOWN in this window returns early:
> 
> raw_bind()                            RTNL holder
>    smp_store_release(&ro->bind_dev)
>    raw_enable_allfilters()
>      /* frames get queued */
>                                        dev_close()
>                                          clears IFF_UP
>                                          raw_notify(NETDEV_DOWN)
>                                            ro->dev != dev -> return
>                                        dev_open()
>                                          sets IFF_UP
>    WRITE_ONCE(ro->dev, dev)
>    smp_mb()
>    IFF_UP is set -> no ENETDOWN
> 
> isotp_bind() has the same window. so->dev is published early, but
> isotp_notify() drops NETDEV_DOWN while so->bound is still 0:
> 
> net/can/isotp.c:isotp_notify() {
>      ...
> 	case NETDEV_DOWN:
> 		if (!READ_ONCE(so->bound))
> 			break;
>      ...
> }
> 
> isotp_bind() only sets so->bound after both can_rx_register() calls,
> and samples IFF_UP after that.
> 
> Before this patch, holding RTNL across bind() ruled out this
> interleaving. Is it acceptable to miss ENETDOWN in this case? If so,
> should the commit message only promise this for a down state that is
> still in effect when bind() samples IFF_UP?
> 


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

* Re: [PATCH net 13/15] can: dev: can_fill_info(): don't wake up controller if interface is down
  2026-11-04 23:08     ` Marc Kleine-Budde
@ 2026-11-05  8:20       ` Paolo Abeni
  0 siblings, 0 replies; 25+ messages in thread
From: Paolo Abeni @ 2026-11-05  8:20 UTC (permalink / raw)
  To: Marc Kleine-Budde, netdev-bot+sashiko
  Cc: netdev, davem, kuba, linux-can, kernel, liucj1228, s.grosjean,
	stable

On 11/5/26 00:08, Marc Kleine-Budde wrote:
> On 04.11.2026 20:46:50, netdev-bot+sashiko@kernel.org wrote:
>> The commit message does say that the attribute is no longer reported.
>> Could it also say that this applies to all CAN drivers, and that it
>> replaces the per-driver support for down interfaces?
> 
> If we need to re-spin this series/PR we'll update the commit message.
No need to re-spin: your replies make sense to me, and my understanding
is that some follow-ups (on next) is the best course of action.

/P


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

* Re: [PATCH net 01/15] can: dev: can_dropped_invalid_skb: drop CAN XL frames on non-CAN XL devices
  2026-11-03 20:30 ` [PATCH net 01/15] can: dev: can_dropped_invalid_skb: drop CAN XL frames on non-CAN XL devices Marc Kleine-Budde
  2026-11-03 20:48   ` netdev-bot+sinfo
@ 2026-11-05  9:10   ` patchwork-bot+netdevbpf
  1 sibling, 0 replies; 25+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-11-05  9:10 UTC (permalink / raw)
  To: Marc Kleine-Budde; +Cc: netdev, davem, kuba, linux-can, kernel, mailhol, stable

Hello:

This series was applied to netdev/net.git (main)
by Marc Kleine-Budde <mkl@pengutronix.de>:

On Tue,  3 Nov 2026 21:30:22 +0100 you wrote:
> From: Vincent Mailhol <mailhol@kernel.org>
> 
> Sending a PF_PACKET bypasses the CAN framework logic and can directly
> reach a CAN driver's xmit() function. The PF_PACKET framework only
> checks that skb->len does not exceed the net_device MTU.
> 
> For a CAN device that is not CAN XL capable, anything above CANFD_MTU
> (72 bytes) is therefore dropped before it reaches the driver. However,
> CAN XL frames are variable length. can_is_canxl_skb() accepts lengths in
> the range CANXL_HDR_SIZE + CANXL_MIN_DLEN up to CANXL_MTU, i.e. 13 to
> 2060 bytes.
> 
> [...]

Here is the summary with links:
  - [net,01/15] can: dev: can_dropped_invalid_skb: drop CAN XL frames on non-CAN XL devices
    https://git.kernel.org/netdev/net/c/0fce44e29fce
  - [net,02/15] can: isotp: check the frame type, not just the length
    https://git.kernel.org/netdev/net/c/933bf0fcb325
  - [net,03/15] can: skb: make echo skb freeing safe in any IRQ context
    https://git.kernel.org/netdev/net/c/d74307196492
  - [net,04/15] can: skb: make CAN skb allocation failure paths IRQ-safe
    https://git.kernel.org/netdev/net/c/d4b616a38118
  - [net,05/15] can: dev: can_put_echo_skb(): free skb on invalid echo index
    https://git.kernel.org/netdev/net/c/b4ebcbbf66ba
  - [net,06/15] can: replace ARPHRD_CAN type checks with can_get_ml_priv()
    https://git.kernel.org/netdev/net/c/962947b69589
  - [net,07/15] can: isotp: check the return values of can_rx_register() in isotp_bind()
    https://git.kernel.org/netdev/net/c/874f5f14cf62
  - [net,08/15] can: protect the receive lists with a per-device lock
    https://git.kernel.org/netdev/net/c/0f3ad9ab40e3
  - [net,09/15] can: raw, isotp: copy socket options outside of lock_sock()
    https://git.kernel.org/netdev/net/c/43c973a0887d
  - [net,10/15] can: proc: reset pkg_stats atomics individually
    https://git.kernel.org/netdev/net/c/12297f855bbd
  - [net,11/15] can: proc: skip stats calculation after reset in can_stat_update()
    https://git.kernel.org/netdev/net/c/01b19ed9fa03
  - [net,12/15] can: proc: remove pointers from CAN specific proc output
    https://git.kernel.org/netdev/net/c/b98179a6e194
  - [net,13/15] can: dev: can_fill_info(): don't wake up controller if interface is down
    https://git.kernel.org/netdev/net/c/75332c410c81
  - [net,14/15] can: dev: can_tdc_fill_info(): don't read automatic TDCV if interface is down
    https://git.kernel.org/netdev/net/c/f5128a767261
  - [net,15/15] can: peak_usb: pcan_usb_fd_decode_buf(): accept short uCAN end-of-list records
    https://git.kernel.org/netdev/net/c/2d4c272e816a

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



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

end of thread, other threads:[~2026-11-05  9:10 UTC | newest]

Thread overview: 25+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-03 20:30 [PATCH net 0/15] pull-request: can 2026-11-03 Marc Kleine-Budde
2026-11-03 20:30 ` [PATCH net 01/15] can: dev: can_dropped_invalid_skb: drop CAN XL frames on non-CAN XL devices Marc Kleine-Budde
2026-11-03 20:48   ` netdev-bot+sinfo
2026-11-05  9:10   ` patchwork-bot+netdevbpf
2026-11-03 20:30 ` [PATCH net 02/15] can: isotp: check the frame type, not just the length Marc Kleine-Budde
2026-11-03 20:30 ` [PATCH net 03/15] can: skb: make echo skb freeing safe in any IRQ context Marc Kleine-Budde
2026-11-03 20:30 ` [PATCH net 04/15] can: skb: make CAN skb allocation failure paths IRQ-safe Marc Kleine-Budde
2026-11-03 20:30 ` [PATCH net 05/15] can: dev: can_put_echo_skb(): free skb on invalid echo index Marc Kleine-Budde
2026-11-03 20:30 ` [PATCH net 06/15] can: replace ARPHRD_CAN type checks with can_get_ml_priv() Marc Kleine-Budde
2026-11-03 20:30 ` [PATCH net 07/15] can: isotp: check the return values of can_rx_register() in isotp_bind() Marc Kleine-Budde
2026-11-03 20:30 ` [PATCH net 08/15] can: protect the receive lists with a per-device lock Marc Kleine-Budde
2026-11-04 20:46   ` netdev-bot+sashiko
2026-11-05  8:10     ` Oliver Hartkopp
2026-11-03 20:30 ` [PATCH net 09/15] can: raw, isotp: copy socket options outside of lock_sock() Marc Kleine-Budde
2026-11-04 20:46   ` netdev-bot+sashiko
2026-11-05  7:47     ` Oliver Hartkopp
2026-11-03 20:30 ` [PATCH net 10/15] can: proc: reset pkg_stats atomics individually Marc Kleine-Budde
2026-11-03 20:30 ` [PATCH net 11/15] can: proc: skip stats calculation after reset in can_stat_update() Marc Kleine-Budde
2026-11-03 20:30 ` [PATCH net 12/15] can: proc: remove pointers from CAN specific proc output Marc Kleine-Budde
2026-11-03 20:30 ` [PATCH net 13/15] can: dev: can_fill_info(): don't wake up controller if interface is down Marc Kleine-Budde
2026-11-04 20:46   ` netdev-bot+sashiko
2026-11-04 23:08     ` Marc Kleine-Budde
2026-11-05  8:20       ` Paolo Abeni
2026-11-03 20:30 ` [PATCH net 14/15] can: dev: can_tdc_fill_info(): don't read automatic TDCV " Marc Kleine-Budde
2026-11-03 20:30 ` [PATCH net 15/15] can: peak_usb: pcan_usb_fd_decode_buf(): accept short uCAN end-of-list records Marc Kleine-Budde

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