From: Oliver Hartkopp <socketcan@hartkopp.net>
To: linux-can@vger.kernel.org
Cc: Oliver Hartkopp <socketcan@hartkopp.net>,
Joerg Willmann <joe@clnt.de>,
stable@vger.kernel.org
Subject: [PATCH v4 3/3] can: fix unique skb identifier regression under RPS
Date: Tue, 29 Sep 2026 17:41:25 +0200 [thread overview]
Message-ID: <20260929154125.3298-4-socketcan@hartkopp.net> (raw)
In-Reply-To: <20260929154125.3298-1-socketcan@hartkopp.net>
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 still 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 clones
never end up with the same UID.
can-gw and vxcan additionally clear the UID of forwarded/duplicated
frames so each newly routed frame gets its own unique identifier.
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/
Cc: stable@vger.kernel.org
Tested-by: Oliver Hartkopp <socketcan@hartkopp.net>
Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
---
v2: We need a non-zero UID for the cfecho checks in isotp.c
So restore the former while (!(skb->hash)) statement
v3: omit the problematic use of skb->hash reported by sashiko bot
v4: address tc mirred or netem duplicate clones suggested by sashiko bot
---
drivers/net/can/vxcan.c | 3 +++
include/linux/can/core.h | 3 ++-
include/linux/can/skb.h | 4 ++-
include/net/can.h | 2 ++
net/can/af_can.c | 57 ++++++++++++++++++++++++++++++----------
net/can/gw.c | 3 +++
net/can/isotp.c | 16 ++++++-----
net/can/raw.c | 10 ++++---
8 files changed, 73 insertions(+), 25 deletions(-)
diff --git a/drivers/net/can/vxcan.c b/drivers/net/can/vxcan.c
index 9e2e25d02471..51148a81d1d9 100644
--- a/drivers/net/can/vxcan.c
+++ b/drivers/net/can/vxcan.c
@@ -77,10 +77,13 @@ static netdev_tx_t vxcan_xmit(struct sk_buff *oskb, struct net_device *dev)
goto out_unlock;
}
/* reset CAN GW hop counter */
csx->can_gw_hops = 0;
+ /* start with new CAN skb UID in the other namespace */
+ csx->can_skb_uid = 0;
+
skb->pkt_type = PACKET_BROADCAST;
skb->dev = peer;
skb->ip_summed = CHECKSUM_UNNECESSARY;
len = can_skb_get_data_len(skb);
diff --git a/include/linux/can/core.h b/include/linux/can/core.h
index 3287232e3cad..2de74c2b78b6 100644
--- a/include/linux/can/core.h
+++ b/include/linux/can/core.h
@@ -15,10 +15,11 @@
#define _CAN_CORE_H
#include <linux/can.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
+#include <net/can.h>
#define DNAME(dev) ((dev) ? (dev)->name : "any")
/**
* struct can_proto - CAN protocol structure
@@ -56,9 +57,9 @@ extern void can_rx_unregister(struct net *net, struct net_device *dev,
canid_t can_id, canid_t mask,
void (*func)(struct sk_buff *, void *),
void *data);
extern int can_send(struct sk_buff *skb, int loop);
-void can_set_skb_uid(struct sk_buff *skb);
+void can_set_skb_uid(struct can_skb_ext *csx);
void can_sock_destruct(struct sock *sk);
#endif /* !_CAN_CORE_H */
diff --git a/include/linux/can/skb.h b/include/linux/can/skb.h
index a70a02967071..5d27843862fc 100644
--- a/include/linux/can/skb.h
+++ b/include/linux/can/skb.h
@@ -41,12 +41,14 @@ bool can_dropped_invalid_skb(struct net_device *dev, struct sk_buff *skb);
static inline struct can_skb_ext *can_skb_ext_add(struct sk_buff *skb)
{
struct can_skb_ext *csx = skb_ext_add(skb, SKB_EXT_CAN);
/* skb_ext_add() returns uninitialized space */
- if (csx)
+ if (csx) {
csx->can_gw_hops = 0;
+ csx->can_skb_uid = 0;
+ }
return csx;
}
static inline struct can_skb_ext *can_skb_ext_find(struct sk_buff *skb)
diff --git a/include/net/can.h b/include/net/can.h
index 6db9e826f0e0..2b8af2598732 100644
--- a/include/net/can.h
+++ b/include/net/can.h
@@ -15,14 +15,16 @@
* struct can_skb_ext - skb extensions for CAN specific content
* @can_iif: ifindex of the first interface the CAN frame appeared on
* @can_framelen: cached echo CAN frame length for bql
* @can_gw_hops: can-gw CAN frame time-to-live counter
* @can_ext_flags: CAN skb extensions flags
+ * @can_skb_uid: CAN skb UID for raw_rcv and isotp echo handling
*/
struct can_skb_ext {
int can_iif;
u16 can_framelen;
u8 can_gw_hops;
u8 can_ext_flags;
+ u32 can_skb_uid;
};
#endif /* _NET_CAN_H */
diff --git a/net/can/af_can.c b/net/can/af_can.c
index ef435f22ac93..4aa233d7e8a1 100644
--- a/net/can/af_can.c
+++ b/net/can/af_can.c
@@ -639,17 +639,14 @@ static int can_rcv_filter(struct can_dev_rcv_lists *dev_rcv_lists, struct sk_buf
}
return matches;
}
-void can_set_skb_uid(struct sk_buff *skb)
+void can_set_skb_uid(struct can_skb_ext *csx)
{
- /* create non-zero unique skb identifier together with *skb */
- while (!(skb->hash))
- skb->hash = atomic_inc_return(&skbcounter);
-
- skb->sw_hash = 1;
+ while (!(csx->can_skb_uid))
+ csx->can_skb_uid = atomic_inc_return(&skbcounter);
}
EXPORT_SYMBOL(can_set_skb_uid);
static void can_receive(struct sk_buff *skb, struct net_device *dev)
{
@@ -660,12 +657,10 @@ static void can_receive(struct sk_buff *skb, struct net_device *dev)
/* update statistics */
atomic_long_inc(&pkg_stats->rx_frames);
atomic_long_inc(&pkg_stats->rx_frames_delta);
- can_set_skb_uid(skb);
-
rcu_read_lock();
/* deliver the packet to sockets listening on all devices */
matches = can_rcv_filter(net->can.rx_alldev_list, skb);
@@ -682,54 +677,88 @@ static void can_receive(struct sk_buff *skb, struct net_device *dev)
atomic_long_inc(&pkg_stats->matches);
atomic_long_inc(&pkg_stats->matches_delta);
}
}
+static struct can_skb_ext *can_get_unshared_skb_ext(struct sk_buff *skb,
+ struct can_skb_ext *csx)
+{
+ /* create unshared CAN skb_extension for netem/mirred skb clones */
+ if (unlikely(skb_cloned(skb)))
+ return skb_ext_add(skb, SKB_EXT_CAN);
+ else
+ return csx;
+}
+
static int can_rcv(struct sk_buff *skb, struct net_device *dev,
struct packet_type *pt, struct net_device *orig_dev)
{
- if (unlikely(!can_get_ml_priv(dev) ||
- !can_skb_ext_find(skb) || !can_is_can_skb(skb))) {
+ struct can_skb_ext *csx = can_skb_ext_find(skb);
+
+ if (unlikely(!can_get_ml_priv(dev) || !csx || !can_is_can_skb(skb))) {
pr_warn_once("PF_CAN: dropped non conform CAN skbuff: dev type %d, len %d\n",
dev->type, skb->len);
kfree_skb_reason(skb, SKB_DROP_REASON_CAN_RX_INVALID_FRAME);
return NET_RX_DROP;
}
+ csx = can_get_unshared_skb_ext(skb, csx);
+ if (unlikely(!csx)) {
+ kfree_skb_reason(skb, SKB_DROP_REASON_NOMEM);
+ return NET_RX_DROP;
+ }
+
+ can_set_skb_uid(csx);
can_receive(skb, dev);
return NET_RX_SUCCESS;
}
static int canfd_rcv(struct sk_buff *skb, struct net_device *dev,
struct packet_type *pt, struct net_device *orig_dev)
{
- if (unlikely(!can_get_ml_priv(dev) ||
- !can_skb_ext_find(skb) || !can_is_canfd_skb(skb))) {
+ struct can_skb_ext *csx = can_skb_ext_find(skb);
+
+ if (unlikely(!can_get_ml_priv(dev) || !csx || !can_is_canfd_skb(skb))) {
pr_warn_once("PF_CAN: dropped non conform CAN FD skbuff: dev type %d, len %d\n",
dev->type, skb->len);
kfree_skb_reason(skb, SKB_DROP_REASON_CANFD_RX_INVALID_FRAME);
return NET_RX_DROP;
}
+ csx = can_get_unshared_skb_ext(skb, csx);
+ if (unlikely(!csx)) {
+ kfree_skb_reason(skb, SKB_DROP_REASON_NOMEM);
+ return NET_RX_DROP;
+ }
+
+ can_set_skb_uid(csx);
can_receive(skb, dev);
return NET_RX_SUCCESS;
}
static int canxl_rcv(struct sk_buff *skb, struct net_device *dev,
struct packet_type *pt, struct net_device *orig_dev)
{
- if (unlikely(!can_get_ml_priv(dev) ||
- !can_skb_ext_find(skb) || !can_is_canxl_skb(skb))) {
+ struct can_skb_ext *csx = can_skb_ext_find(skb);
+
+ if (unlikely(!can_get_ml_priv(dev) || !csx || !can_is_canxl_skb(skb))) {
pr_warn_once("PF_CAN: dropped non conform CAN XL skbuff: dev type %d, len %d\n",
dev->type, skb->len);
kfree_skb_reason(skb, SKB_DROP_REASON_CANXL_RX_INVALID_FRAME);
return NET_RX_DROP;
}
+ csx = can_get_unshared_skb_ext(skb, csx);
+ if (unlikely(!csx)) {
+ kfree_skb_reason(skb, SKB_DROP_REASON_NOMEM);
+ return NET_RX_DROP;
+ }
+
+ can_set_skb_uid(csx);
can_receive(skb, dev);
return NET_RX_SUCCESS;
}
/* af_can protocol functions */
diff --git a/net/can/gw.c b/net/can/gw.c
index d9912dea738b..54bb5bd3242a 100644
--- a/net/can/gw.c
+++ b/net/can/gw.c
@@ -526,10 +526,13 @@ static void can_can_gw_rcv(struct sk_buff *skb, void *data)
}
/* put the incremented hop counter in the cloned skb */
ncsx->can_gw_hops = csx->can_gw_hops + 1;
+ /* force a new CAN UID generation for the routed frame */
+ ncsx->can_skb_uid = 0;
+
/* first processing of this CAN frame -> adjust to private hop limit */
if (gwj->limit_hops && ncsx->can_gw_hops == 1)
ncsx->can_gw_hops = max_hops - gwj->limit_hops + 1;
nskb->dev = gwj->dst.dev;
diff --git a/net/can/isotp.c b/net/can/isotp.c
index d3f79efc9c1e..860c5221e299 100644
--- a/net/can/isotp.c
+++ b/net/can/isotp.c
@@ -890,11 +890,11 @@ static void isotp_send_cframe(struct isotp_sock *so)
}
csx->can_iif = dev->ifindex;
/* set uid in tx skb to identify CF echo frames */
- can_set_skb_uid(skb);
+ can_set_skb_uid(csx);
cf = (struct canfd_frame *)skb->data;
skb_put_zero(skb, so->ll.mtu);
/* create consecutive frame */
@@ -916,11 +916,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, csx->can_skb_uid);
/* 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",
@@ -968,22 +968,26 @@ static void isotp_create_fframe(struct canfd_frame *cf, struct isotp_sock *so,
static void isotp_rcv_echo(struct sk_buff *skb, void *data)
{
struct sock *sk = (struct sock *)data;
struct isotp_sock *so = isotp_sk(sk);
+ struct can_skb_ext *csx = can_skb_ext_find(skb);
/* only handle my own local echo CF/SF skb's (no FF!) */
if (skb->sk != sk)
return;
+ if (WARN_ON_ONCE(!csx))
+ return;
+
/* unlike isotp_rcv_fc()/isotp_rcv_cf(), not already under so->rx_lock
* (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) != csx->can_skb_uid)
goto out_unlock;
/* cancel local echo timeout */
hrtimer_cancel(&so->echotimer);
@@ -1221,11 +1225,11 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
}
csx->can_iif = dev->ifindex;
/* set uid in tx skb to identify CF echo frames */
- can_set_skb_uid(skb);
+ can_set_skb_uid(csx);
so->tx.len = size;
so->tx.idx = 0;
cf = (struct canfd_frame *)skb->data;
@@ -1260,11 +1264,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, csx->can_skb_uid);
} else {
/* send first frame */
isotp_create_fframe(cf, so, ae);
@@ -1277,11 +1281,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, csx->can_skb_uid);
} else {
/* standard flow control check */
new_state = ISOTP_WAIT_FIRST_FC;
/* start timeout for FC */
diff --git a/net/can/raw.c b/net/can/raw.c
index 7c48ff36e6cd..7873bfc584ef 100644
--- a/net/can/raw.c
+++ b/net/can/raw.c
@@ -75,11 +75,11 @@ MODULE_ALIAS("can-proto-1");
* storing the single filter in dfilter, to avoid using dynamic memory.
*/
struct uniqframe {
const struct sk_buff *skb;
- u32 hash;
+ u32 can_skb_uid;
unsigned int join_rx_count;
};
struct raw_sock {
struct sock sk;
@@ -131,16 +131,20 @@ static void raw_rcv(struct sk_buff *oskb, void *data)
struct sock *sk = (struct sock *)data;
struct raw_sock *ro = raw_sk(sk);
enum skb_drop_reason reason;
struct sockaddr_can *addr;
struct sk_buff *skb;
+ struct can_skb_ext *csx = can_skb_ext_find(oskb);
unsigned int *pflags;
/* check the received tx sock reference */
if (!ro->recv_own_msgs && oskb->sk == sk)
return;
+ if (WARN_ON_ONCE(!csx))
+ return;
+
/* make sure to not pass oversized frames to the socket */
if (!ro->fd_frames && can_is_canfd_skb(oskb))
return;
if (can_is_canxl_skb(oskb)) {
@@ -163,21 +167,21 @@ static void raw_rcv(struct sk_buff *oskb, void *data)
}
}
/* 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)->can_skb_uid == csx->can_skb_uid) {
if (!ro->join_filters)
return;
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 {
this_cpu_ptr(ro->uniq)->skb = oskb;
- this_cpu_ptr(ro->uniq)->hash = oskb->hash;
+ this_cpu_ptr(ro->uniq)->can_skb_uid = csx->can_skb_uid;
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;
}
--
2.53.0
next prev parent reply other threads:[~2026-09-29 15:41 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-29 15:41 [PATCH v4 0/3] CAN netlayer fixes for stable Oliver Hartkopp
2026-09-29 15:41 ` [PATCH v4 1/3] can: remove CAN filters independent from namespace Oliver Hartkopp
2026-10-03 6:00 ` netdev-bot+sashiko
2026-09-29 15:41 ` [PATCH v4 2/3] can: convert unreliable ARPHRD_CAN type checks to robust can_get_ml_priv() Oliver Hartkopp
2026-09-29 15:41 ` Oliver Hartkopp [this message]
2026-09-29 16:09 ` [PATCH v4 3/3] can: fix unique skb identifier regression under RPS sashiko-bot
2026-10-03 6:00 ` netdev-bot+sashiko
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=20260929154125.3298-4-socketcan@hartkopp.net \
--to=socketcan@hartkopp.net \
--cc=joe@clnt.de \
--cc=linux-can@vger.kernel.org \
--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