FAILED: patch "[PATCH] can: bcm: defer rx_op deallocation to workqueue to fix" failed to apply to 5.15-stable tree
Subsystems (2)
[email protected]
maintainerThe patch below does not apply to the 5.15-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-5.15.y
git checkout FETCH_HEAD
git cherry-pick -x 68973f9db76144825e4f35dfdc80fb8279eb2d57
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026072036-giving-knee-3a33@gregkh' --subject-prefix 'PATCH 5.15.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 68973f9db76144825e4f35dfdc80fb8279eb2d57 Mon Sep 17 00:00:00 2001
From: Lee Jones <[email protected]>
Date: Tue, 14 Jul 2026 18:55:23 +0200
Subject: [PATCH] can: bcm: defer rx_op deallocation to workqueue to fix
thrtimer UAF
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.
net/can/bcm.c +34 -3
@@ -58,6 +58,7 @@#include <linux/can/skb.h> #include <linux/can/bcm.h> #include <linux/slab.h> +#include <linux/workqueue.h> #include <linux/spinlock.h> #include <net/can.h> #include <net/sock.h>
@@ -92,6 +93,8 @@ MODULE_ALIAS("can-proto-2");#define BCM_MIN_NAMELEN CAN_REQUIRED_SIZE(struct sockaddr_can, can_ifindex) +static struct workqueue_struct *bcm_wq; +/* * easy access to the first 64 bit of can(fd)_frame payload. cp->data is * 64 bit aligned so the offset has to be multiples of 8 which is ensured
@@ -105,6 +108,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;
@@ -793,9 +797,12 @@ static struct bcm_op *bcm_find_op(struct list_head *ops,return NULL ; } -static void bcm_free_op_rcu (struct rcu_head *rcu_head) +static void bcm_free_op_work (struct work_struct *work) { - struct bcm_op *op = container_of(rcu_head, struct bcm_op, rcu); + struct bcm_op *op = container_of(work, struct bcm_op, work); + +hrtimer_cancel (&op->timer); +hrtimer_cancel (&op->thrtimer);if ((op->frames) && (op->frames != &op->sframe))kfree (op->frames);
@@ -803,9 +810,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);
@@ -1060,6 +1081,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 */
@@ -1221,6 +1243,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 */
@@ -1839,11 +1862,15 @@ static int __init bcm_module_init(void) {int err; + bcm_wq =alloc_workqueue ("can-bcm-wq" ,WQ_UNBOUND ,0 ); +if (!bcm_wq) +return -ENOMEM ; +pr_info ("can: broadcast manager protocol\n" ); err =register_pernet_subsys (&canbcm_pernet_ops);if (err) -return err; + goto register_pernet_failed; err =register_netdevice_notifier (&canbcm_notifier);if (err)
@@ -1861,6 +1888,8 @@ static int __init bcm_module_init(void)unregister_netdevice_notifier (&canbcm_notifier); register_notifier_failed:unregister_pernet_subsys (&canbcm_pernet_ops); +register_pernet_failed: +destroy_workqueue (bcm_wq);return err; }
@@ -1869,6 +1898,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);
Oliver Hartkopp
maintainerPATCH 1/9 can: bcm: defer rx_op deallocation to workqueue to fix thrtimer UAF
From: Lee Jones <[email protected]>
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.
--- net/can/bcm.c | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-)
net/can/bcm.c +33 -3
@@ -58,6 +58,7 @@#include <linux/can/skb.h> #include <linux/can/bcm.h> #include <linux/slab.h> +#include <linux/workqueue.h> #include <linux/spinlock.h> #include <net/sock.h> #include <net/net_namespace.h>
@@ -89,6 +90,8 @@ MODULE_ALIAS("can-proto-2");#define BCM_MIN_NAMELEN CAN_REQUIRED_SIZE(struct sockaddr_can, can_ifindex) +static struct workqueue_struct *bcm_wq; +/* * easy access to the first 64 bit of can(fd)_frame payload. cp->data is * 64 bit aligned so the offset has to be multiples of 8 which is ensured
@@ -102,6 +105,7 @@ static inline u64 get_u64(const struct canfd_frame *cp, int offset)struct bcm_op {struct list_head list;struct rcu_head rcu; +struct work_struct work;int ifindex;canid_t can_id;u32 flags;
@@ -740,9 +744,12 @@ static struct bcm_op *bcm_find_op(struct list_head *ops,return NULL ; } -static void bcm_free_op_rcu (struct rcu_head *rcu_head) +static void bcm_free_op_work (struct work_struct *work) { - struct bcm_op *op = container_of(rcu_head, struct bcm_op, rcu); + struct bcm_op *op = container_of(work, struct bcm_op, work); + +hrtimer_cancel (&op->timer); +hrtimer_cancel (&op->thrtimer);if ((op->frames) && (op->frames != &op->sframe))kfree (op->frames);
@@ -750,9 +757,23 @@ static void bcm_free_op_rcu(struct rcu_head *rcu_head)if ((op->last_frames) && (op->last_frames != &op->last_sframe))kfree (op->last_frames); +/* the last possible access to op->timer/op->thrtimer has now +* happened above via hrtimer_cancel() - op->sk is no longer +* needed by any pending timer callback, so drop our reference +*/ +sock_put (op->sk); +kfree (op); } +static void bcm_free_op_rcu (struct rcu_head *rcu_head) +{ + struct bcm_op *op = container_of(rcu_head, struct bcm_op, rcu); + +INIT_WORK (&op->work, bcm_free_op_work); +queue_work (bcm_wq, &op->work); +} +static void bcm_remove_op (struct bcm_op *op) {hrtimer_cancel (&op->timer);
@@ -1008,6 +1029,7 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,/* bcm_can_tx / bcm_tx_timeout_handler needs this */ op->sk = sk; +sock_hold (sk); op->ifindex = ifindex;/* initialize uninitialized (kzalloc) structure */
@@ -1173,6 +1195,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,/* bcm_can_tx / bcm_tx_timeout_handler needs this */ op->sk = sk; +sock_hold (sk); op->ifindex = ifindex;/* ifindex for timeout events w/o previous frame reception */
@@ -1699,7 +1722,7 @@ static int bcm_connect(struct socket *sock, struct sockaddr *uaddr, int len,#if IS_ENABLED (CONFIG_PROC_FS )if (net->can.bcmproc_dir) {/* unique socket address as filename */ -sprintf (bo->procname,"%lu" ,sock_i_ino (sk)); +sprintf (bo->procname,"%llu" ,sock_i_ino (sk)); bo->bcm_proc_read =proc_create_net_single (bo->procname,0644 , net->can.bcmproc_dir, bcm_proc_show, sk);
@@ -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);
Signature · 1 line
2.43.0
Oliver Hartkopp
maintainerPATCH 1/9 can: bcm: defer rx_op deallocation to workqueue to fix thrtimer UAF
In reply to [email protected]From: Lee Jones <[email protected]>
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.
--- net/can/bcm.c | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-)
net/can/bcm.c +33 -3
@@ -58,6 +58,7 @@#include <linux/can/skb.h> #include <linux/can/bcm.h> #include <linux/slab.h> +#include <linux/workqueue.h> #include <linux/spinlock.h> #include <net/sock.h> #include <net/net_namespace.h>
@@ -89,6 +90,8 @@ MODULE_ALIAS("can-proto-2");#define BCM_MIN_NAMELEN CAN_REQUIRED_SIZE(struct sockaddr_can, can_ifindex) +static struct workqueue_struct *bcm_wq; +/* * easy access to the first 64 bit of can(fd)_frame payload. cp->data is * 64 bit aligned so the offset has to be multiples of 8 which is ensured
@@ -102,6 +105,7 @@ static inline u64 get_u64(const struct canfd_frame *cp, int offset)struct bcm_op {struct list_head list;struct rcu_head rcu; +struct work_struct work;int ifindex;canid_t can_id;u32 flags;
@@ -740,9 +744,12 @@ static struct bcm_op *bcm_find_op(struct list_head *ops,return NULL ; } -static void bcm_free_op_rcu (struct rcu_head *rcu_head) +static void bcm_free_op_work (struct work_struct *work) { - struct bcm_op *op = container_of(rcu_head, struct bcm_op, rcu); + struct bcm_op *op = container_of(work, struct bcm_op, work); + +hrtimer_cancel (&op->timer); +hrtimer_cancel (&op->thrtimer);if ((op->frames) && (op->frames != &op->sframe))kfree (op->frames);
@@ -750,9 +757,23 @@ static void bcm_free_op_rcu(struct rcu_head *rcu_head)if ((op->last_frames) && (op->last_frames != &op->last_sframe))kfree (op->last_frames); +/* the last possible access to op->timer/op->thrtimer has now +* happened above via hrtimer_cancel() - op->sk is no longer +* needed by any pending timer callback, so drop our reference +*/ +sock_put (op->sk); +kfree (op); } +static void bcm_free_op_rcu (struct rcu_head *rcu_head) +{ + struct bcm_op *op = container_of(rcu_head, struct bcm_op, rcu); + +INIT_WORK (&op->work, bcm_free_op_work); +queue_work (bcm_wq, &op->work); +} +static void bcm_remove_op (struct bcm_op *op) {hrtimer_cancel (&op->timer);
@@ -1008,6 +1029,7 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,/* bcm_can_tx / bcm_tx_timeout_handler needs this */ op->sk = sk; +sock_hold (sk); op->ifindex = ifindex;/* initialize uninitialized (kzalloc) structure */
@@ -1173,6 +1195,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,/* bcm_can_tx / bcm_tx_timeout_handler needs this */ op->sk = sk; +sock_hold (sk); op->ifindex = ifindex;/* ifindex for timeout events w/o previous frame reception */
@@ -1699,7 +1722,7 @@ static int bcm_connect(struct socket *sock, struct sockaddr *uaddr, int len,#if IS_ENABLED (CONFIG_PROC_FS )if (net->can.bcmproc_dir) {/* unique socket address as filename */ -sprintf (bo->procname,"%lu" ,sock_i_ino (sk)); +sprintf (bo->procname,"%llu" ,sock_i_ino (sk)); bo->bcm_proc_read =proc_create_net_single (bo->procname,0644 , net->can.bcmproc_dir, bcm_proc_show, sk);
@@ -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);
Signature · 1 line
2.43.0
Oliver Hartkopp
maintainerPATCH 2/9 can: bcm: add locking when updating filter and timer values
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.
--- net/can/bcm.c | 178 +++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 134 insertions(+), 44 deletions(-)
net/can/bcm.c +134 -44
@@ -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 @@ free_op: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)
Signature · 1 line
2.43.0
Oliver Hartkopp
maintainerPATCH 3/9 can: bcm: fix CAN frame rx/tx statistics
In reply to Oliver Hartkoppcommit 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.
--- net/can/bcm.c | 67 +++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 22 deletions(-)
net/can/bcm.c +45 -22
@@ -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 */
Signature · 1 line
2.43.0
Oliver Hartkopp
maintainerPATCH 4/9 can: bcm: extend bcm_tx_lock usage for data and timer updates
In reply to Oliver Hartkoppcommit 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.
--- net/can/bcm.c | 104 ++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 75 insertions(+), 29 deletions(-)
net/can/bcm.c +75 -29
@@ -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)
Signature · 1 line
2.43.0
Oliver Hartkopp
maintainerPATCH 5/9 can: bcm: validate frame length in bcm_rx_setup() for RTR replies
In reply to Oliver Hartkoppcommit 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.
--- net/can/bcm.c | 59 +++++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 18 deletions(-)
net/can/bcm.c +41 -18
@@ -1194,22 +1194,37 @@ free_op: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; }/*
Signature · 1 line
2.43.0
Oliver Hartkopp
maintainerPATCH 6/9 can: bcm: add missing device refcount for CAN filter removal
In reply to Oliver Hartkoppcommit 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.
--- net/can/bcm.c | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-)
net/can/bcm.c +24 -20
@@ -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),
Signature · 1 line
2.43.0
Oliver Hartkopp
maintainerPATCH 7/9 can: bcm: fix stale rx/tx ops after device removal
In reply to Oliver Hartkoppcommit 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.
--- net/can/bcm.c | 54 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 10 deletions(-)
net/can/bcm.c +44 -10
@@ -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 )
Signature · 1 line
2.43.0
Oliver Hartkopp
maintainerPATCH 8/9 can: bcm: fix data race on rx_stamp/rx_ifindex in bcm_rx_handler()
In reply to Oliver Hartkoppcommit 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.
--- net/can/bcm.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-)
net/can/bcm.c +8 -5
@@ -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);
Signature · 1 line
2.43.0
Oliver Hartkopp
maintainerPATCH 9/9 can: bcm: track a single source interface for ANYDEV timeout/throttle ops
In reply to Oliver Hartkoppcommit 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().
--- net/can/bcm.c | 49 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 5 deletions(-)
net/can/bcm.c +44 -5
@@ -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.
Signature · 1 line
2.43.0
Sasha Levin
maintainerRe: [PATCH 5.15.y 1/9] can: bcm: defer rx_op deallocation to workqueue to fix thrtimer UAF
In reply to Oliver Hartkoppnet/can/bcm.c static int bcm_connect(struct socket *sock, struct sockaddr *uaddr, int len, · line 1725
Quoted 2 lines
net/can/bcm.c +1
-1-sprintf (bo->procname,"%lu" ,sock_i_ino (sk)); +sprintf (bo->procname,"%llu" ,sock_i_ino (sk));
This hunk is not part of 68973f9db761 ("can: bcm: defer rx_op deallocation to workqueue to fix thrtimer UAF"). It belongs to 0fe27e598592 ("net:
change sock.sk_ino and sock_i_ino() to u64"), which is not in 5.15.y - sock_i_ino() still returns unsigned long there. The result is a -Wformat warning (a hard build failure under CONFIG_WERROR) and, on 32-bit arches, a /proc/net/can-bcm/ file name built partly out of adjacent stack.
Patches 2/9 to 9/9 look fine on both 5.10.y and 5.15.y. Could you respin 1/9 without that hunk? I'll take the full series once it's in.
Signature · 2 lines
Thanks, Sasha
Oliver Hartkopp
maintainerRe: [PATCH 5.15.y 1/9] can: bcm: defer rx_op deallocation to workqueue to fix thrtimer UAF
Quoted 10 lines · Sasha Levin
net/can/bcm.c static int bcm_connect(struct socket *sock, struct sockaddr *uaddr, int len, · line 1725
Quoted 2 lines
net/can/bcm.c +1
-1-sprintf (bo->procname,"%lu" ,sock_i_ino (sk)); +sprintf (bo->procname,"%llu" ,sock_i_ino (sk));This hunk is not part of
68973f9db761("can: bcm: deferrx_opdeallocation to workqueue to fix thrtimer UAF"). It belongs to0fe27e598592("net:
change sock.sk_ino andsock_i_ino()to u64"), which is not in 5.15.y -sock_i_ino()still returns unsigned long there. The result is a -Wformat warning (a hard build failure underCONFIG_WERROR) and, on 32-bit arches, a/proc/net/can-bcm/file name built partly out of adjacent stack.
Quoted 3 lines
Patches 2/9 to 9/9 look fine on both 5.10.y and 5.15.y. Could you respin 1/9 without that hunk? I'll take the full series once it's in.
[PATCH 5.15.y v2 1/9] can: bcm: defer rx_op deallocation to workqueue to fix thrtimer UAF
without the "%llu" hunk is here:
https://lore.kernel.org/stable/[email protected]/
Many thanks!
Oliver