From: Oliver Hartkopp <socketcan@hartkopp.net>
To: stable@vger.kernel.org
Cc: Marc Kleine-Budde <mkl@pengutronix.de>,
Oliver Hartkopp <socketcan@hartkopp.net>,
Joerg Willmann <joe@clnt.de>
Subject: [PATCH 5.15.y] can: fix unique skb identifier regression under RPS
Date: Tue, 13 Oct 2026 09:41:52 +0200 [thread overview]
Message-ID: <20261013074152.288306-1-socketcan@hartkopp.net> (raw)
In-Reply-To: <'2026101253-dress-blemish-4bd3@gregkh'>
commit 7093c3b8314a4ef7405c4066a2521398c455efe7 upstream.
Commit d4fb6514ff8e ("can: use skb hash instead of private variable in
headroom") moved the per-skb unique identifier used for raw_rcv()
duplicate detection into skb->hash.
With RPS enabled, get_rps_cpu() calls skb_get_hash() before the frame
reaches the CAN subsystem. Since CAN skbs have no L3/L4 headers, the
flow dissector assigns every CAN frame the same non-zero software
hash. can_set_skb_uid() only generated a new identifier when
skb->hash was 0, so it kept this constant hash. When the SLAB
allocator later reused the same skb address, raw_rcv() mistook the
next legitimate frame for a duplicate and dropped it.
Fix this by storing the CAN UID in the CAN skb extension
(struct can_skb_ext::can_skb_uid) instead of skb->hash, decoupling it
from any hash the network stack may compute. can_set_skb_uid() keeps
its "assign only if unset" behaviour, which preserves UIDs set before
transmission (e.g. by isotp to identify echo frames) across the local
loopback path.
Frames whose extension is shared with another clone (e.g. via tc mirred
or netem duplicate) are given a private extension copy before the UID
is assigned, so that independently received skb clones from real CAN
interfaces (skbcnt = 0) don't end up with the same UID.
The limitation that tc mirred/netem skb duplicates might be dropped in
raw_rcv for looped back isotp echo frame skbs is accepted. There is no
valid use-case for tc mirred or netem together with isotp which is not
capable to operate on different CAN interfaces simultaneously.
For routing and modifying CAN frames together with isotp use can-gw.
can-gw and vxcan additionally clear the UID of forwarded/duplicated
frames so each newly routed frame gets its own unique identifier.
[ backport to 5.15: no CAN skb extensions available, so the UID is kept
in struct can_skb_priv::skbcnt in the headroom (effectively reverting
d4fb6514ff8e) and isotp is adapted accordingly. As skb clones share
the headroom, the private extension copy for tc mirred/netem
duplicates is not applicable. The UID resets in can-gw and vxcan are
omitted, as writing to the shared headroom of cloned skbs would also
alter the UID of the original skb. This keeps the pre-d4fb6514ff8e
behaviour for routed and vxcan forwarded frames. ]
Fixes: d4fb6514ff8e ("can: use skb hash instead of private variable in headroom")
Reported-by: Joerg Willmann <joe@clnt.de>
Closes: https://lore.kernel.org/linux-can/2859AD3D-C805-41A0-9036-C5E8EE152419@clnt.de/
Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
---
drivers/net/can/dev/skb.c | 2 ++
drivers/net/can/slcan.c | 1 +
include/linux/can/skb.h | 3 +++
net/can/af_can.c | 6 ++----
net/can/bcm.c | 2 ++
net/can/isotp.c | 11 +++++++----
net/can/j1939/socket.c | 1 +
net/can/j1939/transport.c | 2 ++
net/can/raw.c | 7 ++++---
9 files changed, 24 insertions(+), 11 deletions(-)
diff --git a/drivers/net/can/dev/skb.c b/drivers/net/can/dev/skb.c
index ec624cfc1b07..e59d5cbb644a 100644
--- a/drivers/net/can/dev/skb.c
+++ b/drivers/net/can/dev/skb.c
@@ -201,10 +201,11 @@ struct sk_buff *alloc_can_skb(struct net_device *dev, struct can_frame **cf)
skb_reset_network_header(skb);
skb_reset_transport_header(skb);
can_skb_reserve(skb);
can_skb_prv(skb)->ifindex = dev->ifindex;
+ can_skb_prv(skb)->skbcnt = 0;
*cf = skb_put_zero(skb, sizeof(struct can_frame));
return skb;
}
@@ -231,10 +232,11 @@ struct sk_buff *alloc_canfd_skb(struct net_device *dev,
skb_reset_network_header(skb);
skb_reset_transport_header(skb);
can_skb_reserve(skb);
can_skb_prv(skb)->ifindex = dev->ifindex;
+ can_skb_prv(skb)->skbcnt = 0;
*cfd = skb_put_zero(skb, sizeof(struct canfd_frame));
return skb;
}
diff --git a/drivers/net/can/slcan.c b/drivers/net/can/slcan.c
index 0b2403e9853e..012da4b8abe0 100644
--- a/drivers/net/can/slcan.c
+++ b/drivers/net/can/slcan.c
@@ -211,10 +211,11 @@ static void slc_bump(struct slcan *sl)
skb->pkt_type = PACKET_BROADCAST;
skb->ip_summed = CHECKSUM_UNNECESSARY;
can_skb_reserve(skb);
can_skb_prv(skb)->ifindex = sl->dev->ifindex;
+ can_skb_prv(skb)->skbcnt = 0;
skb_put_data(skb, &cf, sizeof(struct can_frame));
sl->dev->stats.rx_packets++;
sl->dev->stats.rx_bytes += cf.len;
diff --git a/include/linux/can/skb.h b/include/linux/can/skb.h
index f887eb21e178..a7d2283d4996 100644
--- a/include/linux/can/skb.h
+++ b/include/linux/can/skb.h
@@ -41,15 +41,17 @@ struct sk_buff *alloc_can_err_skb(struct net_device *dev,
*/
/**
* struct can_skb_priv - private additional data inside CAN sk_buffs
* @ifindex: ifindex of the first interface the CAN frame appeared on
+ * @skbcnt: atomic counter to have an unique id together with skb pointer
* @frame_len: length of CAN frame in data link layer
* @cf: align to the following CAN frame at skb->data
*/
struct can_skb_priv {
int ifindex;
+ u32 skbcnt;
unsigned int frame_len;
struct can_frame cf[];
};
static inline struct can_skb_priv *can_skb_prv(struct sk_buff *skb)
@@ -103,10 +105,11 @@ static inline bool can_skb_headroom_valid(struct net_device *dev,
/* af_packet does not apply CAN skb specific settings */
if (skb->ip_summed == CHECKSUM_NONE) {
/* init headroom */
can_skb_prv(skb)->ifindex = dev->ifindex;
+ can_skb_prv(skb)->skbcnt = 0;
skb->ip_summed = CHECKSUM_UNNECESSARY;
/* perform proper loopback on capable devices */
if (dev->flags & IFF_ECHO)
diff --git a/net/can/af_can.c b/net/can/af_can.c
index 95b7cc548675..cd31c4e014d1 100644
--- a/net/can/af_can.c
+++ b/net/can/af_can.c
@@ -642,14 +642,12 @@ static int can_rcv_filter(struct can_dev_rcv_lists *dev_rcv_lists, struct sk_buf
}
void can_set_skb_uid(struct sk_buff *skb)
{
/* create non-zero unique skb identifier together with *skb */
- while (!(skb->hash))
- skb->hash = atomic_inc_return(&skbcounter);
-
- skb->sw_hash = 1;
+ while (!(can_skb_prv(skb)->skbcnt))
+ can_skb_prv(skb)->skbcnt = atomic_inc_return(&skbcounter);
}
EXPORT_SYMBOL(can_set_skb_uid);
static void can_receive(struct sk_buff *skb, struct net_device *dev)
{
diff --git a/net/can/bcm.c b/net/can/bcm.c
index 235156223394..2cf63940f6a9 100644
--- a/net/can/bcm.c
+++ b/net/can/bcm.c
@@ -336,10 +336,11 @@ static void bcm_can_tx(struct bcm_op *op, struct canfd_frame *cf)
if (!skb)
goto out;
can_skb_reserve(skb);
can_skb_prv(skb)->ifindex = dev->ifindex;
+ can_skb_prv(skb)->skbcnt = 0;
skb_put_data(skb, cf, op->cfsiz);
/* send with loopback */
skb->dev = dev;
@@ -1574,10 +1575,11 @@ static int bcm_tx_send(struct msghdr *msg, int ifindex, struct sock *sk,
kfree_skb(skb);
return -ENODEV;
}
can_skb_prv(skb)->ifindex = dev->ifindex;
+ can_skb_prv(skb)->skbcnt = 0;
skb->dev = dev;
can_skb_set_owner(skb, sk);
err = can_send(skb, 1); /* send with loopback */
dev_put(dev);
diff --git a/net/can/isotp.c b/net/can/isotp.c
index 1037d2c2e61a..46b71e2bded2 100644
--- a/net/can/isotp.c
+++ b/net/can/isotp.c
@@ -285,10 +285,11 @@ static int isotp_send_fc(struct sock *sk, int ae, u8 flowstatus)
return 1;
}
can_skb_reserve(nskb);
can_skb_prv(nskb)->ifindex = dev->ifindex;
+ can_skb_prv(nskb)->skbcnt = 0;
nskb->dev = dev;
can_skb_set_owner(nskb, sk);
ncf = (struct canfd_frame *)nskb->data;
skb_put_zero(nskb, so->ll.mtu);
@@ -846,10 +847,11 @@ static void isotp_send_cframe(struct isotp_sock *so)
return;
}
can_skb_reserve(skb);
can_skb_prv(skb)->ifindex = dev->ifindex;
+ can_skb_prv(skb)->skbcnt = 0;
/* set uid in tx skb to identify CF echo frames */
can_set_skb_uid(skb);
cf = (struct canfd_frame *)skb->data;
@@ -874,11 +876,11 @@ static void isotp_send_cframe(struct isotp_sock *so)
old_cfecho = READ_ONCE(so->cfecho);
if (old_cfecho)
pr_notice_once("can-isotp: cfecho is %08X != 0\n", old_cfecho);
/* set consecutive frame echo tag */
- WRITE_ONCE(so->cfecho, skb->hash);
+ WRITE_ONCE(so->cfecho, can_skb_prv(skb)->skbcnt);
/* send frame with local echo enabled */
can_send_ret = can_send(skb, 1);
if (can_send_ret) {
pr_notice_once("can-isotp: %s: can_send_ret %pe\n",
@@ -937,11 +939,11 @@ static void isotp_rcv_echo(struct sk_buff *skb, void *data)
* (no isotp_rcv() caller here), so take it ourselves
*/
spin_lock(&so->rx_lock);
/* so->cfecho may since belong to a new transfer; recheck under lock */
- if (READ_ONCE(so->cfecho) != skb->hash)
+ if (READ_ONCE(so->cfecho) != can_skb_prv(skb)->skbcnt)
goto out_unlock;
/* cancel local echo timeout */
hrtimer_cancel(&so->echotimer);
@@ -1159,10 +1161,11 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
goto err_out_drop;
}
can_skb_reserve(skb);
can_skb_prv(skb)->ifindex = dev->ifindex;
+ can_skb_prv(skb)->skbcnt = 0;
/* set uid in tx skb to identify CF echo frames */
can_set_skb_uid(skb);
so->tx.len = size;
@@ -1200,11 +1203,11 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
cf->data[SF_PCI_SZ4 + ae] = size;
else
cf->data[ae] |= size;
/* set CF echo tag for isotp_rcv_echo() (SF-mode) */
- WRITE_ONCE(so->cfecho, skb->hash);
+ WRITE_ONCE(so->cfecho, can_skb_prv(skb)->skbcnt);
} else {
/* send first frame */
isotp_create_fframe(cf, so, ae);
@@ -1217,11 +1220,11 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
/* disable wait for FCs due to activated block size */
so->txfc.bs = 0;
/* set CF echo tag for isotp_rcv_echo() (CF-mode) */
- WRITE_ONCE(so->cfecho, skb->hash);
+ WRITE_ONCE(so->cfecho, can_skb_prv(skb)->skbcnt);
} else {
/* standard flow control check */
new_state = ISOTP_WAIT_FIRST_FC;
/* start timeout for FC */
diff --git a/net/can/j1939/socket.c b/net/can/j1939/socket.c
index c088f1d8150f..585de1dad6f4 100644
--- a/net/can/j1939/socket.c
+++ b/net/can/j1939/socket.c
@@ -893,10 +893,11 @@ static struct sk_buff *j1939_sk_alloc_skb(struct net_device *ndev,
if (!skb)
goto failure;
can_skb_reserve(skb);
can_skb_prv(skb)->ifindex = ndev->ifindex;
+ can_skb_prv(skb)->skbcnt = 0;
skb_reserve(skb, offsetof(struct can_frame, data));
ret = memcpy_from_msg(skb_put(skb, size), msg, size);
if (ret < 0)
goto free_skb;
diff --git a/net/can/j1939/transport.c b/net/can/j1939/transport.c
index 74638cf04172..55bbcf73c873 100644
--- a/net/can/j1939/transport.c
+++ b/net/can/j1939/transport.c
@@ -610,10 +610,11 @@ sk_buff *j1939_tp_tx_dat_new(struct j1939_priv *priv,
return ERR_PTR(-ENOMEM);
skb->dev = priv->ndev;
can_skb_reserve(skb);
can_skb_prv(skb)->ifindex = priv->ndev->ifindex;
+ can_skb_prv(skb)->skbcnt = 0;
/* reserve CAN header */
skb_reserve(skb, offsetof(struct can_frame, data));
/* skb->cb must be large enough to hold a j1939_sk_buff_cb structure */
BUILD_BUG_ON(sizeof(skb->cb) < sizeof(*re_skcb));
@@ -1548,10 +1549,11 @@ j1939_session *j1939_session_fresh_new(struct j1939_priv *priv,
return NULL;
skb->dev = priv->ndev;
can_skb_reserve(skb);
can_skb_prv(skb)->ifindex = priv->ndev->ifindex;
+ can_skb_prv(skb)->skbcnt = 0;
skcb = j1939_skb_to_cb(skb);
memcpy(skcb, rel_skcb, sizeof(*skcb));
session = j1939_session_new(priv, skb, size);
if (!session) {
diff --git a/net/can/raw.c b/net/can/raw.c
index ffbb622f8842..fccc2904bb18 100644
--- a/net/can/raw.c
+++ b/net/can/raw.c
@@ -72,12 +72,12 @@ MODULE_ALIAS("can-proto-1");
* list containing only one item. This common case is optimized by
* storing the single filter in dfilter, to avoid using dynamic memory.
*/
struct uniqframe {
+ u32 skbcnt;
const struct sk_buff *skb;
- u32 hash;
unsigned int join_rx_count;
};
struct raw_sock {
struct sock sk;
@@ -134,22 +134,22 @@ static void raw_rcv(struct sk_buff *oskb, void *data)
if (!ro->fd_frames && oskb->len != CAN_MTU)
return;
/* eliminate multiple filter matches for the same skb */
if (this_cpu_ptr(ro->uniq)->skb == oskb &&
- this_cpu_ptr(ro->uniq)->hash == oskb->hash) {
+ this_cpu_ptr(ro->uniq)->skbcnt == can_skb_prv(oskb)->skbcnt) {
if (ro->join_filters) {
this_cpu_inc(ro->uniq->join_rx_count);
/* drop frame until all enabled filters matched */
if (this_cpu_ptr(ro->uniq)->join_rx_count < ro->count)
return;
} else {
return;
}
} else {
this_cpu_ptr(ro->uniq)->skb = oskb;
- this_cpu_ptr(ro->uniq)->hash = oskb->hash;
+ this_cpu_ptr(ro->uniq)->skbcnt = can_skb_prv(oskb)->skbcnt;
this_cpu_ptr(ro->uniq)->join_rx_count = 1;
/* drop first frame to check all enabled filters? */
if (ro->join_filters && ro->count > 1)
return;
}
@@ -827,10 +827,11 @@ static int raw_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
if (!skb)
goto put_dev;
can_skb_reserve(skb);
can_skb_prv(skb)->ifindex = dev->ifindex;
+ can_skb_prv(skb)->skbcnt = 0;
err = memcpy_from_msg(skb_put(skb, size), msg, size);
if (err < 0)
goto free_skb;
--
2.53.0
next parent reply other threads:[~2026-10-13 7:42 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
[not found] <'2026101253-dress-blemish-4bd3@gregkh'>
2026-10-13 7:41 ` Oliver Hartkopp [this message]
2026-10-14 1:27 ` [PATCH 5.15.y] can: fix unique skb identifier regression under RPS Sasha Levin
2026-10-12 11:33 FAILED: patch "[PATCH] can: fix unique skb identifier regression under RPS" failed to apply to 5.15-stable tree gregkh
2026-10-13 17:05 ` [PATCH 5.15.y] can: fix unique skb identifier regression under RPS Oliver Hartkopp
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261013074152.288306-1-socketcan@hartkopp.net \
--to=socketcan@hartkopp.net \
--cc=joe@clnt.de \
--cc=mkl@pengutronix.de \
--cc=stable@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox