]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/commitdiff
Fixes for all trees master
authorSasha Levin <sashal@kernel.org>
Wed, 29 Jul 2026 03:17:49 +0000 (23:17 -0400)
committerSasha Levin <sashal@kernel.org>
Wed, 29 Jul 2026 03:17:49 +0000 (23:17 -0400)
Signed-off-by: Sasha Levin <sashal@kernel.org>
20 files changed:
queue-5.10/can-bcm-add-locking-when-updating-filter-and-timer-v.patch [new file with mode: 0644]
queue-5.10/can-bcm-add-missing-device-refcount-for-can-filter-r.patch [new file with mode: 0644]
queue-5.10/can-bcm-defer-rx_op-deallocation-to-workqueue-to-fix.patch [new file with mode: 0644]
queue-5.10/can-bcm-extend-bcm_tx_lock-usage-for-data-and-timer-.patch [new file with mode: 0644]
queue-5.10/can-bcm-fix-can-frame-rx-tx-statistics.patch [new file with mode: 0644]
queue-5.10/can-bcm-fix-data-race-on-rx_stamp-rx_ifindex-in-bcm_.patch [new file with mode: 0644]
queue-5.10/can-bcm-fix-stale-rx-tx-ops-after-device-removal.patch [new file with mode: 0644]
queue-5.10/can-bcm-track-a-single-source-interface-for-anydev-t.patch [new file with mode: 0644]
queue-5.10/can-bcm-validate-frame-length-in-bcm_rx_setup-for-rt.patch [new file with mode: 0644]
queue-5.10/series
queue-5.15/can-bcm-add-locking-when-updating-filter-and-timer-v.patch [new file with mode: 0644]
queue-5.15/can-bcm-add-missing-device-refcount-for-can-filter-r.patch [new file with mode: 0644]
queue-5.15/can-bcm-defer-rx_op-deallocation-to-workqueue-to-fix.patch [new file with mode: 0644]
queue-5.15/can-bcm-extend-bcm_tx_lock-usage-for-data-and-timer-.patch [new file with mode: 0644]
queue-5.15/can-bcm-fix-can-frame-rx-tx-statistics.patch [new file with mode: 0644]
queue-5.15/can-bcm-fix-data-race-on-rx_stamp-rx_ifindex-in-bcm_.patch [new file with mode: 0644]
queue-5.15/can-bcm-fix-stale-rx-tx-ops-after-device-removal.patch [new file with mode: 0644]
queue-5.15/can-bcm-track-a-single-source-interface-for-anydev-t.patch [new file with mode: 0644]
queue-5.15/can-bcm-validate-frame-length-in-bcm_rx_setup-for-rt.patch [new file with mode: 0644]
queue-5.15/series

