From: Sasha Levin Date: Wed, 29 Jul 2026 03:17:49 +0000 (-0400) Subject: Fixes for all trees X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=273c87fb54aeaa97626013fbf65ce5ef7928d9d7;p=thirdparty%2Fkernel%2Fstable-queue.git Fixes for all trees Signed-off-by: Sasha Levin --- 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 index 0000000000..3c30f7a3e3 --- /dev/null +++ b/queue-5.10/can-bcm-add-locking-when-updating-filter-and-timer-v.patch @@ -0,0 +1,413 @@ +From de58298c50269931fdf484d18b536074b2cb64c2 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:13:35 +0200 +Subject: can: bcm: add locking when updating filter and timer values + +From: Oliver Hartkopp + +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 +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-3-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + 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 index 0000000000..920bab8c4c --- /dev/null +++ b/queue-5.10/can-bcm-add-missing-device-refcount-for-can-filter-r.patch @@ -0,0 +1,121 @@ +From 2deb1956ecd5c0864de65e9a417aa27f2d66f1b6 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:13:39 +0200 +Subject: can: bcm: add missing device refcount for CAN filter removal + +From: Oliver Hartkopp + +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 +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-8-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + 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 index 0000000000..3b6b228fc5 --- /dev/null +++ b/queue-5.10/can-bcm-defer-rx_op-deallocation-to-workqueue-to-fix.patch @@ -0,0 +1,166 @@ +From 81357d5205b8f9e6b5d1798da1b2efb917fc8411 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +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 + +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 +Tested-by: Oliver Hartkopp +Signed-off-by: Lee Jones +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-1-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + 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 + #include + #include ++#include + #include + #include + #include +@@ -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 index 0000000000..b919e3cdc1 --- /dev/null +++ b/queue-5.10/can-bcm-extend-bcm_tx_lock-usage-for-data-and-timer-.patch @@ -0,0 +1,230 @@ +From 45e930037ec1b9eac10d49cf525c931519a94714 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +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 + +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 +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-6-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + 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 index 0000000000..0c8c1ea263 --- /dev/null +++ b/queue-5.10/can-bcm-fix-can-frame-rx-tx-statistics.patch @@ -0,0 +1,191 @@ +From 95ec4c6e157ca8f15bc0bf64e62ae008e9a0401b Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:13:36 +0200 +Subject: can: bcm: fix CAN frame rx/tx statistics + +From: Oliver Hartkopp + +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 +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-4-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + 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 index 0000000000..a321bbe3d6 --- /dev/null +++ b/queue-5.10/can-bcm-fix-data-race-on-rx_stamp-rx_ifindex-in-bcm_.patch @@ -0,0 +1,74 @@ +From d882912b7ff9d73b942418c0ef1e3e5c95b0e972 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +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 + +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 +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-10-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + 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 index 0000000000..cf3e0cce33 --- /dev/null +++ b/queue-5.10/can-bcm-fix-stale-rx-tx-ops-after-device-removal.patch @@ -0,0 +1,164 @@ +From 99f31f2addae169c087376c48e3505720ecceaed Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:13:40 +0200 +Subject: can: bcm: fix stale rx/tx ops after device removal + +From: Oliver Hartkopp + +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 +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-9-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + 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 index 0000000000..9d7962a920 --- /dev/null +++ b/queue-5.10/can-bcm-track-a-single-source-interface-for-anydev-t.patch @@ -0,0 +1,143 @@ +From 81ab3bee66367e82ce05042c398f119e69b55df1 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +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 + +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 +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-11-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + 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 index 0000000000..cf07a4a798 --- /dev/null +++ b/queue-5.10/can-bcm-validate-frame-length-in-bcm_rx_setup-for-rt.patch @@ -0,0 +1,128 @@ +From 52c04ac78d9bf458516fb6ab6442dbb861473205 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +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 + +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 +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-7-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + 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 + diff --git a/queue-5.10/series b/queue-5.10/series index aa4b53e215..873512179e 100644 --- a/queue-5.10/series +++ b/queue-5.10/series @@ -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 index 0000000000..ba1fe0066f --- /dev/null +++ b/queue-5.15/can-bcm-add-locking-when-updating-filter-and-timer-v.patch @@ -0,0 +1,413 @@ +From c4af7dcbccbd407527f7fe82b813f2889531d396 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:21:42 +0200 +Subject: can: bcm: add locking when updating filter and timer values + +From: Oliver Hartkopp + +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 +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-3-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + 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 index 0000000000..5511062624 --- /dev/null +++ b/queue-5.15/can-bcm-add-missing-device-refcount-for-can-filter-r.patch @@ -0,0 +1,121 @@ +From 4a97fce2809debe6a543c17ee89b74472f595ff2 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:21:46 +0200 +Subject: can: bcm: add missing device refcount for CAN filter removal + +From: Oliver Hartkopp + +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 +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-8-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + 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 index 0000000000..b7559cdfe8 --- /dev/null +++ b/queue-5.15/can-bcm-defer-rx_op-deallocation-to-workqueue-to-fix.patch @@ -0,0 +1,166 @@ +From 2027827c836f1c9e02a709fd4e6ea1597260c2c9 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +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 + +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 +Tested-by: Oliver Hartkopp +Signed-off-by: Lee Jones +Signed-off-by: Oliver Hartkopp +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-1-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + 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 + #include + #include ++#include + #include + #include + #include +@@ -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 index 0000000000..fc7c06efd6 --- /dev/null +++ b/queue-5.15/can-bcm-extend-bcm_tx_lock-usage-for-data-and-timer-.patch @@ -0,0 +1,230 @@ +From 1bd7020c22d6a7c76cc8ee5154c29270b368cdbf Mon Sep 17 00:00:00 2001 +From: Sasha Levin +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 + +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 +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-6-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + 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 index 0000000000..8e0b317731 --- /dev/null +++ b/queue-5.15/can-bcm-fix-can-frame-rx-tx-statistics.patch @@ -0,0 +1,191 @@ +From 7f71c2ea9b3cf7cce7dee53f905546ac18a27b1b Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:21:43 +0200 +Subject: can: bcm: fix CAN frame rx/tx statistics + +From: Oliver Hartkopp + +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 +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-4-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + 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 index 0000000000..e90b5d2529 --- /dev/null +++ b/queue-5.15/can-bcm-fix-data-race-on-rx_stamp-rx_ifindex-in-bcm_.patch @@ -0,0 +1,74 @@ +From b24b85bb26e2189fb07e32056f1f490ae93c2dfd Mon Sep 17 00:00:00 2001 +From: Sasha Levin +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 + +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 +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-10-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + 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 index 0000000000..9309ad77b9 --- /dev/null +++ b/queue-5.15/can-bcm-fix-stale-rx-tx-ops-after-device-removal.patch @@ -0,0 +1,164 @@ +From 3a7d6a43fb567b61ac5abab6c5c313f78613804b Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 24 Jul 2026 12:21:47 +0200 +Subject: can: bcm: fix stale rx/tx ops after device removal + +From: Oliver Hartkopp + +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 +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-9-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + 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 index 0000000000..2496451df4 --- /dev/null +++ b/queue-5.15/can-bcm-track-a-single-source-interface-for-anydev-t.patch @@ -0,0 +1,143 @@ +From a1e88cb611103f09ef0e9edd90aec62d8a26c83b Mon Sep 17 00:00:00 2001 +From: Sasha Levin +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 + +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 +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-11-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + 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 index 0000000000..88f4282f83 --- /dev/null +++ b/queue-5.15/can-bcm-validate-frame-length-in-bcm_rx_setup-for-rt.patch @@ -0,0 +1,128 @@ +From da7bd2ed09734f49954740bab566c047fd2f9fc9 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +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 + +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 +Link: https://patch.msgid.link/20260714-bcm_fixes-v15-7-562f7e3e42da@hartkopp.net +Cc: stable@kernel.org +Signed-off-by: Marc Kleine-Budde +Signed-off-by: Oliver Hartkopp +Signed-off-by: Sasha Levin +--- + 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 + diff --git a/queue-5.15/series b/queue-5.15/series index bea4f987a3..fad2138a29 100644 --- a/queue-5.15/series +++ b/queue-5.15/series @@ -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