Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

can: statistics: use atomic access in hot path

In can_send() and can_receive() CAN messages and CAN filter matches are
counted to be visible in the CAN procfs files.

KCSAN detected a data race within can_send() when two CAN frames have
been generated by a timer event writing to the same CAN netdevice at the
same time. Use atomic operations to access the statistics in the hot path
to fix the KCSAN complaint.

Reported-by: syzbot+78ce4489b812515d5e4d@syzkaller.appspotmail.com
Closes: https://lore.kernel.org/all/67cd717d.050a0220.e1a89.0006.GAE@google.com
Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
Reviewed-by: Vincent Mailhol <mailhol.vincent@wanadoo.fr>
Link: https://patch.msgid.link/20250310143353.3242-1-socketcan@hartkopp.net
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>

authored by

Oliver Hartkopp and committed by
Marc Kleine-Budde
80b5f901 1d22a122

+39 -31
+6 -6
net/can/af_can.c
··· 287 287 netif_rx(newskb); 288 288 289 289 /* update statistics */ 290 - pkg_stats->tx_frames++; 291 - pkg_stats->tx_frames_delta++; 290 + atomic_long_inc(&pkg_stats->tx_frames); 291 + atomic_long_inc(&pkg_stats->tx_frames_delta); 292 292 293 293 return 0; 294 294 ··· 647 647 int matches; 648 648 649 649 /* update statistics */ 650 - pkg_stats->rx_frames++; 651 - pkg_stats->rx_frames_delta++; 650 + atomic_long_inc(&pkg_stats->rx_frames); 651 + atomic_long_inc(&pkg_stats->rx_frames_delta); 652 652 653 653 /* create non-zero unique skb identifier together with *skb */ 654 654 while (!(can_skb_prv(skb)->skbcnt)) ··· 669 669 consume_skb(skb); 670 670 671 671 if (matches > 0) { 672 - pkg_stats->matches++; 673 - pkg_stats->matches_delta++; 672 + atomic_long_inc(&pkg_stats->matches); 673 + atomic_long_inc(&pkg_stats->matches_delta); 674 674 } 675 675 } 676 676
+6 -6
net/can/af_can.h
··· 66 66 struct can_pkg_stats { 67 67 unsigned long jiffies_init; 68 68 69 - unsigned long rx_frames; 70 - unsigned long tx_frames; 71 - unsigned long matches; 69 + atomic_long_t rx_frames; 70 + atomic_long_t tx_frames; 71 + atomic_long_t matches; 72 72 73 73 unsigned long total_rx_rate; 74 74 unsigned long total_tx_rate; ··· 82 82 unsigned long max_tx_rate; 83 83 unsigned long max_rx_match_ratio; 84 84 85 - unsigned long rx_frames_delta; 86 - unsigned long tx_frames_delta; 87 - unsigned long matches_delta; 85 + atomic_long_t rx_frames_delta; 86 + atomic_long_t tx_frames_delta; 87 + atomic_long_t matches_delta; 88 88 }; 89 89 90 90 /* persistent statistics */
+27 -19
net/can/proc.c
··· 118 118 struct can_pkg_stats *pkg_stats = net->can.pkg_stats; 119 119 unsigned long j = jiffies; /* snapshot */ 120 120 121 + long rx_frames = atomic_long_read(&pkg_stats->rx_frames); 122 + long tx_frames = atomic_long_read(&pkg_stats->tx_frames); 123 + long matches = atomic_long_read(&pkg_stats->matches); 124 + long rx_frames_delta = atomic_long_read(&pkg_stats->rx_frames_delta); 125 + long tx_frames_delta = atomic_long_read(&pkg_stats->tx_frames_delta); 126 + long matches_delta = atomic_long_read(&pkg_stats->matches_delta); 127 + 121 128 /* restart counting in timer context on user request */ 122 129 if (user_reset) 123 130 can_init_stats(net); ··· 134 127 can_init_stats(net); 135 128 136 129 /* prevent overflow in calc_rate() */ 137 - if (pkg_stats->rx_frames > (ULONG_MAX / HZ)) 130 + if (rx_frames > (LONG_MAX / HZ)) 138 131 can_init_stats(net); 139 132 140 133 /* prevent overflow in calc_rate() */ 141 - if (pkg_stats->tx_frames > (ULONG_MAX / HZ)) 134 + if (tx_frames > (LONG_MAX / HZ)) 142 135 can_init_stats(net); 143 136 144 137 /* matches overflow - very improbable */ 145 - if (pkg_stats->matches > (ULONG_MAX / 100)) 138 + if (matches > (LONG_MAX / 100)) 146 139 can_init_stats(net); 147 140 148 141 /* calc total values */ 149 - if (pkg_stats->rx_frames) 150 - pkg_stats->total_rx_match_ratio = (pkg_stats->matches * 100) / 151 - pkg_stats->rx_frames; 142 + if (rx_frames) 143 + pkg_stats->total_rx_match_ratio = (matches * 100) / rx_frames; 152 144 153 145 pkg_stats->total_tx_rate = calc_rate(pkg_stats->jiffies_init, j, 154 - pkg_stats->tx_frames); 146 + tx_frames); 155 147 pkg_stats->total_rx_rate = calc_rate(pkg_stats->jiffies_init, j, 156 - pkg_stats->rx_frames); 148 + rx_frames); 157 149 158 150 /* calc current values */ 159 - if (pkg_stats->rx_frames_delta) 151 + if (rx_frames_delta) 160 152 pkg_stats->current_rx_match_ratio = 161 - (pkg_stats->matches_delta * 100) / 162 - pkg_stats->rx_frames_delta; 153 + (matches_delta * 100) / rx_frames_delta; 163 154 164 - pkg_stats->current_tx_rate = calc_rate(0, HZ, pkg_stats->tx_frames_delta); 165 - pkg_stats->current_rx_rate = calc_rate(0, HZ, pkg_stats->rx_frames_delta); 155 + pkg_stats->current_tx_rate = calc_rate(0, HZ, tx_frames_delta); 156 + pkg_stats->current_rx_rate = calc_rate(0, HZ, rx_frames_delta); 166 157 167 158 /* check / update maximum values */ 168 159 if (pkg_stats->max_tx_rate < pkg_stats->current_tx_rate) ··· 173 168 pkg_stats->max_rx_match_ratio = pkg_stats->current_rx_match_ratio; 174 169 175 170 /* clear values for 'current rate' calculation */ 176 - pkg_stats->tx_frames_delta = 0; 177 - pkg_stats->rx_frames_delta = 0; 178 - pkg_stats->matches_delta = 0; 171 + atomic_long_set(&pkg_stats->tx_frames_delta, 0); 172 + atomic_long_set(&pkg_stats->rx_frames_delta, 0); 173 + atomic_long_set(&pkg_stats->matches_delta, 0); 179 174 180 175 /* restart timer (one second) */ 181 176 mod_timer(&net->can.stattimer, round_jiffies(jiffies + HZ)); ··· 219 214 struct can_rcv_lists_stats *rcv_lists_stats = net->can.rcv_lists_stats; 220 215 221 216 seq_putc(m, '\n'); 222 - seq_printf(m, " %8ld transmitted frames (TXF)\n", pkg_stats->tx_frames); 223 - seq_printf(m, " %8ld received frames (RXF)\n", pkg_stats->rx_frames); 224 - seq_printf(m, " %8ld matched frames (RXMF)\n", pkg_stats->matches); 217 + seq_printf(m, " %8ld transmitted frames (TXF)\n", 218 + atomic_long_read(&pkg_stats->tx_frames)); 219 + seq_printf(m, " %8ld received frames (RXF)\n", 220 + atomic_long_read(&pkg_stats->rx_frames)); 221 + seq_printf(m, " %8ld matched frames (RXMF)\n", 222 + atomic_long_read(&pkg_stats->matches)); 225 223 226 224 seq_putc(m, '\n'); 227 225