diff --git a/queue-5.10/can-bcm-add-locking-when-updating-filter-and-timer-v.patch b/queue-5.10/can-bcm-add-locking-when-updating-filter-and-timer-v.patch
new file mode 100644 (file)
index 0000000..3c30f7a
--- /dev/null
@@ -0,0 +1,413 @@
+From de58298c50269931fdf484d18b536074b2cb64c2 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 12:13:35 +0200
+Subject: can: bcm: add locking when updating filter and timer values
+
+From: Oliver Hartkopp <socketcan@hartkopp.net>
+
+commit 749179c2e25b95d22499ed29096b3e02d6dfd2b4 upstream.
+
+KCSAN detected a simultaneous access to timer values that can be
+overwritten in bcm_rx_setup() when updating timer and filter content
+while bcm_rx_handler(), bcm_rx_timeout_handler() or bcm_rx_thr_handler()
+run concurrently on incoming CAN traffic.
+
+Protect the timer (ival1/ival2/kt_ival1/kt_ival2/kt_lastmsg) and filter
+(nframes/flags/frames/last_frames) updates in bcm_rx_setup() with a new
+per-op bcm_rx_update_lock, taken with the matching scope in the RX
+handlers. memcpy_from_msg() is staged into a temporary buffer before the
+lock is taken, since it can sleep and must not run under a spinlock.
+
+hrtimer_cancel() is always called without bcm_rx_update_lock held, since
+bcm_rx_timeout_handler()/bcm_rx_thr_handler() take the same lock and a
+running callback would otherwise deadlock against the canceller.
+
+Also close a related race: bcm_rx_setup() cleared the RTR flag in the
+stored reply frame's can_id as a separate, unprotected step after the
+frame content was already installed, so a concurrent bcm_rx_handler()
+could transmit a stale reply with CAN_RTR_FLAG still set. Fold that
+normalization into the initial frame preparation instead (on the staged
+buffer for updates, directly on op->frames pre-registration for new
+ops), so the installed frame is always atomically self-consistent.
+
+bcm_rx_handler()'s RX_RTR_FRAME check now takes a lock-protected
+snapshot of op->flags before deciding whether to call bcm_can_tx(),
+but does not hold the lock across that call.
+
+Also take a lock-protected snapshot of the currframe in bcm_can_tx()
+to avoid partly overwrites by content updates in bcm_tx_setup().
+Finally check if a TX_RESET_MULTI_IDX/SETTIMER might have reset
+op->currframe between the two locked sections in bcm_can_tx().
+
+Omit calling hrtimer_forward() with zero interval in bcm_rx_thr_handler().
+kt_ival2 may have been concurrently cleared by bcm_rx_setup() before it
+cancels this timer, so check kt_ival2 inside the bcm_rx_update_lock.
+
+Fixes: c2aba69d0c36 ("can: bcm: add locking for bcm_op runtime updates")
+Reported-by: syzbot+75e5e4ae00c3b4bb544e@syzkaller.appspotmail.com
+Closes: https://lore.kernel.org/linux-can/6975d5cf.a00a0220.33ccc7.0022.GAE@google.com/
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Link: https://patch.msgid.link/20260714-bcm_fixes-v15-3-562f7e3e42da@hartkopp.net
+Cc: stable@kernel.org
+Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/can/bcm.c | 178 +++++++++++++++++++++++++++++++++++++-------------
+ 1 file changed, 134 insertions(+), 44 deletions(-)
+
+diff --git a/net/can/bcm.c b/net/can/bcm.c
+index ee2b14e56b17b6..131c7783b92d33 100644
+--- a/net/can/bcm.c
++++ b/net/can/bcm.c
+@@ -126,6 +126,7 @@ struct bcm_op {
+       struct sock *sk;
+       struct net_device *rx_reg_dev;
+       spinlock_t bcm_tx_lock; /* protect currframe/count in runtime updates */
++      spinlock_t bcm_rx_update_lock; /* protect filter/timer data updates */
+ };
+ struct bcm_sock {
+@@ -280,21 +281,27 @@ static int bcm_proc_show(struct seq_file *m, void *v)
+  * 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 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) {
+@@ -323,14 +330,20 @@ static void bcm_can_tx(struct bcm_op *op)
+       if (!err)
+               op->frames_abs++;
+-      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:
+@@ -429,7 +442,7 @@ static enum hrtimer_restart bcm_tx_timeout_handler(struct hrtimer *hrtimer)
+       struct bcm_msg_head msg_head;
+       if (op->kt_ival1 && (op->count > 0)) {
+-              bcm_can_tx(op);
++              bcm_can_tx(op, NULL);
+               if (!op->count && (op->flags & TX_COUNTEVT)) {
+                       /* create notification to user */
+@@ -446,7 +459,7 @@ static enum hrtimer_restart bcm_tx_timeout_handler(struct hrtimer *hrtimer)
+               }
+       } else if (op->kt_ival2) {
+-              bcm_can_tx(op);
++              bcm_can_tx(op, NULL);
+       }
+       return bcm_tx_set_expiry(op, &op->timer) ?
+@@ -585,6 +598,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' */
+@@ -601,6 +616,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;
+@@ -649,15 +666,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;
+-      if (bcm_rx_thr_flush(op)) {
+-              hrtimer_forward(hrtimer, ktime_get(), op->kt_ival2);
+-              return HRTIMER_RESTART;
++      spin_lock_bh(&op->bcm_rx_update_lock);
++
++      /* 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);
++              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;
+ }
+ /*
+@@ -667,7 +695,9 @@ 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;
++      bool rtr_frame;
+       if (op->can_id != rxframe->can_id)
+               return;
+@@ -686,12 +716,23 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data)
+       /* update statistics */
+       op->frames_abs++;
+-      if (op->flags & RX_RTR_FRAME) {
++      /* snapshot the flag under lock: 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)
++              memcpy(&rtrframe, op->frames, op->cfsiz);
++      spin_unlock_bh(&op->bcm_rx_update_lock);
++
++      if (rtr_frame) {
+               /* send reply for RTR-request (placed in op->frames[0]) */
+-              bcm_can_tx(op);
++              bcm_can_tx(op, &rtrframe);
+               return;
+       }
++      spin_lock_bh(&op->bcm_rx_update_lock);
++
+       if (op->flags & RX_FILTER_ID) {
+               /* the easiest case */
+               bcm_rx_update_and_send(op, op->last_frames, rxframe);
+@@ -725,6 +766,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);
+ }
+ /*
+@@ -1068,7 +1111,7 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
+               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);
+@@ -1082,6 +1125,24 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
+       return err;
+ }
++static void bcm_rx_setup_rtr_check(struct bcm_msg_head *msg_head,
++                                 struct bcm_op *op, void *new_frames)
++{
++      /* 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 & RX_RTR_FRAME) {
++              struct canfd_frame *frame0 = new_frames;
++
++              if ((msg_head->flags & TX_CP_CAN_ID) ||
++                  frame0->can_id == op->can_id)
++                      frame0->can_id = op->can_id & ~CAN_RTR_FLAG;
++      }
++}
++
+ /*
+  * bcm_rx_setup - create or update a bcm rx op (for bcm_sendmsg)
+  */
+@@ -1116,6 +1177,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 */
+               /*
+@@ -1127,19 +1190,48 @@ 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);
++                      bcm_rx_setup_rtr_check(msg_head, op, new_frames);
+               }
++              spin_lock_bh(&op->bcm_rx_update_lock);
+               op->nframes = msg_head->nframes;
+               op->flags = msg_head->flags;
++              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;
++              }
++              spin_unlock_bh(&op->bcm_rx_update_lock);
++
++              /* free temporary frames / kfree(NULL) is safe */
++              kfree(new_frames);
++
+               /* Only an update -> do not call can_rx_register() */
+               do_rx_register = 0;
+@@ -1150,6 +1242,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);
+@@ -1191,6 +1284,8 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
+                               kfree(op);
+                               return err;
+                       }
++
++                      bcm_rx_setup_rtr_check(msg_head, op, op->frames);
+               }
+               /* bcm_can_tx / bcm_tx_timeout_handler needs this */
+@@ -1218,29 +1313,22 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
+       /* 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 (do_rx_register) {
++                              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)
+@@ -1250,9 +1338,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)
+-- 
+2.53.0
+
diff --git a/queue-5.10/can-bcm-add-missing-device-refcount-for-can-filter-r.patch b/queue-5.10/can-bcm-add-missing-device-refcount-for-can-filter-r.patch
new file mode 100644 (file)
index 0000000..920bab8
--- /dev/null
@@ -0,0 +1,121 @@
+From 2deb1956ecd5c0864de65e9a417aa27f2d66f1b6 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 12:13:39 +0200
+Subject: can: bcm: add missing device refcount for CAN filter removal
+
+From: Oliver Hartkopp <socketcan@hartkopp.net>
+
+commit d59948293ea34b6337ce2b5febab8510de70048c upstream.
+
+sashiko-bot remarked a problem with a concurrent device unregistration
+in isotp.c which also is present in the bcm.c code. A former fix for raw.c
+commit c275a176e4b6 ("can: raw: add missing refcount for memory leak fix")
+introduced a netdevice_tracker which solves the issue for bcm.c too.
+
+bcm_release(), bcm_delete_rx_op() and bcm_notifier() relied on
+dev_get_by_index(ifindex) to re-find the device for an rx_op before
+unregistering its filter. If a concurrent NETDEV_UNREGISTER has already
+unlisted the device from the ifindex table, that lookup fails and
+can_rx_unregister() is silently skipped, leaving a stale CAN filter
+pointing at the soon-to-be-freed bcm_op/socket.
+
+Hold a netdev_hold()/netdev_put() tracked reference on op->rx_reg_dev
+from the moment the rx filter is registered in bcm_rx_setup() until it
+is unregistered in bcm_rx_unreg(), and use that reference directly in
+bcm_release() and bcm_delete_rx_op() instead of re-looking the device
+up by ifindex.
+
+Reported-by: sashiko-bot@kernel.org
+Closes: https://sashiko.dev/#/patchset/20260707094716.63578-1-socketcan@hartkopp.net
+Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol")
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Link: https://patch.msgid.link/20260714-bcm_fixes-v15-8-562f7e3e42da@hartkopp.net
+Cc: stable@kernel.org
+Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/can/bcm.c | 44 ++++++++++++++++++++++++--------------------
+ 1 file changed, 24 insertions(+), 20 deletions(-)
+
+diff --git a/net/can/bcm.c b/net/can/bcm.c
+index d74c5d1e29358f..59fc7cee12f41f 100644
+--- a/net/can/bcm.c
++++ b/net/can/bcm.c
+@@ -884,6 +884,7 @@ static void bcm_rx_unreg(struct net_device *dev, struct bcm_op *op)
+               /* mark as removed subscription */
+               op->rx_reg_dev = NULL;
++              dev_put(dev);
+       } else
+               printk(KERN_ERR "can-bcm: bcm_rx_unreg: registered device "
+                      "mismatch %p %p\n", op->rx_reg_dev, dev);
+@@ -914,17 +915,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,
+@@ -1447,7 +1445,15 @@ 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 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;
++                                      dev_hold(dev);
++                              }
+                               dev_put(dev);
+                       } else {
+                               /* the requested device is gone - do not
+@@ -1820,16 +1826,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),
+-- 
+2.53.0
+
diff --git a/queue-5.10/can-bcm-defer-rx_op-deallocation-to-workqueue-to-fix.patch b/queue-5.10/can-bcm-defer-rx_op-deallocation-to-workqueue-to-fix.patch
new file mode 100644 (file)
index 0000000..3b6b228
--- /dev/null
@@ -0,0 +1,166 @@
+From 81357d5205b8f9e6b5d1798da1b2efb917fc8411 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 18:39:02 +0200
+Subject: can: bcm: defer rx_op deallocation to workqueue to fix thrtimer UAF
+
+From: Lee Jones <lee@kernel.org>
+
+commit 68973f9db76144825e4f35dfdc80fb8279eb2d57 upstream.
+
+Commit f1b4e32aca08 ("can: bcm: use call_rcu() instead of costly
+synchronize_rcu()") replaced synchronize_rcu() in bcm_delete_rx_op()
+with call_rcu() and introduced the RX_NO_AUTOTIMER flag.
+
+However, this flag check was omitted for thrtimer in the packet rx
+fast-path. During BCM RX operation teardown, a concurrent RCU reader
+(bcm_rx_handler) can race and re-arm thrtimer via
+bcm_rx_update_and_send() after call_rcu() has been scheduled.  Once
+the RCU grace period elapses, bcm_op is freed.  The subsequently
+firing thrtimer then dereferences the deallocated op, causing a UAF.
+
+Adding flag checks to the rx fast-path (bcm_rx_update_and_send) does not
+fully close the TOCTOU race and introduces latency for every CAN frame.
+Conversely, calling hrtimer_cancel() directly inside the RCU callback
+(softirq context) is fatal as hrtimer_cancel() can sleep, triggering
+a "scheduling while atomic" panic.
+
+Resolve this by deferring the timer cancellation and memory free to a
+dedicated unbound workqueue (bcm_wq).  The RCU callback now queues a
+work item to bcm_wq, which safely cancels both timers and deallocates
+memory in sleepable process context.  A dedicated workqueue is used to
+prevent system-wide WQ saturation and is cleanly flushed/destroyed
+on module unload to avoid rmmod page faults.
+
+Since the deferred work can now outlive the calling context by an
+unbounded amount, also take a reference on op->sk when it is assigned
+and drop it only once the deferred work has cancelled both timers, so a
+socket can no longer be freed out from under a still-armed timer whose
+callback (bcm_send_to_user()) dereferences op->sk.
+
+Fixes: f1b4e32aca08 ("can: bcm: use call_rcu() instead of costly synchronize_rcu()")
+Tested-by: Feng Xue <feng.xue@outlook.com>
+Tested-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Signed-off-by: Lee Jones <lee@kernel.org>
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Link: https://patch.msgid.link/20260714-bcm_fixes-v15-1-562f7e3e42da@hartkopp.net
+Cc: stable@kernel.org
+Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/can/bcm.c | 34 ++++++++++++++++++++++++++++++++--
+ 1 file changed, 32 insertions(+), 2 deletions(-)
+
+diff --git a/net/can/bcm.c b/net/can/bcm.c
+index 06ee3bfe87c99d..ee2b14e56b17b6 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/sock.h>
+ #include <net/net_namespace.h>
+@@ -89,6 +90,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
+@@ -102,6 +105,7 @@ 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;
+@@ -740,9 +744,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);
+@@ -750,9 +757,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);
+@@ -1008,6 +1029,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 */
+@@ -1173,6 +1195,7 @@ static int bcm_rx_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;
+               /* ifindex for timeout events w/o previous frame reception */
+@@ -1830,11 +1853,16 @@ 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 = can_proto_register(&bcm_can_proto);
+       if (err < 0) {
+               printk(KERN_ERR "can: registration of bcm protocol failed\n");
++              destroy_workqueue(bcm_wq);
+               return err;
+       }
+@@ -1848,6 +1876,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);
+-- 
+2.53.0
+
diff --git a/queue-5.10/can-bcm-extend-bcm_tx_lock-usage-for-data-and-timer-.patch b/queue-5.10/can-bcm-extend-bcm_tx_lock-usage-for-data-and-timer-.patch
new file mode 100644 (file)
index 0000000..b919e3c
--- /dev/null
@@ -0,0 +1,230 @@
+From 45e930037ec1b9eac10d49cf525c931519a94714 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 12:13:37 +0200
+Subject: can: bcm: extend bcm_tx_lock usage for data and timer updates
+
+From: Oliver Hartkopp <socketcan@hartkopp.net>
+
+commit 12ce799f7ab1e05bd8fbf79e46f403bfe5597ebc upstream.
+
+Stage new CAN frame content for an existing tx op into a kmalloc()'d
+buffer and validate it there, mirroring the approach already used in
+bcm_rx_setup(). Only copy the validated data into op->frames while
+holding op->bcm_tx_lock, so bcm_can_tx() and bcm_tx_timeout_handler()
+can no longer observe a partially updated or unvalidated frame.
+
+Add a missing error path for memcpy_from_msg() when copying CAN frame
+data from userspace.
+
+Also move the kt_ival1/kt_ival2/ival1/ival2 updates in bcm_tx_setup()
+under op->bcm_tx_lock, and read kt_ival1/kt_ival2/count under the same
+lock in bcm_tx_set_expiry() and bcm_tx_timeout_handler(), closing the
+torn 64-bit ktime_t read on 32-bit platforms.
+
+Fixes: c2aba69d0c36 ("can: bcm: add locking for bcm_op runtime updates")
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Link: https://patch.msgid.link/20260714-bcm_fixes-v15-6-562f7e3e42da@hartkopp.net
+Cc: stable@kernel.org
+Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/can/bcm.c | 104 ++++++++++++++++++++++++++++++++++++--------------
+ 1 file changed, 75 insertions(+), 29 deletions(-)
+
+diff --git a/net/can/bcm.c b/net/can/bcm.c
+index 5d5904076d211d..93552b65c00ee4 100644
+--- a/net/can/bcm.c
++++ b/net/can/bcm.c
+@@ -125,7 +125,7 @@ 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 */
++      spinlock_t bcm_tx_lock; /* protect tx data and timer updates */
+       spinlock_t bcm_rx_update_lock; /* protect filter/timer data updates */
+ };
+@@ -440,12 +440,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;
+@@ -462,25 +468,47 @@ 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 (tx_ival1) {
++              u32 flags, count;
++              struct bcm_timeval ival1, ival2;
+-      if (op->kt_ival1 && (op->count > 0)) {
+               bcm_can_tx(op, NULL);
+-              if (!op->count && (op->flags & TX_COUNTEVT)) {
++              /* 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) {
++      } else if (tx_ival2) {
+               bcm_can_tx(op, NULL);
+       }
+@@ -983,6 +1011,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 */
+               /*
+@@ -993,11 +1023,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)
+@@ -1007,36 +1049,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 */
+@@ -1113,10 +1157,12 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
+       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)
+-- 
+2.53.0
+
diff --git a/queue-5.10/can-bcm-fix-can-frame-rx-tx-statistics.patch b/queue-5.10/can-bcm-fix-can-frame-rx-tx-statistics.patch
new file mode 100644 (file)
index 0000000..0c8c1ea
--- /dev/null
@@ -0,0 +1,191 @@
+From 95ec4c6e157ca8f15bc0bf64e62ae008e9a0401b Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 12:13:36 +0200
+Subject: can: bcm: fix CAN frame rx/tx statistics
+
+From: Oliver Hartkopp <socketcan@hartkopp.net>
+
+commit e6c24ba95fc3f1b5e1dcd28b1c6e59ef61a9daa5 upstream.
+
+KCSAN detected a data race within the bcm_rx_handler() when two CAN frames
+have been simultaneously received and processed in a single rx op by two
+different CPUs.
+
+Use atomic operations with (signed) long data types to access the
+statistics in the hot path to fix the KCSAN complaint.
+
+Additionally simplify the update and check of statistics overflow by
+using the atomic operations in separate bcm_update_[rx|tx]_stats()
+functions. The rx variant runs under bcm_rx_update_lock to prevent
+races when resetting the two rx counters; the tx variant runs under
+bcm_tx_lock and only needs to guard its own counter's overflow.
+
+As the rx path resets its values already at LONG_MAX / 100, there is
+no conflict between the two locking domains (bcm_rx_update_lock vs.
+bcm_tx_lock) even for ops that use both paths.
+
+The rx statistics update and the frames_filtered update in
+bcm_rx_changed() were previously performed in two separate
+bcm_rx_update_lock sections. For an rx op subscribed on all interfaces
+(ifindex == 0), bcm_rx_handler() can run concurrently on different
+CPUs, so a counter reset by one CPU between these two sections could
+leave frames_filtered larger than frames_abs on another CPU, producing
+a bogus (even negative) reduction percentage in procfs. Update the
+statistics in the same critical section as bcm_rx_changed() to close
+this gap, which also removes the now unneeded extra lock/unlock pair
+around the traffic_flags calculation.
+
+Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol")
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Link: https://patch.msgid.link/20260714-bcm_fixes-v15-4-562f7e3e42da@hartkopp.net
+Cc: stable@kernel.org
+Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/can/bcm.c | 67 ++++++++++++++++++++++++++++++++++-----------------
+ 1 file changed, 45 insertions(+), 22 deletions(-)
+
+diff --git a/net/can/bcm.c b/net/can/bcm.c
+index 131c7783b92d33..5d5904076d211d 100644
+--- a/net/can/bcm.c
++++ b/net/can/bcm.c
+@@ -109,7 +109,7 @@ struct bcm_op {
+       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;
+@@ -216,10 +216,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,
+@@ -241,9 +244,9 @@ 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);
+@@ -267,7 +270,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');
+@@ -277,6 +281,24 @@ 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
+@@ -328,7 +350,7 @@ static void bcm_can_tx(struct bcm_op *op, struct canfd_frame *cf)
+       spin_lock_bh(&op->bcm_tx_lock);
+       if (!err)
+-              op->frames_abs++;
++              bcm_update_tx_stats(op);
+       /* only advance the cyclic sequence if nothing reset currframe while
+        * we were sending - a concurrent TX_RESET_MULTI_IDX means this
+@@ -473,12 +495,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 &= (BCM_CAN_FLAGS_MASK|RX_RECV);
+@@ -713,25 +732,29 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data)
+       op->rx_stamp = skb->tstamp;
+       /* save originator for recvfrom() */
+       op->rx_ifindex = skb->dev->ifindex;
+-      /* update statistics */
+-      op->frames_abs++;
+-      /* snapshot the flag under lock: op->flags/op->frames may be updated
+-       * concurrently by bcm_rx_setup().
+-       */
++      /* 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)
++      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);
++              spin_unlock_bh(&op->bcm_rx_update_lock);
+-      if (rtr_frame) {
+               /* send reply for RTR-request (placed in op->frames[0]) */
+               bcm_can_tx(op, &rtrframe);
+               return;
+       }
+-      spin_lock_bh(&op->bcm_rx_update_lock);
++      /* 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);
+       if (op->flags & RX_FILTER_ID) {
+               /* the easiest case */
+-- 
+2.53.0
+
diff --git a/queue-5.10/can-bcm-fix-data-race-on-rx_stamp-rx_ifindex-in-bcm_.patch b/queue-5.10/can-bcm-fix-data-race-on-rx_stamp-rx_ifindex-in-bcm_.patch
new file mode 100644 (file)
index 0000000..a321bbe
--- /dev/null
@@ -0,0 +1,74 @@
+From d882912b7ff9d73b942418c0ef1e3e5c95b0e972 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 12:13:41 +0200
+Subject: can: bcm: fix data race on rx_stamp/rx_ifindex in bcm_rx_handler()
+
+From: Oliver Hartkopp <socketcan@hartkopp.net>
+
+commit 58fd6cbc8541216af1d7ed272ea7ac2b66d50fd8 upstream.
+
+For an rx op subscribed on all interfaces (ifindex == 0), the same op
+is registered once in the shared per-netns wildcard filter list, so
+bcm_rx_handler() can run concurrently on different CPUs for frames
+arriving on different net devices.
+
+op->rx_stamp and op->rx_ifindex were written before bcm_rx_update_lock was
+taken, allowing concurrent writers to race each other - including a torn
+store of the 64-bit rx_stamp on 32-bit platforms.
+
+Beyond a torn store bcm_send_to_user() must report the timestamp/ifindex
+of the very same frame whose content it is delivering. So the assignment
+is placed in the same unbroken bcm_rx_update_lock section as the content
+comparison.
+
+As a side effect, the RTR-request frame feature (which never reach
+bcm_send_to_user()) no longer updates rx_stamp/rx_ifindex, since only
+the notification path needs them.
+
+Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol")
+Reported-by: sashiko-bot@kernel.org
+Closes: https://lore.kernel.org/linux-can/20260707145135.5BC831F00A3A@smtp.kernel.org/
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Link: https://patch.msgid.link/20260714-bcm_fixes-v15-10-562f7e3e42da@hartkopp.net
+Cc: stable@kernel.org
+Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/can/bcm.c | 13 ++++++++-----
+ 1 file changed, 8 insertions(+), 5 deletions(-)
+
+diff --git a/net/can/bcm.c b/net/can/bcm.c
+index 3c4abb6397bc66..93572cb59cf7a9 100644
+--- a/net/can/bcm.c
++++ b/net/can/bcm.c
+@@ -756,11 +756,6 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data)
+       /* disable timeout */
+       hrtimer_cancel(&op->timer);
+-      /* save rx timestamp */
+-      op->rx_stamp = skb->tstamp;
+-      /* save originator for recvfrom() */
+-      op->rx_ifindex = skb->dev->ifindex;
+-
+       /* op->flags/op->frames may be updated concurrently by bcm_rx_setup() */
+       spin_lock_bh(&op->bcm_rx_update_lock);
+@@ -784,6 +779,14 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data)
+        */
+       bcm_update_rx_stats(op);
++      /* save rx timestamp and originator for recvfrom() under lock.
++       * For an op subscribed on all interfaces (ifindex == 0)
++       * bcm_rx_handler() can run concurrently on different CPUs so
++       * the CAN content and the meta data must be bundled correctly.
++       */
++      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);
+-- 
+2.53.0
+
diff --git a/queue-5.10/can-bcm-fix-stale-rx-tx-ops-after-device-removal.patch b/queue-5.10/can-bcm-fix-stale-rx-tx-ops-after-device-removal.patch
new file mode 100644 (file)
index 0000000..cf3e0cc
--- /dev/null
@@ -0,0 +1,164 @@
+From 99f31f2addae169c087376c48e3505720ecceaed Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 12:13:40 +0200
+Subject: can: bcm: fix stale rx/tx ops after device removal
+
+From: Oliver Hartkopp <socketcan@hartkopp.net>
+
+commit 3b762c0d950383ab7a002686c9136b9aa55d2d70 upstream.
+
+RX: an RX_SETUP update(!) for an existing op skipped can_rx_register()
+unconditionally, even when a concurrent NETDEV_UNREGISTER had already
+torn down its registration (op->rx_reg_dev == NULL). This silently
+did not re-enable frame delivery for that updated filter. bcm_rx_setup()
+now re-registers in that case, while leaving rx_ops with ifindex = 0
+(all CAN devices) which never carry a tracked rx_reg_dev registered as-is.
+
+TX: bcm_notify() only handled bo->rx_ops on NETDEV_UNREGISTER, leaving
+tx_ops with an active cyclic transmission re-arming its hrtimer
+indefinitely to execute bcm_tx_timeout_handler(). Cancelling the hrtimer
+prevents the runaway timer and any injection into a later reused ifindex,
+since nothing else calls bcm_can_tx() for the op until an explicit
+TX_SETUP update re-arms it.
+
+Unlike bcm_rx_unreg(), which clears the tracked rx_reg_dev for rx_ops,
+the ifindex is intentionally left unchanged for tx_ops. bcm_tx_setup()
+always rejects ifindex 0, so clearing it would strand the op: neither a
+later TX_SETUP (bcm_find_op()) nor TX_DELETE (bcm_delete_tx_op()) could
+ever find it again, since both require an exact ifindex match.
+
+Reported-by: sashiko-bot@kernel.org
+Closes: https://lore.kernel.org/linux-can/20260708094536.DDF821F00A3A@smtp.kernel.org/
+Closes: https://lore.kernel.org/linux-can/20260708154039.347ED1F000E9@smtp.kernel.org/
+Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol")
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Link: https://patch.msgid.link/20260714-bcm_fixes-v15-9-562f7e3e42da@hartkopp.net
+Cc: stable@kernel.org
+Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/can/bcm.c | 54 +++++++++++++++++++++++++++++++++++++++++----------
+ 1 file changed, 44 insertions(+), 10 deletions(-)
+
+diff --git a/net/can/bcm.c b/net/can/bcm.c
+index 59fc7cee12f41f..3c4abb6397bc66 100644
+--- a/net/can/bcm.c
++++ b/net/can/bcm.c
+@@ -1234,6 +1234,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))) {
+@@ -1318,8 +1319,15 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
+               /* free temporary frames / kfree(NULL) is safe */
+               kfree(new_frames);
+-              /* Only an update -> do not call can_rx_register() */
+-              do_rx_register = 0;
++              /* 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 */
+@@ -1389,6 +1397,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
+               /* call can_rx_register() */
+               do_rx_register = 1;
++              new_op = 1;
+       } /* if ((op = bcm_find_op(&bo->rx_ops, msg_head->can_id, ifindex))) */
+@@ -1402,7 +1411,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
+               if (op->flags & SETTIMER) {
+                       /* set timers (locked) for newly created op */
+-                      if (do_rx_register) {
++                      if (new_op) {
+                               spin_lock_bh(&op->bcm_rx_update_lock);
+                               op->ival1 = msg_head->ival1;
+                               op->ival2 = msg_head->ival2;
+@@ -1432,7 +1441,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;
+@@ -1462,18 +1474,32 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
+                               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 */
+-                      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 this bcm_op to the list of the rx_ops */
+-              list_add_rcu(&op->list, &bo->rx_ops);
++              /* 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;
+@@ -1689,11 +1715,19 @@ static void bcm_notify(struct bcm_sock *bo, unsigned long msg,
+       case NETDEV_UNREGISTER:
+               lock_sock(sk);
+-              /* remove device specific receive entries */
++              /* 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);
++              /* 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)
+-- 
+2.53.0
+
diff --git a/queue-5.10/can-bcm-track-a-single-source-interface-for-anydev-t.patch b/queue-5.10/can-bcm-track-a-single-source-interface-for-anydev-t.patch
new file mode 100644 (file)
index 0000000..9d7962a
--- /dev/null
@@ -0,0 +1,143 @@
+From 81ab3bee66367e82ce05042c398f119e69b55df1 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 12:13:42 +0200
+Subject: can: bcm: track a single source interface for ANYDEV timeout/throttle
+ ops
+
+From: Oliver Hartkopp <socketcan@hartkopp.net>
+
+commit 2f5976f54a04e9f18b25283036ac3136be453b17 upstream.
+
+An ANYDEV rx op (ifindex == 0) with an active RX timeout and/or
+throttle timer has no defined semantics when matching frames arrive
+from several interfaces: bcm_rx_handler() can run concurrently for
+the same op on different CPUs, racing hrtimer_cancel()/
+bcm_rx_starttimer() against bcm_rx_timeout_handler() and causing
+spurious RX_TIMEOUT notifications and last_frames corruption. The
+same concurrency lets throttled multiplex frames from different
+interfaces clobber the single rx_ifindex/rx_stamp fields shared by
+the op.
+
+Add op->if_detected to track the first interface that delivers a
+matching frame while a timeout/throttle timer is configured, and
+reject frames from any other interface for that op. The claim is
+decided in bcm_rx_handler() before hrtimer_cancel() touches
+op->timer, so a rejected frame can never disturb the claimed
+interface's watchdog. RTR-mode ops are excluded via RX_RTR_FRAME,
+independent of kt_ival1/kt_ival2, since those may briefly hold a
+stale value from an earlier non-RTR configuration.
+
+The claim is released in bcm_notify() on NETDEV_UNREGISTER and in
+bcm_rx_setup() when SETTIMER reconfigures the timer values.
+
+A (re-)claim is only possible on CAN devices in NETREG_REGISTERED
+dev->reg_state to cover the release in bcm_notify() where reg_state
+becomes NETREG_UNREGISTERING until synchronize_net().
+
+Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol")
+Reported-by: sashiko-bot@kernel.org
+Closes: https://lore.kernel.org/linux-can/20260709105031.1A39C1F000E9@smtp.kernel.org/
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Link: https://patch.msgid.link/20260714-bcm_fixes-v15-11-562f7e3e42da@hartkopp.net
+Cc: stable@kernel.org
+Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/can/bcm.c | 49 ++++++++++++++++++++++++++++++++++++++++++++-----
+ 1 file changed, 44 insertions(+), 5 deletions(-)
+
+diff --git a/net/can/bcm.c b/net/can/bcm.c
+index 93572cb59cf7a9..f655764aae83ef 100644
+--- a/net/can/bcm.c
++++ b/net/can/bcm.c
+@@ -114,6 +114,7 @@ struct bcm_op {
+       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;
+@@ -753,6 +754,33 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data)
+       if (skb->len != op->cfsiz)
+               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 &&
++                          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);
+@@ -779,10 +807,9 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data)
+        */
+       bcm_update_rx_stats(op);
+-      /* save rx timestamp and originator for recvfrom() under lock.
+-       * For an op subscribed on all interfaces (ifindex == 0)
+-       * bcm_rx_handler() can run concurrently on different CPUs so
+-       * the CAN content and the meta data must be bundled correctly.
++      /* 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;
+@@ -1316,6 +1343,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
+                       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);
+@@ -1719,10 +1747,21 @@ static void bcm_notify(struct bcm_sock *bo, unsigned long msg,
+               lock_sock(sk);
+               /* rx_ops: remove device specific receive entries */
+-              list_for_each_entry(op, &bo->rx_ops, list)
++              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.
+-- 
+2.53.0
+
diff --git a/queue-5.10/can-bcm-validate-frame-length-in-bcm_rx_setup-for-rt.patch b/queue-5.10/can-bcm-validate-frame-length-in-bcm_rx_setup-for-rt.patch
new file mode 100644 (file)
index 0000000..cf07a4a
--- /dev/null
@@ -0,0 +1,128 @@
+From 52c04ac78d9bf458516fb6ab6442dbb861473205 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 12:13:38 +0200
+Subject: can: bcm: validate frame length in bcm_rx_setup() for RTR replies
+
+From: Oliver Hartkopp <socketcan@hartkopp.net>
+
+commit 62ec41f364648be79d54d94d0d240ee326948afd upstream.
+
+bcm_tx_setup() validates cf->len against the CAN/CAN FD DLC limits
+before installing frames for TX_SETUP, but bcm_rx_setup() never did
+the same for the RTR-reply frame configured via RX_SETUP with
+RX_RTR_FRAME.
+
+Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol")
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Link: https://patch.msgid.link/20260714-bcm_fixes-v15-7-562f7e3e42da@hartkopp.net
+Cc: stable@kernel.org
+Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/can/bcm.c | 59 +++++++++++++++++++++++++++++++++++----------------
+ 1 file changed, 41 insertions(+), 18 deletions(-)
+
+diff --git a/net/can/bcm.c b/net/can/bcm.c
+index 93552b65c00ee4..d74c5d1e29358f 100644
+--- a/net/can/bcm.c
++++ b/net/can/bcm.c
+@@ -1194,22 +1194,37 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
+       return err;
+ }
+-static void bcm_rx_setup_rtr_check(struct bcm_msg_head *msg_head,
+-                                 struct bcm_op *op, void *new_frames)
++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 & RX_RTR_FRAME) {
+-              struct canfd_frame *frame0 = new_frames;
++      if ((msg_head->flags & TX_CP_CAN_ID) ||
++          frame0->can_id == op->can_id)
++              frame0->can_id = op->can_id & ~CAN_RTR_FLAG;
+-              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;
+ }
+ /*
+@@ -1272,7 +1287,11 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
+                               return err;
+                       }
+-                      bcm_rx_setup_rtr_check(msg_head, op, new_frames);
++                      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);
+@@ -1345,16 +1364,12 @@ 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;
+-                      bcm_rx_setup_rtr_check(msg_head, op, op->frames);
++                      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 */
+@@ -1456,6 +1471,14 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
+       }
+       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;
+ }
+ /*
+-- 
+2.53.0
+
index aa4b53e2156024f013eed8dc879486d736073568..873512179e20b3980db34f5ccb2aa458efea2810 100644 (file)
@@ -100,3 +100,12 @@ net-mlx5e-reject-unsupported-cb-shaper-tsa-in-ets-va.patch
 raw-use-more-conventional-iterators.patch
 net-ipv6-fix-dif-and-sdif-mismatch-in-raw6_icmp_erro.patch
 bpf-sockmap-fix-cork-use-after-free-in-tcp_bpf_sendm.patch
+can-bcm-defer-rx_op-deallocation-to-workqueue-to-fix.patch
+can-bcm-add-locking-when-updating-filter-and-timer-v.patch
+can-bcm-fix-can-frame-rx-tx-statistics.patch
+can-bcm-extend-bcm_tx_lock-usage-for-data-and-timer-.patch
+can-bcm-validate-frame-length-in-bcm_rx_setup-for-rt.patch
+can-bcm-add-missing-device-refcount-for-can-filter-r.patch
+can-bcm-fix-stale-rx-tx-ops-after-device-removal.patch
+can-bcm-fix-data-race-on-rx_stamp-rx_ifindex-in-bcm_.patch
+can-bcm-track-a-single-source-interface-for-anydev-t.patch
diff --git a/queue-5.15/can-bcm-add-locking-when-updating-filter-and-timer-v.patch b/queue-5.15/can-bcm-add-locking-when-updating-filter-and-timer-v.patch
new file mode 100644 (file)
index 0000000..ba1fe00
--- /dev/null
@@ -0,0 +1,413 @@
+From c4af7dcbccbd407527f7fe82b813f2889531d396 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 12:21:42 +0200
+Subject: can: bcm: add locking when updating filter and timer values
+
+From: Oliver Hartkopp <socketcan@hartkopp.net>
+
+commit 749179c2e25b95d22499ed29096b3e02d6dfd2b4 upstream.
+
+KCSAN detected a simultaneous access to timer values that can be
+overwritten in bcm_rx_setup() when updating timer and filter content
+while bcm_rx_handler(), bcm_rx_timeout_handler() or bcm_rx_thr_handler()
+run concurrently on incoming CAN traffic.
+
+Protect the timer (ival1/ival2/kt_ival1/kt_ival2/kt_lastmsg) and filter
+(nframes/flags/frames/last_frames) updates in bcm_rx_setup() with a new
+per-op bcm_rx_update_lock, taken with the matching scope in the RX
+handlers. memcpy_from_msg() is staged into a temporary buffer before the
+lock is taken, since it can sleep and must not run under a spinlock.
+
+hrtimer_cancel() is always called without bcm_rx_update_lock held, since
+bcm_rx_timeout_handler()/bcm_rx_thr_handler() take the same lock and a
+running callback would otherwise deadlock against the canceller.
+
+Also close a related race: bcm_rx_setup() cleared the RTR flag in the
+stored reply frame's can_id as a separate, unprotected step after the
+frame content was already installed, so a concurrent bcm_rx_handler()
+could transmit a stale reply with CAN_RTR_FLAG still set. Fold that
+normalization into the initial frame preparation instead (on the staged
+buffer for updates, directly on op->frames pre-registration for new
+ops), so the installed frame is always atomically self-consistent.
+
+bcm_rx_handler()'s RX_RTR_FRAME check now takes a lock-protected
+snapshot of op->flags before deciding whether to call bcm_can_tx(),
+but does not hold the lock across that call.
+
+Also take a lock-protected snapshot of the currframe in bcm_can_tx()
+to avoid partly overwrites by content updates in bcm_tx_setup().
+Finally check if a TX_RESET_MULTI_IDX/SETTIMER might have reset
+op->currframe between the two locked sections in bcm_can_tx().
+
+Omit calling hrtimer_forward() with zero interval in bcm_rx_thr_handler().
+kt_ival2 may have been concurrently cleared by bcm_rx_setup() before it
+cancels this timer, so check kt_ival2 inside the bcm_rx_update_lock.
+
+Fixes: c2aba69d0c36 ("can: bcm: add locking for bcm_op runtime updates")
+Reported-by: syzbot+75e5e4ae00c3b4bb544e@syzkaller.appspotmail.com
+Closes: https://lore.kernel.org/linux-can/6975d5cf.a00a0220.33ccc7.0022.GAE@google.com/
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Link: https://patch.msgid.link/20260714-bcm_fixes-v15-3-562f7e3e42da@hartkopp.net
+Cc: stable@kernel.org
+Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/can/bcm.c | 178 +++++++++++++++++++++++++++++++++++++-------------
+ 1 file changed, 134 insertions(+), 44 deletions(-)
+
+diff --git a/net/can/bcm.c b/net/can/bcm.c
+index ca0f5d0c4e9ba3..f62361d9719f56 100644
+--- a/net/can/bcm.c
++++ b/net/can/bcm.c
+@@ -126,6 +126,7 @@ struct bcm_op {
+       struct sock *sk;
+       struct net_device *rx_reg_dev;
+       spinlock_t bcm_tx_lock; /* protect currframe/count in runtime updates */
++      spinlock_t bcm_rx_update_lock; /* protect filter/timer data updates */
+ };
+ struct bcm_sock {
+@@ -280,21 +281,27 @@ static int bcm_proc_show(struct seq_file *m, void *v)
+  * 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 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) {
+@@ -323,14 +330,20 @@ static void bcm_can_tx(struct bcm_op *op)
+       if (!err)
+               op->frames_abs++;
+-      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:
+@@ -429,7 +442,7 @@ static enum hrtimer_restart bcm_tx_timeout_handler(struct hrtimer *hrtimer)
+       struct bcm_msg_head msg_head;
+       if (op->kt_ival1 && (op->count > 0)) {
+-              bcm_can_tx(op);
++              bcm_can_tx(op, NULL);
+               if (!op->count && (op->flags & TX_COUNTEVT)) {
+                       /* create notification to user */
+@@ -446,7 +459,7 @@ static enum hrtimer_restart bcm_tx_timeout_handler(struct hrtimer *hrtimer)
+               }
+       } else if (op->kt_ival2) {
+-              bcm_can_tx(op);
++              bcm_can_tx(op, NULL);
+       }
+       return bcm_tx_set_expiry(op, &op->timer) ?
+@@ -585,6 +598,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' */
+@@ -601,6 +616,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;
+@@ -649,15 +666,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;
+-      if (bcm_rx_thr_flush(op)) {
+-              hrtimer_forward(hrtimer, ktime_get(), op->kt_ival2);
+-              return HRTIMER_RESTART;
++      spin_lock_bh(&op->bcm_rx_update_lock);
++
++      /* 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);
++              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;
+ }
+ /*
+@@ -667,7 +695,9 @@ 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;
++      bool rtr_frame;
+       if (op->can_id != rxframe->can_id)
+               return;
+@@ -686,12 +716,23 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data)
+       /* update statistics */
+       op->frames_abs++;
+-      if (op->flags & RX_RTR_FRAME) {
++      /* snapshot the flag under lock: 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)
++              memcpy(&rtrframe, op->frames, op->cfsiz);
++      spin_unlock_bh(&op->bcm_rx_update_lock);
++
++      if (rtr_frame) {
+               /* send reply for RTR-request (placed in op->frames[0]) */
+-              bcm_can_tx(op);
++              bcm_can_tx(op, &rtrframe);
+               return;
+       }
++      spin_lock_bh(&op->bcm_rx_update_lock);
++
+       if (op->flags & RX_FILTER_ID) {
+               /* the easiest case */
+               bcm_rx_update_and_send(op, op->last_frames, rxframe);
+@@ -725,6 +766,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);
+ }
+ /*
+@@ -1068,7 +1111,7 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
+               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);
+@@ -1082,6 +1125,24 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
+       return err;
+ }
++static void bcm_rx_setup_rtr_check(struct bcm_msg_head *msg_head,
++                                 struct bcm_op *op, void *new_frames)
++{
++      /* 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 & RX_RTR_FRAME) {
++              struct canfd_frame *frame0 = new_frames;
++
++              if ((msg_head->flags & TX_CP_CAN_ID) ||
++                  frame0->can_id == op->can_id)
++                      frame0->can_id = op->can_id & ~CAN_RTR_FLAG;
++      }
++}
++
+ /*
+  * bcm_rx_setup - create or update a bcm rx op (for bcm_sendmsg)
+  */
+@@ -1116,6 +1177,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 */
+               /*
+@@ -1127,19 +1190,48 @@ 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);
++                      bcm_rx_setup_rtr_check(msg_head, op, new_frames);
+               }
++              spin_lock_bh(&op->bcm_rx_update_lock);
+               op->nframes = msg_head->nframes;
+               op->flags = msg_head->flags;
++              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;
++              }
++              spin_unlock_bh(&op->bcm_rx_update_lock);
++
++              /* free temporary frames / kfree(NULL) is safe */
++              kfree(new_frames);
++
+               /* Only an update -> do not call can_rx_register() */
+               do_rx_register = 0;
+@@ -1150,6 +1242,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);
+@@ -1191,6 +1284,8 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
+                               kfree(op);
+                               return err;
+                       }
++
++                      bcm_rx_setup_rtr_check(msg_head, op, op->frames);
+               }
+               /* bcm_can_tx / bcm_tx_timeout_handler needs this */
+@@ -1218,29 +1313,22 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
+       /* 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 (do_rx_register) {
++                              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)
+@@ -1250,9 +1338,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)
+-- 
+2.53.0
+
diff --git a/queue-5.15/can-bcm-add-missing-device-refcount-for-can-filter-r.patch b/queue-5.15/can-bcm-add-missing-device-refcount-for-can-filter-r.patch
new file mode 100644 (file)
index 0000000..5511062
--- /dev/null
@@ -0,0 +1,121 @@
+From 4a97fce2809debe6a543c17ee89b74472f595ff2 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 12:21:46 +0200
+Subject: can: bcm: add missing device refcount for CAN filter removal
+
+From: Oliver Hartkopp <socketcan@hartkopp.net>
+
+commit d59948293ea34b6337ce2b5febab8510de70048c upstream.
+
+sashiko-bot remarked a problem with a concurrent device unregistration
+in isotp.c which also is present in the bcm.c code. A former fix for raw.c
+commit c275a176e4b6 ("can: raw: add missing refcount for memory leak fix")
+introduced a netdevice_tracker which solves the issue for bcm.c too.
+
+bcm_release(), bcm_delete_rx_op() and bcm_notifier() relied on
+dev_get_by_index(ifindex) to re-find the device for an rx_op before
+unregistering its filter. If a concurrent NETDEV_UNREGISTER has already
+unlisted the device from the ifindex table, that lookup fails and
+can_rx_unregister() is silently skipped, leaving a stale CAN filter
+pointing at the soon-to-be-freed bcm_op/socket.
+
+Hold a netdev_hold()/netdev_put() tracked reference on op->rx_reg_dev
+from the moment the rx filter is registered in bcm_rx_setup() until it
+is unregistered in bcm_rx_unreg(), and use that reference directly in
+bcm_release() and bcm_delete_rx_op() instead of re-looking the device
+up by ifindex.
+
+Reported-by: sashiko-bot@kernel.org
+Closes: https://sashiko.dev/#/patchset/20260707094716.63578-1-socketcan@hartkopp.net
+Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol")
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Link: https://patch.msgid.link/20260714-bcm_fixes-v15-8-562f7e3e42da@hartkopp.net
+Cc: stable@kernel.org
+Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/can/bcm.c | 44 ++++++++++++++++++++++++--------------------
+ 1 file changed, 24 insertions(+), 20 deletions(-)
+
+diff --git a/net/can/bcm.c b/net/can/bcm.c
+index b0c20d62a32d9a..6682440585b26f 100644
+--- a/net/can/bcm.c
++++ b/net/can/bcm.c
+@@ -884,6 +884,7 @@ static void bcm_rx_unreg(struct net_device *dev, struct bcm_op *op)
+               /* mark as removed subscription */
+               op->rx_reg_dev = NULL;
++              dev_put(dev);
+       } else
+               printk(KERN_ERR "can-bcm: bcm_rx_unreg: registered device "
+                      "mismatch %p %p\n", op->rx_reg_dev, dev);
+@@ -914,17 +915,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,
+@@ -1447,7 +1445,15 @@ 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 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;
++                                      dev_hold(dev);
++                              }
+                               dev_put(dev);
+                       } else {
+                               /* the requested device is gone - do not
+@@ -1820,16 +1826,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),
+-- 
+2.53.0
+
diff --git a/queue-5.15/can-bcm-defer-rx_op-deallocation-to-workqueue-to-fix.patch b/queue-5.15/can-bcm-defer-rx_op-deallocation-to-workqueue-to-fix.patch
new file mode 100644 (file)
index 0000000..b7559cd
--- /dev/null
@@ -0,0 +1,166 @@
+From 2027827c836f1c9e02a709fd4e6ea1597260c2c9 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 18:39:44 +0200
+Subject: can: bcm: defer rx_op deallocation to workqueue to fix thrtimer UAF
+
+From: Lee Jones <lee@kernel.org>
+
+commit 68973f9db76144825e4f35dfdc80fb8279eb2d57 upstream.
+
+Commit f1b4e32aca08 ("can: bcm: use call_rcu() instead of costly
+synchronize_rcu()") replaced synchronize_rcu() in bcm_delete_rx_op()
+with call_rcu() and introduced the RX_NO_AUTOTIMER flag.
+
+However, this flag check was omitted for thrtimer in the packet rx
+fast-path. During BCM RX operation teardown, a concurrent RCU reader
+(bcm_rx_handler) can race and re-arm thrtimer via
+bcm_rx_update_and_send() after call_rcu() has been scheduled.  Once
+the RCU grace period elapses, bcm_op is freed.  The subsequently
+firing thrtimer then dereferences the deallocated op, causing a UAF.
+
+Adding flag checks to the rx fast-path (bcm_rx_update_and_send) does not
+fully close the TOCTOU race and introduces latency for every CAN frame.
+Conversely, calling hrtimer_cancel() directly inside the RCU callback
+(softirq context) is fatal as hrtimer_cancel() can sleep, triggering
+a "scheduling while atomic" panic.
+
+Resolve this by deferring the timer cancellation and memory free to a
+dedicated unbound workqueue (bcm_wq).  The RCU callback now queues a
+work item to bcm_wq, which safely cancels both timers and deallocates
+memory in sleepable process context.  A dedicated workqueue is used to
+prevent system-wide WQ saturation and is cleanly flushed/destroyed
+on module unload to avoid rmmod page faults.
+
+Since the deferred work can now outlive the calling context by an
+unbounded amount, also take a reference on op->sk when it is assigned
+and drop it only once the deferred work has cancelled both timers, so a
+socket can no longer be freed out from under a still-armed timer whose
+callback (bcm_send_to_user()) dereferences op->sk.
+
+Fixes: f1b4e32aca08 ("can: bcm: use call_rcu() instead of costly synchronize_rcu()")
+Tested-by: Feng Xue <feng.xue@outlook.com>
+Tested-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Signed-off-by: Lee Jones <lee@kernel.org>
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Link: https://patch.msgid.link/20260714-bcm_fixes-v15-1-562f7e3e42da@hartkopp.net
+Cc: stable@kernel.org
+Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/can/bcm.c | 34 ++++++++++++++++++++++++++++++++--
+ 1 file changed, 32 insertions(+), 2 deletions(-)
+
+diff --git a/net/can/bcm.c b/net/can/bcm.c
+index 317cd4c415ba28..ca0f5d0c4e9ba3 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/sock.h>
+ #include <net/net_namespace.h>
+@@ -89,6 +90,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
+@@ -102,6 +105,7 @@ 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;
+@@ -740,9 +744,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);
+@@ -750,9 +757,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);
+@@ -1008,6 +1029,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 */
+@@ -1173,6 +1195,7 @@ static int bcm_rx_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;
+               /* ifindex for timeout events w/o previous frame reception */
+@@ -1830,11 +1853,16 @@ 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 = can_proto_register(&bcm_can_proto);
+       if (err < 0) {
+               printk(KERN_ERR "can: registration of bcm protocol failed\n");
++              destroy_workqueue(bcm_wq);
+               return err;
+       }
+@@ -1848,6 +1876,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);
+-- 
+2.53.0
+
diff --git a/queue-5.15/can-bcm-extend-bcm_tx_lock-usage-for-data-and-timer-.patch b/queue-5.15/can-bcm-extend-bcm_tx_lock-usage-for-data-and-timer-.patch
new file mode 100644 (file)
index 0000000..fc7c06e
--- /dev/null
@@ -0,0 +1,230 @@
+From 1bd7020c22d6a7c76cc8ee5154c29270b368cdbf Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 12:21:44 +0200
+Subject: can: bcm: extend bcm_tx_lock usage for data and timer updates
+
+From: Oliver Hartkopp <socketcan@hartkopp.net>
+
+commit 12ce799f7ab1e05bd8fbf79e46f403bfe5597ebc upstream.
+
+Stage new CAN frame content for an existing tx op into a kmalloc()'d
+buffer and validate it there, mirroring the approach already used in
+bcm_rx_setup(). Only copy the validated data into op->frames while
+holding op->bcm_tx_lock, so bcm_can_tx() and bcm_tx_timeout_handler()
+can no longer observe a partially updated or unvalidated frame.
+
+Add a missing error path for memcpy_from_msg() when copying CAN frame
+data from userspace.
+
+Also move the kt_ival1/kt_ival2/ival1/ival2 updates in bcm_tx_setup()
+under op->bcm_tx_lock, and read kt_ival1/kt_ival2/count under the same
+lock in bcm_tx_set_expiry() and bcm_tx_timeout_handler(), closing the
+torn 64-bit ktime_t read on 32-bit platforms.
+
+Fixes: c2aba69d0c36 ("can: bcm: add locking for bcm_op runtime updates")
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Link: https://patch.msgid.link/20260714-bcm_fixes-v15-6-562f7e3e42da@hartkopp.net
+Cc: stable@kernel.org
+Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/can/bcm.c | 104 ++++++++++++++++++++++++++++++++++++--------------
+ 1 file changed, 75 insertions(+), 29 deletions(-)
+
+diff --git a/net/can/bcm.c b/net/can/bcm.c
+index 596e015b35cd12..dafc5a8df4a4d0 100644
+--- a/net/can/bcm.c
++++ b/net/can/bcm.c
+@@ -125,7 +125,7 @@ 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 */
++      spinlock_t bcm_tx_lock; /* protect tx data and timer updates */
+       spinlock_t bcm_rx_update_lock; /* protect filter/timer data updates */
+ };
+@@ -440,12 +440,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;
+@@ -462,25 +468,47 @@ 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 (tx_ival1) {
++              u32 flags, count;
++              struct bcm_timeval ival1, ival2;
+-      if (op->kt_ival1 && (op->count > 0)) {
+               bcm_can_tx(op, NULL);
+-              if (!op->count && (op->flags & TX_COUNTEVT)) {
++              /* 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) {
++      } else if (tx_ival2) {
+               bcm_can_tx(op, NULL);
+       }
+@@ -983,6 +1011,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 */
+               /*
+@@ -993,11 +1023,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)
+@@ -1007,36 +1049,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 */
+@@ -1113,10 +1157,12 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
+       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)
+-- 
+2.53.0
+
diff --git a/queue-5.15/can-bcm-fix-can-frame-rx-tx-statistics.patch b/queue-5.15/can-bcm-fix-can-frame-rx-tx-statistics.patch
new file mode 100644 (file)
index 0000000..8e0b317
--- /dev/null
@@ -0,0 +1,191 @@
+From 7f71c2ea9b3cf7cce7dee53f905546ac18a27b1b Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 12:21:43 +0200
+Subject: can: bcm: fix CAN frame rx/tx statistics
+
+From: Oliver Hartkopp <socketcan@hartkopp.net>
+
+commit e6c24ba95fc3f1b5e1dcd28b1c6e59ef61a9daa5 upstream.
+
+KCSAN detected a data race within the bcm_rx_handler() when two CAN frames
+have been simultaneously received and processed in a single rx op by two
+different CPUs.
+
+Use atomic operations with (signed) long data types to access the
+statistics in the hot path to fix the KCSAN complaint.
+
+Additionally simplify the update and check of statistics overflow by
+using the atomic operations in separate bcm_update_[rx|tx]_stats()
+functions. The rx variant runs under bcm_rx_update_lock to prevent
+races when resetting the two rx counters; the tx variant runs under
+bcm_tx_lock and only needs to guard its own counter's overflow.
+
+As the rx path resets its values already at LONG_MAX / 100, there is
+no conflict between the two locking domains (bcm_rx_update_lock vs.
+bcm_tx_lock) even for ops that use both paths.
+
+The rx statistics update and the frames_filtered update in
+bcm_rx_changed() were previously performed in two separate
+bcm_rx_update_lock sections. For an rx op subscribed on all interfaces
+(ifindex == 0), bcm_rx_handler() can run concurrently on different
+CPUs, so a counter reset by one CPU between these two sections could
+leave frames_filtered larger than frames_abs on another CPU, producing
+a bogus (even negative) reduction percentage in procfs. Update the
+statistics in the same critical section as bcm_rx_changed() to close
+this gap, which also removes the now unneeded extra lock/unlock pair
+around the traffic_flags calculation.
+
+Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol")
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Link: https://patch.msgid.link/20260714-bcm_fixes-v15-4-562f7e3e42da@hartkopp.net
+Cc: stable@kernel.org
+Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/can/bcm.c | 67 ++++++++++++++++++++++++++++++++++-----------------
+ 1 file changed, 45 insertions(+), 22 deletions(-)
+
+diff --git a/net/can/bcm.c b/net/can/bcm.c
+index f62361d9719f56..596e015b35cd12 100644
+--- a/net/can/bcm.c
++++ b/net/can/bcm.c
+@@ -109,7 +109,7 @@ struct bcm_op {
+       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;
+@@ -216,10 +216,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,
+@@ -241,9 +244,9 @@ 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);
+@@ -267,7 +270,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');
+@@ -277,6 +281,24 @@ 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
+@@ -328,7 +350,7 @@ static void bcm_can_tx(struct bcm_op *op, struct canfd_frame *cf)
+       spin_lock_bh(&op->bcm_tx_lock);
+       if (!err)
+-              op->frames_abs++;
++              bcm_update_tx_stats(op);
+       /* only advance the cyclic sequence if nothing reset currframe while
+        * we were sending - a concurrent TX_RESET_MULTI_IDX means this
+@@ -473,12 +495,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 &= (BCM_CAN_FLAGS_MASK|RX_RECV);
+@@ -713,25 +732,29 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data)
+       op->rx_stamp = skb->tstamp;
+       /* save originator for recvfrom() */
+       op->rx_ifindex = skb->dev->ifindex;
+-      /* update statistics */
+-      op->frames_abs++;
+-      /* snapshot the flag under lock: op->flags/op->frames may be updated
+-       * concurrently by bcm_rx_setup().
+-       */
++      /* 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)
++      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);
++              spin_unlock_bh(&op->bcm_rx_update_lock);
+-      if (rtr_frame) {
+               /* send reply for RTR-request (placed in op->frames[0]) */
+               bcm_can_tx(op, &rtrframe);
+               return;
+       }
+-      spin_lock_bh(&op->bcm_rx_update_lock);
++      /* 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);
+       if (op->flags & RX_FILTER_ID) {
+               /* the easiest case */
+-- 
+2.53.0
+
diff --git a/queue-5.15/can-bcm-fix-data-race-on-rx_stamp-rx_ifindex-in-bcm_.patch b/queue-5.15/can-bcm-fix-data-race-on-rx_stamp-rx_ifindex-in-bcm_.patch
new file mode 100644 (file)
index 0000000..e90b5d2
--- /dev/null
@@ -0,0 +1,74 @@
+From b24b85bb26e2189fb07e32056f1f490ae93c2dfd Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 12:21:48 +0200
+Subject: can: bcm: fix data race on rx_stamp/rx_ifindex in bcm_rx_handler()
+
+From: Oliver Hartkopp <socketcan@hartkopp.net>
+
+commit 58fd6cbc8541216af1d7ed272ea7ac2b66d50fd8 upstream.
+
+For an rx op subscribed on all interfaces (ifindex == 0), the same op
+is registered once in the shared per-netns wildcard filter list, so
+bcm_rx_handler() can run concurrently on different CPUs for frames
+arriving on different net devices.
+
+op->rx_stamp and op->rx_ifindex were written before bcm_rx_update_lock was
+taken, allowing concurrent writers to race each other - including a torn
+store of the 64-bit rx_stamp on 32-bit platforms.
+
+Beyond a torn store bcm_send_to_user() must report the timestamp/ifindex
+of the very same frame whose content it is delivering. So the assignment
+is placed in the same unbroken bcm_rx_update_lock section as the content
+comparison.
+
+As a side effect, the RTR-request frame feature (which never reach
+bcm_send_to_user()) no longer updates rx_stamp/rx_ifindex, since only
+the notification path needs them.
+
+Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol")
+Reported-by: sashiko-bot@kernel.org
+Closes: https://lore.kernel.org/linux-can/20260707145135.5BC831F00A3A@smtp.kernel.org/
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Link: https://patch.msgid.link/20260714-bcm_fixes-v15-10-562f7e3e42da@hartkopp.net
+Cc: stable@kernel.org
+Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/can/bcm.c | 13 ++++++++-----
+ 1 file changed, 8 insertions(+), 5 deletions(-)
+
+diff --git a/net/can/bcm.c b/net/can/bcm.c
+index 8d8756f39e9b56..b4255b06ff30ac 100644
+--- a/net/can/bcm.c
++++ b/net/can/bcm.c
+@@ -756,11 +756,6 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data)
+       /* disable timeout */
+       hrtimer_cancel(&op->timer);
+-      /* save rx timestamp */
+-      op->rx_stamp = skb->tstamp;
+-      /* save originator for recvfrom() */
+-      op->rx_ifindex = skb->dev->ifindex;
+-
+       /* op->flags/op->frames may be updated concurrently by bcm_rx_setup() */
+       spin_lock_bh(&op->bcm_rx_update_lock);
+@@ -784,6 +779,14 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data)
+        */
+       bcm_update_rx_stats(op);
++      /* save rx timestamp and originator for recvfrom() under lock.
++       * For an op subscribed on all interfaces (ifindex == 0)
++       * bcm_rx_handler() can run concurrently on different CPUs so
++       * the CAN content and the meta data must be bundled correctly.
++       */
++      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);
+-- 
+2.53.0
+
diff --git a/queue-5.15/can-bcm-fix-stale-rx-tx-ops-after-device-removal.patch b/queue-5.15/can-bcm-fix-stale-rx-tx-ops-after-device-removal.patch
new file mode 100644 (file)
index 0000000..9309ad7
--- /dev/null
@@ -0,0 +1,164 @@
+From 3a7d6a43fb567b61ac5abab6c5c313f78613804b Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 12:21:47 +0200
+Subject: can: bcm: fix stale rx/tx ops after device removal
+
+From: Oliver Hartkopp <socketcan@hartkopp.net>
+
+commit 3b762c0d950383ab7a002686c9136b9aa55d2d70 upstream.
+
+RX: an RX_SETUP update(!) for an existing op skipped can_rx_register()
+unconditionally, even when a concurrent NETDEV_UNREGISTER had already
+torn down its registration (op->rx_reg_dev == NULL). This silently
+did not re-enable frame delivery for that updated filter. bcm_rx_setup()
+now re-registers in that case, while leaving rx_ops with ifindex = 0
+(all CAN devices) which never carry a tracked rx_reg_dev registered as-is.
+
+TX: bcm_notify() only handled bo->rx_ops on NETDEV_UNREGISTER, leaving
+tx_ops with an active cyclic transmission re-arming its hrtimer
+indefinitely to execute bcm_tx_timeout_handler(). Cancelling the hrtimer
+prevents the runaway timer and any injection into a later reused ifindex,
+since nothing else calls bcm_can_tx() for the op until an explicit
+TX_SETUP update re-arms it.
+
+Unlike bcm_rx_unreg(), which clears the tracked rx_reg_dev for rx_ops,
+the ifindex is intentionally left unchanged for tx_ops. bcm_tx_setup()
+always rejects ifindex 0, so clearing it would strand the op: neither a
+later TX_SETUP (bcm_find_op()) nor TX_DELETE (bcm_delete_tx_op()) could
+ever find it again, since both require an exact ifindex match.
+
+Reported-by: sashiko-bot@kernel.org
+Closes: https://lore.kernel.org/linux-can/20260708094536.DDF821F00A3A@smtp.kernel.org/
+Closes: https://lore.kernel.org/linux-can/20260708154039.347ED1F000E9@smtp.kernel.org/
+Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol")
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Link: https://patch.msgid.link/20260714-bcm_fixes-v15-9-562f7e3e42da@hartkopp.net
+Cc: stable@kernel.org
+Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/can/bcm.c | 54 +++++++++++++++++++++++++++++++++++++++++----------
+ 1 file changed, 44 insertions(+), 10 deletions(-)
+
+diff --git a/net/can/bcm.c b/net/can/bcm.c
+index 6682440585b26f..8d8756f39e9b56 100644
+--- a/net/can/bcm.c
++++ b/net/can/bcm.c
+@@ -1234,6 +1234,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))) {
+@@ -1318,8 +1319,15 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
+               /* free temporary frames / kfree(NULL) is safe */
+               kfree(new_frames);
+-              /* Only an update -> do not call can_rx_register() */
+-              do_rx_register = 0;
++              /* 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 */
+@@ -1389,6 +1397,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
+               /* call can_rx_register() */
+               do_rx_register = 1;
++              new_op = 1;
+       } /* if ((op = bcm_find_op(&bo->rx_ops, msg_head->can_id, ifindex))) */
+@@ -1402,7 +1411,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
+               if (op->flags & SETTIMER) {
+                       /* set timers (locked) for newly created op */
+-                      if (do_rx_register) {
++                      if (new_op) {
+                               spin_lock_bh(&op->bcm_rx_update_lock);
+                               op->ival1 = msg_head->ival1;
+                               op->ival2 = msg_head->ival2;
+@@ -1432,7 +1441,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;
+@@ -1462,18 +1474,32 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
+                               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 */
+-                      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 this bcm_op to the list of the rx_ops */
+-              list_add_rcu(&op->list, &bo->rx_ops);
++              /* 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;
+@@ -1689,11 +1715,19 @@ static void bcm_notify(struct bcm_sock *bo, unsigned long msg,
+       case NETDEV_UNREGISTER:
+               lock_sock(sk);
+-              /* remove device specific receive entries */
++              /* 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);
++              /* 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)
+-- 
+2.53.0
+
diff --git a/queue-5.15/can-bcm-track-a-single-source-interface-for-anydev-t.patch b/queue-5.15/can-bcm-track-a-single-source-interface-for-anydev-t.patch
new file mode 100644 (file)
index 0000000..2496451
--- /dev/null
@@ -0,0 +1,143 @@
+From a1e88cb611103f09ef0e9edd90aec62d8a26c83b Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 12:21:49 +0200
+Subject: can: bcm: track a single source interface for ANYDEV timeout/throttle
+ ops
+
+From: Oliver Hartkopp <socketcan@hartkopp.net>
+
+commit 2f5976f54a04e9f18b25283036ac3136be453b17 upstream.
+
+An ANYDEV rx op (ifindex == 0) with an active RX timeout and/or
+throttle timer has no defined semantics when matching frames arrive
+from several interfaces: bcm_rx_handler() can run concurrently for
+the same op on different CPUs, racing hrtimer_cancel()/
+bcm_rx_starttimer() against bcm_rx_timeout_handler() and causing
+spurious RX_TIMEOUT notifications and last_frames corruption. The
+same concurrency lets throttled multiplex frames from different
+interfaces clobber the single rx_ifindex/rx_stamp fields shared by
+the op.
+
+Add op->if_detected to track the first interface that delivers a
+matching frame while a timeout/throttle timer is configured, and
+reject frames from any other interface for that op. The claim is
+decided in bcm_rx_handler() before hrtimer_cancel() touches
+op->timer, so a rejected frame can never disturb the claimed
+interface's watchdog. RTR-mode ops are excluded via RX_RTR_FRAME,
+independent of kt_ival1/kt_ival2, since those may briefly hold a
+stale value from an earlier non-RTR configuration.
+
+The claim is released in bcm_notify() on NETDEV_UNREGISTER and in
+bcm_rx_setup() when SETTIMER reconfigures the timer values.
+
+A (re-)claim is only possible on CAN devices in NETREG_REGISTERED
+dev->reg_state to cover the release in bcm_notify() where reg_state
+becomes NETREG_UNREGISTERING until synchronize_net().
+
+Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol")
+Reported-by: sashiko-bot@kernel.org
+Closes: https://lore.kernel.org/linux-can/20260709105031.1A39C1F000E9@smtp.kernel.org/
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Link: https://patch.msgid.link/20260714-bcm_fixes-v15-11-562f7e3e42da@hartkopp.net
+Cc: stable@kernel.org
+Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/can/bcm.c | 49 ++++++++++++++++++++++++++++++++++++++++++++-----
+ 1 file changed, 44 insertions(+), 5 deletions(-)
+
+diff --git a/net/can/bcm.c b/net/can/bcm.c
+index b4255b06ff30ac..2cf63940f6a9bb 100644
+--- a/net/can/bcm.c
++++ b/net/can/bcm.c
+@@ -114,6 +114,7 @@ struct bcm_op {
+       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;
+@@ -753,6 +754,33 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data)
+       if (skb->len != op->cfsiz)
+               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 &&
++                          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);
+@@ -779,10 +807,9 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data)
+        */
+       bcm_update_rx_stats(op);
+-      /* save rx timestamp and originator for recvfrom() under lock.
+-       * For an op subscribed on all interfaces (ifindex == 0)
+-       * bcm_rx_handler() can run concurrently on different CPUs so
+-       * the CAN content and the meta data must be bundled correctly.
++      /* 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;
+@@ -1316,6 +1343,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
+                       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);
+@@ -1719,10 +1747,21 @@ static void bcm_notify(struct bcm_sock *bo, unsigned long msg,
+               lock_sock(sk);
+               /* rx_ops: remove device specific receive entries */
+-              list_for_each_entry(op, &bo->rx_ops, list)
++              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.
+-- 
+2.53.0
+
diff --git a/queue-5.15/can-bcm-validate-frame-length-in-bcm_rx_setup-for-rt.patch b/queue-5.15/can-bcm-validate-frame-length-in-bcm_rx_setup-for-rt.patch
new file mode 100644 (file)
index 0000000..88f4282
--- /dev/null
@@ -0,0 +1,128 @@
+From da7bd2ed09734f49954740bab566c047fd2f9fc9 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 12:21:45 +0200
+Subject: can: bcm: validate frame length in bcm_rx_setup() for RTR replies
+
+From: Oliver Hartkopp <socketcan@hartkopp.net>
+
+commit 62ec41f364648be79d54d94d0d240ee326948afd upstream.
+
+bcm_tx_setup() validates cf->len against the CAN/CAN FD DLC limits
+before installing frames for TX_SETUP, but bcm_rx_setup() never did
+the same for the RTR-reply frame configured via RX_SETUP with
+RX_RTR_FRAME.
+
+Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol")
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Link: https://patch.msgid.link/20260714-bcm_fixes-v15-7-562f7e3e42da@hartkopp.net
+Cc: stable@kernel.org
+Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
+Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/can/bcm.c | 59 +++++++++++++++++++++++++++++++++++----------------
+ 1 file changed, 41 insertions(+), 18 deletions(-)
+
+diff --git a/net/can/bcm.c b/net/can/bcm.c
+index dafc5a8df4a4d0..b0c20d62a32d9a 100644
+--- a/net/can/bcm.c
++++ b/net/can/bcm.c
+@@ -1194,22 +1194,37 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
+       return err;
+ }
+-static void bcm_rx_setup_rtr_check(struct bcm_msg_head *msg_head,
+-                                 struct bcm_op *op, void *new_frames)
++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 & RX_RTR_FRAME) {
+-              struct canfd_frame *frame0 = new_frames;
++      if ((msg_head->flags & TX_CP_CAN_ID) ||
++          frame0->can_id == op->can_id)
++              frame0->can_id = op->can_id & ~CAN_RTR_FLAG;
+-              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;
+ }
+ /*
+@@ -1272,7 +1287,11 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
+                               return err;
+                       }
+-                      bcm_rx_setup_rtr_check(msg_head, op, new_frames);
++                      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);
+@@ -1345,16 +1364,12 @@ 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;
+-                      bcm_rx_setup_rtr_check(msg_head, op, op->frames);
++                      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 */
+@@ -1456,6 +1471,14 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
+       }
+       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;
+ }
+ /*
+-- 
+2.53.0
+
index bea4f987a3d690c560c56b1f870dd56e41f42c98..fad2138a2940e25403476ed6ff1c83b3ead4b57b 100644 (file)
@@ -132,3 +132,12 @@ net-mlx5e-reject-unsupported-cb-shaper-tsa-in-ets-va.patch
 raw-use-more-conventional-iterators.patch
 net-ipv6-fix-dif-and-sdif-mismatch-in-raw6_icmp_erro.patch
 bpf-sockmap-fix-cork-use-after-free-in-tcp_bpf_sendm.patch
+can-bcm-defer-rx_op-deallocation-to-workqueue-to-fix.patch
+can-bcm-add-locking-when-updating-filter-and-timer-v.patch
+can-bcm-fix-can-frame-rx-tx-statistics.patch
+can-bcm-extend-bcm_tx_lock-usage-for-data-and-timer-.patch
+can-bcm-validate-frame-length-in-bcm_rx_setup-for-rt.patch
+can-bcm-add-missing-device-refcount-for-can-filter-r.patch
+can-bcm-fix-stale-rx-tx-ops-after-device-removal.patch
+can-bcm-fix-data-race-on-rx_stamp-rx_ifindex-in-bcm_.patch
+can-bcm-track-a-single-source-interface-for-anydev-t.patch