| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/07/16 21:21 | flow | patch-triage |
0mArgs:null Results: {
"EnableConfigs": [
"CAN_BCM",
"CAN_ISOTP",
"CAN_J1939",
"CAN_RAW",
"CAN_ESD_USB"
],
"FocusSymbols": [
"bcm_tx_setup",
"bcm_rx_setup",
"bcm_sendmsg",
"bcm_release",
"bcm_tx_timeout_handler",
"bcm_rx_timeout_handler",
"isotp_sendmsg",
"isotp_bind",
"isotp_release",
"isotp_tx_timeout",
"isotp_rcv_echo",
"isotp_rcv_fc",
"isotp_rcv_cf",
"j1939_tp_send",
"raw_setsockopt",
"esd_usb_disconnect"
],
"Reasoning": "The patch series makes significant functional changes to the CAN subsystem, particularly in the BCM and ISOTP protocols. It introduces major reworks of timers, locking, and state machines in `net/can/bcm.c` and `net/can/isotp.c`. It also fixes a bug in J1939 transport, changes locking in `raw_setsockopt`, and fixes a memory leak in the `esd_usb` driver's disconnect path. These changes are highly reachable from userspace and should be fuzzed.",
"WorthFuzzing": true
} |
| 1/1 | 2026/07/16 21:21 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit cce599b07db5450d9336aa06c7b2997adfe36295\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Thu Jul 16 21:21:31 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/.mailmap b/.mailmap\nindex 12f3acdebd7226..918fcd6575b12a 100644\n--- a/.mailmap\n+++ b/.mailmap\n@@ -825,8 +825,8 @@ Sriram Yagnaraman \u003csriram.yagnaraman@ericsson.com\u003e \u003csriram.yagnaraman@est.tech\u003e\n Stanislav Fomichev \u003csdf@fomichev.me\u003e \u003csdf@google.com\u003e\n Stanislav Fomichev \u003csdf@fomichev.me\u003e \u003cstfomichev@gmail.com\u003e\n Stefan Wahren \u003cwahrenst@gmx.net\u003e \u003cstefan.wahren@i2se.com\u003e\n-Stéphane Grosjean \u003cstephane.grosjean@hms-networks.com\u003e \u003cs.grosjean@peak-system.com\u003e\n-Stéphane Grosjean \u003cstephane.grosjean@hms-networks.com\u003e \u003cstephane.grosjean@free.fr\u003e\n+Stéphane Grosjean \u003cs.grosjean@peak-system.fr\u003e \u003cs.grosjean@peak-system.com\u003e\n+Stéphane Grosjean \u003cs.grosjean@peak-system.fr\u003e \u003cstephane.grosjean@free.fr\u003e\n Stéphane Witzmann \u003cstephane.witzmann@ubpmes.univ-bpclermont.fr\u003e\n Stephen Hemminger \u003cstephen@networkplumber.org\u003e \u003cshemminger@linux-foundation.org\u003e\n Stephen Hemminger \u003cstephen@networkplumber.org\u003e \u003cshemminger@osdl.org\u003e\ndiff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig\nindex e4058708ae6833..a8fad6fe530207 100644\n--- a/drivers/net/can/Kconfig\n+++ b/drivers/net/can/Kconfig\n@@ -40,11 +40,8 @@ config CAN_VXCAN\n \t When one end receives the packet it appears on its pair and vice\n \t versa. The vxcan can be used for cross namespace communication.\n \n-\t In opposite to vcan loopback devices the vxcan only forwards CAN\n-\t frames to its pair and does *not* provide a local echo of sent\n-\t CAN frames. To disable a potential echo in af_can.c the vxcan driver\n-\t announces IFF_ECHO in the interface flags. To have a clean start\n-\t in each namespace the CAN GW hop counter is set to zero.\n+\t To have a clean start in each namespace the CAN GW hop counter is\n+\t set to zero.\n \n \t This driver can also be built as a module. If so, the module\n \t will be called vxcan.\ndiff --git a/drivers/net/can/peak_canfd/peak_canfd.c b/drivers/net/can/peak_canfd/peak_canfd.c\nindex 06cb2629f66abc..4fd1aefb780fdb 100644\n--- a/drivers/net/can/peak_canfd/peak_canfd.c\n+++ b/drivers/net/can/peak_canfd/peak_canfd.c\n@@ -2,7 +2,7 @@\n /* Copyright (C) 2007, 2011 Wolfgang Grandegger \u003cwg@grandegger.com\u003e\n *\n * Copyright (C) 2016-2025 PEAK System-Technik GmbH\n- * Author: Stéphane Grosjean \u003cstephane.grosjean@hms-networks.com\u003e\n+ * Author: Stéphane Grosjean \u003cs.grosjean@peak-system.fr\u003e\n */\n \n #include \u003clinux/can.h\u003e\ndiff --git a/drivers/net/can/peak_canfd/peak_canfd_user.h b/drivers/net/can/peak_canfd/peak_canfd_user.h\nindex 60c6542028cf44..dc0ecb566a85a3 100644\n--- a/drivers/net/can/peak_canfd/peak_canfd_user.h\n+++ b/drivers/net/can/peak_canfd/peak_canfd_user.h\n@@ -2,7 +2,7 @@\n /* CAN driver for PEAK System micro-CAN based adapters\n *\n * Copyright (C) 2003-2025 PEAK System-Technik GmbH\n- * Author: Stéphane Grosjean \u003cstephane.grosjean@hms-networks.com\u003e\n+ * Author: Stéphane Grosjean \u003cs.grosjean@peak-system.fr\u003e\n */\n #ifndef PEAK_CANFD_USER_H\n #define PEAK_CANFD_USER_H\ndiff --git a/drivers/net/can/peak_canfd/peak_pciefd_main.c b/drivers/net/can/peak_canfd/peak_pciefd_main.c\nindex 93558e33bc02bb..7c749301ea84cc 100644\n--- a/drivers/net/can/peak_canfd/peak_pciefd_main.c\n+++ b/drivers/net/can/peak_canfd/peak_pciefd_main.c\n@@ -4,7 +4,7 @@\n * Derived from the PCAN project file driver/src/pcan_pci.c:\n *\n * Copyright (C) 2001-2025 PEAK System-Technik GmbH\n- * Author: Stéphane Grosjean \u003cstephane.grosjean@hms-networks.com\u003e\n+ * Author: Stéphane Grosjean \u003cs.grosjean@peak-system.fr\u003e\n */\n \n #include \u003clinux/kernel.h\u003e\n@@ -19,7 +19,7 @@\n \n #include \"peak_canfd_user.h\"\n \n-MODULE_AUTHOR(\"Stéphane Grosjean \u003cstephane.grosjean@hms-networks.com\u003e\");\n+MODULE_AUTHOR(\"Stéphane Grosjean \u003cs.grosjean@peak-system.fr\u003e\");\n MODULE_DESCRIPTION(\"Socket-CAN driver for PEAK PCAN PCIe/M.2 FD family cards\");\n MODULE_LICENSE(\"GPL v2\");\n \ndiff --git a/drivers/net/can/sja1000/peak_pci.c b/drivers/net/can/sja1000/peak_pci.c\nindex 4cc4a1581dd115..69c61ccf621df8 100644\n--- a/drivers/net/can/sja1000/peak_pci.c\n+++ b/drivers/net/can/sja1000/peak_pci.c\n@@ -5,7 +5,7 @@\n * Derived from the PCAN project file driver/src/pcan_pci.c:\n *\n * Copyright (C) 2001-2025 PEAK System-Technik GmbH\n- * Author: Stéphane Grosjean \u003cstephane.grosjean@hms-networks.com\u003e\n+ * Author: Stéphane Grosjean \u003cs.grosjean@peak-system.fr\u003e\n */\n \n #include \u003clinux/kernel.h\u003e\n@@ -22,7 +22,7 @@\n \n #include \"sja1000.h\"\n \n-MODULE_AUTHOR(\"Stéphane Grosjean \u003cstephane.grosjean@hms-networks.com\u003e\");\n+MODULE_AUTHOR(\"Stéphane Grosjean \u003cs.grosjean@peak-system.fr\u003e\");\n MODULE_DESCRIPTION(\"Socket-CAN driver for PEAK PCAN PCI family cards\");\n MODULE_LICENSE(\"GPL v2\");\n \ndiff --git a/drivers/net/can/sja1000/peak_pcmcia.c b/drivers/net/can/sja1000/peak_pcmcia.c\nindex 42a77d435b3920..c3c2aa21da47b2 100644\n--- a/drivers/net/can/sja1000/peak_pcmcia.c\n+++ b/drivers/net/can/sja1000/peak_pcmcia.c\n@@ -4,7 +4,7 @@\n * Derived from the PCAN project file driver/src/pcan_pccard.c\n *\n * Copyright (C) 2006-2025 PEAK System-Technik GmbH\n- * Author: Stéphane Grosjean \u003cstephane.grosjean@hms-networks.com\u003e\n+ * Author: Stéphane Grosjean \u003cs.grosjean@peak-system.fr\u003e\n */\n #include \u003clinux/kernel.h\u003e\n #include \u003clinux/module.h\u003e\n@@ -19,7 +19,7 @@\n #include \u003clinux/can/dev.h\u003e\n #include \"sja1000.h\"\n \n-MODULE_AUTHOR(\"Stéphane Grosjean \u003cstephane.grosjean@hms-networks.com\u003e\");\n+MODULE_AUTHOR(\"Stéphane Grosjean \u003cs.grosjean@peak-system.fr\u003e\");\n MODULE_DESCRIPTION(\"CAN driver for PEAK-System PCAN-PC Cards\");\n MODULE_LICENSE(\"GPL v2\");\n \ndiff --git a/drivers/net/can/usb/esd_usb.c b/drivers/net/can/usb/esd_usb.c\nindex d257440fa01ff1..f41d4a0d140f7e 100644\n--- a/drivers/net/can/usb/esd_usb.c\n+++ b/drivers/net/can/usb/esd_usb.c\n@@ -1390,10 +1390,13 @@ static void esd_usb_disconnect(struct usb_interface *intf)\n \t\t\t\tnetdev = dev-\u003enets[i]-\u003enetdev;\n \t\t\t\tnetdev_info(netdev, \"unregister\\n\");\n \t\t\t\tunregister_netdev(netdev);\n-\t\t\t\tfree_candev(netdev);\n \t\t\t}\n \t\t}\n \t\tunlink_all_urbs(dev);\n+\t\tfor (i = 0; i \u003c dev-\u003enet_count; i++) {\n+\t\t\tif (dev-\u003enets[i])\n+\t\t\t\tfree_candev(dev-\u003enets[i]-\u003enetdev);\n+\t\t}\n \t\tkfree(dev);\n \t}\n }\ndiff --git a/drivers/net/can/usb/peak_usb/pcan_usb.c b/drivers/net/can/usb/peak_usb/pcan_usb.c\nindex 9278a1522aae5e..8fd058c328564e 100644\n--- a/drivers/net/can/usb/peak_usb/pcan_usb.c\n+++ b/drivers/net/can/usb/peak_usb/pcan_usb.c\n@@ -4,7 +4,7 @@\n * Derived from the PCAN project file driver/src/pcan_usb.c\n *\n * Copyright (C) 2003-2025 PEAK System-Technik GmbH\n- * Author: Stéphane Grosjean \u003cstephane.grosjean@hms-networks.com\u003e\n+ * Author: Stéphane Grosjean \u003cs.grosjean@peak-system.fr\u003e\n *\n * Many thanks to Klaus Hitschler \u003cklaus.hitschler@gmx.de\u003e\n */\ndiff --git a/drivers/net/can/usb/peak_usb/pcan_usb_core.c b/drivers/net/can/usb/peak_usb/pcan_usb_core.c\nindex cf48bb26d46d23..c7933d1acc99a9 100644\n--- a/drivers/net/can/usb/peak_usb/pcan_usb_core.c\n+++ b/drivers/net/can/usb/peak_usb/pcan_usb_core.c\n@@ -4,7 +4,7 @@\n * Derived from the PCAN project file driver/src/pcan_usb_core.c\n *\n * Copyright (C) 2003-2025 PEAK System-Technik GmbH\n- * Author: Stéphane Grosjean \u003cstephane.grosjean@hms-networks.com\u003e\n+ * Author: Stéphane Grosjean \u003cs.grosjean@peak-system.fr\u003e\n *\n * Many thanks to Klaus Hitschler \u003cklaus.hitschler@gmx.de\u003e\n */\n@@ -24,7 +24,7 @@\n \n #include \"pcan_usb_core.h\"\n \n-MODULE_AUTHOR(\"Stéphane Grosjean \u003cstephane.grosjean@hms-networks.com\u003e\");\n+MODULE_AUTHOR(\"Stéphane Grosjean \u003cs.grosjean@peak-system.fr\u003e\");\n MODULE_DESCRIPTION(\"CAN driver for PEAK-System USB adapters\");\n MODULE_LICENSE(\"GPL v2\");\n \ndiff --git a/drivers/net/can/usb/peak_usb/pcan_usb_core.h b/drivers/net/can/usb/peak_usb/pcan_usb_core.h\nindex d1c1897d47b9c6..65999f04f4b76e 100644\n--- a/drivers/net/can/usb/peak_usb/pcan_usb_core.h\n+++ b/drivers/net/can/usb/peak_usb/pcan_usb_core.h\n@@ -4,7 +4,7 @@\n * Derived from the PCAN project file driver/src/pcan_usb_core.c\n *\n * Copyright (C) 2003-2025 PEAK System-Technik GmbH\n- * Author: Stéphane Grosjean \u003cstephane.grosjean@hms-networks.com\u003e\n+ * Author: Stéphane Grosjean \u003cs.grosjean@peak-system.fr\u003e\n *\n * Many thanks to Klaus Hitschler \u003cklaus.hitschler@gmx.de\u003e\n */\ndiff --git a/drivers/net/can/usb/peak_usb/pcan_usb_fd.c b/drivers/net/can/usb/peak_usb/pcan_usb_fd.c\nindex eb4f5884ad7358..ef9fd693e9bd38 100644\n--- a/drivers/net/can/usb/peak_usb/pcan_usb_fd.c\n+++ b/drivers/net/can/usb/peak_usb/pcan_usb_fd.c\n@@ -3,7 +3,7 @@\n * CAN driver for PEAK System PCAN-USB FD / PCAN-USB Pro FD adapter\n *\n * Copyright (C) 2013-2025 PEAK System-Technik GmbH\n- * Author: Stéphane Grosjean \u003cstephane.grosjean@hms-networks.com\u003e\n+ * Author: Stéphane Grosjean \u003cs.grosjean@peak-system.fr\u003e\n */\n #include \u003clinux/ethtool.h\u003e\n #include \u003clinux/module.h\u003e\ndiff --git a/drivers/net/can/usb/peak_usb/pcan_usb_pro.c b/drivers/net/can/usb/peak_usb/pcan_usb_pro.c\nindex 4bfa8d0fbb32fd..aefcded8e12a8f 100644\n--- a/drivers/net/can/usb/peak_usb/pcan_usb_pro.c\n+++ b/drivers/net/can/usb/peak_usb/pcan_usb_pro.c\n@@ -4,7 +4,7 @@\n * Derived from the PCAN project file driver/src/pcan_usbpro.c\n *\n * Copyright (C) 2003-2025 PEAK System-Technik GmbH\n- * Author: Stéphane Grosjean \u003cstephane.grosjean@hms-networks.com\u003e\n+ * Author: Stéphane Grosjean \u003cs.grosjean@peak-system.fr\u003e\n */\n #include \u003clinux/ethtool.h\u003e\n #include \u003clinux/module.h\u003e\ndiff --git a/drivers/net/can/usb/peak_usb/pcan_usb_pro.h b/drivers/net/can/usb/peak_usb/pcan_usb_pro.h\nindex 162c7546d3a833..d669c9e610c7e8 100644\n--- a/drivers/net/can/usb/peak_usb/pcan_usb_pro.h\n+++ b/drivers/net/can/usb/peak_usb/pcan_usb_pro.h\n@@ -4,7 +4,7 @@\n * Derived from the PCAN project file driver/src/pcan_usbpro_fw.h\n *\n * Copyright (C) 2003-2025 PEAK System-Technik GmbH\n- * Author: Stéphane Grosjean \u003cstephane.grosjean@hms-networks.com\u003e\n+ * Author: Stéphane Grosjean \u003cs.grosjean@peak-system.fr\u003e\n */\n #ifndef PCAN_USB_PRO_H\n #define PCAN_USB_PRO_H\ndiff --git a/include/linux/can/dev/peak_canfd.h b/include/linux/can/dev/peak_canfd.h\nindex d3788a3d0942b2..056e0efa649fd3 100644\n--- a/include/linux/can/dev/peak_canfd.h\n+++ b/include/linux/can/dev/peak_canfd.h\n@@ -3,7 +3,7 @@\n * CAN driver for PEAK System micro-CAN based adapters\n *\n * Copyright (C) 2003-2025 PEAK System-Technik GmbH\n- * Author: Stéphane Grosjean \u003cstephane.grosjean@hms-networks.com\u003e\n+ * Author: Stéphane Grosjean \u003cs.grosjean@peak-system.fr\u003e\n */\n #ifndef PUCAN_H\n #define PUCAN_H\ndiff --git a/net/can/bcm.c b/net/can/bcm.c\nindex a4bef2c48a559c..3d637a1e0ac1a9 100644\n--- a/net/can/bcm.c\n+++ b/net/can/bcm.c\n@@ -58,6 +58,7 @@\n #include \u003clinux/can/skb.h\u003e\n #include \u003clinux/can/bcm.h\u003e\n #include \u003clinux/slab.h\u003e\n+#include \u003clinux/workqueue.h\u003e\n #include \u003clinux/spinlock.h\u003e\n #include \u003cnet/can.h\u003e\n #include \u003cnet/sock.h\u003e\n@@ -92,6 +93,8 @@ MODULE_ALIAS(\"can-proto-2\");\n \n #define BCM_MIN_NAMELEN CAN_REQUIRED_SIZE(struct sockaddr_can, can_ifindex)\n \n+static struct workqueue_struct *bcm_wq;\n+\n /*\n * easy access to the first 64 bit of can(fd)_frame payload. cp-\u003edata is\n * 64 bit aligned so the offset has to be multiples of 8 which is ensured\n@@ -105,14 +108,16 @@ static inline u64 get_u64(const struct canfd_frame *cp, int offset)\n struct bcm_op {\n \tstruct list_head list;\n \tstruct rcu_head rcu;\n+\tstruct work_struct work;\n \tint ifindex;\n \tcanid_t can_id;\n \tu32 flags;\n-\tunsigned long frames_abs, frames_filtered;\n+\tatomic_long_t frames_abs, frames_filtered;\n \tstruct bcm_timeval ival1, ival2;\n \tstruct hrtimer timer, thrtimer;\n \tktime_t rx_stamp, kt_ival1, kt_ival2, kt_lastmsg;\n \tint rx_ifindex;\n+\tint if_detected; /* first received ifindex in ANYDEV rx_op mode */\n \tint cfsiz;\n \tu32 count;\n \tu32 nframes;\n@@ -124,7 +129,9 @@ struct bcm_op {\n \tstruct canfd_frame last_sframe;\n \tstruct sock *sk;\n \tstruct net_device *rx_reg_dev;\n-\tspinlock_t bcm_tx_lock; /* protect currframe/count in runtime updates */\n+\tnetdevice_tracker rx_reg_dev_tracker;\n+\tspinlock_t bcm_tx_lock; /* protect tx data and timer updates */\n+\tspinlock_t bcm_rx_update_lock; /* protect filter/timer data updates */\n };\n \n struct bcm_sock {\n@@ -224,10 +231,13 @@ static int bcm_proc_show(struct seq_file *m, void *v)\n \n \tlist_for_each_entry_rcu(op, \u0026bo-\u003erx_ops, list) {\n \n-\t\tunsigned long reduction;\n+\t\tlong reduction, frames_filtered, frames_abs;\n+\n+\t\tframes_filtered = atomic_long_read(\u0026op-\u003eframes_filtered);\n+\t\tframes_abs = atomic_long_read(\u0026op-\u003eframes_abs);\n \n \t\t/* print only active entries \u0026 prevent division by zero */\n-\t\tif (!op-\u003eframes_abs)\n+\t\tif (!frames_abs)\n \t\t\tcontinue;\n \n \t\tseq_printf(m, \"rx_op: %03X %-5s \", op-\u003ecan_id,\n@@ -249,15 +259,15 @@ static int bcm_proc_show(struct seq_file *m, void *v)\n \t\t\t\t (long long)ktime_to_us(op-\u003ekt_ival2));\n \n \t\tseq_printf(m, \"# recv %ld (%ld) =\u003e reduction: \",\n-\t\t\t op-\u003eframes_filtered, op-\u003eframes_abs);\n+\t\t\t frames_filtered, frames_abs);\n \n-\t\treduction = 100 - (op-\u003eframes_filtered * 100) / op-\u003eframes_abs;\n+\t\treduction = 100 - (frames_filtered * 100) / frames_abs;\n \n \t\tseq_printf(m, \"%s%ld%%\\n\",\n \t\t\t (reduction == 100) ? \"near \" : \"\", reduction);\n \t}\n \n-\tlist_for_each_entry(op, \u0026bo-\u003etx_ops, list) {\n+\tlist_for_each_entry_rcu(op, \u0026bo-\u003etx_ops, list) {\n \n \t\tseq_printf(m, \"tx_op: %03X %s \", op-\u003ecan_id,\n \t\t\t bcm_proc_getifname(net, ifname, op-\u003eifindex));\n@@ -275,7 +285,8 @@ static int bcm_proc_show(struct seq_file *m, void *v)\n \t\t\tseq_printf(m, \"t2=%lld \",\n \t\t\t\t (long long)ktime_to_us(op-\u003ekt_ival2));\n \n-\t\tseq_printf(m, \"# sent %ld\\n\", op-\u003eframes_abs);\n+\t\tseq_printf(m, \"# sent %ld\\n\",\n+\t\t\t atomic_long_read(\u0026op-\u003eframes_abs));\n \t}\n \tseq_putc(m, '\\n');\n \n@@ -285,26 +296,50 @@ static int bcm_proc_show(struct seq_file *m, void *v)\n }\n #endif /* CONFIG_PROC_FS */\n \n+static void bcm_update_rx_stats(struct bcm_op *op)\n+{\n+\t/* prevent overflow of the reduction% calculation in bcm_proc_show() */\n+\tif (atomic_long_inc_return(\u0026op-\u003eframes_abs) \u003e LONG_MAX / 100) {\n+\t\tatomic_long_set(\u0026op-\u003eframes_filtered, 0);\n+\t\tatomic_long_set(\u0026op-\u003eframes_abs, 0);\n+\t}\n+}\n+\n+static void bcm_update_tx_stats(struct bcm_op *op)\n+{\n+\t/* tx_op has no reduction% calculation - use the full range and\n+\t * just keep the displayed counter non-negative on overflow\n+\t */\n+\tif (atomic_long_inc_return(\u0026op-\u003eframes_abs) == LONG_MAX)\n+\t\tatomic_long_set(\u0026op-\u003eframes_abs, 0);\n+}\n+\n /*\n * bcm_can_tx - send the (next) CAN frame to the appropriate CAN interface\n * of the given bcm tx op\n */\n-static void bcm_can_tx(struct bcm_op *op)\n+static void bcm_can_tx(struct bcm_op *op, struct canfd_frame *cf)\n {\n \tstruct sk_buff *skb;\n \tstruct can_skb_ext *csx;\n \tstruct net_device *dev;\n-\tstruct canfd_frame *cf;\n+\tstruct canfd_frame cframe;\n+\tbool cyclic = !cf;\n+\tunsigned int idx = 0;\n \tint err;\n \n \t/* no target device? =\u003e exit */\n \tif (!op-\u003eifindex)\n \t\treturn;\n \n-\t/* read currframe under lock protection */\n-\tspin_lock_bh(\u0026op-\u003ebcm_tx_lock);\n-\tcf = op-\u003eframes + op-\u003ecfsiz * op-\u003ecurrframe;\n-\tspin_unlock_bh(\u0026op-\u003ebcm_tx_lock);\n+\tif (cyclic) {\n+\t\t/* read currframe under lock protection */\n+\t\tspin_lock_bh(\u0026op-\u003ebcm_tx_lock);\n+\t\tidx = op-\u003ecurrframe;\n+\t\tmemcpy(\u0026cframe, op-\u003eframes + op-\u003ecfsiz * idx, op-\u003ecfsiz);\n+\t\tcf = \u0026cframe;\n+\t\tspin_unlock_bh(\u0026op-\u003ebcm_tx_lock);\n+\t}\n \n \tdev = dev_get_by_index(sock_net(op-\u003esk), op-\u003eifindex);\n \tif (!dev) {\n@@ -335,16 +370,22 @@ static void bcm_can_tx(struct bcm_op *op)\n \tspin_lock_bh(\u0026op-\u003ebcm_tx_lock);\n \n \tif (!err)\n-\t\top-\u003eframes_abs++;\n+\t\tbcm_update_tx_stats(op);\n \n-\top-\u003ecurrframe++;\n+\t/* only advance the cyclic sequence if nothing reset currframe while\n+\t * we were sending - a concurrent TX_RESET_MULTI_IDX means this\n+\t * frame's bookkeeping belongs to a sequence that no longer exists\n+\t */\n+\tif (!cyclic || op-\u003ecurrframe == idx) {\n+\t\top-\u003ecurrframe++;\n \n-\t/* reached last frame? */\n-\tif (op-\u003ecurrframe \u003e= op-\u003enframes)\n-\t\top-\u003ecurrframe = 0;\n+\t\t/* reached last frame? */\n+\t\tif (op-\u003ecurrframe \u003e= op-\u003enframes)\n+\t\t\top-\u003ecurrframe = 0;\n \n-\tif (op-\u003ecount \u003e 0)\n-\t\top-\u003ecount--;\n+\t\tif (op-\u003ecount \u003e 0)\n+\t\t\top-\u003ecount--;\n+\t}\n \n \tspin_unlock_bh(\u0026op-\u003ebcm_tx_lock);\n out:\n@@ -433,12 +474,18 @@ static bool bcm_tx_set_expiry(struct bcm_op *op, struct hrtimer *hrt)\n {\n \tktime_t ival;\n \n+\tspin_lock_bh(\u0026op-\u003ebcm_tx_lock);\n+\n \tif (op-\u003ekt_ival1 \u0026\u0026 op-\u003ecount)\n \t\tival = op-\u003ekt_ival1;\n-\telse if (op-\u003ekt_ival2)\n+\telse if (op-\u003ekt_ival2) {\n \t\tival = op-\u003ekt_ival2;\n-\telse\n+\t} else {\n+\t\tspin_unlock_bh(\u0026op-\u003ebcm_tx_lock);\n \t\treturn false;\n+\t}\n+\n+\tspin_unlock_bh(\u0026op-\u003ebcm_tx_lock);\n \n \thrtimer_set_expires(hrt, ktime_add(ktime_get(), ival));\n \treturn true;\n@@ -455,26 +502,48 @@ static enum hrtimer_restart bcm_tx_timeout_handler(struct hrtimer *hrtimer)\n {\n \tstruct bcm_op *op = container_of(hrtimer, struct bcm_op, timer);\n \tstruct bcm_msg_head msg_head;\n+\tbool tx_ival1, tx_ival2;\n+\n+\t/* snapshot kt_ival1/kt_ival2/count under lock to avoid torn\n+\t * ktime_t reads racing with concurrent bcm_tx_setup() updates\n+\t */\n+\tspin_lock_bh(\u0026op-\u003ebcm_tx_lock);\n+\ttx_ival1 = op-\u003ekt_ival1 \u0026\u0026 (op-\u003ecount \u003e 0);\n+\ttx_ival2 = !!op-\u003ekt_ival2;\n+\tspin_unlock_bh(\u0026op-\u003ebcm_tx_lock);\n \n-\tif (op-\u003ekt_ival1 \u0026\u0026 (op-\u003ecount \u003e 0)) {\n-\t\tbcm_can_tx(op);\n-\t\tif (!op-\u003ecount \u0026\u0026 (op-\u003eflags \u0026 TX_COUNTEVT)) {\n+\tif (tx_ival1) {\n+\t\tu32 flags, count;\n+\t\tstruct bcm_timeval ival1, ival2;\n \n+\t\tbcm_can_tx(op, NULL);\n+\n+\t\t/* snapshot variables under lock to avoid torn reads racing\n+\t\t * with concurrent bcm_tx_setup() updates\n+\t\t */\n+\t\tspin_lock_bh(\u0026op-\u003ebcm_tx_lock);\n+\t\tflags = op-\u003eflags;\n+\t\tcount = op-\u003ecount;\n+\t\tival1 = op-\u003eival1;\n+\t\tival2 = op-\u003eival2;\n+\t\tspin_unlock_bh(\u0026op-\u003ebcm_tx_lock);\n+\n+\t\tif (!count \u0026\u0026 (flags \u0026 TX_COUNTEVT)) {\n \t\t\t/* create notification to user */\n \t\t\tmemset(\u0026msg_head, 0, sizeof(msg_head));\n \t\t\tmsg_head.opcode = TX_EXPIRED;\n-\t\t\tmsg_head.flags = op-\u003eflags;\n-\t\t\tmsg_head.count = op-\u003ecount;\n-\t\t\tmsg_head.ival1 = op-\u003eival1;\n-\t\t\tmsg_head.ival2 = op-\u003eival2;\n+\t\t\tmsg_head.flags = flags;\n+\t\t\tmsg_head.count = count;\n+\t\t\tmsg_head.ival1 = ival1;\n+\t\t\tmsg_head.ival2 = ival2;\n \t\t\tmsg_head.can_id = op-\u003ecan_id;\n \t\t\tmsg_head.nframes = 0;\n \n \t\t\tbcm_send_to_user(op, \u0026msg_head, NULL, 0);\n \t\t}\n \n-\t} else if (op-\u003ekt_ival2) {\n-\t\tbcm_can_tx(op);\n+\t} else if (tx_ival2) {\n+\t\tbcm_can_tx(op, NULL);\n \t}\n \n \treturn bcm_tx_set_expiry(op, \u0026op-\u003etimer) ?\n@@ -488,12 +557,9 @@ static void bcm_rx_changed(struct bcm_op *op, struct canfd_frame *data)\n {\n \tstruct bcm_msg_head head;\n \n-\t/* update statistics */\n-\top-\u003eframes_filtered++;\n-\n-\t/* prevent statistics overflow */\n-\tif (op-\u003eframes_filtered \u003e ULONG_MAX/100)\n-\t\top-\u003eframes_filtered = op-\u003eframes_abs = 0;\n+\t/* update statistics (frames_filtered \u003c= frames_abs) */\n+\tif (atomic_long_read(\u0026op-\u003eframes_abs))\n+\t\tatomic_long_inc(\u0026op-\u003eframes_filtered);\n \n \t/* this element is not throttled anymore */\n \tdata-\u003eflags \u0026= ~RX_THR;\n@@ -618,6 +684,8 @@ static enum hrtimer_restart bcm_rx_timeout_handler(struct hrtimer *hrtimer)\n \tstruct bcm_op *op = container_of(hrtimer, struct bcm_op, timer);\n \tstruct bcm_msg_head msg_head;\n \n+\tspin_lock_bh(\u0026op-\u003ebcm_rx_update_lock);\n+\n \t/* if user wants to be informed, when cyclic CAN-Messages come back */\n \tif ((op-\u003eflags \u0026 RX_ANNOUNCE_RESUME) \u0026\u0026 op-\u003elast_frames) {\n \t\t/* clear received CAN frames to indicate 'nothing received' */\n@@ -634,6 +702,8 @@ static enum hrtimer_restart bcm_rx_timeout_handler(struct hrtimer *hrtimer)\n \tmsg_head.can_id = op-\u003ecan_id;\n \tmsg_head.nframes = 0;\n \n+\tspin_unlock_bh(\u0026op-\u003ebcm_rx_update_lock);\n+\n \tbcm_send_to_user(op, \u0026msg_head, NULL, 0);\n \n \treturn HRTIMER_NORESTART;\n@@ -682,15 +752,26 @@ static int bcm_rx_thr_flush(struct bcm_op *op)\n static enum hrtimer_restart bcm_rx_thr_handler(struct hrtimer *hrtimer)\n {\n \tstruct bcm_op *op = container_of(hrtimer, struct bcm_op, thrtimer);\n+\tenum hrtimer_restart ret;\n+\n+\tspin_lock_bh(\u0026op-\u003ebcm_rx_update_lock);\n \n-\tif (bcm_rx_thr_flush(op)) {\n+\t/* kt_ival2 may have been concurrently cleared by bcm_rx_setup()\n+\t * before it cancels this timer - never forward with a zero\n+\t * interval in that case.\n+\t */\n+\tif (bcm_rx_thr_flush(op) \u0026\u0026 op-\u003ekt_ival2) {\n \t\thrtimer_forward_now(hrtimer, op-\u003ekt_ival2);\n-\t\treturn HRTIMER_RESTART;\n+\t\tret = HRTIMER_RESTART;\n \t} else {\n \t\t/* rearm throttle handling */\n \t\top-\u003ekt_lastmsg = 0;\n-\t\treturn HRTIMER_NORESTART;\n+\t\tret = HRTIMER_NORESTART;\n \t}\n+\n+\tspin_unlock_bh(\u0026op-\u003ebcm_rx_update_lock);\n+\n+\treturn ret;\n }\n \n /*\n@@ -700,8 +781,10 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data)\n {\n \tstruct bcm_op *op = (struct bcm_op *)data;\n \tconst struct canfd_frame *rxframe = (struct canfd_frame *)skb-\u003edata;\n+\tstruct canfd_frame rtrframe;\n \tunsigned int i;\n \tunsigned char traffic_flags;\n+\tbool rtr_frame;\n \n \tif (op-\u003ecan_id != rxframe-\u003ecan_id)\n \t\treturn;\n@@ -715,22 +798,59 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data)\n \t\t\treturn;\n \t}\n \n+\t/* An ANYDEV op with an active RX timeout and/or throttle timer\n+\t * tracks a single source interface: claim the first interface that\n+\t * delivers a matching frame and reject frames from any other one,\n+\t * before hrtimer_cancel() below can touch op-\u003etimer - this avoids\n+\t * racing bcm_rx_timeout_handler() across concurrent interfaces.\n+\t * RX_RTR_FRAME ops are excluded, as kt_ival1/kt_ival2 may briefly\n+\t * hold a stale value from an earlier non-RTR configuration.\n+\t */\n+\tif (!op-\u003eifindex) {\n+\t\tspin_lock_bh(\u0026op-\u003ebcm_rx_update_lock);\n+\n+\t\tif (!(op-\u003eflags \u0026 RX_RTR_FRAME) \u0026\u0026\n+\t\t (op-\u003ekt_ival1 || op-\u003ekt_ival2)) {\n+\t\t\t/* don't claim to vanishing interface */\n+\t\t\tif (!op-\u003eif_detected \u0026\u0026\n+\t\t\t READ_ONCE(skb-\u003edev-\u003ereg_state) == NETREG_REGISTERED)\n+\t\t\t\top-\u003eif_detected = skb-\u003edev-\u003eifindex;\n+\n+\t\t\tif (op-\u003eif_detected != skb-\u003edev-\u003eifindex) {\n+\t\t\t\tspin_unlock_bh(\u0026op-\u003ebcm_rx_update_lock);\n+\t\t\t\treturn;\n+\t\t\t}\n+\t\t}\n+\n+\t\tspin_unlock_bh(\u0026op-\u003ebcm_rx_update_lock);\n+\t}\n+\n \t/* disable timeout */\n \thrtimer_cancel(\u0026op-\u003etimer);\n \n-\t/* save rx timestamp */\n-\top-\u003erx_stamp = skb-\u003etstamp;\n-\t/* save originator for recvfrom() */\n-\top-\u003erx_ifindex = skb-\u003edev-\u003eifindex;\n-\t/* update statistics */\n-\top-\u003eframes_abs++;\n+\t/* op-\u003eflags/op-\u003eframes may be updated concurrently by bcm_rx_setup() */\n+\tspin_lock_bh(\u0026op-\u003ebcm_rx_update_lock);\n+\n+\trtr_frame = op-\u003eflags \u0026 RX_RTR_FRAME;\n+\tif (rtr_frame) {\n+\t\tbcm_update_rx_stats(op);\n+\t\t/* snapshot RTR content under lock */\n+\t\tmemcpy(\u0026rtrframe, op-\u003eframes, op-\u003ecfsiz);\n+\t\tspin_unlock_bh(\u0026op-\u003ebcm_rx_update_lock);\n \n-\tif (op-\u003eflags \u0026 RX_RTR_FRAME) {\n \t\t/* send reply for RTR-request (placed in op-\u003eframes[0]) */\n-\t\tbcm_can_tx(op);\n+\t\tbcm_can_tx(op, \u0026rtrframe);\n \t\treturn;\n \t}\n \n+\t/* update statistics in the same critical section as bcm_rx_changed()\n+\t * below: frames_filtered must never be checked/incremented against a\n+\t * frames_abs snapshot from a concurrent bcm_rx_handler() call on\n+\t * another CPU for the same (wildcard) op, or frames_filtered can end\n+\t * up larger than frames_abs.\n+\t */\n+\tbcm_update_rx_stats(op);\n+\n \t/* compute flags to distinguish between own/local/remote CAN traffic */\n \ttraffic_flags = 0;\n \tif (skb-\u003esk) {\n@@ -739,6 +859,13 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data)\n \t\t\ttraffic_flags |= RX_OWN;\n \t}\n \n+\t/* save rx timestamp and originator for recvfrom() under lock: an\n+\t * ANYDEV op without an active timer can still run concurrently on\n+\t * different CPUs, so content and meta data must be bundled here.\n+\t */\n+\top-\u003erx_stamp = skb-\u003etstamp;\n+\top-\u003erx_ifindex = skb-\u003edev-\u003eifindex;\n+\n \tif (op-\u003eflags \u0026 RX_FILTER_ID) {\n \t\t/* the easiest case */\n \t\tbcm_rx_update_and_send(op, op-\u003elast_frames, rxframe,\n@@ -774,6 +901,8 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data)\n \n rx_starttimer:\n \tbcm_rx_starttimer(op);\n+\n+\tspin_unlock_bh(\u0026op-\u003ebcm_rx_update_lock);\n }\n \n /*\n@@ -793,9 +922,12 @@ static struct bcm_op *bcm_find_op(struct list_head *ops,\n \treturn NULL;\n }\n \n-static void bcm_free_op_rcu(struct rcu_head *rcu_head)\n+static void bcm_free_op_work(struct work_struct *work)\n {\n-\tstruct bcm_op *op = container_of(rcu_head, struct bcm_op, rcu);\n+\tstruct bcm_op *op = container_of(work, struct bcm_op, work);\n+\n+\thrtimer_cancel(\u0026op-\u003etimer);\n+\thrtimer_cancel(\u0026op-\u003ethrtimer);\n \n \tif ((op-\u003eframes) \u0026\u0026 (op-\u003eframes != \u0026op-\u003esframe))\n \t\tkfree(op-\u003eframes);\n@@ -803,9 +935,23 @@ static void bcm_free_op_rcu(struct rcu_head *rcu_head)\n \tif ((op-\u003elast_frames) \u0026\u0026 (op-\u003elast_frames != \u0026op-\u003elast_sframe))\n \t\tkfree(op-\u003elast_frames);\n \n+\t/* the last possible access to op-\u003etimer/op-\u003ethrtimer has now\n+\t * happened above via hrtimer_cancel() - op-\u003esk is no longer\n+\t * needed by any pending timer callback, so drop our reference\n+\t */\n+\tsock_put(op-\u003esk);\n+\n \tkfree(op);\n }\n \n+static void bcm_free_op_rcu(struct rcu_head *rcu_head)\n+{\n+\tstruct bcm_op *op = container_of(rcu_head, struct bcm_op, rcu);\n+\n+\tINIT_WORK(\u0026op-\u003ework, bcm_free_op_work);\n+\tqueue_work(bcm_wq, \u0026op-\u003ework);\n+}\n+\n static void bcm_remove_op(struct bcm_op *op)\n {\n \thrtimer_cancel(\u0026op-\u003etimer);\n@@ -822,6 +968,7 @@ static void bcm_rx_unreg(struct net_device *dev, struct bcm_op *op)\n \n \t\t/* mark as removed subscription */\n \t\top-\u003erx_reg_dev = NULL;\n+\t\tnetdev_put(dev, \u0026op-\u003erx_reg_dev_tracker);\n \t} else\n \t\tprintk(KERN_ERR \"can-bcm: bcm_rx_unreg: registered device \"\n \t\t \"mismatch %p %p\\n\", op-\u003erx_reg_dev, dev);\n@@ -852,17 +999,14 @@ static int bcm_delete_rx_op(struct list_head *ops, struct bcm_msg_head *mh,\n \t\t\t\t * Only remove subscriptions that had not\n \t\t\t\t * been removed due to NETDEV_UNREGISTER\n \t\t\t\t * in bcm_notifier()\n+\t\t\t\t *\n+\t\t\t\t * op-\u003erx_reg_dev is a tracked reference taken\n+\t\t\t\t * when the subscription was registered, so it\n+\t\t\t\t * stays valid here even if a concurrent\n+\t\t\t\t * NETDEV_UNREGISTER already unlisted the dev.\n \t\t\t\t */\n-\t\t\t\tif (op-\u003erx_reg_dev) {\n-\t\t\t\t\tstruct net_device *dev;\n-\n-\t\t\t\t\tdev = dev_get_by_index(sock_net(op-\u003esk),\n-\t\t\t\t\t\t\t op-\u003eifindex);\n-\t\t\t\t\tif (dev) {\n-\t\t\t\t\t\tbcm_rx_unreg(dev, op);\n-\t\t\t\t\t\tdev_put(dev);\n-\t\t\t\t\t}\n-\t\t\t\t}\n+\t\t\t\tif (op-\u003erx_reg_dev)\n+\t\t\t\t\tbcm_rx_unreg(op-\u003erx_reg_dev, op);\n \t\t\t} else\n \t\t\t\tcan_rx_unregister(sock_net(op-\u003esk), NULL,\n \t\t\t\t\t\t op-\u003ecan_id,\n@@ -930,6 +1074,7 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,\n \tstruct bcm_sock *bo = bcm_sk(sk);\n \tstruct bcm_op *op;\n \tstruct canfd_frame *cf;\n+\tbool add_op_to_list = false;\n \tunsigned int i;\n \tint err;\n \n@@ -948,6 +1093,8 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,\n \t/* check the given can_id */\n \top = bcm_find_op(\u0026bo-\u003etx_ops, msg_head, ifindex);\n \tif (op) {\n+\t\tvoid *new_frames;\n+\n \t\t/* update existing BCM operation */\n \n \t\t/*\n@@ -958,11 +1105,23 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,\n \t\tif (msg_head-\u003enframes \u003e op-\u003enframes)\n \t\t\treturn -E2BIG;\n \n-\t\t/* update CAN frames content */\n+\t\t/* get new CAN frames content into a staging buffer before\n+\t\t * locking: validate and normalize the frames there so that\n+\t\t * bcm_can_tx() / bcm_tx_timeout_handler() never observe a\n+\t\t * partially updated or unvalidated frame in op-\u003eframes\n+\t\t */\n+\t\tnew_frames = kmalloc(msg_head-\u003enframes * op-\u003ecfsiz, GFP_KERNEL);\n+\t\tif (!new_frames)\n+\t\t\treturn -ENOMEM;\n+\n \t\tfor (i = 0; i \u003c msg_head-\u003enframes; i++) {\n \n-\t\t\tcf = op-\u003eframes + op-\u003ecfsiz * i;\n+\t\t\tcf = new_frames + op-\u003ecfsiz * i;\n \t\t\terr = memcpy_from_msg((u8 *)cf, msg, op-\u003ecfsiz);\n+\t\t\tif (err \u003c 0) {\n+\t\t\t\tkfree(new_frames);\n+\t\t\t\treturn err;\n+\t\t\t}\n \n \t\t\tif (op-\u003eflags \u0026 CAN_FD_FRAME) {\n \t\t\t\tif (cf-\u003elen \u003e 64)\n@@ -972,36 +1131,38 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,\n \t\t\t\t\terr = -EINVAL;\n \t\t\t}\n \n-\t\t\tif (err \u003c 0)\n+\t\t\tif (err \u003c 0) {\n+\t\t\t\tkfree(new_frames);\n \t\t\t\treturn err;\n+\t\t\t}\n \n \t\t\tif (msg_head-\u003eflags \u0026 TX_CP_CAN_ID) {\n \t\t\t\t/* copy can_id into frame */\n \t\t\t\tcf-\u003ecan_id = msg_head-\u003ecan_id;\n \t\t\t}\n \t\t}\n+\n+\t\tspin_lock_bh(\u0026op-\u003ebcm_tx_lock);\n+\n+\t\t/* update CAN frames content */\n+\t\tmemcpy(op-\u003eframes, new_frames, msg_head-\u003enframes * op-\u003ecfsiz);\n+\n \t\top-\u003eflags = msg_head-\u003eflags;\n \n-\t\t/* only lock for unlikely count/nframes/currframe changes */\n \t\tif (op-\u003enframes != msg_head-\u003enframes ||\n-\t\t op-\u003eflags \u0026 TX_RESET_MULTI_IDX ||\n-\t\t op-\u003eflags \u0026 SETTIMER) {\n-\n-\t\t\tspin_lock_bh(\u0026op-\u003ebcm_tx_lock);\n+\t\t op-\u003eflags \u0026 TX_RESET_MULTI_IDX) {\n+\t\t\t/* potentially update changed nframes */\n+\t\t\top-\u003enframes = msg_head-\u003enframes;\n+\t\t\t/* restart multiple frame transmission */\n+\t\t\top-\u003ecurrframe = 0;\n+\t\t}\n \n-\t\t\tif (op-\u003enframes != msg_head-\u003enframes ||\n-\t\t\t op-\u003eflags \u0026 TX_RESET_MULTI_IDX) {\n-\t\t\t\t/* potentially update changed nframes */\n-\t\t\t\top-\u003enframes = msg_head-\u003enframes;\n-\t\t\t\t/* restart multiple frame transmission */\n-\t\t\t\top-\u003ecurrframe = 0;\n-\t\t\t}\n+\t\tif (op-\u003eflags \u0026 SETTIMER)\n+\t\t\top-\u003ecount = msg_head-\u003ecount;\n \n-\t\t\tif (op-\u003eflags \u0026 SETTIMER)\n-\t\t\t\top-\u003ecount = msg_head-\u003ecount;\n+\t\tspin_unlock_bh(\u0026op-\u003ebcm_tx_lock);\n \n-\t\t\tspin_unlock_bh(\u0026op-\u003ebcm_tx_lock);\n-\t\t}\n+\t\tkfree(new_frames);\n \n \t} else {\n \t\t/* insert new BCM operation for the given can_id */\n@@ -1060,6 +1221,7 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,\n \n \t\t/* bcm_can_tx / bcm_tx_timeout_handler needs this */\n \t\top-\u003esk = sk;\n+\t\tsock_hold(sk);\n \t\top-\u003eifindex = ifindex;\n \n \t\t/* initialize uninitialized (kzalloc) structure */\n@@ -1070,17 +1232,18 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,\n \t\thrtimer_setup(\u0026op-\u003ethrtimer, hrtimer_dummy_timeout, CLOCK_MONOTONIC,\n \t\t\t HRTIMER_MODE_REL_SOFT);\n \n-\t\t/* add this bcm_op to the list of the tx_ops */\n-\t\tlist_add(\u0026op-\u003elist, \u0026bo-\u003etx_ops);\n+\t\tadd_op_to_list = true;\n \n \t} /* if ((op = bcm_find_op(\u0026bo-\u003etx_ops, msg_head-\u003ecan_id, ifindex))) */\n \n \tif (op-\u003eflags \u0026 SETTIMER) {\n \t\t/* set timer values */\n+\t\tspin_lock_bh(\u0026op-\u003ebcm_tx_lock);\n \t\top-\u003eival1 = msg_head-\u003eival1;\n \t\top-\u003eival2 = msg_head-\u003eival2;\n \t\top-\u003ekt_ival1 = bcm_timeval_to_ktime(msg_head-\u003eival1);\n \t\top-\u003ekt_ival2 = bcm_timeval_to_ktime(msg_head-\u003eival2);\n+\t\tspin_unlock_bh(\u0026op-\u003ebcm_tx_lock);\n \n \t\t/* disable an active timer due to zero values? */\n \t\tif (!op-\u003ekt_ival1 \u0026\u0026 !op-\u003ekt_ival2)\n@@ -1093,8 +1256,12 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,\n \t\top-\u003eflags |= TX_ANNOUNCE;\n \t}\n \n+\t/* add this bcm_op to the list of the tx_ops? */\n+\tif (add_op_to_list)\n+\t\tlist_add_rcu(\u0026op-\u003elist, \u0026bo-\u003etx_ops);\n+\n \tif (op-\u003eflags \u0026 TX_ANNOUNCE)\n-\t\tbcm_can_tx(op);\n+\t\tbcm_can_tx(op, NULL);\n \n \tif (op-\u003eflags \u0026 STARTTIMER)\n \t\tbcm_tx_start_timer(op);\n@@ -1108,6 +1275,39 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,\n \treturn err;\n }\n \n+static int bcm_rx_setup_rtr_check(struct bcm_msg_head *msg_head,\n+\t\t\t\t struct bcm_op *op, void *new_frames)\n+{\n+\tstruct canfd_frame *frame0 = new_frames;\n+\n+\tif (!(msg_head-\u003eflags \u0026 RX_RTR_FRAME))\n+\t\treturn 0;\n+\n+\t/* this frame is sent out as-is by bcm_can_tx() whenever a matching\n+\t * remote request is received, so validate its length the same way\n+\t * bcm_tx_setup() validates TX_SETUP frames before installing it\n+\t */\n+\tif (msg_head-\u003eflags \u0026 CAN_FD_FRAME) {\n+\t\tif (frame0-\u003elen \u003e 64)\n+\t\t\treturn -EINVAL;\n+\t} else {\n+\t\tif (frame0-\u003elen \u003e 8)\n+\t\t\treturn -EINVAL;\n+\t}\n+\n+\t/* funny feature in RX(!)_SETUP only for RTR-mode:\n+\t * copy can_id into frame BUT without RTR-flag to\n+\t * prevent a full-load-loopback-test ... ;-]\n+\t * normalize this on the staged buffer, before it is\n+\t * ever installed into op-\u003eframes.\n+\t */\n+\tif ((msg_head-\u003eflags \u0026 TX_CP_CAN_ID) ||\n+\t frame0-\u003ecan_id == op-\u003ecan_id)\n+\t\tframe0-\u003ecan_id = op-\u003ecan_id \u0026 ~CAN_RTR_FLAG;\n+\n+\treturn 0;\n+}\n+\n /*\n * bcm_rx_setup - create or update a bcm rx op (for bcm_sendmsg)\n */\n@@ -1117,6 +1317,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,\n \tstruct bcm_sock *bo = bcm_sk(sk);\n \tstruct bcm_op *op;\n \tint do_rx_register;\n+\tint new_op = 0;\n \tint err = 0;\n \n \tif ((msg_head-\u003eflags \u0026 RX_FILTER_ID) || (!(msg_head-\u003enframes))) {\n@@ -1142,6 +1343,8 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,\n \t/* check the given can_id */\n \top = bcm_find_op(\u0026bo-\u003erx_ops, msg_head, ifindex);\n \tif (op) {\n+\t\tvoid *new_frames = NULL;\n+\n \t\t/* update existing BCM operation */\n \n \t\t/*\n@@ -1153,21 +1356,62 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,\n \t\t\treturn -E2BIG;\n \n \t\tif (msg_head-\u003enframes) {\n-\t\t\t/* update CAN frames content */\n-\t\t\terr = memcpy_from_msg(op-\u003eframes, msg,\n+\t\t\t/* get new CAN frames content before locking */\n+\t\t\tnew_frames = kmalloc(msg_head-\u003enframes * op-\u003ecfsiz,\n+\t\t\t\t\t GFP_KERNEL);\n+\t\t\tif (!new_frames)\n+\t\t\t\treturn -ENOMEM;\n+\n+\t\t\terr = memcpy_from_msg(new_frames, msg,\n \t\t\t\t\t msg_head-\u003enframes * op-\u003ecfsiz);\n-\t\t\tif (err \u003c 0)\n+\t\t\tif (err \u003c 0) {\n+\t\t\t\tkfree(new_frames);\n \t\t\t\treturn err;\n+\t\t\t}\n \n-\t\t\t/* clear last_frames to indicate 'nothing received' */\n-\t\t\tmemset(op-\u003elast_frames, 0, msg_head-\u003enframes * op-\u003ecfsiz);\n+\t\t\terr = bcm_rx_setup_rtr_check(msg_head, op, new_frames);\n+\t\t\tif (err \u003c 0) {\n+\t\t\t\tkfree(new_frames);\n+\t\t\t\treturn err;\n+\t\t\t}\n \t\t}\n \n+\t\tspin_lock_bh(\u0026op-\u003ebcm_rx_update_lock);\n \t\top-\u003enframes = msg_head-\u003enframes;\n \t\top-\u003eflags = msg_head-\u003eflags;\n \n-\t\t/* Only an update -\u003e do not call can_rx_register() */\n-\t\tdo_rx_register = 0;\n+\t\tif (msg_head-\u003enframes) {\n+\t\t\t/* update CAN frames content */\n+\t\t\tmemcpy(op-\u003eframes, new_frames,\n+\t\t\t msg_head-\u003enframes * op-\u003ecfsiz);\n+\n+\t\t\t/* clear last_frames to indicate 'nothing received' */\n+\t\t\tmemset(op-\u003elast_frames, 0,\n+\t\t\t msg_head-\u003enframes * op-\u003ecfsiz);\n+\t\t}\n+\n+\t\tif (msg_head-\u003eflags \u0026 SETTIMER) {\n+\t\t\top-\u003eival1 = msg_head-\u003eival1;\n+\t\t\top-\u003eival2 = msg_head-\u003eival2;\n+\t\t\top-\u003ekt_ival1 = bcm_timeval_to_ktime(msg_head-\u003eival1);\n+\t\t\top-\u003ekt_ival2 = bcm_timeval_to_ktime(msg_head-\u003eival2);\n+\t\t\top-\u003ekt_lastmsg = 0;\n+\t\t\top-\u003eif_detected = 0; /* reclaim ifindex in ANYDEV mode */\n+\t\t}\n+\t\tspin_unlock_bh(\u0026op-\u003ebcm_rx_update_lock);\n+\n+\t\t/* free temporary frames / kfree(NULL) is safe */\n+\t\tkfree(new_frames);\n+\n+\t\t/* Don't register a new CAN filter for the rx_op update unless\n+\t\t * a concurrent NETDEV_UNREGISTER notifier already tore down\n+\t\t * the previous registration. In this case the receiver needs\n+\t\t * to be re-registered here so that this update doesn't\n+\t\t * silently stop delivering frames for the given ifindex.\n+\t\t * Ops with ifindex = 0 (all CAN interfaces) never carry a\n+\t\t * tracked rx_reg_dev and stay registered as-is.\n+\t\t */\n+\t\tdo_rx_register = (ifindex \u0026\u0026 !op-\u003erx_reg_dev) ? 1 : 0;\n \n \t} else {\n \t\t/* insert new BCM operation for the given can_id */\n@@ -1176,6 +1420,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,\n \t\t\treturn -ENOMEM;\n \n \t\tspin_lock_init(\u0026op-\u003ebcm_tx_lock);\n+\t\tspin_lock_init(\u0026op-\u003ebcm_rx_update_lock);\n \t\top-\u003ecan_id = msg_head-\u003ecan_id;\n \t\top-\u003enframes = msg_head-\u003enframes;\n \t\top-\u003ecfsiz = CFSIZ(msg_head-\u003eflags);\n@@ -1209,18 +1454,17 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,\n \t\tif (msg_head-\u003enframes) {\n \t\t\terr = memcpy_from_msg(op-\u003eframes, msg,\n \t\t\t\t\t msg_head-\u003enframes * op-\u003ecfsiz);\n-\t\t\tif (err \u003c 0) {\n-\t\t\t\tif (op-\u003eframes != \u0026op-\u003esframe)\n-\t\t\t\t\tkfree(op-\u003eframes);\n-\t\t\t\tif (op-\u003elast_frames != \u0026op-\u003elast_sframe)\n-\t\t\t\t\tkfree(op-\u003elast_frames);\n-\t\t\t\tkfree(op);\n-\t\t\t\treturn err;\n-\t\t\t}\n+\t\t\tif (err \u003c 0)\n+\t\t\t\tgoto free_op;\n+\n+\t\t\terr = bcm_rx_setup_rtr_check(msg_head, op, op-\u003eframes);\n+\t\t\tif (err \u003c 0)\n+\t\t\t\tgoto free_op;\n \t\t}\n \n \t\t/* bcm_can_tx / bcm_tx_timeout_handler needs this */\n \t\top-\u003esk = sk;\n+\t\tsock_hold(sk);\n \t\top-\u003eifindex = ifindex;\n \n \t\t/* ifindex for timeout events w/o previous frame reception */\n@@ -1232,40 +1476,31 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,\n \t\thrtimer_setup(\u0026op-\u003ethrtimer, bcm_rx_thr_handler, CLOCK_MONOTONIC,\n \t\t\t HRTIMER_MODE_REL_SOFT);\n \n-\t\t/* add this bcm_op to the list of the rx_ops */\n-\t\tlist_add(\u0026op-\u003elist, \u0026bo-\u003erx_ops);\n-\n \t\t/* call can_rx_register() */\n \t\tdo_rx_register = 1;\n+\t\tnew_op = 1;\n \n \t} /* if ((op = bcm_find_op(\u0026bo-\u003erx_ops, msg_head-\u003ecan_id, ifindex))) */\n \n \t/* check flags */\n \n \tif (op-\u003eflags \u0026 RX_RTR_FRAME) {\n-\t\tstruct canfd_frame *frame0 = op-\u003eframes;\n-\n \t\t/* no timers in RTR-mode */\n \t\thrtimer_cancel(\u0026op-\u003ethrtimer);\n \t\thrtimer_cancel(\u0026op-\u003etimer);\n-\n-\t\t/*\n-\t\t * funny feature in RX(!)_SETUP only for RTR-mode:\n-\t\t * copy can_id into frame BUT without RTR-flag to\n-\t\t * prevent a full-load-loopback-test ... ;-]\n-\t\t */\n-\t\tif ((op-\u003eflags \u0026 TX_CP_CAN_ID) ||\n-\t\t (frame0-\u003ecan_id == op-\u003ecan_id))\n-\t\t\tframe0-\u003ecan_id = op-\u003ecan_id \u0026 ~CAN_RTR_FLAG;\n-\n \t} else {\n \t\tif (op-\u003eflags \u0026 SETTIMER) {\n \n-\t\t\t/* set timer value */\n-\t\t\top-\u003eival1 = msg_head-\u003eival1;\n-\t\t\top-\u003eival2 = msg_head-\u003eival2;\n-\t\t\top-\u003ekt_ival1 = bcm_timeval_to_ktime(msg_head-\u003eival1);\n-\t\t\top-\u003ekt_ival2 = bcm_timeval_to_ktime(msg_head-\u003eival2);\n+\t\t\t/* set timers (locked) for newly created op */\n+\t\t\tif (new_op) {\n+\t\t\t\tspin_lock_bh(\u0026op-\u003ebcm_rx_update_lock);\n+\t\t\t\top-\u003eival1 = msg_head-\u003eival1;\n+\t\t\t\top-\u003eival2 = msg_head-\u003eival2;\n+\t\t\t\top-\u003ekt_ival1 = bcm_timeval_to_ktime(msg_head-\u003eival1);\n+\t\t\t\top-\u003ekt_ival2 = bcm_timeval_to_ktime(msg_head-\u003eival2);\n+\t\t\t\top-\u003ekt_lastmsg = 0;\n+\t\t\t\tspin_unlock_bh(\u0026op-\u003ebcm_rx_update_lock);\n+\t\t\t}\n \n \t\t\t/* disable an active timer due to zero value? */\n \t\t\tif (!op-\u003ekt_ival1)\n@@ -1275,9 +1510,11 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,\n \t\t\t * In any case cancel the throttle timer, flush\n \t\t\t * potentially blocked msgs and reset throttle handling\n \t\t\t */\n-\t\t\top-\u003ekt_lastmsg = 0;\n \t\t\thrtimer_cancel(\u0026op-\u003ethrtimer);\n+\n+\t\t\tspin_lock_bh(\u0026op-\u003ebcm_rx_update_lock);\n \t\t\tbcm_rx_thr_flush(op);\n+\t\t\tspin_unlock_bh(\u0026op-\u003ebcm_rx_update_lock);\n \t\t}\n \n \t\tif ((op-\u003eflags \u0026 STARTTIMER) \u0026\u0026 op-\u003ekt_ival1)\n@@ -1285,7 +1522,10 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,\n \t\t\t\t HRTIMER_MODE_REL_SOFT);\n \t}\n \n-\t/* now we can register for can_ids, if we added a new bcm_op */\n+\t/* now we can register for can_ids, if we added a new bcm_op\n+\t * or need to re-register after a NETDEV_UNREGISTER tore down\n+\t * the previous registration of an existing op\n+\t */\n \tif (do_rx_register) {\n \t\tif (ifindex) {\n \t\t\tstruct net_device *dev;\n@@ -1298,23 +1538,62 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,\n \t\t\t\t\t\t bcm_rx_handler, op,\n \t\t\t\t\t\t \"bcm\", sk);\n \n-\t\t\t\top-\u003erx_reg_dev = dev;\n+\t\t\t\t/* keep a tracked reference so that a later\n+\t\t\t\t * unregister can safely reach the device even\n+\t\t\t\t * if a concurrent NETDEV_UNREGISTER has\n+\t\t\t\t * already unlisted it by ifindex\n+\t\t\t\t */\n+\t\t\t\tif (!err) {\n+\t\t\t\t\top-\u003erx_reg_dev = dev;\n+\t\t\t\t\tnetdev_hold(dev,\n+\t\t\t\t\t\t \u0026op-\u003erx_reg_dev_tracker,\n+\t\t\t\t\t\t GFP_KERNEL);\n+\t\t\t\t}\n \t\t\t\tdev_put(dev);\n+\t\t\t} else {\n+\t\t\t\t/* the requested device is gone - do not\n+\t\t\t\t * silently succeed without registering\n+\t\t\t\t */\n+\t\t\t\terr = -ENODEV;\n \t\t\t}\n \n-\t\t} else\n+\t\t} else {\n \t\t\terr = can_rx_register(sock_net(sk), NULL, op-\u003ecan_id,\n \t\t\t\t\t REGMASK(op-\u003ecan_id),\n \t\t\t\t\t bcm_rx_handler, op, \"bcm\", sk);\n+\t\t}\n+\n \t\tif (err) {\n-\t\t\t/* this bcm rx op is broken -\u003e remove it */\n-\t\t\tlist_del_rcu(\u0026op-\u003elist);\n-\t\t\tbcm_remove_op(op);\n+\t\t\t/* newly created bcm rx op is broken -\u003e remove it */\n+\t\t\tif (new_op) {\n+\t\t\t\tbcm_remove_op(op);\n+\t\t\t\treturn err;\n+\t\t\t}\n+\n+\t\t\t/* an existing op just stays unregistered.\n+\t\t\t * Cancel op-\u003etimer and (defensively) op-\u003ethrtimer.\n+\t\t\t * Other settings can't be reached until the next\n+\t\t\t * successful RX_SETUP.\n+\t\t\t */\n+\t\t\thrtimer_cancel(\u0026op-\u003etimer);\n+\t\t\thrtimer_cancel(\u0026op-\u003ethrtimer);\n \t\t\treturn err;\n \t\t}\n+\n+\t\t/* add a new bcm_op to the list of the rx_ops */\n+\t\tif (new_op)\n+\t\t\tlist_add_rcu(\u0026op-\u003elist, \u0026bo-\u003erx_ops);\n \t}\n \n \treturn msg_head-\u003enframes * op-\u003ecfsiz + MHSIZ;\n+\n+free_op:\n+\tif (op-\u003eframes != \u0026op-\u003esframe)\n+\t\tkfree(op-\u003eframes);\n+\tif (op-\u003elast_frames != \u0026op-\u003elast_sframe)\n+\t\tkfree(op-\u003elast_frames);\n+\tkfree(op);\n+\treturn err;\n }\n \n /*\n@@ -1373,12 +1652,13 @@ static int bcm_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)\n {\n \tstruct sock *sk = sock-\u003esk;\n \tstruct bcm_sock *bo = bcm_sk(sk);\n-\tint ifindex = bo-\u003eifindex; /* default ifindex for this bcm_op */\n+\tint ifindex;\n \tstruct bcm_msg_head msg_head;\n \tint cfsiz;\n \tint ret; /* read bytes or error codes as return value */\n \n-\tif (!bo-\u003ebound)\n+\t/* Lockless fast-path check for bound socket */\n+\tif (!READ_ONCE(bo-\u003ebound))\n \t\treturn -ENOTCONN;\n \n \t/* check for valid message length from userspace */\n@@ -1394,17 +1674,38 @@ static int bcm_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)\n \tif ((size - MHSIZ) % cfsiz)\n \t\treturn -EINVAL;\n \n+\tlock_sock(sk);\n+\n+\t/* Re-validate under the socket lock: a concurrent bcm_notify()\n+\t * may have unbound this socket (device removal) after the\n+\t * lockless fast-path check above. bo-\u003eifindex is only ever\n+\t * mutated under lock_sock(), so reading it here - instead of\n+\t * before taking the lock - guarantees it can't be observed\n+\t * torn against bo-\u003ebound.\n+\t */\n+\tif (!bo-\u003ebound) {\n+\t\tret = -ENOTCONN;\n+\t\tgoto out_release;\n+\t}\n+\n+\t/* default ifindex for this bcm_op */\n+\tifindex = bo-\u003eifindex;\n+\n \t/* check for alternative ifindex for this bcm_op */\n \n \tif (!ifindex \u0026\u0026 msg-\u003emsg_name) {\n \t\t/* no bound device as default =\u003e check msg_name */\n \t\tDECLARE_SOCKADDR(struct sockaddr_can *, addr, msg-\u003emsg_name);\n \n-\t\tif (msg-\u003emsg_namelen \u003c BCM_MIN_NAMELEN)\n-\t\t\treturn -EINVAL;\n+\t\tif (msg-\u003emsg_namelen \u003c BCM_MIN_NAMELEN) {\n+\t\t\tret = -EINVAL;\n+\t\t\tgoto out_release;\n+\t\t}\n \n-\t\tif (addr-\u003ecan_family != AF_CAN)\n-\t\t\treturn -EINVAL;\n+\t\tif (addr-\u003ecan_family != AF_CAN) {\n+\t\t\tret = -EINVAL;\n+\t\t\tgoto out_release;\n+\t\t}\n \n \t\t/* ifindex from sendto() */\n \t\tifindex = addr-\u003ecan_ifindex;\n@@ -1413,20 +1714,21 @@ static int bcm_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)\n \t\t\tstruct net_device *dev;\n \n \t\t\tdev = dev_get_by_index(sock_net(sk), ifindex);\n-\t\t\tif (!dev)\n-\t\t\t\treturn -ENODEV;\n+\t\t\tif (!dev) {\n+\t\t\t\tret = -ENODEV;\n+\t\t\t\tgoto out_release;\n+\t\t\t}\n \n \t\t\tif (dev-\u003etype != ARPHRD_CAN) {\n \t\t\t\tdev_put(dev);\n-\t\t\t\treturn -ENODEV;\n+\t\t\t\tret = -ENODEV;\n+\t\t\t\tgoto out_release;\n \t\t\t}\n \n \t\t\tdev_put(dev);\n \t\t}\n \t}\n \n-\tlock_sock(sk);\n-\n \tswitch (msg_head.opcode) {\n \n \tcase TX_SETUP:\n@@ -1476,6 +1778,7 @@ static int bcm_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)\n \t\tbreak;\n \t}\n \n+out_release:\n \trelease_sock(sk);\n \n \treturn ret;\n@@ -1499,11 +1802,30 @@ static void bcm_notify(struct bcm_sock *bo, unsigned long msg,\n \tcase NETDEV_UNREGISTER:\n \t\tlock_sock(sk);\n \n-\t\t/* remove device specific receive entries */\n-\t\tlist_for_each_entry(op, \u0026bo-\u003erx_ops, list)\n+\t\t/* rx_ops: remove device specific receive entries */\n+\t\tlist_for_each_entry(op, \u0026bo-\u003erx_ops, list) {\n \t\t\tif (op-\u003erx_reg_dev == dev)\n \t\t\t\tbcm_rx_unreg(dev, op);\n \n+\t\t\t/* release an ANYDEV op's claim (see bcm_rx_handler())\n+\t\t\t * on this now confirmed-gone interface.\n+\t\t\t */\n+\t\t\tif (!op-\u003eifindex) {\n+\t\t\t\tspin_lock_bh(\u0026op-\u003ebcm_rx_update_lock);\n+\t\t\t\tif (op-\u003eif_detected == dev-\u003eifindex)\n+\t\t\t\t\top-\u003eif_detected = 0;\n+\t\t\t\tspin_unlock_bh(\u0026op-\u003ebcm_rx_update_lock);\n+\t\t\t}\n+\t\t}\n+\n+\t\t/* tx_ops: stop device specific cyclic transmissions on the\n+\t\t * vanishing ifindex. Cancelling the timer is enough to stop\n+\t\t * cyclic bcm_can_tx() calls as there is no re-arming.\n+\t\t */\n+\t\tlist_for_each_entry(op, \u0026bo-\u003etx_ops, list)\n+\t\t\tif (op-\u003eifindex == dev-\u003eifindex)\n+\t\t\t\thrtimer_cancel(\u0026op-\u003etimer);\n+\n \t\t/* remove device reference, if this is our bound device */\n \t\tif (bo-\u003ebound \u0026\u0026 bo-\u003eifindex == dev-\u003eifindex) {\n #if IS_ENABLED(CONFIG_PROC_FS)\n@@ -1512,7 +1834,12 @@ static void bcm_notify(struct bcm_sock *bo, unsigned long msg,\n \t\t\t\tbo-\u003ebcm_proc_read = NULL;\n \t\t\t}\n #endif\n-\t\t\tbo-\u003ebound = 0;\n+\t\t\t/* Paired with the lockless fast-path check in\n+\t\t\t * bcm_sendmsg(); bo-\u003eifindex itself is only ever\n+\t\t\t * accessed under lock_sock() so it needs no\n+\t\t\t * annotation.\n+\t\t\t */\n+\t\t\tWRITE_ONCE(bo-\u003ebound, 0);\n \t\t\tbo-\u003eifindex = 0;\n \t\t\tnotify_enodev = 1;\n \t\t}\n@@ -1616,8 +1943,10 @@ static int bcm_release(struct socket *sock)\n \t\tremove_proc_entry(bo-\u003eprocname, net-\u003ecan.bcmproc_dir);\n #endif /* CONFIG_PROC_FS */\n \n-\tlist_for_each_entry_safe(op, next, \u0026bo-\u003etx_ops, list)\n+\tlist_for_each_entry_safe(op, next, \u0026bo-\u003etx_ops, list) {\n+\t\tlist_del_rcu(\u0026op-\u003elist);\n \t\tbcm_remove_op(op);\n+\t}\n \n \tlist_for_each_entry_safe(op, next, \u0026bo-\u003erx_ops, list) {\n \t\t/*\n@@ -1629,16 +1958,14 @@ static int bcm_release(struct socket *sock)\n \t\t\t * Only remove subscriptions that had not\n \t\t\t * been removed due to NETDEV_UNREGISTER\n \t\t\t * in bcm_notifier()\n+\t\t\t *\n+\t\t\t * op-\u003erx_reg_dev is a tracked reference taken\n+\t\t\t * when the subscription was registered, so it\n+\t\t\t * stays valid here even if a concurrent\n+\t\t\t * NETDEV_UNREGISTER already unlisted the device.\n \t\t\t */\n-\t\t\tif (op-\u003erx_reg_dev) {\n-\t\t\t\tstruct net_device *dev;\n-\n-\t\t\t\tdev = dev_get_by_index(net, op-\u003eifindex);\n-\t\t\t\tif (dev) {\n-\t\t\t\t\tbcm_rx_unreg(dev, op);\n-\t\t\t\t\tdev_put(dev);\n-\t\t\t\t}\n-\t\t\t}\n+\t\t\tif (op-\u003erx_reg_dev)\n+\t\t\t\tbcm_rx_unreg(op-\u003erx_reg_dev, op);\n \t\t} else\n \t\t\tcan_rx_unregister(net, NULL, op-\u003ecan_id,\n \t\t\t\t\t REGMASK(op-\u003ecan_id),\n@@ -1648,12 +1975,14 @@ static int bcm_release(struct socket *sock)\n \n \tsynchronize_rcu();\n \n-\tlist_for_each_entry_safe(op, next, \u0026bo-\u003erx_ops, list)\n+\tlist_for_each_entry_safe(op, next, \u0026bo-\u003erx_ops, list) {\n+\t\tlist_del_rcu(\u0026op-\u003elist);\n \t\tbcm_remove_op(op);\n+\t}\n \n \t/* remove device reference */\n \tif (bo-\u003ebound) {\n-\t\tbo-\u003ebound = 0;\n+\t\tWRITE_ONCE(bo-\u003ebound, 0);\n \t\tbo-\u003eifindex = 0;\n \t}\n \n@@ -1723,7 +2052,10 @@ static int bcm_connect(struct socket *sock, struct sockaddr_unsized *uaddr, int\n \t}\n #endif /* CONFIG_PROC_FS */\n \n-\tbo-\u003ebound = 1;\n+\t/* bo-\u003eifindex above is fully assigned before this point; pairs\n+\t * with the lockless fast-path check in bcm_sendmsg()\n+\t */\n+\tWRITE_ONCE(bo-\u003ebound, 1);\n \n fail:\n \trelease_sock(sk);\n@@ -1839,11 +2171,15 @@ static int __init bcm_module_init(void)\n {\n \tint err;\n \n+\tbcm_wq = alloc_workqueue(\"can-bcm-wq\", WQ_UNBOUND, 0);\n+\tif (!bcm_wq)\n+\t\treturn -ENOMEM;\n+\n \tpr_info(\"can: broadcast manager protocol\\n\");\n \n \terr = register_pernet_subsys(\u0026canbcm_pernet_ops);\n \tif (err)\n-\t\treturn err;\n+\t\tgoto register_pernet_failed;\n \n \terr = register_netdevice_notifier(\u0026canbcm_notifier);\n \tif (err)\n@@ -1861,6 +2197,8 @@ static int __init bcm_module_init(void)\n \tunregister_netdevice_notifier(\u0026canbcm_notifier);\n register_notifier_failed:\n \tunregister_pernet_subsys(\u0026canbcm_pernet_ops);\n+register_pernet_failed:\n+\tdestroy_workqueue(bcm_wq);\n \treturn err;\n }\n \n@@ -1869,6 +2207,8 @@ static void __exit bcm_module_exit(void)\n \tcan_proto_unregister(\u0026bcm_can_proto);\n \tunregister_netdevice_notifier(\u0026canbcm_notifier);\n \tunregister_pernet_subsys(\u0026canbcm_pernet_ops);\n+\trcu_barrier();\n+\tdestroy_workqueue(bcm_wq);\n }\n \n module_init(bcm_module_init);\ndiff --git a/net/can/isotp.c b/net/can/isotp.c\nindex c48b4a818297e2..54becaf6898f13 100644\n--- a/net/can/isotp.c\n+++ b/net/can/isotp.c\n@@ -152,11 +152,13 @@ struct isotp_sock {\n \tstruct sock sk;\n \tint bound;\n \tint ifindex;\n+\tstruct net_device *dev;\n+\tnetdevice_tracker dev_tracker;\n \tcanid_t txid;\n \tcanid_t rxid;\n \tktime_t tx_gap;\n \tktime_t lastrxcf_tstamp;\n-\tstruct hrtimer rxtimer, txtimer, txfrtimer;\n+\tstruct hrtimer rxtimer, txtimer, txfrtimer, echotimer;\n \tstruct can_isotp_options opt;\n \tstruct can_isotp_fc_options rxfc, txfc;\n \tstruct can_isotp_ll_options ll;\n@@ -164,6 +166,7 @@ struct isotp_sock {\n \tu32 force_tx_stmin;\n \tu32 force_rx_stmin;\n \tu32 cfecho; /* consecutive frame echo tag */\n+\tu32 tx_gen; /* generation, bumped per new tx transfer */\n \tstruct tpcon rx, tx;\n \tstruct list_head notifier;\n \twait_queue_head_t wait;\n@@ -376,6 +379,15 @@ static int isotp_rcv_fc(struct isotp_sock *so, struct canfd_frame *cf, int ae)\n \n \thrtimer_cancel(\u0026so-\u003etxtimer);\n \n+\t/* isotp_tx_timeout() may have given up on this job while\n+\t * hrtimer_cancel() above waited for it to finish; so-\u003erx_lock\n+\t * (held by our caller isotp_rcv()) rules out a concurrent claim,\n+\t * so a plain recheck is enough here.\n+\t */\n+\tif (so-\u003etx.state != ISOTP_WAIT_FC \u0026\u0026\n+\t so-\u003etx.state != ISOTP_WAIT_FIRST_FC)\n+\t\treturn 1;\n+\n \tif ((cf-\u003elen \u003c ae + FC_CONTENT_SZ) ||\n \t ((so-\u003eopt.flags \u0026 ISOTP_CHECK_PADDING) \u0026\u0026\n \t check_pad(so, cf, ae + FC_CONTENT_SZ, so-\u003eopt.rxpad_content))) {\n@@ -422,7 +434,7 @@ static int isotp_rcv_fc(struct isotp_sock *so, struct canfd_frame *cf, int ae)\n \t\tso-\u003etx.bs = 0;\n \t\tso-\u003etx.state = ISOTP_SENDING;\n \t\t/* send CF frame and enable echo timeout handling */\n-\t\thrtimer_start(\u0026so-\u003etxtimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0),\n+\t\thrtimer_start(\u0026so-\u003eechotimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0),\n \t\t\t HRTIMER_MODE_REL_SOFT);\n \t\tisotp_send_cframe(so);\n \t\tbreak;\n@@ -575,6 +587,14 @@ static int isotp_rcv_cf(struct sock *sk, struct canfd_frame *cf, int ae,\n \n \thrtimer_cancel(\u0026so-\u003erxtimer);\n \n+\t/* isotp_rx_timer_handler() may have raced us for so-\u003erx.state\n+\t * while hrtimer_cancel() above waited for it to finish, already\n+\t * reporting ETIMEDOUT and resetting the reception; don't process\n+\t * this CF into a reassembly that has already been given up on.\n+\t */\n+\tif (so-\u003erx.state != ISOTP_WAIT_DATA)\n+\t\treturn 1;\n+\n \t/* CFs are never longer than the FF */\n \tif (cf-\u003elen \u003e so-\u003erx.ll_dl)\n \t\treturn 1;\n@@ -870,20 +890,36 @@ static void isotp_rcv_echo(struct sk_buff *skb, void *data)\n \tstruct canfd_frame *cf = (struct canfd_frame *)skb-\u003edata;\n \n \t/* only handle my own local echo CF/SF skb's (no FF!) */\n-\tif (skb-\u003esk != sk || so-\u003ecfecho != *(u32 *)cf-\u003edata)\n+\tif (skb-\u003esk != sk)\n \t\treturn;\n \n+\t/* unlike isotp_rcv_fc()/isotp_rcv_cf(), not already under so-\u003erx_lock\n+\t * (no isotp_rcv() caller here), so take it ourselves\n+\t */\n+\tspin_lock(\u0026so-\u003erx_lock);\n+\n+\t/* so-\u003ecfecho may since belong to a new transfer; recheck under lock */\n+\tif (so-\u003ecfecho != *(u32 *)cf-\u003edata)\n+\t\tgoto out_unlock;\n+\n \t/* cancel local echo timeout */\n-\thrtimer_cancel(\u0026so-\u003etxtimer);\n+\thrtimer_cancel(\u0026so-\u003eechotimer);\n \n \t/* local echo skb with consecutive frame has been consumed */\n \tso-\u003ecfecho = 0;\n \n+\t/* claiming a transfer also takes so-\u003erx_lock, so a plain recheck\n+\t * is enough: so-\u003etx.state can't have flipped to ISOTP_SENDING for\n+\t * a new claim while we're still in here\n+\t */\n+\tif (so-\u003etx.state != ISOTP_SENDING)\n+\t\tgoto out_unlock;\n+\n \tif (so-\u003etx.idx \u003e= so-\u003etx.len) {\n \t\t/* we are done */\n \t\tso-\u003etx.state = ISOTP_IDLE;\n \t\twake_up_interruptible(\u0026so-\u003ewait);\n-\t\treturn;\n+\t\tgoto out_unlock;\n \t}\n \n \tif (so-\u003etxfc.bs \u0026\u0026 so-\u003etx.bs \u003e= so-\u003etxfc.bs) {\n@@ -891,53 +927,83 @@ static void isotp_rcv_echo(struct sk_buff *skb, void *data)\n \t\tso-\u003etx.state = ISOTP_WAIT_FC;\n \t\thrtimer_start(\u0026so-\u003etxtimer, ktime_set(ISOTP_FC_TIMEOUT, 0),\n \t\t\t HRTIMER_MODE_REL_SOFT);\n-\t\treturn;\n+\t\tgoto out_unlock;\n \t}\n \n \t/* no gap between data frames needed =\u003e use burst mode */\n \tif (!so-\u003etx_gap) {\n \t\t/* enable echo timeout handling */\n-\t\thrtimer_start(\u0026so-\u003etxtimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0),\n+\t\thrtimer_start(\u0026so-\u003eechotimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0),\n \t\t\t HRTIMER_MODE_REL_SOFT);\n \t\tisotp_send_cframe(so);\n-\t\treturn;\n+\t\tgoto out_unlock;\n \t}\n \n \t/* start timer to send next consecutive frame with correct delay */\n \thrtimer_start(\u0026so-\u003etxfrtimer, so-\u003etx_gap, HRTIMER_MODE_REL_SOFT);\n+\n+out_unlock:\n+\tspin_unlock(\u0026so-\u003erx_lock);\n }\n \n-static enum hrtimer_restart isotp_tx_timer_handler(struct hrtimer *hrtimer)\n+/* shared by so-\u003etxtimer's and so-\u003eechotimer's callbacks. Both timers get\n+ * cancelled under so-\u003erx_lock elsewhere, so this must stay lock-free to\n+ * avoid deadlocking with that; uses so-\u003etx_gen instead to avoid tainting\n+ * a new transfer with an error from the one that just timed out.\n+ */\n+static enum hrtimer_restart isotp_tx_timeout(struct isotp_sock *so)\n {\n-\tstruct isotp_sock *so = container_of(hrtimer, struct isotp_sock,\n-\t\t\t\t\t txtimer);\n \tstruct sock *sk = \u0026so-\u003esk;\n+\tu32 gen = READ_ONCE(so-\u003etx_gen);\n+\tu32 old_state = READ_ONCE(so-\u003etx.state);\n \n \t/* don't handle timeouts in IDLE or SHUTDOWN state */\n-\tif (so-\u003etx.state == ISOTP_IDLE || so-\u003etx.state == ISOTP_SHUTDOWN)\n+\tif (old_state == ISOTP_IDLE || old_state == ISOTP_SHUTDOWN)\n+\t\treturn HRTIMER_NORESTART;\n+\n+\t/* only claim the timeout if the state is still unchanged */\n+\tif (cmpxchg(\u0026so-\u003etx.state, old_state, ISOTP_IDLE) != old_state)\n \t\treturn HRTIMER_NORESTART;\n \n \t/* we did not get any flow control or echo frame in time */\n \n-\t/* report 'communication error on send' */\n-\tsk-\u003esk_err = ECOMM;\n-\tif (!sock_flag(sk, SOCK_DEAD))\n-\t\tsk_error_report(sk);\n+\tif (READ_ONCE(so-\u003etx_gen) == gen) {\n+\t\t/* report 'communication error on send' */\n+\t\tsk-\u003esk_err = ECOMM;\n+\t\tif (!sock_flag(sk, SOCK_DEAD))\n+\t\t\tsk_error_report(sk);\n+\t}\n \n-\t/* reset tx state */\n-\tso-\u003etx.state = ISOTP_IDLE;\n \twake_up_interruptible(\u0026so-\u003ewait);\n \n \treturn HRTIMER_NORESTART;\n }\n \n+/* so-\u003etxtimer: fires when a Flow Control frame does not arrive in time */\n+static enum hrtimer_restart isotp_tx_timer_handler(struct hrtimer *hrtimer)\n+{\n+\tstruct isotp_sock *so = container_of(hrtimer, struct isotp_sock,\n+\t\t\t\t\t txtimer);\n+\n+\treturn isotp_tx_timeout(so);\n+}\n+\n+/* so-\u003eechotimer: fires when a sent CF/SF's local echo does not arrive */\n+static enum hrtimer_restart isotp_echo_timer_handler(struct hrtimer *hrtimer)\n+{\n+\tstruct isotp_sock *so = container_of(hrtimer, struct isotp_sock,\n+\t\t\t\t\t echotimer);\n+\n+\treturn isotp_tx_timeout(so);\n+}\n+\n static enum hrtimer_restart isotp_txfr_timer_handler(struct hrtimer *hrtimer)\n {\n \tstruct isotp_sock *so = container_of(hrtimer, struct isotp_sock,\n \t\t\t\t\t txfrtimer);\n \n \t/* start echo timeout handling and cover below protocol error */\n-\thrtimer_start(\u0026so-\u003etxtimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0),\n+\thrtimer_start(\u0026so-\u003eechotimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0),\n \t\t HRTIMER_MODE_REL_SOFT);\n \n \t/* cfecho should be consumed by isotp_rcv_echo() here */\n@@ -958,13 +1024,24 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)\n \tint ae = (so-\u003eopt.flags \u0026 CAN_ISOTP_EXTEND_ADDR) ? 1 : 0;\n \tint wait_tx_done = (so-\u003eopt.flags \u0026 CAN_ISOTP_WAIT_TX_DONE) ? 1 : 0;\n \ts64 hrtimer_sec = ISOTP_ECHO_TIMEOUT;\n+\tstruct hrtimer *tx_hrt = \u0026so-\u003eechotimer;\n+\tu32 new_state = ISOTP_SENDING;\n \tint off;\n \tint err;\n \n \tif (!so-\u003ebound || so-\u003etx.state == ISOTP_SHUTDOWN)\n \t\treturn -EADDRNOTAVAIL;\n \n-\twhile (cmpxchg(\u0026so-\u003etx.state, ISOTP_IDLE, ISOTP_SENDING) != ISOTP_IDLE) {\n+\t/* claim the socket under so-\u003erx_lock: this serializes the claim\n+\t * with the RX path and with sendmsg()'s own error paths below, so\n+\t * none of them can ever see a transfer mid-claim\n+\t */\n+\tfor (;;) {\n+\t\tspin_lock_bh(\u0026so-\u003erx_lock);\n+\t\tif (READ_ONCE(so-\u003etx.state) == ISOTP_IDLE)\n+\t\t\tbreak;\n+\t\tspin_unlock_bh(\u0026so-\u003erx_lock);\n+\n \t\t/* we do not support multiple buffers - for now */\n \t\tif (msg-\u003emsg_flags \u0026 MSG_DONTWAIT)\n \t\t\treturn -EAGAIN;\n@@ -973,9 +1050,29 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)\n \t\t\treturn -EADDRNOTAVAIL;\n \n \t\t/* wait for complete transmission of current pdu */\n-\t\terr = wait_event_interruptible(so-\u003ewait, so-\u003etx.state == ISOTP_IDLE);\n+\t\terr = wait_event_interruptible(so-\u003ewait,\n+\t\t\t\t\t so-\u003etx.state == ISOTP_IDLE);\n \t\tif (err)\n-\t\t\tgoto err_event_drop;\n+\t\t\treturn err;\n+\t}\n+\n+\t/* new transfer: bump so-\u003etx_gen and drain the old one's timers,\n+\t * still under the so-\u003erx_lock we just claimed the socket with\n+\t */\n+\tWRITE_ONCE(so-\u003etx.state, ISOTP_SENDING);\n+\tWRITE_ONCE(so-\u003etx_gen, READ_ONCE(so-\u003etx_gen) + 1);\n+\thrtimer_cancel(\u0026so-\u003etxtimer);\n+\thrtimer_cancel(\u0026so-\u003eechotimer);\n+\thrtimer_cancel(\u0026so-\u003etxfrtimer);\n+\tso-\u003ecfecho = 0;\n+\tspin_unlock_bh(\u0026so-\u003erx_lock);\n+\n+\t/* so-\u003ebound is only checked once above - a wakeup may have\n+\t * unbound/rebound the socket meanwhile, so re-validate it\n+\t */\n+\tif (!so-\u003ebound) {\n+\t\terr = -EADDRNOTAVAIL;\n+\t\tgoto err_out_drop;\n \t}\n \n \t/* PDU size \u003e default =\u003e try max_pdu_size */\n@@ -1086,18 +1183,33 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)\n \t\t\tso-\u003ecfecho = *(u32 *)cf-\u003edata;\n \t\t} else {\n \t\t\t/* standard flow control check */\n-\t\t\tso-\u003etx.state = ISOTP_WAIT_FIRST_FC;\n+\t\t\tnew_state = ISOTP_WAIT_FIRST_FC;\n \n \t\t\t/* start timeout for FC */\n \t\t\thrtimer_sec = ISOTP_FC_TIMEOUT;\n+\t\t\ttx_hrt = \u0026so-\u003etxtimer;\n \n \t\t\t/* no CF echo tag for isotp_rcv_echo() (FF-mode) */\n \t\t\tso-\u003ecfecho = 0;\n \t\t}\n \t}\n \n-\thrtimer_start(\u0026so-\u003etxtimer, ktime_set(hrtimer_sec, 0),\n+\tspin_lock_bh(\u0026so-\u003erx_lock);\n+\tif (so-\u003etx.state == ISOTP_SHUTDOWN) {\n+\t\t/* isotp_release() has since taken over and already drained\n+\t\t * our timers - don't send into a socket that's going away\n+\t\t */\n+\t\tspin_unlock_bh(\u0026so-\u003erx_lock);\n+\t\tkfree_skb(skb);\n+\t\tdev_put(dev);\n+\t\twake_up_interruptible(\u0026so-\u003ewait);\n+\t\treturn -EADDRNOTAVAIL;\n+\t}\n+\t/* WAIT_FIRST_FC for standard FF, else stays ISOTP_SENDING */\n+\tso-\u003etx.state = new_state;\n+\thrtimer_start(tx_hrt, ktime_set(hrtimer_sec, 0),\n \t\t HRTIMER_MODE_REL_SOFT);\n+\tspin_unlock_bh(\u0026so-\u003erx_lock);\n \n \t/* send the first or only CAN frame */\n \tcf-\u003eflags = so-\u003ell.tx_flags;\n@@ -1110,13 +1222,10 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)\n \t\tpr_notice_once(\"can-isotp: %s: can_send_ret %pe\\n\",\n \t\t\t __func__, ERR_PTR(err));\n \n+\t\tspin_lock_bh(\u0026so-\u003erx_lock);\n \t\t/* no transmission -\u003e no timeout monitoring */\n-\t\thrtimer_cancel(\u0026so-\u003etxtimer);\n-\n-\t\t/* reset consecutive frame echo tag */\n-\t\tso-\u003ecfecho = 0;\n-\n-\t\tgoto err_out_drop;\n+\t\thrtimer_cancel(tx_hrt);\n+\t\tgoto err_out_drop_locked;\n \t}\n \n \tif (wait_tx_done) {\n@@ -1132,14 +1241,21 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)\n \n \treturn size;\n \n+err_out_drop:\n+\t/* claimed but nothing sent yet - no timer to cancel */\n+\tspin_lock_bh(\u0026so-\u003erx_lock);\n+\tgoto err_out_drop_locked;\n err_event_drop:\n-\t/* got signal: force tx state machine to be idle */\n-\tso-\u003etx.state = ISOTP_IDLE;\n+\t/* interrupted waiting on our own transfer - drain its timers */\n+\tspin_lock_bh(\u0026so-\u003erx_lock);\n \thrtimer_cancel(\u0026so-\u003etxfrtimer);\n \thrtimer_cancel(\u0026so-\u003etxtimer);\n-err_out_drop:\n-\t/* drop this PDU and unlock a potential wait queue */\n+\thrtimer_cancel(\u0026so-\u003eechotimer);\n+err_out_drop_locked:\n+\t/* release the claim; so-\u003erx_lock still held from above */\n+\tso-\u003ecfecho = 0;\n \tso-\u003etx.state = ISOTP_IDLE;\n+\tspin_unlock_bh(\u0026so-\u003erx_lock);\n \twake_up_interruptible(\u0026so-\u003ewait);\n \n \treturn err;\n@@ -1201,13 +1317,20 @@ static int isotp_release(struct socket *sock)\n \tso = isotp_sk(sk);\n \tnet = sock_net(sk);\n \n-\t/* wait for complete transmission of current pdu */\n-\twhile (wait_event_interruptible(so-\u003ewait, so-\u003etx.state == ISOTP_IDLE) == 0 \u0026\u0026\n-\t cmpxchg(\u0026so-\u003etx.state, ISOTP_IDLE, ISOTP_SHUTDOWN) != ISOTP_IDLE)\n+\t/* best-effort: wait for a running pdu to finish, but don't block on\n+\t * it forever - give up after the first signal\n+\t */\n+\twhile (so-\u003etx.state != ISOTP_IDLE \u0026\u0026\n+\t wait_event_interruptible(so-\u003ewait, so-\u003etx.state == ISOTP_IDLE) == 0)\n \t\t;\n \n-\t/* force state machines to be idle also when a signal occurred */\n+\t/* claim the socket under so-\u003erx_lock like sendmsg() does, so its\n+\t * claim can't race the forced ISOTP_SHUTDOWN below; force it\n+\t * unconditionally, even when a signal cut the wait above short\n+\t */\n+\tspin_lock_bh(\u0026so-\u003erx_lock);\n \tso-\u003etx.state = ISOTP_SHUTDOWN;\n+\tspin_unlock_bh(\u0026so-\u003erx_lock);\n \tso-\u003erx.state = ISOTP_IDLE;\n \n \tspin_lock(\u0026isotp_notifier_lock);\n@@ -1219,36 +1342,43 @@ static int isotp_release(struct socket *sock)\n \tlist_del(\u0026so-\u003enotifier);\n \tspin_unlock(\u0026isotp_notifier_lock);\n \n+\trtnl_lock();\n \tlock_sock(sk);\n \n-\t/* remove current filters \u0026 unregister */\n-\tif (so-\u003ebound) {\n-\t\tif (so-\u003eifindex) {\n-\t\t\tstruct net_device *dev;\n-\n-\t\t\tdev = dev_get_by_index(net, so-\u003eifindex);\n-\t\t\tif (dev) {\n-\t\t\t\tif (isotp_register_rxid(so))\n-\t\t\t\t\tcan_rx_unregister(net, dev, so-\u003erxid,\n-\t\t\t\t\t\t\t SINGLE_MASK(so-\u003erxid),\n-\t\t\t\t\t\t\t isotp_rcv, sk);\n-\n-\t\t\t\tcan_rx_unregister(net, dev, so-\u003etxid,\n-\t\t\t\t\t\t SINGLE_MASK(so-\u003etxid),\n-\t\t\t\t\t\t isotp_rcv_echo, sk);\n-\t\t\t\tdev_put(dev);\n-\t\t\t\tsynchronize_rcu();\n-\t\t\t}\n-\t\t}\n+\t/* remove current filters \u0026 unregister\n+\t * tracked reference so-\u003edev is taken at bind() time with rtnl_lock\n+\t */\n+\tif (so-\u003ebound \u0026\u0026 so-\u003edev) {\n+\t\tif (isotp_register_rxid(so))\n+\t\t\tcan_rx_unregister(net, so-\u003edev, so-\u003erxid,\n+\t\t\t\t\t SINGLE_MASK(so-\u003erxid),\n+\t\t\t\t\t isotp_rcv, sk);\n+\n+\t\tcan_rx_unregister(net, so-\u003edev, so-\u003etxid,\n+\t\t\t\t SINGLE_MASK(so-\u003etxid),\n+\t\t\t\t isotp_rcv_echo, sk);\n+\t\tnetdev_put(so-\u003edev, \u0026so-\u003edev_tracker);\n \t}\n \n+\tso-\u003eifindex = 0;\n+\tso-\u003ebound = 0;\n+\tso-\u003edev = NULL;\n+\n+\trtnl_unlock();\n+\n+\t/* Always wait for a grace period before touching the timers below.\n+\t * A concurrent NETDEV_UNREGISTER may have already unregistered our\n+\t * filters and cleared so-\u003ebound in isotp_notify() without waiting\n+\t * for in-flight isotp_rcv() callers to finish, so this call must not\n+\t * be skipped just because so-\u003ebound is already 0 here.\n+\t */\n+\tsynchronize_rcu();\n+\n \thrtimer_cancel(\u0026so-\u003etxfrtimer);\n \thrtimer_cancel(\u0026so-\u003etxtimer);\n+\thrtimer_cancel(\u0026so-\u003eechotimer);\n \thrtimer_cancel(\u0026so-\u003erxtimer);\n \n-\tso-\u003eifindex = 0;\n-\tso-\u003ebound = 0;\n-\n \tsock_orphan(sk);\n \tsock-\u003esk = NULL;\n \n@@ -1303,6 +1433,7 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l\n \tif (!addr-\u003ecan_ifindex)\n \t\treturn -ENODEV;\n \n+\trtnl_lock();\n \tlock_sock(sk);\n \n \tif (so-\u003ebound) {\n@@ -1310,6 +1441,17 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l\n \t\tgoto out;\n \t}\n \n+\t/* A transmission or reception that outlived a previous binding\n+\t * (unbound by NETDEV_UNREGISTER) may still be draining; the FC/echo\n+\t * and RX watchdog timers bound how long this takes. Checked together\n+\t * with so-\u003ebound in the same lock_sock() section above, so there is\n+\t * no window in which a concurrent isotp_notify() could be missed.\n+\t */\n+\tif (so-\u003etx.state != ISOTP_IDLE || so-\u003erx.state != ISOTP_IDLE) {\n+\t\terr = -EAGAIN;\n+\t\tgoto out;\n+\t}\n+\n \t/* ensure different CAN IDs when the rx_id is to be registered */\n \tif (isotp_register_rxid(so) \u0026\u0026 rx_id == tx_id) {\n \t\terr = -EADDRNOTAVAIL;\n@@ -1322,14 +1464,12 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l\n \t\tgoto out;\n \t}\n \tif (dev-\u003etype != ARPHRD_CAN) {\n-\t\tdev_put(dev);\n \t\terr = -ENODEV;\n-\t\tgoto out;\n+\t\tgoto out_put_dev;\n \t}\n \tif (READ_ONCE(dev-\u003emtu) \u003c so-\u003ell.mtu) {\n-\t\tdev_put(dev);\n \t\terr = -EINVAL;\n-\t\tgoto out;\n+\t\tgoto out_put_dev;\n \t}\n \tif (!(dev-\u003eflags \u0026 IFF_UP))\n \t\tnotify_enetdown = 1;\n@@ -1347,16 +1487,25 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l\n \tcan_rx_register(net, dev, tx_id, SINGLE_MASK(tx_id),\n \t\t\tisotp_rcv_echo, sk, \"isotpe\", sk);\n \n-\tdev_put(dev);\n-\n \t/* switch to new settings */\n \tso-\u003eifindex = ifindex;\n \tso-\u003erxid = rx_id;\n \tso-\u003etxid = tx_id;\n \tso-\u003ebound = 1;\n \n+\t/* bind() ok -\u003e hold a reference for so-\u003edev so that isotp_release()\n+\t * can safely reach the device later, even if a concurrent\n+\t * NETDEV_UNREGISTER has already unlisted it by ifindex.\n+\t */\n+\tso-\u003edev = dev;\n+\tnetdev_hold(so-\u003edev, \u0026so-\u003edev_tracker, GFP_KERNEL);\n+\n+out_put_dev:\n+\t/* remove potential reference from dev_get_by_index() */\n+\tdev_put(dev);\n out:\n \trelease_sock(sk);\n+\trtnl_unlock();\n \n \tif (notify_enetdown) {\n \t\tsk-\u003esk_err = ENETDOWN;\n@@ -1559,7 +1708,7 @@ static void isotp_notify(struct isotp_sock *so, unsigned long msg,\n \tif (!net_eq(dev_net(dev), sock_net(sk)))\n \t\treturn;\n \n-\tif (so-\u003eifindex != dev-\u003eifindex)\n+\tif (so-\u003edev != dev)\n \t\treturn;\n \n \tswitch (msg) {\n@@ -1575,10 +1724,12 @@ static void isotp_notify(struct isotp_sock *so, unsigned long msg,\n \t\t\tcan_rx_unregister(dev_net(dev), dev, so-\u003etxid,\n \t\t\t\t\t SINGLE_MASK(so-\u003etxid),\n \t\t\t\t\t isotp_rcv_echo, sk);\n+\t\t\tnetdev_put(so-\u003edev, \u0026so-\u003edev_tracker);\n \t\t}\n \n \t\tso-\u003eifindex = 0;\n \t\tso-\u003ebound = 0;\n+\t\tso-\u003edev = NULL;\n \t\trelease_sock(sk);\n \n \t\tsk-\u003esk_err = ENODEV;\n@@ -1638,6 +1789,7 @@ static int isotp_init(struct sock *sk)\n \n \tso-\u003eifindex = 0;\n \tso-\u003ebound = 0;\n+\tso-\u003edev = NULL;\n \n \tso-\u003eopt.flags = CAN_ISOTP_DEFAULT_FLAGS;\n \tso-\u003eopt.ext_address = CAN_ISOTP_DEFAULT_EXT_ADDRESS;\n@@ -1664,10 +1816,14 @@ static int isotp_init(struct sock *sk)\n \tso-\u003erx.buflen = ARRAY_SIZE(so-\u003erx.sbuf);\n \tso-\u003etx.buflen = ARRAY_SIZE(so-\u003etx.sbuf);\n \n-\thrtimer_setup(\u0026so-\u003erxtimer, isotp_rx_timer_handler, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT);\n-\thrtimer_setup(\u0026so-\u003etxtimer, isotp_tx_timer_handler, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT);\n-\thrtimer_setup(\u0026so-\u003etxfrtimer, isotp_txfr_timer_handler, CLOCK_MONOTONIC,\n-\t\t HRTIMER_MODE_REL_SOFT);\n+\thrtimer_setup(\u0026so-\u003erxtimer, isotp_rx_timer_handler,\n+\t\t CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT);\n+\thrtimer_setup(\u0026so-\u003etxtimer, isotp_tx_timer_handler,\n+\t\t CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT);\n+\thrtimer_setup(\u0026so-\u003eechotimer, isotp_echo_timer_handler,\n+\t\t CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT);\n+\thrtimer_setup(\u0026so-\u003etxfrtimer, isotp_txfr_timer_handler,\n+\t\t CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT);\n \n \tinit_waitqueue_head(\u0026so-\u003ewait);\n \tspin_lock_init(\u0026so-\u003erx_lock);\ndiff --git a/net/can/j1939/transport.c b/net/can/j1939/transport.c\nindex df93d57907da7e..8a31cb23bc76d0 100644\n--- a/net/can/j1939/transport.c\n+++ b/net/can/j1939/transport.c\n@@ -351,6 +351,18 @@ static void j1939_session_skb_drop_old(struct j1939_session *session)\n \t}\n }\n \n+static bool j1939_address_is_local(struct j1939_priv *priv, u8 addr)\n+{\n+\tbool local = false;\n+\n+\tread_lock_bh(\u0026priv-\u003elock);\n+\tif (j1939_address_is_unicast(addr) \u0026\u0026 priv-\u003eents[addr].nusers)\n+\t\tlocal = true;\n+\tread_unlock_bh(\u0026priv-\u003elock);\n+\n+\treturn local;\n+}\n+\n void j1939_session_skb_queue(struct j1939_session *session,\n \t\t\t struct sk_buff *skb)\n {\n@@ -359,8 +371,7 @@ void j1939_session_skb_queue(struct j1939_session *session,\n \n \tj1939_ac_fixup(priv, skb);\n \n-\tif (j1939_address_is_unicast(skcb-\u003eaddr.da) \u0026\u0026\n-\t priv-\u003eents[skcb-\u003eaddr.da].nusers)\n+\tif (j1939_address_is_local(priv, skcb-\u003eaddr.da))\n \t\tskcb-\u003eflags |= J1939_ECU_LOCAL_DST;\n \n \tskcb-\u003eflags |= J1939_ECU_LOCAL_SRC;\n@@ -2038,8 +2049,7 @@ struct j1939_session *j1939_tp_send(struct j1939_priv *priv,\n \t\treturn ERR_PTR(ret);\n \n \t/* fix DST flags, it may be used there soon */\n-\tif (j1939_address_is_unicast(skcb-\u003eaddr.da) \u0026\u0026\n-\t priv-\u003eents[skcb-\u003eaddr.da].nusers)\n+\tif (j1939_address_is_local(priv, skcb-\u003eaddr.da))\n \t\tskcb-\u003eflags |= J1939_ECU_LOCAL_DST;\n \n \t/* src is always local, I'm sending ... */\ndiff --git a/net/can/raw.c b/net/can/raw.c\nindex a26942e78e6887..82d9c0499c95fa 100644\n--- a/net/can/raw.c\n+++ b/net/can/raw.c\n@@ -562,8 +562,8 @@ static int raw_getname(struct socket *sock, struct sockaddr *uaddr,\n \treturn RAW_MIN_NAMELEN;\n }\n \n-static int raw_setsockopt(struct socket *sock, int level, int optname,\n-\t\t\t sockptr_t optval, unsigned int optlen)\n+static int raw_setsockopt_locked(struct socket *sock, int optname,\n+\t\t\t\t sockptr_t optval, unsigned int optlen)\n {\n \tstruct sock *sk = sock-\u003esk;\n \tstruct raw_sock *ro = raw_sk(sk);\n@@ -575,9 +575,6 @@ static int raw_setsockopt(struct socket *sock, int level, int optname,\n \tint flag;\n \tint err = 0;\n \n-\tif (level != SOL_CAN_RAW)\n-\t\treturn -EINVAL;\n-\n \tswitch (optname) {\n \tcase CAN_RAW_FILTER:\n \t\tif (optlen % sizeof(struct can_filter) != 0)\n@@ -598,17 +595,11 @@ static int raw_setsockopt(struct socket *sock, int level, int optname,\n \t\t\t\treturn -EFAULT;\n \t\t}\n \n-\t\trtnl_lock();\n-\t\tlock_sock(sk);\n-\n \t\tdev = ro-\u003edev;\n-\t\tif (ro-\u003ebound \u0026\u0026 dev) {\n-\t\t\tif (dev-\u003ereg_state != NETREG_REGISTERED) {\n-\t\t\t\tif (count \u003e 1)\n-\t\t\t\t\tkfree(filter);\n-\t\t\t\terr = -ENODEV;\n-\t\t\t\tgoto out_fil;\n-\t\t\t}\n+\t\tif (ro-\u003ebound \u0026\u0026 dev \u0026\u0026 dev-\u003ereg_state != NETREG_REGISTERED) {\n+\t\t\tif (count \u003e 1)\n+\t\t\t\tkfree(filter);\n+\t\t\treturn -ENODEV;\n \t\t}\n \n \t\tif (ro-\u003ebound) {\n@@ -622,7 +613,7 @@ static int raw_setsockopt(struct socket *sock, int level, int optname,\n \t\t\tif (err) {\n \t\t\t\tif (count \u003e 1)\n \t\t\t\t\tkfree(filter);\n-\t\t\t\tgoto out_fil;\n+\t\t\t\treturn err;\n \t\t\t}\n \n \t\t\t/* remove old filter registrations */\n@@ -642,11 +633,6 @@ static int raw_setsockopt(struct socket *sock, int level, int optname,\n \t\t}\n \t\tro-\u003efilter = filter;\n \t\tro-\u003ecount = count;\n-\n- out_fil:\n-\t\trelease_sock(sk);\n-\t\trtnl_unlock();\n-\n \t\tbreak;\n \n \tcase CAN_RAW_ERR_FILTER:\n@@ -658,16 +644,9 @@ static int raw_setsockopt(struct socket *sock, int level, int optname,\n \n \t\terr_mask \u0026= CAN_ERR_MASK;\n \n-\t\trtnl_lock();\n-\t\tlock_sock(sk);\n-\n \t\tdev = ro-\u003edev;\n-\t\tif (ro-\u003ebound \u0026\u0026 dev) {\n-\t\t\tif (dev-\u003ereg_state != NETREG_REGISTERED) {\n-\t\t\t\terr = -ENODEV;\n-\t\t\t\tgoto out_err;\n-\t\t\t}\n-\t\t}\n+\t\tif (ro-\u003ebound \u0026\u0026 dev \u0026\u0026 dev-\u003ereg_state != NETREG_REGISTERED)\n+\t\t\treturn -ENODEV;\n \n \t\t/* remove current error mask */\n \t\tif (ro-\u003ebound) {\n@@ -676,7 +655,7 @@ static int raw_setsockopt(struct socket *sock, int level, int optname,\n \t\t\t\t\t\t err_mask);\n \n \t\t\tif (err)\n-\t\t\t\tgoto out_err;\n+\t\t\t\treturn err;\n \n \t\t\t/* remove old err_mask registration */\n \t\t\traw_disable_errfilter(sock_net(sk), dev, sk,\n@@ -685,11 +664,6 @@ static int raw_setsockopt(struct socket *sock, int level, int optname,\n \n \t\t/* link new err_mask to the socket */\n \t\tro-\u003eerr_mask = err_mask;\n-\n- out_err:\n-\t\trelease_sock(sk);\n-\t\trtnl_unlock();\n-\n \t\tbreak;\n \n \tcase CAN_RAW_LOOPBACK:\n@@ -769,6 +743,26 @@ static int raw_setsockopt(struct socket *sock, int level, int optname,\n \treturn err;\n }\n \n+static int raw_setsockopt(struct socket *sock, int level, int optname,\n+\t\t\t sockptr_t optval, unsigned int optlen)\n+{\n+\tstruct sock *sk = sock-\u003esk;\n+\tint err;\n+\n+\tif (level != SOL_CAN_RAW)\n+\t\treturn -EINVAL;\n+\n+\trtnl_lock();\n+\tlock_sock(sk);\n+\n+\terr = raw_setsockopt_locked(sock, optname, optval, optlen);\n+\n+\trelease_sock(sk);\n+\trtnl_unlock();\n+\n+\treturn err;\n+}\n+\n static int raw_getsockopt(struct socket *sock, int level, int optname,\n \t\t\t sockopt_t *opt)\n {\n"
} |
| 2/1 | 2026/07/16 21:21 | agent | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: {
"EnableConfigs": [
"CAN_BCM",
"CAN_ISOTP",
"CAN_J1939",
"CAN_RAW",
"CAN_ESD_USB"
],
"FocusSymbols": [
"bcm_tx_setup",
"bcm_rx_setup",
"bcm_sendmsg",
"bcm_release",
"bcm_tx_timeout_handler",
"bcm_rx_timeout_handler",
"isotp_sendmsg",
"isotp_bind",
"isotp_release",
"isotp_tx_timeout",
"isotp_rcv_echo",
"isotp_rcv_fc",
"isotp_rcv_cf",
"j1939_tp_send",
"raw_setsockopt",
"esd_usb_disconnect"
],
"Reasoning": "The patch series makes significant functional changes to the CAN subsystem, particularly in the BCM and ISOTP protocols. It introduces major reworks of timers, locking, and state machines in `net/can/bcm.c` and `net/can/isotp.c`. It also fixes a bug in J1939 transport, changes locking in `raw_setsockopt`, and fixes a memory leak in the `esd_usb` driver's disconnect path. These changes are highly reachable from userspace and should be fuzzed.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Do not list too generic configs (we already have them enabled). Only list those that specifically cover the modified code. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit cce599b07db5450d9336aa06c7b2997adfe36295
Author: syz-cluster <triage@syzkaller.com>
Date: Thu Jul 16 21:21:31 2026 +0000
syz-cluster: applied patch under review
diff --git a/.mailmap b/.mailmap
index 12f3acdebd7226..918fcd6575b12a 100644
--- a/.mailmap
+++ b/.mailmap
@@ -825,8 +825,8 @@ Sriram Yagnaraman <sriram.yagnaraman@ericsson.com> <sriram.yagnaraman@est.tech>
Stanislav Fomichev <sdf@fomichev.me> <sdf@google.com>
Stanislav Fomichev <sdf@fomichev.me> <stfomichev@gmail.com>
Stefan Wahren <wahrenst@gmx.net> <stefan.wahren@i2se.com>
-Stéphane Grosjean <stephane.grosjean@hms-networks.com> <s.grosjean@peak-system.com>
-Stéphane Grosjean <stephane.grosjean@hms-networks.com> <stephane.grosjean@free.fr>
+Stéphane Grosjean <s.grosjean@peak-system.fr> <s.grosjean@peak-system.com>
+Stéphane Grosjean <s.grosjean@peak-system.fr> <stephane.grosjean@free.fr>
Stéphane Witzmann <stephane.witzmann@ubpmes.univ-bpclermont.fr>
Stephen Hemminger <stephen@networkplumber.org> <shemminger@linux-foundation.org>
Stephen Hemminger <stephen@networkplumber.org> <shemminger@osdl.org>
diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig
index e4058708ae6833..a8fad6fe530207 100644
--- a/drivers/net/can/Kconfig
+++ b/drivers/net/can/Kconfig
@@ -40,11 +40,8 @@ config CAN_VXCAN
When one end receives the packet it appears on its pair and vice
versa. The vxcan can be used for cross namespace communication.
- In opposite to vcan loopback devices the vxcan only forwards CAN
- frames to its pair and does *not* provide a local echo of sent
- CAN frames. To disable a potential echo in af_can.c the vxcan driver
- announces IFF_ECHO in the interface flags. To have a clean start
- in each namespace the CAN GW hop counter is set to zero.
+ To have a clean start in each namespace the CAN GW hop counter is
+ set to zero.
This driver can also be built as a module. If so, the module
will be called vxcan.
diff --git a/drivers/net/can/peak_canfd/peak_canfd.c b/drivers/net/can/peak_canfd/peak_canfd.c
index 06cb2629f66abc..4fd1aefb780fdb 100644
--- a/drivers/net/can/peak_canfd/peak_canfd.c
+++ b/drivers/net/can/peak_canfd/peak_canfd.c
@@ -2,7 +2,7 @@
/* Copyright (C) 2007, 2011 Wolfgang Grandegger <wg@grandegger.com>
*
* Copyright (C) 2016-2025 PEAK System-Technik GmbH
- * Author: Stéphane Grosjean <stephane.grosjean@hms-networks.com>
+ * Author: Stéphane Grosjean <s.grosjean@peak-system.fr>
*/
#include <linux/can.h>
diff --git a/drivers/net/can/peak_canfd/peak_canfd_user.h b/drivers/net/can/peak_canfd/peak_canfd_user.h
index 60c6542028cf44..dc0ecb566a85a3 100644
--- a/drivers/net/can/peak_canfd/peak_canfd_user.h
+++ b/drivers/net/can/peak_canfd/peak_canfd_user.h
@@ -2,7 +2,7 @@
/* CAN driver for PEAK System micro-CAN based adapters
*
* Copyright (C) 2003-2025 PEAK System-Technik GmbH
- * Author: Stéphane Grosjean <stephane.grosjean@hms-networks.com>
+ * Author: Stéphane Grosjean <s.grosjean@peak-system.fr>
*/
#ifndef PEAK_CANFD_USER_H
#define PEAK_CANFD_USER_H
diff --git a/drivers/net/can/peak_canfd/peak_pciefd_main.c b/drivers/net/can/peak_canfd/peak_pciefd_main.c
index 93558e33bc02bb..7c749301ea84cc 100644
--- a/drivers/net/can/peak_canfd/peak_pciefd_main.c
+++ b/drivers/net/can/peak_canfd/peak_pciefd_main.c
@@ -4,7 +4,7 @@
* Derived from the PCAN project file driver/src/pcan_pci.c:
*
* Copyright (C) 2001-2025 PEAK System-Technik GmbH
- * Author: Stéphane Grosjean <stephane.grosjean@hms-networks.com>
+ * Author: Stéphane Grosjean <s.grosjean@peak-system.fr>
*/
#include <linux/kernel.h>
@@ -19,7 +19,7 @@
#include "peak_canfd_user.h"
-MODULE_AUTHOR("Stéphane Grosjean <stephane.grosjean@hms-networks.com>");
+MODULE_AUTHOR("Stéphane Grosjean <s.grosjean@peak-system.fr>");
MODULE_DESCRIPTION("Socket-CAN driver for PEAK PCAN PCIe/M.2 FD family cards");
MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/can/sja1000/peak_pci.c b/drivers/net/can/sja1000/peak_pci.c
index 4cc4a1581dd115..69c61ccf621df8 100644
--- a/drivers/net/can/sja1000/peak_pci.c
+++ b/drivers/net/can/sja1000/peak_pci.c
@@ -5,7 +5,7 @@
* Derived from the PCAN project file driver/src/pcan_pci.c:
*
* Copyright (C) 2001-2025 PEAK System-Technik GmbH
- * Author: Stéphane Grosjean <stephane.grosjean@hms-networks.com>
+ * Author: Stéphane Grosjean <s.grosjean@peak-system.fr>
*/
#include <linux/kernel.h>
@@ -22,7 +22,7 @@
#include "sja1000.h"
-MODULE_AUTHOR("Stéphane Grosjean <stephane.grosjean@hms-networks.com>");
+MODULE_AUTHOR("Stéphane Grosjean <s.grosjean@peak-system.fr>");
MODULE_DESCRIPTION("Socket-CAN driver for PEAK PCAN PCI family cards");
MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/can/sja1000/peak_pcmcia.c b/drivers/net/can/sja1000/peak_pcmcia.c
index 42a77d435b3920..c3c2aa21da47b2 100644
--- a/drivers/net/can/sja1000/peak_pcmcia.c
+++ b/drivers/net/can/sja1000/peak_pcmcia.c
@@ -4,7 +4,7 @@
* Derived from the PCAN project file driver/src/pcan_pccard.c
*
* Copyright (C) 2006-2025 PEAK System-Technik GmbH
- * Author: Stéphane Grosjean <stephane.grosjean@hms-networks.com>
+ * Author: Stéphane Grosjean <s.grosjean@peak-system.fr>
*/
#include <linux/kernel.h>
#include <linux/module.h>
@@ -19,7 +19,7 @@
#include <linux/can/dev.h>
#include "sja1000.h"
-MODULE_AUTHOR("Stéphane Grosjean <stephane.grosjean@hms-networks.com>");
+MODULE_AUTHOR("Stéphane Grosjean <s.grosjean@peak-system.fr>");
MODULE_DESCRIPTION("CAN driver for PEAK-System PCAN-PC Cards");
MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/can/usb/esd_usb.c b/drivers/net/can/usb/esd_usb.c
index d257440fa01ff1..f41d4a0d140f7e 100644
--- a/drivers/net/can/usb/esd_usb.c
+++ b/drivers/net/can/usb/esd_usb.c
@@ -1390,10 +1390,13 @@ static void esd_usb_disconnect(struct usb_interface *intf)
netdev = dev->nets[i]->netdev;
netdev_info(netdev, "unregister\n");
unregister_netdev(netdev);
- free_candev(netdev);
}
}
unlink_all_urbs(dev);
+ for (i = 0; i < dev->net_count; i++) {
+ if (dev->nets[i])
+ free_candev(dev->nets[i]->netdev);
+ }
kfree(dev);
}
}
diff --git a/drivers/net/can/usb/peak_usb/pcan_usb.c b/drivers/net/can/usb/peak_usb/pcan_usb.c
index 9278a1522aae5e..8fd058c328564e 100644
--- a/drivers/net/can/usb/peak_usb/pcan_usb.c
+++ b/drivers/net/can/usb/peak_usb/pcan_usb.c
@@ -4,7 +4,7 @@
* Derived from the PCAN project file driver/src/pcan_usb.c
*
* Copyright (C) 2003-2025 PEAK System-Technik GmbH
- * Author: Stéphane Grosjean <stephane.grosjean@hms-networks.com>
+ * Author: Stéphane Grosjean <s.grosjean@peak-system.fr>
*
* Many thanks to Klaus Hitschler <klaus.hitschler@gmx.de>
*/
diff --git a/drivers/net/can/usb/peak_usb/pcan_usb_core.c b/drivers/net/can/usb/peak_usb/pcan_usb_core.c
index cf48bb26d46d23..c7933d1acc99a9 100644
--- a/drivers/net/can/usb/peak_usb/pcan_usb_core.c
+++ b/drivers/net/can/usb/peak_usb/pcan_usb_core.c
@@ -4,7 +4,7 @@
* Derived from the PCAN project file driver/src/pcan_usb_core.c
*
* Copyright (C) 2003-2025 PEAK System-Technik GmbH
- * Author: Stéphane Grosjean <stephane.grosjean@hms-networks.com>
+ * Author: Stéphane Grosjean <s.grosjean@peak-system.fr>
*
* Many thanks to Klaus Hitschler <klaus.hitschler@gmx.de>
*/
@@ -24,7 +24,7 @@
#include "pcan_usb_core.h"
-MODULE_AUTHOR("Stéphane Grosjean <stephane.grosjean@hms-networks.com>");
+MODULE_AUTHOR("Stéphane Grosjean <s.grosjean@peak-system.fr>");
MODULE_DESCRIPTION("CAN driver for PEAK-System USB adapters");
MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/can/usb/peak_usb/pcan_usb_core.h b/drivers/net/can/usb/peak_usb/pcan_usb_core.h
index d1c1897d47b9c6..65999f04f4b76e 100644
--- a/drivers/net/can/usb/peak_usb/pcan_usb_core.h
+++ b/drivers/net/can/usb/peak_usb/pcan_usb_core.h
@@ -4,7 +4,7 @@
* Derived from the PCAN project file driver/src/pcan_usb_core.c
*
* Copyright (C) 2003-2025 PEAK System-Technik GmbH
- * Author: Stéphane Grosjean <stephane.grosjean@hms-networks.com>
+ * Author: Stéphane Grosjean <s.grosjean@peak-system.fr>
*
* Many thanks to Klaus Hitschler <klaus.hitschler@gmx.de>
*/
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 eb4f5884ad7358..ef9fd693e9bd38 100644
--- a/drivers/net/can/usb/peak_usb/pcan_usb_fd.c
+++ b/drivers/net/can/usb/peak_usb/pcan_usb_fd.c
@@ -3,7 +3,7 @@
* CAN driver for PEAK System PCAN-USB FD / PCAN-USB Pro FD adapter
*
* Copyright (C) 2013-2025 PEAK System-Technik GmbH
- * Author: Stéphane Grosjean <stephane.grosjean@hms-networks.com>
+ * Author: Stéphane Grosjean <s.grosjean@peak-system.fr>
*/
#include <linux/ethtool.h>
#include <linux/module.h>
diff --git a/drivers/net/can/usb/peak_usb/pcan_usb_pro.c b/drivers/net/can/usb/peak_usb/pcan_usb_pro.c
index 4bfa8d0fbb32fd..aefcded8e12a8f 100644
--- a/drivers/net/can/usb/peak_usb/pcan_usb_pro.c
+++ b/drivers/net/can/usb/peak_usb/pcan_usb_pro.c
@@ -4,7 +4,7 @@
* Derived from the PCAN project file driver/src/pcan_usbpro.c
*
* Copyright (C) 2003-2025 PEAK System-Technik GmbH
- * Author: Stéphane Grosjean <stephane.grosjean@hms-networks.com>
+ * Author: Stéphane Grosjean <s.grosjean@peak-system.fr>
*/
#include <linux/ethtool.h>
#include <linux/module.h>
diff --git a/drivers/net/can/usb/peak_usb/pcan_usb_pro.h b/drivers/net/can/usb/peak_usb/pcan_usb_pro.h
index 162c7546d3a833..d669c9e610c7e8 100644
--- a/drivers/net/can/usb/peak_usb/pcan_usb_pro.h
+++ b/drivers/net/can/usb/peak_usb/pcan_usb_pro.h
@@ -4,7 +4,7 @@
* Derived from the PCAN project file driver/src/pcan_usbpro_fw.h
*
* Copyright (C) 2003-2025 PEAK System-Technik GmbH
- * Author: Stéphane Grosjean <stephane.grosjean@hms-networks.com>
+ * Author: Stéphane Grosjean <s.grosjean@peak-system.fr>
*/
#ifndef PCAN_USB_PRO_H
#define PCAN_USB_PRO_H
diff --git a/include/linux/can/dev/peak_canfd.h b/include/linux/can/dev/peak_canfd.h
index d3788a3d0942b2..056e0efa649fd3 100644
--- a/include/linux/can/dev/peak_canfd.h
+++ b/include/linux/can/dev/peak_canfd.h
@@ -3,7 +3,7 @@
* CAN driver for PEAK System micro-CAN based adapters
*
* Copyright (C) 2003-2025 PEAK System-Technik GmbH
- * Author: Stéphane Grosjean <stephane.grosjean@hms-networks.com>
+ * Author: Stéphane Grosjean <s.grosjean@peak-system.fr>
*/
#ifndef PUCAN_H
#define PUCAN_H
diff --git a/net/can/bcm.c b/net/can/bcm.c
index a4bef2c48a559c..3d637a1e0ac1a9 100644
--- a/net/can/bcm.c
+++ b/net/can/bcm.c
@@ -58,6 +58,7 @@
#include <linux/can/skb.h>
#include <linux/can/bcm.h>
#include <linux/slab.h>
+#include <linux/workqueue.h>
#include <linux/spinlock.h>
#include <net/can.h>
#include <net/sock.h>
@@ -92,6 +93,8 @@ MODULE_ALIAS("can-proto-2");
#define BCM_MIN_NAMELEN CAN_REQUIRED_SIZE(struct sockaddr_can, can_ifindex)
+static struct workqueue_struct *bcm_wq;
+
/*
* easy access to the first 64 bit of can(fd)_frame payload. cp->data is
* 64 bit aligned so the offset has to be multiples of 8 which is ensured
@@ -105,14 +108,16 @@ static inline u64 get_u64(const struct canfd_frame *cp, int offset)
struct bcm_op {
struct list_head list;
struct rcu_head rcu;
+ struct work_struct work;
int ifindex;
canid_t can_id;
u32 flags;
- unsigned long frames_abs, frames_filtered;
+ atomic_long_t frames_abs, frames_filtered;
struct bcm_timeval ival1, ival2;
struct hrtimer timer, thrtimer;
ktime_t rx_stamp, kt_ival1, kt_ival2, kt_lastmsg;
int rx_ifindex;
+ int if_detected; /* first received ifindex in ANYDEV rx_op mode */
int cfsiz;
u32 count;
u32 nframes;
@@ -124,7 +129,9 @@ struct bcm_op {
struct canfd_frame last_sframe;
struct sock *sk;
struct net_device *rx_reg_dev;
- spinlock_t bcm_tx_lock; /* protect currframe/count in runtime updates */
+ netdevice_tracker rx_reg_dev_tracker;
+ spinlock_t bcm_tx_lock; /* protect tx data and timer updates */
+ spinlock_t bcm_rx_update_lock; /* protect filter/timer data updates */
};
struct bcm_sock {
@@ -224,10 +231,13 @@ static int bcm_proc_show(struct seq_file *m, void *v)
list_for_each_entry_rcu(op, &bo->rx_ops, list) {
- unsigned long reduction;
+ long reduction, frames_filtered, frames_abs;
+
+ frames_filtered = atomic_long_read(&op->frames_filtered);
+ frames_abs = atomic_long_read(&op->frames_abs);
/* print only active entries & prevent division by zero */
- if (!op->frames_abs)
+ if (!frames_abs)
continue;
seq_printf(m, "rx_op: %03X %-5s ", op->can_id,
@@ -249,15 +259,15 @@ static int bcm_proc_show(struct seq_file *m, void *v)
(long long)ktime_to_us(op->kt_ival2));
seq_printf(m, "# recv %ld (%ld) => reduction: ",
- op->frames_filtered, op->frames_abs);
+ frames_filtered, frames_abs);
- reduction = 100 - (op->frames_filtered * 100) / op->frames_abs;
+ reduction = 100 - (frames_filtered * 100) / frames_abs;
seq_printf(m, "%s%ld%%\n",
(reduction == 100) ? "near " : "", reduction);
}
- list_for_each_entry(op, &bo->tx_ops, list) {
+ list_for_each_entry_rcu(op, &bo->tx_ops, list) {
seq_printf(m, "tx_op: %03X %s ", op->can_id,
bcm_proc_getifname(net, ifname, op->ifindex));
@@ -275,7 +285,8 @@ static int bcm_proc_show(struct seq_file *m, void *v)
seq_printf(m, "t2=%lld ",
(long long)ktime_to_us(op->kt_ival2));
- seq_printf(m, "# sent %ld\n", op->frames_abs);
+ seq_printf(m, "# sent %ld\n",
+ atomic_long_read(&op->frames_abs));
}
seq_putc(m, '\n');
@@ -285,26 +296,50 @@ static int bcm_proc_show(struct seq_file *m, void *v)
}
#endif /* CONFIG_PROC_FS */
+static void bcm_update_rx_stats(struct bcm_op *op)
+{
+ /* prevent overflow of the reduction% calculation in bcm_proc_show() */
+ if (atomic_long_inc_return(&op->frames_abs) > LONG_MAX / 100) {
+ atomic_long_set(&op->frames_filtered, 0);
+ atomic_long_set(&op->frames_abs, 0);
+ }
+}
+
+static void bcm_update_tx_stats(struct bcm_op *op)
+{
+ /* tx_op has no reduction% calculation - use the full range and
+ * just keep the displayed counter non-negative on overflow
+ */
+ if (atomic_long_inc_return(&op->frames_abs) == LONG_MAX)
+ atomic_long_set(&op->frames_abs, 0);
+}
+
/*
* bcm_can_tx - send the (next) CAN frame to the appropriate CAN interface
* of the given bcm tx op
*/
-static void bcm_can_tx(struct bcm_op *op)
+static void bcm_can_tx(struct bcm_op *op, struct canfd_frame *cf)
{
struct sk_buff *skb;
struct can_skb_ext *csx;
struct net_device *dev;
- struct canfd_frame *cf;
+ struct canfd_frame cframe;
+ bool cyclic = !cf;
+ unsigned int idx = 0;
int err;
/* no target device? => exit */
if (!op->ifindex)
return;
- /* read currframe under lock protection */
- spin_lock_bh(&op->bcm_tx_lock);
- cf = op->frames + op->cfsiz * op->currframe;
- spin_unlock_bh(&op->bcm_tx_lock);
+ if (cyclic) {
+ /* read currframe under lock protection */
+ spin_lock_bh(&op->bcm_tx_lock);
+ idx = op->currframe;
+ memcpy(&cframe, op->frames + op->cfsiz * idx, op->cfsiz);
+ cf = &cframe;
+ spin_unlock_bh(&op->bcm_tx_lock);
+ }
dev = dev_get_by_index(sock_net(op->sk), op->ifindex);
if (!dev) {
@@ -335,16 +370,22 @@ static void bcm_can_tx(struct bcm_op *op)
spin_lock_bh(&op->bcm_tx_lock);
if (!err)
- op->frames_abs++;
+ bcm_update_tx_stats(op);
- op->currframe++;
+ /* only advance the cyclic sequence if nothing reset currframe while
+ * we were sending - a concurrent TX_RESET_MULTI_IDX means this
+ * frame's bookkeeping belongs to a sequence that no longer exists
+ */
+ if (!cyclic || op->currframe == idx) {
+ op->currframe++;
- /* reached last frame? */
- if (op->currframe >= op->nframes)
- op->currframe = 0;
+ /* reached last frame? */
+ if (op->currframe >= op->nframes)
+ op->currframe = 0;
- if (op->count > 0)
- op->count--;
+ if (op->count > 0)
+ op->count--;
+ }
spin_unlock_bh(&op->bcm_tx_lock);
out:
@@ -433,12 +474,18 @@ static bool bcm_tx_set_expiry(struct bcm_op *op, struct hrtimer *hrt)
{
ktime_t ival;
+ spin_lock_bh(&op->bcm_tx_lock);
+
if (op->kt_ival1 && op->count)
ival = op->kt_ival1;
- else if (op->kt_ival2)
+ else if (op->kt_ival2) {
ival = op->kt_ival2;
- else
+ } else {
+ spin_unlock_bh(&op->bcm_tx_lock);
return false;
+ }
+
+ spin_unlock_bh(&op->bcm_tx_lock);
hrtimer_set_expires(hrt, ktime_add(ktime_get(), ival));
return true;
@@ -455,26 +502,48 @@ static enum hrtimer_restart bcm_tx_timeout_handler(struct hrtimer *hrtimer)
{
struct bcm_op *op = container_of(hrtimer, struct bcm_op, timer);
struct bcm_msg_head msg_head;
+ bool tx_ival1, tx_ival2;
+
+ /* snapshot kt_ival1/kt_ival2/count under lock to avoid torn
+ * ktime_t reads racing with concurrent bcm_tx_setup() updates
+ */
+ spin_lock_bh(&op->bcm_tx_lock);
+ tx_ival1 = op->kt_ival1 && (op->count > 0);
+ tx_ival2 = !!op->kt_ival2;
+ spin_unlock_bh(&op->bcm_tx_lock);
- if (op->kt_ival1 && (op->count > 0)) {
- bcm_can_tx(op);
- if (!op->count && (op->flags & TX_COUNTEVT)) {
+ if (tx_ival1) {
+ u32 flags, count;
+ struct bcm_timeval ival1, ival2;
+ bcm_can_tx(op, NULL);
+
+ /* snapshot variables under lock to avoid torn reads racing
+ * with concurrent bcm_tx_setup() updates
+ */
+ spin_lock_bh(&op->bcm_tx_lock);
+ flags = op->flags;
+ count = op->count;
+ ival1 = op->ival1;
+ ival2 = op->ival2;
+ spin_unlock_bh(&op->bcm_tx_lock);
+
+ if (!count && (flags & TX_COUNTEVT)) {
/* create notification to user */
memset(&msg_head, 0, sizeof(msg_head));
msg_head.opcode = TX_EXPIRED;
- msg_head.flags = op->flags;
- msg_head.count = op->count;
- msg_head.ival1 = op->ival1;
- msg_head.ival2 = op->ival2;
+ msg_head.flags = flags;
+ msg_head.count = count;
+ msg_head.ival1 = ival1;
+ msg_head.ival2 = ival2;
msg_head.can_id = op->can_id;
msg_head.nframes = 0;
bcm_send_to_user(op, &msg_head, NULL, 0);
}
- } else if (op->kt_ival2) {
- bcm_can_tx(op);
+ } else if (tx_ival2) {
+ bcm_can_tx(op, NULL);
}
return bcm_tx_set_expiry(op, &op->timer) ?
@@ -488,12 +557,9 @@ static void bcm_rx_changed(struct bcm_op *op, struct canfd_frame *data)
{
struct bcm_msg_head head;
- /* update statistics */
- op->frames_filtered++;
-
- /* prevent statistics overflow */
- if (op->frames_filtered > ULONG_MAX/100)
- op->frames_filtered = op->frames_abs = 0;
+ /* update statistics (frames_filtered <= frames_abs) */
+ if (atomic_long_read(&op->frames_abs))
+ atomic_long_inc(&op->frames_filtered);
/* this element is not throttled anymore */
data->flags &= ~RX_THR;
@@ -618,6 +684,8 @@ static enum hrtimer_restart bcm_rx_timeout_handler(struct hrtimer *hrtimer)
struct bcm_op *op = container_of(hrtimer, struct bcm_op, timer);
struct bcm_msg_head msg_head;
+ spin_lock_bh(&op->bcm_rx_update_lock);
+
/* if user wants to be informed, when cyclic CAN-Messages come back */
if ((op->flags & RX_ANNOUNCE_RESUME) && op->last_frames) {
/* clear received CAN frames to indicate 'nothing received' */
@@ -634,6 +702,8 @@ static enum hrtimer_restart bcm_rx_timeout_handler(struct hrtimer *hrtimer)
msg_head.can_id = op->can_id;
msg_head.nframes = 0;
+ spin_unlock_bh(&op->bcm_rx_update_lock);
+
bcm_send_to_user(op, &msg_head, NULL, 0);
return HRTIMER_NORESTART;
@@ -682,15 +752,26 @@ static int bcm_rx_thr_flush(struct bcm_op *op)
static enum hrtimer_restart bcm_rx_thr_handler(struct hrtimer *hrtimer)
{
struct bcm_op *op = container_of(hrtimer, struct bcm_op, thrtimer);
+ enum hrtimer_restart ret;
+
+ spin_lock_bh(&op->bcm_rx_update_lock);
- if (bcm_rx_thr_flush(op)) {
+ /* kt_ival2 may have been concurrently cleared by bcm_rx_setup()
+ * before it cancels this timer - never forward with a zero
+ * interval in that case.
+ */
+ if (bcm_rx_thr_flush(op) && op->kt_ival2) {
hrtimer_forward_now(hrtimer, op->kt_ival2);
- return HRTIMER_RESTART;
+ ret = HRTIMER_RESTART;
} else {
/* rearm throttle handling */
op->kt_lastmsg = 0;
- return HRTIMER_NORESTART;
+ ret = HRTIMER_NORESTART;
}
+
+ spin_unlock_bh(&op->bcm_rx_update_lock);
+
+ return ret;
}
/*
@@ -700,8 +781,10 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data)
{
struct bcm_op *op = (struct bcm_op *)data;
const struct canfd_frame *rxframe = (struct canfd_frame *)skb->data;
+ struct canfd_frame rtrframe;
unsigned int i;
unsigned char traffic_flags;
+ bool rtr_frame;
if (op->can_id != rxframe->can_id)
return;
@@ -715,22 +798,59 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data)
return;
}
+ /* An ANYDEV op with an active RX timeout and/or throttle timer
+ * tracks a single source interface: claim the first interface that
+ * delivers a matching frame and reject frames from any other one,
+ * before hrtimer_cancel() below can touch op->timer - this avoids
+ * racing bcm_rx_timeout_handler() across concurrent interfaces.
+ * RX_RTR_FRAME ops are excluded, as kt_ival1/kt_ival2 may briefly
+ * hold a stale value from an earlier non-RTR configuration.
+ */
+ if (!op->ifindex) {
+ spin_lock_bh(&op->bcm_rx_update_lock);
+
+ if (!(op->flags & RX_RTR_FRAME) &&
+ (op->kt_ival1 || op->kt_ival2)) {
+ /* don't claim to vanishing interface */
+ if (!op->if_detected &&
+ READ_ONCE(skb->dev->reg_state) == NETREG_REGISTERED)
+ op->if_detected = skb->dev->ifindex;
+
+ if (op->if_detected != skb->dev->ifindex) {
+ spin_unlock_bh(&op->bcm_rx_update_lock);
+ return;
+ }
+ }
+
+ spin_unlock_bh(&op->bcm_rx_update_lock);
+ }
+
/* disable timeout */
hrtimer_cancel(&op->timer);
- /* save rx timestamp */
- op->rx_stamp = skb->tstamp;
- /* save originator for recvfrom() */
- op->rx_ifindex = skb->dev->ifindex;
- /* update statistics */
- op->frames_abs++;
+ /* op->flags/op->frames may be updated concurrently by bcm_rx_setup() */
+ spin_lock_bh(&op->bcm_rx_update_lock);
+
+ rtr_frame = op->flags & RX_RTR_FRAME;
+ if (rtr_frame) {
+ bcm_update_rx_stats(op);
+ /* snapshot RTR content under lock */
+ memcpy(&rtrframe, op->frames, op->cfsiz);
+ spin_unlock_bh(&op->bcm_rx_update_lock);
- if (op->flags & RX_RTR_FRAME) {
/* send reply for RTR-request (placed in op->frames[0]) */
- bcm_can_tx(op);
+ bcm_can_tx(op, &rtrframe);
return;
}
+ /* update statistics in the same critical section as bcm_rx_changed()
+ * below: frames_filtered must never be checked/incremented against a
+ * frames_abs snapshot from a concurrent bcm_rx_handler() call on
+ * another CPU for the same (wildcard) op, or frames_filtered can end
+ * up larger than frames_abs.
+ */
+ bcm_update_rx_stats(op);
+
/* compute flags to distinguish between own/local/remote CAN traffic */
traffic_flags = 0;
if (skb->sk) {
@@ -739,6 +859,13 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data)
traffic_flags |= RX_OWN;
}
+ /* save rx timestamp and originator for recvfrom() under lock: an
+ * ANYDEV op without an active timer can still run concurrently on
+ * different CPUs, so content and meta data must be bundled here.
+ */
+ op->rx_stamp = skb->tstamp;
+ op->rx_ifindex = skb->dev->ifindex;
+
if (op->flags & RX_FILTER_ID) {
/* the easiest case */
bcm_rx_update_and_send(op, op->last_frames, rxframe,
@@ -774,6 +901,8 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data)
rx_starttimer:
bcm_rx_starttimer(op);
+
+ spin_unlock_bh(&op->bcm_rx_update_lock);
}
/*
@@ -793,9 +922,12 @@ static struct bcm_op *bcm_find_op(struct list_head *ops,
return NULL;
}
-static void bcm_free_op_rcu(struct rcu_head *rcu_head)
+static void bcm_free_op_work(struct work_struct *work)
{
- struct bcm_op *op = container_of(rcu_head, struct bcm_op, rcu);
+ struct bcm_op *op = container_of(work, struct bcm_op, work);
+
+ hrtimer_cancel(&op->timer);
+ hrtimer_cancel(&op->thrtimer);
if ((op->frames) && (op->frames != &op->sframe))
kfree(op->frames);
@@ -803,9 +935,23 @@ static void bcm_free_op_rcu(struct rcu_head *rcu_head)
if ((op->last_frames) && (op->last_frames != &op->last_sframe))
kfree(op->last_frames);
+ /* the last possible access to op->timer/op->thrtimer has now
+ * happened above via hrtimer_cancel() - op->sk is no longer
+ * needed by any pending timer callback, so drop our reference
+ */
+ sock_put(op->sk);
+
kfree(op);
}
+static void bcm_free_op_rcu(struct rcu_head *rcu_head)
+{
+ struct bcm_op *op = container_of(rcu_head, struct bcm_op, rcu);
+
+ INIT_WORK(&op->work, bcm_free_op_work);
+ queue_work(bcm_wq, &op->work);
+}
+
static void bcm_remove_op(struct bcm_op *op)
{
hrtimer_cancel(&op->timer);
@@ -822,6 +968,7 @@ static void bcm_rx_unreg(struct net_device *dev, struct bcm_op *op)
/* mark as removed subscription */
op->rx_reg_dev = NULL;
+ netdev_put(dev, &op->rx_reg_dev_tracker);
} else
printk(KERN_ERR "can-bcm: bcm_rx_unreg: registered device "
"mismatch %p %p\n", op->rx_reg_dev, dev);
@@ -852,17 +999,14 @@ static int bcm_delete_rx_op(struct list_head *ops, struct bcm_msg_head *mh,
* 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.
*/
- if (op->rx_reg_dev) {
- struct net_device *dev;
-
- dev = dev_get_by_index(sock_net(op->sk),
- op->ifindex);
- if (dev) {
- bcm_rx_unreg(dev, op);
- dev_put(dev);
- }
- }
+ if (op->rx_reg_dev)
+ bcm_rx_unreg(op->rx_reg_dev, op);
} else
can_rx_unregister(sock_net(op->sk), NULL,
op->can_id,
@@ -930,6 +1074,7 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
struct bcm_sock *bo = bcm_sk(sk);
struct bcm_op *op;
struct canfd_frame *cf;
+ bool add_op_to_list = false;
unsigned int i;
int err;
@@ -948,6 +1093,8 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
/* check the given can_id */
op = bcm_find_op(&bo->tx_ops, msg_head, ifindex);
if (op) {
+ void *new_frames;
+
/* update existing BCM operation */
/*
@@ -958,11 +1105,23 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
if (msg_head->nframes > op->nframes)
return -E2BIG;
- /* update CAN frames content */
+ /* get new CAN frames content into a staging buffer before
+ * locking: validate and normalize the frames there so that
+ * bcm_can_tx() / bcm_tx_timeout_handler() never observe a
+ * partially updated or unvalidated frame in op->frames
+ */
+ new_frames = kmalloc(msg_head->nframes * op->cfsiz, GFP_KERNEL);
+ if (!new_frames)
+ return -ENOMEM;
+
for (i = 0; i < msg_head->nframes; i++) {
- cf = op->frames + op->cfsiz * i;
+ cf = new_frames + op->cfsiz * i;
err = memcpy_from_msg((u8 *)cf, msg, op->cfsiz);
+ if (err < 0) {
+ kfree(new_frames);
+ return err;
+ }
if (op->flags & CAN_FD_FRAME) {
if (cf->len > 64)
@@ -972,36 +1131,38 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
err = -EINVAL;
}
- if (err < 0)
+ if (err < 0) {
+ kfree(new_frames);
return err;
+ }
if (msg_head->flags & TX_CP_CAN_ID) {
/* copy can_id into frame */
cf->can_id = msg_head->can_id;
}
}
+
+ spin_lock_bh(&op->bcm_tx_lock);
+
+ /* update CAN frames content */
+ memcpy(op->frames, new_frames, msg_head->nframes * op->cfsiz);
+
op->flags = msg_head->flags;
- /* only lock for unlikely count/nframes/currframe changes */
if (op->nframes != msg_head->nframes ||
- op->flags & TX_RESET_MULTI_IDX ||
- op->flags & SETTIMER) {
-
- spin_lock_bh(&op->bcm_tx_lock);
+ op->flags & TX_RESET_MULTI_IDX) {
+ /* potentially update changed nframes */
+ op->nframes = msg_head->nframes;
+ /* restart multiple frame transmission */
+ op->currframe = 0;
+ }
- if (op->nframes != msg_head->nframes ||
- op->flags & TX_RESET_MULTI_IDX) {
- /* potentially update changed nframes */
- op->nframes = msg_head->nframes;
- /* restart multiple frame transmission */
- op->currframe = 0;
- }
+ if (op->flags & SETTIMER)
+ op->count = msg_head->count;
- if (op->flags & SETTIMER)
- op->count = msg_head->count;
+ spin_unlock_bh(&op->bcm_tx_lock);
- spin_unlock_bh(&op->bcm_tx_lock);
- }
+ kfree(new_frames);
} else {
/* insert new BCM operation for the given can_id */
@@ -1060,6 +1221,7 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
/* bcm_can_tx / bcm_tx_timeout_handler needs this */
op->sk = sk;
+ sock_hold(sk);
op->ifindex = ifindex;
/* initialize uninitialized (kzalloc) structure */
@@ -1070,17 +1232,18 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
hrtimer_setup(&op->thrtimer, hrtimer_dummy_timeout, CLOCK_MONOTONIC,
HRTIMER_MODE_REL_SOFT);
- /* add this bcm_op to the list of the tx_ops */
- list_add(&op->list, &bo->tx_ops);
+ add_op_to_list = true;
} /* if ((op = bcm_find_op(&bo->tx_ops, msg_head->can_id, ifindex))) */
if (op->flags & SETTIMER) {
/* set timer values */
+ spin_lock_bh(&op->bcm_tx_lock);
op->ival1 = msg_head->ival1;
op->ival2 = msg_head->ival2;
op->kt_ival1 = bcm_timeval_to_ktime(msg_head->ival1);
op->kt_ival2 = bcm_timeval_to_ktime(msg_head->ival2);
+ spin_unlock_bh(&op->bcm_tx_lock);
/* disable an active timer due to zero values? */
if (!op->kt_ival1 && !op->kt_ival2)
@@ -1093,8 +1256,12 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
op->flags |= TX_ANNOUNCE;
}
+ /* add this bcm_op to the list of the tx_ops? */
+ if (add_op_to_list)
+ list_add_rcu(&op->list, &bo->tx_ops);
+
if (op->flags & TX_ANNOUNCE)
- bcm_can_tx(op);
+ bcm_can_tx(op, NULL);
if (op->flags & STARTTIMER)
bcm_tx_start_timer(op);
@@ -1108,6 +1275,39 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
return err;
}
+static int bcm_rx_setup_rtr_check(struct bcm_msg_head *msg_head,
+ struct bcm_op *op, void *new_frames)
+{
+ struct canfd_frame *frame0 = new_frames;
+
+ if (!(msg_head->flags & RX_RTR_FRAME))
+ return 0;
+
+ /* this frame is sent out as-is by bcm_can_tx() whenever a matching
+ * remote request is received, so validate its length the same way
+ * bcm_tx_setup() validates TX_SETUP frames before installing it
+ */
+ if (msg_head->flags & CAN_FD_FRAME) {
+ if (frame0->len > 64)
+ return -EINVAL;
+ } else {
+ if (frame0->len > 8)
+ return -EINVAL;
+ }
+
+ /* funny feature in RX(!)_SETUP only for RTR-mode:
+ * copy can_id into frame BUT without RTR-flag to
+ * prevent a full-load-loopback-test ... ;-]
+ * normalize this on the staged buffer, before it is
+ * ever installed into op->frames.
+ */
+ if ((msg_head->flags & TX_CP_CAN_ID) ||
+ frame0->can_id == op->can_id)
+ frame0->can_id = op->can_id & ~CAN_RTR_FLAG;
+
+ return 0;
+}
+
/*
* bcm_rx_setup - create or update a bcm rx op (for bcm_sendmsg)
*/
@@ -1117,6 +1317,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
struct bcm_sock *bo = bcm_sk(sk);
struct bcm_op *op;
int do_rx_register;
+ int new_op = 0;
int err = 0;
if ((msg_head->flags & RX_FILTER_ID) || (!(msg_head->nframes))) {
@@ -1142,6 +1343,8 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
/* check the given can_id */
op = bcm_find_op(&bo->rx_ops, msg_head, ifindex);
if (op) {
+ void *new_frames = NULL;
+
/* update existing BCM operation */
/*
@@ -1153,21 +1356,62 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
return -E2BIG;
if (msg_head->nframes) {
- /* update CAN frames content */
- err = memcpy_from_msg(op->frames, msg,
+ /* get new CAN frames content before locking */
+ new_frames = kmalloc(msg_head->nframes * op->cfsiz,
+ GFP_KERNEL);
+ if (!new_frames)
+ return -ENOMEM;
+
+ err = memcpy_from_msg(new_frames, msg,
msg_head->nframes * op->cfsiz);
- if (err < 0)
+ if (err < 0) {
+ kfree(new_frames);
return err;
+ }
- /* clear last_frames to indicate 'nothing received' */
- memset(op->last_frames, 0, msg_head->nframes * op->cfsiz);
+ err = bcm_rx_setup_rtr_check(msg_head, op, new_frames);
+ if (err < 0) {
+ kfree(new_frames);
+ return err;
+ }
}
+ spin_lock_bh(&op->bcm_rx_update_lock);
op->nframes = msg_head->nframes;
op->flags = msg_head->flags;
- /* Only an update -> do not call can_rx_register() */
- do_rx_register = 0;
+ if (msg_head->nframes) {
+ /* update CAN frames content */
+ memcpy(op->frames, new_frames,
+ msg_head->nframes * op->cfsiz);
+
+ /* clear last_frames to indicate 'nothing received' */
+ memset(op->last_frames, 0,
+ msg_head->nframes * op->cfsiz);
+ }
+
+ if (msg_head->flags & SETTIMER) {
+ op->ival1 = msg_head->ival1;
+ op->ival2 = msg_head->ival2;
+ op->kt_ival1 = bcm_timeval_to_ktime(msg_head->ival1);
+ op->kt_ival2 = bcm_timeval_to_ktime(msg_head->ival2);
+ op->kt_lastmsg = 0;
+ op->if_detected = 0; /* reclaim ifindex in ANYDEV mode */
+ }
+ spin_unlock_bh(&op->bcm_rx_update_lock);
+
+ /* free temporary frames / kfree(NULL) is safe */
+ kfree(new_frames);
+
+ /* Don't register a new CAN filter for the rx_op update unless
+ * a concurrent NETDEV_UNREGISTER notifier already tore down
+ * the previous registration. In this case the receiver needs
+ * to be re-registered here so that this update doesn't
+ * silently stop delivering frames for the given ifindex.
+ * Ops with ifindex = 0 (all CAN interfaces) never carry a
+ * tracked rx_reg_dev and stay registered as-is.
+ */
+ do_rx_register = (ifindex && !op->rx_reg_dev) ? 1 : 0;
} else {
/* insert new BCM operation for the given can_id */
@@ -1176,6 +1420,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
return -ENOMEM;
spin_lock_init(&op->bcm_tx_lock);
+ spin_lock_init(&op->bcm_rx_update_lock);
op->can_id = msg_head->can_id;
op->nframes = msg_head->nframes;
op->cfsiz = CFSIZ(msg_head->flags);
@@ -1209,18 +1454,17 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
if (msg_head->nframes) {
err = memcpy_from_msg(op->frames, msg,
msg_head->nframes * op->cfsiz);
- if (err < 0) {
- if (op->frames != &op->sframe)
- kfree(op->frames);
- if (op->last_frames != &op->last_sframe)
- kfree(op->last_frames);
- kfree(op);
- return err;
- }
+ if (err < 0)
+ goto free_op;
+
+ err = bcm_rx_setup_rtr_check(msg_head, op, op->frames);
+ if (err < 0)
+ goto free_op;
}
/* bcm_can_tx / bcm_tx_timeout_handler needs this */
op->sk = sk;
+ sock_hold(sk);
op->ifindex = ifindex;
/* ifindex for timeout events w/o previous frame reception */
@@ -1232,40 +1476,31 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
hrtimer_setup(&op->thrtimer, bcm_rx_thr_handler, CLOCK_MONOTONIC,
HRTIMER_MODE_REL_SOFT);
- /* add this bcm_op to the list of the rx_ops */
- list_add(&op->list, &bo->rx_ops);
-
/* call can_rx_register() */
do_rx_register = 1;
+ new_op = 1;
} /* if ((op = bcm_find_op(&bo->rx_ops, msg_head->can_id, ifindex))) */
/* check flags */
if (op->flags & RX_RTR_FRAME) {
- struct canfd_frame *frame0 = op->frames;
-
/* no timers in RTR-mode */
hrtimer_cancel(&op->thrtimer);
hrtimer_cancel(&op->timer);
-
- /*
- * funny feature in RX(!)_SETUP only for RTR-mode:
- * copy can_id into frame BUT without RTR-flag to
- * prevent a full-load-loopback-test ... ;-]
- */
- if ((op->flags & TX_CP_CAN_ID) ||
- (frame0->can_id == op->can_id))
- frame0->can_id = op->can_id & ~CAN_RTR_FLAG;
-
} else {
if (op->flags & SETTIMER) {
- /* set timer value */
- op->ival1 = msg_head->ival1;
- op->ival2 = msg_head->ival2;
- op->kt_ival1 = bcm_timeval_to_ktime(msg_head->ival1);
- op->kt_ival2 = bcm_timeval_to_ktime(msg_head->ival2);
+ /* set timers (locked) for newly created op */
+ if (new_op) {
+ spin_lock_bh(&op->bcm_rx_update_lock);
+ op->ival1 = msg_head->ival1;
+ op->ival2 = msg_head->ival2;
+ op->kt_ival1 = bcm_timeval_to_ktime(msg_head->ival1);
+ op->kt_ival2 = bcm_timeval_to_ktime(msg_head->ival2);
+ op->kt_lastmsg = 0;
+ spin_unlock_bh(&op->bcm_rx_update_lock);
+ }
/* disable an active timer due to zero value? */
if (!op->kt_ival1)
@@ -1275,9 +1510,11 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
* In any case cancel the throttle timer, flush
* potentially blocked msgs and reset throttle handling
*/
- op->kt_lastmsg = 0;
hrtimer_cancel(&op->thrtimer);
+
+ spin_lock_bh(&op->bcm_rx_update_lock);
bcm_rx_thr_flush(op);
+ spin_unlock_bh(&op->bcm_rx_update_lock);
}
if ((op->flags & STARTTIMER) && op->kt_ival1)
@@ -1285,7 +1522,10 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
HRTIMER_MODE_REL_SOFT);
}
- /* now we can register for can_ids, if we added a new bcm_op */
+ /* now we can register for can_ids, if we added a new bcm_op
+ * or need to re-register after a NETDEV_UNREGISTER tore down
+ * the previous registration of an existing op
+ */
if (do_rx_register) {
if (ifindex) {
struct net_device *dev;
@@ -1298,23 +1538,62 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
bcm_rx_handler, op,
"bcm", sk);
- op->rx_reg_dev = dev;
+ /* keep a tracked reference so that a later
+ * unregister can safely reach the device even
+ * if a concurrent NETDEV_UNREGISTER has
+ * already unlisted it by ifindex
+ */
+ if (!err) {
+ op->rx_reg_dev = dev;
+ netdev_hold(dev,
+ &op->rx_reg_dev_tracker,
+ GFP_KERNEL);
+ }
dev_put(dev);
+ } else {
+ /* the requested device is gone - do not
+ * silently succeed without registering
+ */
+ err = -ENODEV;
}
- } else
+ } else {
err = can_rx_register(sock_net(sk), NULL, op->can_id,
REGMASK(op->can_id),
bcm_rx_handler, op, "bcm", sk);
+ }
+
if (err) {
- /* this bcm rx op is broken -> remove it */
- list_del_rcu(&op->list);
- bcm_remove_op(op);
+ /* newly created bcm rx op is broken -> remove it */
+ if (new_op) {
+ bcm_remove_op(op);
+ return err;
+ }
+
+ /* an existing op just stays unregistered.
+ * Cancel op->timer and (defensively) op->thrtimer.
+ * Other settings can't be reached until the next
+ * successful RX_SETUP.
+ */
+ hrtimer_cancel(&op->timer);
+ hrtimer_cancel(&op->thrtimer);
return err;
}
+
+ /* add a new bcm_op to the list of the rx_ops */
+ if (new_op)
+ list_add_rcu(&op->list, &bo->rx_ops);
}
return msg_head->nframes * op->cfsiz + MHSIZ;
+
+free_op:
+ if (op->frames != &op->sframe)
+ kfree(op->frames);
+ if (op->last_frames != &op->last_sframe)
+ kfree(op->last_frames);
+ kfree(op);
+ return err;
}
/*
@@ -1373,12 +1652,13 @@ static int bcm_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
{
struct sock *sk = sock->sk;
struct bcm_sock *bo = bcm_sk(sk);
- int ifindex = bo->ifindex; /* default ifindex for this bcm_op */
+ int ifindex;
struct bcm_msg_head msg_head;
int cfsiz;
int ret; /* read bytes or error codes as return value */
- if (!bo->bound)
+ /* Lockless fast-path check for bound socket */
+ if (!READ_ONCE(bo->bound))
return -ENOTCONN;
/* check for valid message length from userspace */
@@ -1394,17 +1674,38 @@ static int bcm_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
if ((size - MHSIZ) % cfsiz)
return -EINVAL;
+ lock_sock(sk);
+
+ /* Re-validate under the socket lock: a concurrent bcm_notify()
+ * may have unbound this socket (device removal) after the
+ * lockless fast-path check above. bo->ifindex is only ever
+ * mutated under lock_sock(), so reading it here - instead of
+ * before taking the lock - guarantees it can't be observed
+ * torn against bo->bound.
+ */
+ if (!bo->bound) {
+ ret = -ENOTCONN;
+ goto out_release;
+ }
+
+ /* default ifindex for this bcm_op */
+ ifindex = bo->ifindex;
+
/* check for alternative ifindex for this bcm_op */
if (!ifindex && msg->msg_name) {
/* no bound device as default => check msg_name */
DECLARE_SOCKADDR(struct sockaddr_can *, addr, msg->msg_name);
- if (msg->msg_namelen < BCM_MIN_NAMELEN)
- return -EINVAL;
+ if (msg->msg_namelen < BCM_MIN_NAMELEN) {
+ ret = -EINVAL;
+ goto out_release;
+ }
- if (addr->can_family != AF_CAN)
- return -EINVAL;
+ if (addr->can_family != AF_CAN) {
+ ret = -EINVAL;
+ goto out_release;
+ }
/* ifindex from sendto() */
ifindex = addr->can_ifindex;
@@ -1413,20 +1714,21 @@ static int bcm_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
struct net_device *dev;
dev = dev_get_by_index(sock_net(sk), ifindex);
- if (!dev)
- return -ENODEV;
+ if (!dev) {
+ ret = -ENODEV;
+ goto out_release;
+ }
if (dev->type != ARPHRD_CAN) {
dev_put(dev);
- return -ENODEV;
+ ret = -ENODEV;
+ goto out_release;
}
dev_put(dev);
}
}
- lock_sock(sk);
-
switch (msg_head.opcode) {
case TX_SETUP:
@@ -1476,6 +1778,7 @@ static int bcm_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
break;
}
+out_release:
release_sock(sk);
return ret;
@@ -1499,11 +1802,30 @@ static void bcm_notify(struct bcm_sock *bo, unsigned long msg,
case NETDEV_UNREGISTER:
lock_sock(sk);
- /* remove device specific receive entries */
- list_for_each_entry(op, &bo->rx_ops, list)
+ /* rx_ops: remove device specific receive entries */
+ list_for_each_entry(op, &bo->rx_ops, list) {
if (op->rx_reg_dev == dev)
bcm_rx_unreg(dev, op);
+ /* release an ANYDEV op's claim (see bcm_rx_handler())
+ * on this now confirmed-gone interface.
+ */
+ if (!op->ifindex) {
+ spin_lock_bh(&op->bcm_rx_update_lock);
+ if (op->if_detected == dev->ifindex)
+ op->if_detected = 0;
+ spin_unlock_bh(&op->bcm_rx_update_lock);
+ }
+ }
+
+ /* tx_ops: stop device specific cyclic transmissions on the
+ * vanishing ifindex. Cancelling the timer is enough to stop
+ * cyclic bcm_can_tx() calls as there is no re-arming.
+ */
+ list_for_each_entry(op, &bo->tx_ops, list)
+ if (op->ifindex == dev->ifindex)
+ hrtimer_cancel(&op->timer);
+
/* remove device reference, if this is our bound device */
if (bo->bound && bo->ifindex == dev->ifindex) {
#if IS_ENABLED(CONFIG_PROC_FS)
@@ -1512,7 +1834,12 @@ static void bcm_notify(struct bcm_sock *bo, unsigned long msg,
bo->bcm_proc_read = NULL;
}
#endif
- bo->bound = 0;
+ /* Paired with the lockless fast-path check in
+ * bcm_sendmsg(); bo->ifindex itself is only ever
+ * accessed under lock_sock() so it needs no
+ * annotation.
+ */
+ WRITE_ONCE(bo->bound, 0);
bo->ifindex = 0;
notify_enodev = 1;
}
@@ -1616,8 +1943,10 @@ static int bcm_release(struct socket *sock)
remove_proc_entry(bo->procname, net->can.bcmproc_dir);
#endif /* CONFIG_PROC_FS */
- list_for_each_entry_safe(op, next, &bo->tx_ops, list)
+ list_for_each_entry_safe(op, next, &bo->tx_ops, list) {
+ list_del_rcu(&op->list);
bcm_remove_op(op);
+ }
list_for_each_entry_safe(op, next, &bo->rx_ops, list) {
/*
@@ -1629,16 +1958,14 @@ static int bcm_release(struct socket *sock)
* 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.
*/
- if (op->rx_reg_dev) {
- struct net_device *dev;
-
- dev = dev_get_by_index(net, op->ifindex);
- if (dev) {
- bcm_rx_unreg(dev, op);
- dev_put(dev);
- }
- }
+ if (op->rx_reg_dev)
+ bcm_rx_unreg(op->rx_reg_dev, op);
} else
can_rx_unregister(net, NULL, op->can_id,
REGMASK(op->can_id),
@@ -1648,12 +1975,14 @@ static int bcm_release(struct socket *sock)
synchronize_rcu();
- list_for_each_entry_safe(op, next, &bo->rx_ops, list)
+ list_for_each_entry_safe(op, next, &bo->rx_ops, list) {
+ list_del_rcu(&op->list);
bcm_remove_op(op);
+ }
/* remove device reference */
if (bo->bound) {
- bo->bound = 0;
+ WRITE_ONCE(bo->bound, 0);
bo->ifindex = 0;
}
@@ -1723,7 +2052,10 @@ static int bcm_connect(struct socket *sock, struct sockaddr_unsized *uaddr, int
}
#endif /* CONFIG_PROC_FS */
- bo->bound = 1;
+ /* bo->ifindex above is fully assigned before this point; pairs
+ * with the lockless fast-path check in bcm_sendmsg()
+ */
+ WRITE_ONCE(bo->bound, 1);
fail:
release_sock(sk);
@@ -1839,11 +2171,15 @@ static int __init bcm_module_init(void)
{
int err;
+ bcm_wq = alloc_workqueue("can-bcm-wq", WQ_UNBOUND, 0);
+ if (!bcm_wq)
+ return -ENOMEM;
+
pr_info("can: broadcast manager protocol\n");
err = register_pernet_subsys(&canbcm_pernet_ops);
if (err)
- return err;
+ goto register_pernet_failed;
err = register_netdevice_notifier(&canbcm_notifier);
if (err)
@@ -1861,6 +2197,8 @@ static int __init bcm_module_init(void)
unregister_netdevice_notifier(&canbcm_notifier);
register_notifier_failed:
unregister_pernet_subsys(&canbcm_pernet_ops);
+register_pernet_failed:
+ destroy_workqueue(bcm_wq);
return err;
}
@@ -1869,6 +2207,8 @@ static void __exit bcm_module_exit(void)
can_proto_unregister(&bcm_can_proto);
unregister_netdevice_notifier(&canbcm_notifier);
unregister_pernet_subsys(&canbcm_pernet_ops);
+ rcu_barrier();
+ destroy_workqueue(bcm_wq);
}
module_init(bcm_module_init);
diff --git a/net/can/isotp.c b/net/can/isotp.c
index c48b4a818297e2..54becaf6898f13 100644
--- a/net/can/isotp.c
+++ b/net/can/isotp.c
@@ -152,11 +152,13 @@ struct isotp_sock {
struct sock sk;
int bound;
int ifindex;
+ struct net_device *dev;
+ netdevice_tracker dev_tracker;
canid_t txid;
canid_t rxid;
ktime_t tx_gap;
ktime_t lastrxcf_tstamp;
- struct hrtimer rxtimer, txtimer, txfrtimer;
+ struct hrtimer rxtimer, txtimer, txfrtimer, echotimer;
struct can_isotp_options opt;
struct can_isotp_fc_options rxfc, txfc;
struct can_isotp_ll_options ll;
@@ -164,6 +166,7 @@ struct isotp_sock {
u32 force_tx_stmin;
u32 force_rx_stmin;
u32 cfecho; /* consecutive frame echo tag */
+ u32 tx_gen; /* generation, bumped per new tx transfer */
struct tpcon rx, tx;
struct list_head notifier;
wait_queue_head_t wait;
@@ -376,6 +379,15 @@ static int isotp_rcv_fc(struct isotp_sock *so, struct canfd_frame *cf, int ae)
hrtimer_cancel(&so->txtimer);
+ /* isotp_tx_timeout() may have given up on this job while
+ * hrtimer_cancel() above waited for it to finish; so->rx_lock
+ * (held by our caller isotp_rcv()) rules out a concurrent claim,
+ * so a plain recheck is enough here.
+ */
+ if (so->tx.state != ISOTP_WAIT_FC &&
+ so->tx.state != ISOTP_WAIT_FIRST_FC)
+ return 1;
+
if ((cf->len < ae + FC_CONTENT_SZ) ||
((so->opt.flags & ISOTP_CHECK_PADDING) &&
check_pad(so, cf, ae + FC_CONTENT_SZ, so->opt.rxpad_content))) {
@@ -422,7 +434,7 @@ static int isotp_rcv_fc(struct isotp_sock *so, struct canfd_frame *cf, int ae)
so->tx.bs = 0;
so->tx.state = ISOTP_SENDING;
/* send CF frame and enable echo timeout handling */
- hrtimer_start(&so->txtimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0),
+ hrtimer_start(&so->echotimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0),
HRTIMER_MODE_REL_SOFT);
isotp_send_cframe(so);
break;
@@ -575,6 +587,14 @@ static int isotp_rcv_cf(struct sock *sk, struct canfd_frame *cf, int ae,
hrtimer_cancel(&so->rxtimer);
+ /* isotp_rx_timer_handler() may have raced us for so->rx.state
+ * while hrtimer_cancel() above waited for it to finish, already
+ * reporting ETIMEDOUT and resetting the reception; don't process
+ * this CF into a reassembly that has already been given up on.
+ */
+ if (so->rx.state != ISOTP_WAIT_DATA)
+ return 1;
+
/* CFs are never longer than the FF */
if (cf->len > so->rx.ll_dl)
return 1;
@@ -870,20 +890,36 @@ static void isotp_rcv_echo(struct sk_buff *skb, void *data)
struct canfd_frame *cf = (struct canfd_frame *)skb->data;
/* only handle my own local echo CF/SF skb's (no FF!) */
- if (skb->sk != sk || so->cfecho != *(u32 *)cf->data)
+ if (skb->sk != sk)
return;
+ /* unlike isotp_rcv_fc()/isotp_rcv_cf(), not already under so->rx_lock
+ * (no isotp_rcv() caller here), so take it ourselves
+ */
+ spin_lock(&so->rx_lock);
+
+ /* so->cfecho may since belong to a new transfer; recheck under lock */
+ if (so->cfecho != *(u32 *)cf->data)
+ goto out_unlock;
+
/* cancel local echo timeout */
- hrtimer_cancel(&so->txtimer);
+ hrtimer_cancel(&so->echotimer);
/* local echo skb with consecutive frame has been consumed */
so->cfecho = 0;
+ /* claiming a transfer also takes so->rx_lock, so a plain recheck
+ * is enough: so->tx.state can't have flipped to ISOTP_SENDING for
+ * a new claim while we're still in here
+ */
+ if (so->tx.state != ISOTP_SENDING)
+ goto out_unlock;
+
if (so->tx.idx >= so->tx.len) {
/* we are done */
so->tx.state = ISOTP_IDLE;
wake_up_interruptible(&so->wait);
- return;
+ goto out_unlock;
}
if (so->txfc.bs && so->tx.bs >= so->txfc.bs) {
@@ -891,53 +927,83 @@ static void isotp_rcv_echo(struct sk_buff *skb, void *data)
so->tx.state = ISOTP_WAIT_FC;
hrtimer_start(&so->txtimer, ktime_set(ISOTP_FC_TIMEOUT, 0),
HRTIMER_MODE_REL_SOFT);
- return;
+ goto out_unlock;
}
/* no gap between data frames needed => use burst mode */
if (!so->tx_gap) {
/* enable echo timeout handling */
- hrtimer_start(&so->txtimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0),
+ hrtimer_start(&so->echotimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0),
HRTIMER_MODE_REL_SOFT);
isotp_send_cframe(so);
- return;
+ goto out_unlock;
}
/* start timer to send next consecutive frame with correct delay */
hrtimer_start(&so->txfrtimer, so->tx_gap, HRTIMER_MODE_REL_SOFT);
+
+out_unlock:
+ spin_unlock(&so->rx_lock);
}
-static enum hrtimer_restart isotp_tx_timer_handler(struct hrtimer *hrtimer)
+/* shared by so->txtimer's and so->echotimer's callbacks. Both timers get
+ * cancelled under so->rx_lock elsewhere, so this must stay lock-free to
+ * avoid deadlocking with that; uses so->tx_gen instead to avoid tainting
+ * a new transfer with an error from the one that just timed out.
+ */
+static enum hrtimer_restart isotp_tx_timeout(struct isotp_sock *so)
{
- struct isotp_sock *so = container_of(hrtimer, struct isotp_sock,
- txtimer);
struct sock *sk = &so->sk;
+ u32 gen = READ_ONCE(so->tx_gen);
+ u32 old_state = READ_ONCE(so->tx.state);
/* don't handle timeouts in IDLE or SHUTDOWN state */
- if (so->tx.state == ISOTP_IDLE || so->tx.state == ISOTP_SHUTDOWN)
+ if (old_state == ISOTP_IDLE || old_state == ISOTP_SHUTDOWN)
+ return HRTIMER_NORESTART;
+
+ /* only claim the timeout if the state is still unchanged */
+ if (cmpxchg(&so->tx.state, old_state, ISOTP_IDLE) != old_state)
return HRTIMER_NORESTART;
/* we did not get any flow control or echo frame in time */
- /* report 'communication error on send' */
- sk->sk_err = ECOMM;
- if (!sock_flag(sk, SOCK_DEAD))
- sk_error_report(sk);
+ if (READ_ONCE(so->tx_gen) == gen) {
+ /* report 'communication error on send' */
+ sk->sk_err = ECOMM;
+ if (!sock_flag(sk, SOCK_DEAD))
+ sk_error_report(sk);
+ }
- /* reset tx state */
- so->tx.state = ISOTP_IDLE;
wake_up_interruptible(&so->wait);
return HRTIMER_NORESTART;
}
+/* so->txtimer: fires when a Flow Control frame does not arrive in time */
+static enum hrtimer_restart isotp_tx_timer_handler(struct hrtimer *hrtimer)
+{
+ struct isotp_sock *so = container_of(hrtimer, struct isotp_sock,
+ txtimer);
+
+ return isotp_tx_timeout(so);
+}
+
+/* so->echotimer: fires when a sent CF/SF's local echo does not arrive */
+static enum hrtimer_restart isotp_echo_timer_handler(struct hrtimer *hrtimer)
+{
+ struct isotp_sock *so = container_of(hrtimer, struct isotp_sock,
+ echotimer);
+
+ return isotp_tx_timeout(so);
+}
+
static enum hrtimer_restart isotp_txfr_timer_handler(struct hrtimer *hrtimer)
{
struct isotp_sock *so = container_of(hrtimer, struct isotp_sock,
txfrtimer);
/* start echo timeout handling and cover below protocol error */
- hrtimer_start(&so->txtimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0),
+ hrtimer_start(&so->echotimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0),
HRTIMER_MODE_REL_SOFT);
/* cfecho should be consumed by isotp_rcv_echo() here */
@@ -958,13 +1024,24 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
int ae = (so->opt.flags & CAN_ISOTP_EXTEND_ADDR) ? 1 : 0;
int wait_tx_done = (so->opt.flags & CAN_ISOTP_WAIT_TX_DONE) ? 1 : 0;
s64 hrtimer_sec = ISOTP_ECHO_TIMEOUT;
+ struct hrtimer *tx_hrt = &so->echotimer;
+ u32 new_state = ISOTP_SENDING;
int off;
int err;
if (!so->bound || so->tx.state == ISOTP_SHUTDOWN)
return -EADDRNOTAVAIL;
- while (cmpxchg(&so->tx.state, ISOTP_IDLE, ISOTP_SENDING) != ISOTP_IDLE) {
+ /* claim the socket under so->rx_lock: this serializes the claim
+ * with the RX path and with sendmsg()'s own error paths below, so
+ * none of them can ever see a transfer mid-claim
+ */
+ for (;;) {
+ spin_lock_bh(&so->rx_lock);
+ if (READ_ONCE(so->tx.state) == ISOTP_IDLE)
+ break;
+ spin_unlock_bh(&so->rx_lock);
+
/* we do not support multiple buffers - for now */
if (msg->msg_flags & MSG_DONTWAIT)
return -EAGAIN;
@@ -973,9 +1050,29 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
return -EADDRNOTAVAIL;
/* wait for complete transmission of current pdu */
- err = wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE);
+ err = wait_event_interruptible(so->wait,
+ so->tx.state == ISOTP_IDLE);
if (err)
- goto err_event_drop;
+ return err;
+ }
+
+ /* new transfer: bump so->tx_gen and drain the old one's timers,
+ * still under the so->rx_lock we just claimed the socket with
+ */
+ WRITE_ONCE(so->tx.state, ISOTP_SENDING);
+ WRITE_ONCE(so->tx_gen, READ_ONCE(so->tx_gen) + 1);
+ hrtimer_cancel(&so->txtimer);
+ hrtimer_cancel(&so->echotimer);
+ hrtimer_cancel(&so->txfrtimer);
+ so->cfecho = 0;
+ spin_unlock_bh(&so->rx_lock);
+
+ /* so->bound is only checked once above - a wakeup may have
+ * unbound/rebound the socket meanwhile, so re-validate it
+ */
+ if (!so->bound) {
+ err = -EADDRNOTAVAIL;
+ goto err_out_drop;
}
/* PDU size > default => try max_pdu_size */
@@ -1086,18 +1183,33 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
so->cfecho = *(u32 *)cf->data;
} else {
/* standard flow control check */
- so->tx.state = ISOTP_WAIT_FIRST_FC;
+ new_state = ISOTP_WAIT_FIRST_FC;
/* start timeout for FC */
hrtimer_sec = ISOTP_FC_TIMEOUT;
+ tx_hrt = &so->txtimer;
/* no CF echo tag for isotp_rcv_echo() (FF-mode) */
so->cfecho = 0;
}
}
- hrtimer_start(&so->txtimer, ktime_set(hrtimer_sec, 0),
+ spin_lock_bh(&so->rx_lock);
+ if (so->tx.state == ISOTP_SHUTDOWN) {
+ /* isotp_release() has since taken over and already drained
+ * our timers - don't send into a socket that's going away
+ */
+ spin_unlock_bh(&so->rx_lock);
+ kfree_skb(skb);
+ dev_put(dev);
+ wake_up_interruptible(&so->wait);
+ return -EADDRNOTAVAIL;
+ }
+ /* WAIT_FIRST_FC for standard FF, else stays ISOTP_SENDING */
+ so->tx.state = new_state;
+ hrtimer_start(tx_hrt, ktime_set(hrtimer_sec, 0),
HRTIMER_MODE_REL_SOFT);
+ spin_unlock_bh(&so->rx_lock);
/* send the first or only CAN frame */
cf->flags = so->ll.tx_flags;
@@ -1110,13 +1222,10 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
pr_notice_once("can-isotp: %s: can_send_ret %pe\n",
__func__, ERR_PTR(err));
+ spin_lock_bh(&so->rx_lock);
/* no transmission -> no timeout monitoring */
- hrtimer_cancel(&so->txtimer);
-
- /* reset consecutive frame echo tag */
- so->cfecho = 0;
-
- goto err_out_drop;
+ hrtimer_cancel(tx_hrt);
+ goto err_out_drop_locked;
}
if (wait_tx_done) {
@@ -1132,14 +1241,21 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
return size;
+err_out_drop:
+ /* claimed but nothing sent yet - no timer to cancel */
+ spin_lock_bh(&so->rx_lock);
+ goto err_out_drop_locked;
err_event_drop:
- /* got signal: force tx state machine to be idle */
- so->tx.state = ISOTP_IDLE;
+ /* interrupted waiting on our own transfer - drain its timers */
+ spin_lock_bh(&so->rx_lock);
hrtimer_cancel(&so->txfrtimer);
hrtimer_cancel(&so->txtimer);
-err_out_drop:
- /* drop this PDU and unlock a potential wait queue */
+ hrtimer_cancel(&so->echotimer);
+err_out_drop_locked:
+ /* release the claim; so->rx_lock still held from above */
+ so->cfecho = 0;
so->tx.state = ISOTP_IDLE;
+ spin_unlock_bh(&so->rx_lock);
wake_up_interruptible(&so->wait);
return err;
@@ -1201,13 +1317,20 @@ static int isotp_release(struct socket *sock)
so = isotp_sk(sk);
net = sock_net(sk);
- /* wait for complete transmission of current pdu */
- while (wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE) == 0 &&
- cmpxchg(&so->tx.state, ISOTP_IDLE, ISOTP_SHUTDOWN) != ISOTP_IDLE)
+ /* best-effort: wait for a running pdu to finish, but don't block on
+ * it forever - give up after the first signal
+ */
+ while (so->tx.state != ISOTP_IDLE &&
+ wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE) == 0)
;
- /* force state machines to be idle also when a signal occurred */
+ /* claim the socket under so->rx_lock like sendmsg() does, so its
+ * claim can't race the forced ISOTP_SHUTDOWN below; force it
+ * unconditionally, even when a signal cut the wait above short
+ */
+ spin_lock_bh(&so->rx_lock);
so->tx.state = ISOTP_SHUTDOWN;
+ spin_unlock_bh(&so->rx_lock);
so->rx.state = ISOTP_IDLE;
spin_lock(&isotp_notifier_lock);
@@ -1219,36 +1342,43 @@ 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 */
- if (so->bound) {
- if (so->ifindex) {
- struct net_device *dev;
-
- dev = dev_get_by_index(net, so->ifindex);
- if (dev) {
- if (isotp_register_rxid(so))
- can_rx_unregister(net, dev, so->rxid,
- SINGLE_MASK(so->rxid),
- isotp_rcv, sk);
-
- can_rx_unregister(net, dev, so->txid,
- SINGLE_MASK(so->txid),
- isotp_rcv_echo, sk);
- dev_put(dev);
- synchronize_rcu();
- }
- }
+ /* remove current filters & unregister
+ * tracked reference so->dev is taken at bind() time with rtnl_lock
+ */
+ if (so->bound && so->dev) {
+ if (isotp_register_rxid(so))
+ can_rx_unregister(net, so->dev, so->rxid,
+ SINGLE_MASK(so->rxid),
+ isotp_rcv, sk);
+
+ can_rx_unregister(net, so->dev, so->txid,
+ SINGLE_MASK(so->txid),
+ isotp_rcv_echo, sk);
+ netdev_put(so->dev, &so->dev_tracker);
}
+ so->ifindex = 0;
+ 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
+ * for in-flight isotp_rcv() callers to finish, so this call must not
+ * be skipped just because so->bound is already 0 here.
+ */
+ synchronize_rcu();
+
hrtimer_cancel(&so->txfrtimer);
hrtimer_cancel(&so->txtimer);
+ hrtimer_cancel(&so->echotimer);
hrtimer_cancel(&so->rxtimer);
- so->ifindex = 0;
- so->bound = 0;
-
sock_orphan(sk);
sock->sk = NULL;
@@ -1303,6 +1433,7 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l
if (!addr->can_ifindex)
return -ENODEV;
+ rtnl_lock();
lock_sock(sk);
if (so->bound) {
@@ -1310,6 +1441,17 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l
goto out;
}
+ /* A transmission or reception that outlived a previous binding
+ * (unbound by NETDEV_UNREGISTER) may still be draining; the FC/echo
+ * and RX watchdog timers bound how long this takes. Checked together
+ * with so->bound in the same lock_sock() section above, so there is
+ * no window in which a concurrent isotp_notify() could be missed.
+ */
+ if (so->tx.state != ISOTP_IDLE || so->rx.state != ISOTP_IDLE) {
+ err = -EAGAIN;
+ goto out;
+ }
+
/* ensure different CAN IDs when the rx_id is to be registered */
if (isotp_register_rxid(so) && rx_id == tx_id) {
err = -EADDRNOTAVAIL;
@@ -1322,14 +1464,12 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l
goto out;
}
if (dev->type != ARPHRD_CAN) {
- dev_put(dev);
err = -ENODEV;
- goto out;
+ goto out_put_dev;
}
if (READ_ONCE(dev->mtu) < so->ll.mtu) {
- dev_put(dev);
err = -EINVAL;
- goto out;
+ goto out_put_dev;
}
if (!(dev->flags & IFF_UP))
notify_enetdown = 1;
@@ -1347,16 +1487,25 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l
can_rx_register(net, dev, tx_id, SINGLE_MASK(tx_id),
isotp_rcv_echo, sk, "isotpe", sk);
- dev_put(dev);
-
/* 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()
+ * can safely reach the device later, even if a concurrent
+ * NETDEV_UNREGISTER has already unlisted it by ifindex.
+ */
+ so->dev = dev;
+ netdev_hold(so->dev, &so->dev_tracker, GFP_KERNEL);
+
+out_put_dev:
+ /* remove potential reference from dev_get_by_index() */
+ dev_put(dev);
out:
release_sock(sk);
+ rtnl_unlock();
if (notify_enetdown) {
sk->sk_err = ENETDOWN;
@@ -1559,7 +1708,7 @@ static void isotp_notify(struct isotp_sock *so, unsigned long msg,
if (!net_eq(dev_net(dev), sock_net(sk)))
return;
- if (so->ifindex != dev->ifindex)
+ if (so->dev != dev)
return;
switch (msg) {
@@ -1575,10 +1724,12 @@ static void isotp_notify(struct isotp_sock *so, unsigned long msg,
can_rx_unregister(dev_net(dev), dev, so->txid,
SINGLE_MASK(so->txid),
isotp_rcv_echo, sk);
+ netdev_put(so->dev, &so->dev_tracker);
}
so->ifindex = 0;
so->bound = 0;
+ so->dev = NULL;
release_sock(sk);
sk->sk_err = ENODEV;
@@ -1638,6 +1789,7 @@ static int isotp_init(struct sock *sk)
so->ifindex = 0;
so->bound = 0;
+ so->dev = NULL;
so->opt.flags = CAN_ISOTP_DEFAULT_FLAGS;
so->opt.ext_address = CAN_ISOTP_DEFAULT_EXT_ADDRESS;
@@ -1664,10 +1816,14 @@ static int isotp_init(struct sock *sk)
so->rx.buflen = ARRAY_SIZE(so->rx.sbuf);
so->tx.buflen = ARRAY_SIZE(so->tx.sbuf);
- hrtimer_setup(&so->rxtimer, isotp_rx_timer_handler, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT);
- hrtimer_setup(&so->txtimer, isotp_tx_timer_handler, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT);
- hrtimer_setup(&so->txfrtimer, isotp_txfr_timer_handler, CLOCK_MONOTONIC,
- HRTIMER_MODE_REL_SOFT);
+ hrtimer_setup(&so->rxtimer, isotp_rx_timer_handler,
+ CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT);
+ hrtimer_setup(&so->txtimer, isotp_tx_timer_handler,
+ CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT);
+ hrtimer_setup(&so->echotimer, isotp_echo_timer_handler,
+ CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT);
+ hrtimer_setup(&so->txfrtimer, isotp_txfr_timer_handler,
+ CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT);
init_waitqueue_head(&so->wait);
spin_lock_init(&so->rx_lock);
diff --git a/net/can/j1939/transport.c b/net/can/j1939/transport.c
index df93d57907da7e..8a31cb23bc76d0 100644
--- a/net/can/j1939/transport.c
+++ b/net/can/j1939/transport.c
@@ -351,6 +351,18 @@ static void j1939_session_skb_drop_old(struct j1939_session *session)
}
}
+static bool j1939_address_is_local(struct j1939_priv *priv, u8 addr)
+{
+ bool local = false;
+
+ read_lock_bh(&priv->lock);
+ if (j1939_address_is_unicast(addr) && priv->ents[addr].nusers)
+ local = true;
+ read_unlock_bh(&priv->lock);
+
+ return local;
+}
+
void j1939_session_skb_queue(struct j1939_session *session,
struct sk_buff *skb)
{
@@ -359,8 +371,7 @@ void j1939_session_skb_queue(struct j1939_session *session,
j1939_ac_fixup(priv, skb);
- if (j1939_address_is_unicast(skcb->addr.da) &&
- priv->ents[skcb->addr.da].nusers)
+ if (j1939_address_is_local(priv, skcb->addr.da))
skcb->flags |= J1939_ECU_LOCAL_DST;
skcb->flags |= J1939_ECU_LOCAL_SRC;
@@ -2038,8 +2049,7 @@ struct j1939_session *j1939_tp_send(struct j1939_priv *priv,
return ERR_PTR(ret);
/* fix DST flags, it may be used there soon */
- if (j1939_address_is_unicast(skcb->addr.da) &&
- priv->ents[skcb->addr.da].nusers)
+ if (j1939_address_is_local(priv, skcb->addr.da))
skcb->flags |= J1939_ECU_LOCAL_DST;
/* src is always local, I'm sending ... */
diff --git a/net/can/raw.c b/net/can/raw.c
index a26942e78e6887..82d9c0499c95fa 100644
--- a/net/can/raw.c
+++ b/net/can/raw.c
@@ -562,8 +562,8 @@ static int raw_getname(struct socket *sock, struct sockaddr *uaddr,
return RAW_MIN_NAMELEN;
}
-static int raw_setsockopt(struct socket *sock, int level, int optname,
- sockptr_t optval, unsigned int optlen)
+static int raw_setsockopt_locked(struct socket *sock, int optname,
+ sockptr_t optval, unsigned int optlen)
{
struct sock *sk = sock->sk;
struct raw_sock *ro = raw_sk(sk);
@@ -575,9 +575,6 @@ static int raw_setsockopt(struct socket *sock, int level, int optname,
int flag;
int err = 0;
- if (level != SOL_CAN_RAW)
- return -EINVAL;
-
switch (optname) {
case CAN_RAW_FILTER:
if (optlen % sizeof(struct can_filter) != 0)
@@ -598,17 +595,11 @@ static int raw_setsockopt(struct socket *sock, int level, int optname,
return -EFAULT;
}
- rtnl_lock();
- lock_sock(sk);
-
dev = ro->dev;
- if (ro->bound && dev) {
- if (dev->reg_state != NETREG_REGISTERED) {
- if (count > 1)
- kfree(filter);
- err = -ENODEV;
- goto out_fil;
- }
+ if (ro->bound && dev && dev->reg_state != NETREG_REGISTERED) {
+ if (count > 1)
+ kfree(filter);
+ return -ENODEV;
}
if (ro->bound) {
@@ -622,7 +613,7 @@ static int raw_setsockopt(struct socket *sock, int level, int optname,
if (err) {
if (count > 1)
kfree(filter);
- goto out_fil;
+ return err;
}
/* remove old filter registrations */
@@ -642,11 +633,6 @@ static int raw_setsockopt(struct socket *sock, int level, int optname,
}
ro->filter = filter;
ro->count = count;
-
- out_fil:
- release_sock(sk);
- rtnl_unlock();
-
break;
case CAN_RAW_ERR_FILTER:
@@ -658,16 +644,9 @@ static int raw_setsockopt(struct socket *sock, int level, int optname,
err_mask &= CAN_ERR_MASK;
- rtnl_lock();
- lock_sock(sk);
-
dev = ro->dev;
- if (ro->bound && dev) {
- if (dev->reg_state != NETREG_REGISTERED) {
- err = -ENODEV;
- goto out_err;
- }
- }
+ if (ro->bound && dev && dev->reg_state != NETREG_REGISTERED)
+ return -ENODEV;
/* remove current error mask */
if (ro->bound) {
@@ -676,7 +655,7 @@ static int raw_setsockopt(struct socket *sock, int level, int optname,
err_mask);
if (err)
- goto out_err;
+ return err;
/* remove old err_mask registration */
raw_disable_errfilter(sock_net(sk), dev, sk,
@@ -685,11 +664,6 @@ static int raw_setsockopt(struct socket *sock, int level, int optname,
/* link new err_mask to the socket */
ro->err_mask = err_mask;
-
- out_err:
- release_sock(sk);
- rtnl_unlock();
-
break;
case CAN_RAW_LOOPBACK:
@@ -769,6 +743,26 @@ static int raw_setsockopt(struct socket *sock, int level, int optname,
return err;
}
+static int raw_setsockopt(struct socket *sock, int level, int optname,
+ sockptr_t optval, unsigned int optlen)
+{
+ struct sock *sk = sock->sk;
+ int err;
+
+ 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;
+}
+
static int raw_getsockopt(struct socket *sock, int level, int optname,
sockopt_t *opt)
{
|
| 3/2 | 2026/07/16 21:21 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 30341 output: 19 thoughts: 1231 Thoughts: **CAN Subsystem Patch Series Review** Okay, so I'm looking at this patch series modifying the CAN subsystem. Seems like a mixed bag of updates here. We have author email updates scattered across several files in `drivers/net/can/` and `include/linux/can/dev/`, which are probably low-impact, more of a housekeeping thing. The `drivers/net/can/Kconfig` update for the `vxcan` description is also likely straightforward. The real meat of this is in the functional changes. The fixes in `drivers/net/can/usb/esd_usb.c` (specifically the disconnect function) are important since they fix a memory leak. However, the heavy lifting and focus for fuzzing will be in these files: `net/can/bcm.c`, `net/can/isotp.c`, `net/can/j1939/transport.c`, and `net/can/raw.c`. These have significant rework related to timers and locking, so they need a good going-over. Specifically, in `net/can/bcm.c`, I'll want to focus on: `bcm_tx_setup`, `bcm_rx_setup`, `bcm_sendmsg`, `bcm_release`, `bcm_tx_timeout_handler`, `bcm_rx_timeout_handler`, and `bcm_rx_handler`. That should cover the core TX/RX paths and error handling. In `net/can/isotp.c`, the key functions to target are: `isotp_sendmsg`, `isotp_bind`, `isotp_release`, `isotp_rcv_echo`, `isotp_rcv_fc`, `isotp_rcv_cf`, and `isotp_tx_timeout`. These functions are probably the points where the most data is handled, and failures can be catastrophic. For `net/can/j1939/transport.c`, I'll have to investigate `j1939_session_skb_queue` and `j1939_tp_send` - they seem like the crucial transport layer functions. Finally, `net/can/raw.c`, and the focus there should be on `raw_setsockopt`. That function likely handles a lot of socket-level configuration and is a common attack vector for these protocols, as it can be used to set the parameters for future socket traffic. Before I kick off fuzzing, I'll double-check the Kconfig options to confirm that I've enabled the relevant modules: `CAN_BCM`, `CAN_ISOTP`, `CAN_J1939`, `CAN_RAW`, and `CAN_ESD_USB` respectively, just to be sure I can hit all the code. I need to make sure I have a good coverage strategy in place before I start. |
| 4/2 | 2026/07/16 21:21 | tool | grepper |
0mArgs:{
"Expression": "config CAN_BCM"
}Results: {
"Output": "net/can/Kconfig=24=config CAN_RAW\n--\nnet/can/Kconfig-33-\nnet/can/Kconfig:34:config CAN_BCM\nnet/can/Kconfig-35-\ttristate \"Broadcast Manager CAN Protocol (with content filtering)\"\n"
} |
| 5/2 | 2026/07/16 21:21 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 32298 output: 19 thoughts: 28 Thoughts: Okay, here's my interpretation of those CAN protocols, expressed as if they're my internal thoughts, tailored for an expert audience: **CAN Protocol Landscape - A Quick Mental Rundown** Right, let's see... *CAN_BCM* – that immediately brings to mind the CAN Bus Cyclic Management protocol. It's the go-to when you need to handle periodic data transmissions, isn't it? Very efficient for those heartbeat messages and regularly updated sensor values. I'm thinking about those configuration options, the different message types, and the overall reliability it brings to real-time systems. Definitely a solid foundation for many automotive and industrial applications. *CAN_ISOTP* – Ah, ISO 15765-2. That's our data-link layer. It's the transport layer for those CAN applications. It's for segmenting large data frames. It is all about dealing with those multi-frame messages and ensuring reliable delivery across the network. The overhead can be a consideration, especially with real-time requirements, but the error handling capabilities are invaluable. I'm thinking of how it's used for diagnostics and flashing ECUs. *CAN_J1939* – Okay, that's SAE J1939, of course. The standard for heavy-duty vehicles and off-highway equipment. It's a complete protocol stack, isn't it? From the physical layer all the way up to application-level parameters. It defines the SPNs, PGNs, and all the rest. I'm immediately picturing the complexity, the sheer volume of data, and the need for rigorous implementation and compliance. It's a robust solution, but it definitely needs careful design and testing. *CAN_RAW* – The simplest of the bunch. Just raw CAN frames with no defined structure. It's like working directly with the bits and bytes. It requires the most intimate knowledge of the CAN bus and the payload interpretation. It can be useful for very specific, low-level control or for performance-critical applications where you need full control over every bit. But you've got to be extremely careful. *CAN_ESD_USB* – And finally, CAN-over-USB with ESD protection. That's a practical consideration. It means interfacing the CAN bus with a host computer, likely for data logging, diagnostics, or development. The ESD is critical, though, to prevent damage to the interface. I'm picturing the hardware aspects: the transceivers, the isolation, the data rate constraints and the associated drivers. |
| 6/2 | 2026/07/16 21:21 | tool | grepper |
0mArgs:{
"Expression": "config CAN_ISOTP"
}Results: {
"Output": "net/can/Kconfig=57=source \"net/can/j1939/Kconfig\"\nnet/can/Kconfig-58-\nnet/can/Kconfig:59:config CAN_ISOTP\nnet/can/Kconfig-60-\ttristate \"ISO 15765-2 CAN transport protocol\"\n"
} |
| 7/2 | 2026/07/16 21:21 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 32981 output: 22 thoughts: 7 Thoughts: Okay, here's my attempt at summarizing the thought process, tailored for an expert audience, and written in the first person: **CAN_J1939: Analyzing the Protocol's Nuances** Right, let's break this down. J1939. We're talking heavy-duty, commercial vehicle communications. First, I'm thinking about the inherent complexities. It's CAN, so the fundamentals are clear – collision detection, arbitration, all that good stuff. But J1939 layers on the application-level protocols, and that's where things get interesting, and potentially messy. I'm immediately running through the parameter groups (PGs). What specific PGs are relevant to the use-case? Engine data? Transmission? Brake systems? Knowing which PGs are in play defines everything from the data rate to the necessary filters. Then, there's the addressing. Physical, functional...which is it? Understanding the source address and destination address is key to isolating the information. The DM (diagnostic message) side of J1939 is particularly crucial. I'm considering the active diagnostics, the suspected faults. I'd be looking for diagnostic trouble codes (DTCs), obviously. That's the baseline. However, I'm also thinking about interpreting those DTCs in context. What is the history? What other PGs are correlated? And let's not forget the transport protocols. BAM (Broadcast Announce Message), RTS/CTS (Request to Send/Clear to Send), connection management. These are critical for managing large data transfers. What's the protocol handling for data that exceeds the eight-byte frame limit? I'd want to ensure that proper transport layer is implemented to guarantee delivery. Finally, the nuances of the physical layer are key. The bitrate – 250 kbps, 500 kbps? And the termination. Have they got it right? Too much or too little, and you're chasing ghosts. This whole process is all about isolating and validating assumptions; building on a comprehensive knowledge of the standard. |
| 8/2 | 2026/07/16 21:22 | tool | grepper |
0mArgs:{
"Expression": "config CAN_J1939"
}Results: {
"Output": "net/can/j1939/Kconfig-5-\nnet/can/j1939/Kconfig:6:config CAN_J1939\nnet/can/j1939/Kconfig-7-\ttristate \"SAE J1939\"\n"
} |
| 9/2 | 2026/07/16 21:22 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 33530 output: 18 thoughts: 3 Thoughts: Okay, I'm ready. Here's my attempt to summarize the "Thoughts" in the first person, assuming a high level of expertise in the field and responding as a fulsome thought process: **Analyzing the Raw Data** Okay, here's what I'm seeing: "CAN_RAW". That's it. Just... raw CAN bus data. No context, no labels, no indication of what the data represents or the system it's coming from. My initial reaction is a slight frustration, because without more information, this is basically just noise. Immediately, my mind starts running through potential scenarios. Is this a snippet I've seen before, can I derive anything from its cadence? Is it complete, or is more data on the way? I need to start asking questions to build a knowledge base; what's the expected bit rate? Are we looking at standard or extended frames? What IDs are potentially included? Are there any known patterns or typical signals associated with the target system? My experience tells me that without any of this basic information, it's like trying to diagnose a patient with a single, unlabeled vital sign. This "CAN_RAW" format requires me to consider what could have generated it. This is like reverse engineering: What tools were used to collect this? I need to mentally construct the data acquisition process to determine where I can get additional context. If this has come from a capture, I need to know about the instrumentation, the triggering mechanism, and any pre-filtering that might have occurred. Is it a live feed, or is it a dump? Is there a timestamp? Without knowing, I'm almost blind. My expert instinct is to start mentally decoding possibilities. I need to be more resourceful; I need to look for any hidden clues. Is the data length unusual? What can I derive if I can correlate this "CAN_RAW" data with my other knowledge of other bus traffic? So, this is a puzzle. It's a starting point and a challenge; I need to start interrogating the data with every tool in my arsenal to provide any actionable conclusions. |
| 10/2 | 2026/07/16 21:22 | tool | grepper |
0mArgs:{
"Expression": "config CAN_RAW"
}Results: {
"Output": "net/can/Kconfig=22=if CAN\nnet/can/Kconfig-23-\nnet/can/Kconfig:24:config CAN_RAW\nnet/can/Kconfig-25-\ttristate \"Raw CAN Protocol (raw access with CAN-ID filtering)\"\n"
} |
| 11/2 | 2026/07/16 21:22 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 34069 output: 20 thoughts: 5 Thoughts: Okay, here's my attempt at summarizing the thought process, tailored for an expert audience, and written in the first person: **CAN-ESD-USB Considerations: A Deep Dive** Alright, let's break this down. When we're talking about CAN, ESD, and USB, the potential issues are numerous and the devil is definitely in the details. First, the CAN bus itself. We're dealing with differential signaling, which is good, but that doesn't make it immune to ESD events. A direct strike, or even a close proximity discharge, can induce transients that wreak havoc on the transceiver, potentially taking out the entire node. We need to think about robust protection. That means considering TVS diodes, potentially gas discharge tubes (GDTs) for the really nasty stuff, and maybe even some clever filtering on the CANH and CANL lines. Then there's the USB connection. USB ports, especially those exposed externally, are ESD magnets. We're talking about various voltage levels here, and those can propagate through the USB interface to our CAN interface chip. We need to isolate the USB and CAN domains to prevent those transients from getting into the CAN side of things. This usually involves a digital isolator, something like an isolated CAN transceiver with a built-in DC-DC converter, or a separate isolated power supply. The isolation needs to be robust, capable of withstanding the expected ESD voltage levels and any common-mode transients. Let's not forget about grounding. Proper grounding practices are paramount. We need a well-defined ground plane on the PCB and careful consideration of grounding strategies for both the USB and CAN sides. A single-point ground, or a multi-point ground, or a split ground could be the best solution for different applications. This depends heavily on the specific system's architecture, chassis, and external connections. We'll also need to consider the cable shield for the USB, and how that is connected to the ground. That's a crucial consideration. Furthermore, we need to consider the data rates, cable lengths, and the characteristics of the bus termination. Incorrect termination or impedance mismatches exacerbate ESD problems. Proper impedance matching and termination are going to be key, and we have to think about the parasitic capacitance and inductance of the protection components. Ultimately, we have to look at the overall system-level design. We need to run simulation tools to assess the impact of these ESD events, create prototypes, and conduct rigorous ESD testing. We'll need to develop a test plan that includes IEC 61000-4-2 (ESD) and maybe even some EFT testing. This all comes down to robust design and comprehensive validation, making sure we have the required reliability for our application. |
| 12/2 | 2026/07/16 21:22 | tool | grepper |
0mArgs:{
"Expression": "config CAN_ESD_USB"
}Results: {
"Output": "drivers/net/can/usb/Kconfig=11=config CAN_EMS_USB\n--\ndrivers/net/can/usb/Kconfig-16-\ndrivers/net/can/usb/Kconfig:17:config CAN_ESD_USB\ndrivers/net/can/usb/Kconfig-18-\ttristate \"esd electronics gmbh CAN/USB interfaces\"\n"
} |
| 13/2 | 2026/07/16 21:22 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 34762 output: 326 thoughts: 339 Thoughts: **Analyzing the Impact of Recent Changes** Okay, so I'm diving into these changes in `bcm.c` and `isotp.c`, and honestly, it's a bit of a beast. This is a significant refactor; there's a lot going on with the locking mechanisms, timer management, and state machine transitions. It's time to zero in on the critical pieces. I've initially identified a rather comprehensive list of functions, but it's probably too much to handle at once, so I'll refine this list to stay focused. After some consideration, I'm going to concentrate my attention on: `bcm_tx_setup`, `bcm_rx_setup`, `bcm_sendmsg`, `bcm_release`, `isotp_sendmsg`, `isotp_bind`, `isotp_release`, `raw_setsockopt`, and `j1939_tp_send`. These seem to be the core entry points and resource management points. Also, I should keep a close watch on the timeout handlers, they're always trouble spots: `bcm_tx_timeout_handler`, `bcm_rx_timeout_handler`, and `isotp_tx_timeout`. They're probably where the most interesting concurrency and error handling issues will surface. With this narrowed focus, I have a much clearer direction for my analysis. It's time to set up my test cases and see exactly how these changes ripple through the system. |
| 14/2 | 2026/07/16 21:22 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"CAN_BCM",
"CAN_ISOTP",
"CAN_J1939",
"CAN_RAW",
"CAN_ESD_USB"
],
"FocusSymbols": [
"bcm_tx_setup",
"bcm_rx_setup",
"bcm_sendmsg",
"bcm_release",
"bcm_tx_timeout_handler",
"bcm_rx_timeout_handler",
"isotp_sendmsg",
"isotp_bind",
"isotp_release",
"isotp_tx_timeout",
"isotp_rcv_echo",
"isotp_rcv_fc",
"isotp_rcv_cf",
"j1939_tp_send",
"raw_setsockopt",
"esd_usb_disconnect"
],
"Reasoning": "The patch series makes significant functional changes to the CAN subsystem, particularly in the BCM and ISOTP protocols. It introduces major reworks of timers, locking, and state machines in `net/can/bcm.c` and `net/can/isotp.c`. It also fixes a bug in J1939 transport, changes locking in `raw_setsockopt`, and fixes a memory leak in the `esd_usb` driver's disconnect path. These changes are highly reachable from userspace and should be fuzzed.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"CAN_BCM",
"CAN_ISOTP",
"CAN_J1939",
"CAN_RAW",
"CAN_ESD_USB"
],
"FocusSymbols": [
"bcm_tx_setup",
"bcm_rx_setup",
"bcm_sendmsg",
"bcm_release",
"bcm_tx_timeout_handler",
"bcm_rx_timeout_handler",
"isotp_sendmsg",
"isotp_bind",
"isotp_release",
"isotp_tx_timeout",
"isotp_rcv_echo",
"isotp_rcv_fc",
"isotp_rcv_cf",
"j1939_tp_send",
"raw_setsockopt",
"esd_usb_disconnect"
],
"Reasoning": "The patch series makes significant functional changes to the CAN subsystem, particularly in the BCM and ISOTP protocols. It introduces major reworks of timers, locking, and state machines in `net/can/bcm.c` and `net/can/isotp.c`. It also fixes a bug in J1939 transport, changes locking in `raw_setsockopt`, and fixes a memory leak in the `esd_usb` driver's disconnect path. These changes are highly reachable from userspace and should be fuzzed.",
"WorthFuzzing": true
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|