Skip to content

Commit e6c24ba

Browse files
hartkoppmarckleinebudde
authored andcommitted
can: bcm: fix CAN frame rx/tx statistics
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: ffd980f ("[CAN]: Add broadcast manager (bcm) protocol") Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net> Link: https://patch.msgid.link/20260714-bcm_fixes-v15-4-562f7e3e42da@hartkopp.net Cc: stable@kernel.org Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
1 parent 749179c commit e6c24ba

1 file changed

Lines changed: 46 additions & 23 deletions

File tree

net/can/bcm.c

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ struct bcm_op {
112112
int ifindex;
113113
canid_t can_id;
114114
u32 flags;
115-
unsigned long frames_abs, frames_filtered;
115+
atomic_long_t frames_abs, frames_filtered;
116116
struct bcm_timeval ival1, ival2;
117117
struct hrtimer timer, thrtimer;
118118
ktime_t rx_stamp, kt_ival1, kt_ival2, kt_lastmsg;
@@ -229,10 +229,13 @@ static int bcm_proc_show(struct seq_file *m, void *v)
229229

230230
list_for_each_entry_rcu(op, &bo->rx_ops, list) {
231231

232-
unsigned long reduction;
232+
long reduction, frames_filtered, frames_abs;
233+
234+
frames_filtered = atomic_long_read(&op->frames_filtered);
235+
frames_abs = atomic_long_read(&op->frames_abs);
233236

234237
/* print only active entries & prevent division by zero */
235-
if (!op->frames_abs)
238+
if (!frames_abs)
236239
continue;
237240

238241
seq_printf(m, "rx_op: %03X %-5s ", op->can_id,
@@ -254,9 +257,9 @@ static int bcm_proc_show(struct seq_file *m, void *v)
254257
(long long)ktime_to_us(op->kt_ival2));
255258

256259
seq_printf(m, "# recv %ld (%ld) => reduction: ",
257-
op->frames_filtered, op->frames_abs);
260+
frames_filtered, frames_abs);
258261

259-
reduction = 100 - (op->frames_filtered * 100) / op->frames_abs;
262+
reduction = 100 - (frames_filtered * 100) / frames_abs;
260263

261264
seq_printf(m, "%s%ld%%\n",
262265
(reduction == 100) ? "near " : "", reduction);
@@ -280,7 +283,8 @@ static int bcm_proc_show(struct seq_file *m, void *v)
280283
seq_printf(m, "t2=%lld ",
281284
(long long)ktime_to_us(op->kt_ival2));
282285

283-
seq_printf(m, "# sent %ld\n", op->frames_abs);
286+
seq_printf(m, "# sent %ld\n",
287+
atomic_long_read(&op->frames_abs));
284288
}
285289
seq_putc(m, '\n');
286290

@@ -290,6 +294,24 @@ static int bcm_proc_show(struct seq_file *m, void *v)
290294
}
291295
#endif /* CONFIG_PROC_FS */
292296

297+
static void bcm_update_rx_stats(struct bcm_op *op)
298+
{
299+
/* prevent overflow of the reduction% calculation in bcm_proc_show() */
300+
if (atomic_long_inc_return(&op->frames_abs) > LONG_MAX / 100) {
301+
atomic_long_set(&op->frames_filtered, 0);
302+
atomic_long_set(&op->frames_abs, 0);
303+
}
304+
}
305+
306+
static void bcm_update_tx_stats(struct bcm_op *op)
307+
{
308+
/* tx_op has no reduction% calculation - use the full range and
309+
* just keep the displayed counter non-negative on overflow
310+
*/
311+
if (atomic_long_inc_return(&op->frames_abs) == LONG_MAX)
312+
atomic_long_set(&op->frames_abs, 0);
313+
}
314+
293315
/*
294316
* bcm_can_tx - send the (next) CAN frame to the appropriate CAN interface
295317
* of the given bcm tx op
@@ -346,7 +368,7 @@ static void bcm_can_tx(struct bcm_op *op, struct canfd_frame *cf)
346368
spin_lock_bh(&op->bcm_tx_lock);
347369

348370
if (!err)
349-
op->frames_abs++;
371+
bcm_update_tx_stats(op);
350372

351373
/* only advance the cyclic sequence if nothing reset currframe while
352374
* we were sending - a concurrent TX_RESET_MULTI_IDX means this
@@ -505,12 +527,9 @@ static void bcm_rx_changed(struct bcm_op *op, struct canfd_frame *data)
505527
{
506528
struct bcm_msg_head head;
507529

508-
/* update statistics */
509-
op->frames_filtered++;
510-
511-
/* prevent statistics overflow */
512-
if (op->frames_filtered > ULONG_MAX/100)
513-
op->frames_filtered = op->frames_abs = 0;
530+
/* update statistics (frames_filtered <= frames_abs) */
531+
if (atomic_long_read(&op->frames_abs))
532+
atomic_long_inc(&op->frames_filtered);
514533

515534
/* this element is not throttled anymore */
516535
data->flags &= ~RX_THR;
@@ -756,24 +775,30 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data)
756775
op->rx_stamp = skb->tstamp;
757776
/* save originator for recvfrom() */
758777
op->rx_ifindex = skb->dev->ifindex;
759-
/* update statistics */
760-
op->frames_abs++;
761778

762-
/* snapshot the flag under lock: op->flags/op->frames may be updated
763-
* concurrently by bcm_rx_setup().
764-
*/
779+
/* op->flags/op->frames may be updated concurrently by bcm_rx_setup() */
765780
spin_lock_bh(&op->bcm_rx_update_lock);
781+
766782
rtr_frame = op->flags & RX_RTR_FRAME;
767-
if (rtr_frame)
783+
if (rtr_frame) {
784+
bcm_update_rx_stats(op);
785+
/* snapshot RTR content under lock */
768786
memcpy(&rtrframe, op->frames, op->cfsiz);
769-
spin_unlock_bh(&op->bcm_rx_update_lock);
787+
spin_unlock_bh(&op->bcm_rx_update_lock);
770788

771-
if (rtr_frame) {
772789
/* send reply for RTR-request (placed in op->frames[0]) */
773790
bcm_can_tx(op, &rtrframe);
774791
return;
775792
}
776793

794+
/* update statistics in the same critical section as bcm_rx_changed()
795+
* below: frames_filtered must never be checked/incremented against a
796+
* frames_abs snapshot from a concurrent bcm_rx_handler() call on
797+
* another CPU for the same (wildcard) op, or frames_filtered can end
798+
* up larger than frames_abs.
799+
*/
800+
bcm_update_rx_stats(op);
801+
777802
/* compute flags to distinguish between own/local/remote CAN traffic */
778803
traffic_flags = 0;
779804
if (skb->sk) {
@@ -782,8 +807,6 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data)
782807
traffic_flags |= RX_OWN;
783808
}
784809

785-
spin_lock_bh(&op->bcm_rx_update_lock);
786-
787810
if (op->flags & RX_FILTER_ID) {
788811
/* the easiest case */
789812
bcm_rx_update_and_send(op, op->last_frames, rxframe,

0 commit comments

Comments
 (0)