* [PATCH net-next 0/3] net: rmnet: lockless rmnet_fill_info()
@ 2026-09-16 13:22 Eric Dumazet
2026-09-16 13:22 ` [PATCH net-next 1/3] net: rmnet: annotate data-races around port->data_format Eric Dumazet
` (4 more replies)
0 siblings, 5 replies; 8+ messages in thread
From: Eric Dumazet @ 2026-09-16 13:22 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Kuniyuki Iwashima, Andrew Lunn, netdev,
eric.dumazet, Eric Dumazet, Subash Abhinov Kasiviswanathan,
Sean Tranchetti
rmnet_fill_info() currently relies on RTNL being held, because it
uses rmnet_get_port_rtnl() to reach the rmnet_port attached to the
underlying real device.
While auditing the fields exposed there, it appears that both
priv->mux_id and port->data_format are written under RTNL but read
from the data path without any lock. Note that rmnet_newlink() can
update port->data_format of an already active port, and that
rmnet_changelink() can change both fields while traffic is flowing.
- Patch 1 annotates the data-races around port->data_format. It also
samples the field only once per packet and passes the value down,
so that both ends of a packet transformation agree on the format
(TX used to size the MAP headroom and set MAP_NEXT_HEADER_FLAG
from two separate reads), and prevents rmnet_changelink() from
publishing an intermediate value to the data path.
- Patch 2 annotates the data-races around priv->mux_id and
ep->mux_id. It also publishes ep->mux_id before the endpoint is
inserted into its new bucket, so that a concurrent lookup walking
that bucket can no longer miss it and drop the packet.
- Patch 3 converts rmnet_fill_info() to RCU. rmnet_get_port_rcu()
was only called from the data path and thus used
rcu_dereference_bh(); its lockdep condition is relaxed so that it
can also be called from process context.
The lockless lookup is safe because rmnet_get_port_rcu() checks
real_dev->rx_handler before returning rx_handler_data, while
rmnet_unregister_real_device() clears rx_handler and waits for a
grace period (in netdev_rx_handler_unregister()) before freeing the
port.
This is part of a larger effort to remove the RTNL dependency from
rtnl_link_ops->fill_info().
Assisted-by: LLM
Cc: Subash Abhinov Kasiviswanathan <subash.a.kasiviswanathan@oss.qualcomm.com>
Cc: Sean Tranchetti <sean.tranchetti@oss.qualcomm.com>
Eric Dumazet (3):
net: rmnet: annotate data-races around port->data_format
net: rmnet: annotate data-races around mux_id
net: rmnet: no longer rely on RTNL in rmnet_fill_info()
.../ethernet/qualcomm/rmnet/rmnet_config.c | 54 +++++++++----------
.../ethernet/qualcomm/rmnet/rmnet_config.h | 2 +-
.../ethernet/qualcomm/rmnet/rmnet_handlers.c | 34 +++++++-----
.../net/ethernet/qualcomm/rmnet/rmnet_map.h | 9 ++--
.../qualcomm/rmnet/rmnet_map_command.c | 9 ++--
.../ethernet/qualcomm/rmnet/rmnet_map_data.c | 14 ++---
.../net/ethernet/qualcomm/rmnet/rmnet_vnd.c | 2 +-
7 files changed, 65 insertions(+), 59 deletions(-)
--
2.55.0.1032.g73a4cd73de-goog
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH net-next 1/3] net: rmnet: annotate data-races around port->data_format
2026-09-16 13:22 [PATCH net-next 0/3] net: rmnet: lockless rmnet_fill_info() Eric Dumazet
@ 2026-09-16 13:22 ` Eric Dumazet
2026-09-17 13:22 ` netdev-bot+sashiko
2026-09-16 13:22 ` [PATCH net-next 2/3] net: rmnet: annotate data-races around mux_id Eric Dumazet
` (3 subsequent siblings)
4 siblings, 1 reply; 8+ messages in thread
From: Eric Dumazet @ 2026-09-16 13:22 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Kuniyuki Iwashima, Andrew Lunn, netdev,
eric.dumazet, Eric Dumazet, Subash Abhinov Kasiviswanathan,
Sean Tranchetti
port->data_format is written under RTNL from rmnet_newlink() and
rmnet_changelink(), but it is read from the data path without any
lock, both from RX (rmnet_rx_handler() context) and TX
(rmnet_egress_handler() context).
Note that rmnet_newlink() can be called for a real_dev which is
already hooked to rmnet: the rx_handler is live and traffic can
already be flowing when port->data_format is overwritten.
Add the missing READ_ONCE()/WRITE_ONCE() annotations.
While at it, sample port->data_format only once per packet and pass
the value down, so that all the decisions taken for a given packet
are based on a single consistent value.
Otherwise the two ends of a packet transformation could disagree.
On TX, rmnet_map_egress_handler() sized the headroom from one read
while rmnet_map_add_map_header() decided on MAP_NEXT_HEADER_FLAG
from another one, so a concurrent rmnet_changelink() could produce
a MAP header announcing a v5 csum header that was neither reserved
nor written. On RX, rmnet_map_validate_packet_len() and
__rmnet_map_ingress_handler() could likewise disagree on the
expected layout, and rmnet_map_send_ack() could trim a dl csum
trailer that the ingress path never accounted for.
rmnet_map_add_map_header(), rmnet_map_command(),
rmnet_map_deaggregate(), rmnet_map_send_ack(),
rmnet_map_validate_packet_len() and __rmnet_map_ingress_handler()
therefore get the value from their caller instead of re-reading it.
rmnet_changelink() now computes the new value in a local variable
and publishes it with a single store, instead of letting the data
path observe the intermediate (old_data_format & ~flags->mask)
value.
rmnet_vnd_headroom() is left alone, all its callers hold RTNL.
Signed-off-by: Eric Dumazet <edumazet@google.com>
Cc: Subash Abhinov Kasiviswanathan <subash.a.kasiviswanathan@oss.qualcomm.com>
Cc: Sean Tranchetti <sean.tranchetti@oss.qualcomm.com>
---
.../ethernet/qualcomm/rmnet/rmnet_config.c | 10 +++---
.../ethernet/qualcomm/rmnet/rmnet_handlers.c | 32 +++++++++++--------
.../net/ethernet/qualcomm/rmnet/rmnet_map.h | 9 +++---
.../qualcomm/rmnet/rmnet_map_command.c | 9 +++---
.../ethernet/qualcomm/rmnet/rmnet_map_data.c | 14 ++++----
5 files changed, 42 insertions(+), 32 deletions(-)
diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c
index bed6f63facf250bd1f8d07d09f7715918421a88d..59ef8b4ce5321ebbd1d416fd9da0a820e53ecf9e 100644
--- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c
+++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c
@@ -176,7 +176,7 @@ static int rmnet_newlink(struct net_device *dev,
}
netdev_dbg(dev, "data format [0x%08X]\n", data_format);
- port->data_format = data_format;
+ WRITE_ONCE(port->data_format, data_format);
return 0;
@@ -342,14 +342,16 @@ static int rmnet_changelink(struct net_device *dev, struct nlattr *tb[],
if (data[IFLA_RMNET_FLAGS]) {
struct ifla_rmnet_flags *flags;
u32 old_data_format;
+ u32 data_format;
old_data_format = port->data_format;
flags = nla_data(data[IFLA_RMNET_FLAGS]);
- port->data_format &= ~flags->mask;
- port->data_format |= flags->flags & flags->mask;
+ data_format = old_data_format & ~flags->mask;
+ data_format |= flags->flags & flags->mask;
+ WRITE_ONCE(port->data_format, data_format);
if (rmnet_vnd_update_dev_mtu(port, real_dev)) {
- port->data_format = old_data_format;
+ WRITE_ONCE(port->data_format, old_data_format);
NL_SET_ERR_MSG_MOD(extack, "Invalid MTU on real dev");
return -EINVAL;
}
diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_handlers.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_handlers.c
index d055a2628d8c9d0b6e7e85eb98f6fa9b5a1bd531..d4fec2fff227cd93f14eb1802aff185e6d496786 100644
--- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_handlers.c
+++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_handlers.c
@@ -54,7 +54,8 @@ rmnet_deliver_skb(struct sk_buff *skb)
static void
__rmnet_map_ingress_handler(struct sk_buff *skb,
- struct rmnet_port *port)
+ struct rmnet_port *port,
+ u32 data_format)
{
struct rmnet_map_header *map_header = (void *)skb->data;
struct rmnet_endpoint *ep;
@@ -63,8 +64,8 @@ __rmnet_map_ingress_handler(struct sk_buff *skb,
if (map_header->flags & MAP_CMD_FLAG) {
/* Packet contains a MAP command (not data) */
- if (port->data_format & RMNET_FLAGS_INGRESS_MAP_COMMANDS)
- return rmnet_map_command(skb, port);
+ if (data_format & RMNET_FLAGS_INGRESS_MAP_COMMANDS)
+ return rmnet_map_command(skb, port, data_format);
goto free_skb;
}
@@ -82,7 +83,7 @@ __rmnet_map_ingress_handler(struct sk_buff *skb,
skb->dev = ep->egress_dev;
- if ((port->data_format & RMNET_FLAGS_INGRESS_MAP_CKSUMV5) &&
+ if ((data_format & RMNET_FLAGS_INGRESS_MAP_CKSUMV5) &&
(map_header->flags & MAP_NEXT_HEADER_FLAG)) {
if (rmnet_map_process_next_hdr_packet(skb, len))
goto free_skb;
@@ -92,7 +93,7 @@ __rmnet_map_ingress_handler(struct sk_buff *skb,
/* Subtract MAP header */
skb_pull(skb, sizeof(*map_header));
rmnet_set_skb_proto(skb);
- if (port->data_format & RMNET_FLAGS_INGRESS_MAP_CKSUMV4 &&
+ if (data_format & RMNET_FLAGS_INGRESS_MAP_CKSUMV4 &&
!rmnet_map_checksum_downlink_packet(skb, len + pad))
skb->ip_summed = CHECKSUM_UNNECESSARY;
}
@@ -110,6 +111,7 @@ rmnet_map_ingress_handler(struct sk_buff *skb,
struct rmnet_port *port)
{
struct sk_buff *skbn;
+ u32 data_format;
if (skb->dev->type == ARPHRD_ETHER) {
if (pskb_expand_head(skb, ETH_HLEN, 0, GFP_ATOMIC)) {
@@ -120,14 +122,16 @@ rmnet_map_ingress_handler(struct sk_buff *skb,
skb_push(skb, ETH_HLEN);
}
- if (port->data_format & RMNET_FLAGS_INGRESS_DEAGGREGATION) {
- while ((skbn = rmnet_map_deaggregate(skb, port)) != NULL)
- __rmnet_map_ingress_handler(skbn, port);
+ data_format = READ_ONCE(port->data_format);
+
+ if (data_format & RMNET_FLAGS_INGRESS_DEAGGREGATION) {
+ while ((skbn = rmnet_map_deaggregate(skb, data_format)) != NULL)
+ __rmnet_map_ingress_handler(skbn, port, data_format);
consume_skb(skb);
} else {
- if (rmnet_map_validate_packet_len(skb, port))
- __rmnet_map_ingress_handler(skb, port);
+ if (rmnet_map_validate_packet_len(skb, data_format))
+ __rmnet_map_ingress_handler(skb, port, data_format);
else
kfree_skb(skb);
}
@@ -139,14 +143,16 @@ static int rmnet_map_egress_handler(struct sk_buff *skb,
{
int required_headroom, additional_header_len, csum_type = 0;
struct rmnet_map_header *map_header;
+ u32 data_format;
additional_header_len = 0;
required_headroom = sizeof(struct rmnet_map_header);
- if (port->data_format & RMNET_FLAGS_EGRESS_MAP_CKSUMV4) {
+ data_format = READ_ONCE(port->data_format);
+ if (data_format & RMNET_FLAGS_EGRESS_MAP_CKSUMV4) {
additional_header_len = sizeof(struct rmnet_map_ul_csum_header);
csum_type = RMNET_FLAGS_EGRESS_MAP_CKSUMV4;
- } else if (port->data_format & RMNET_FLAGS_EGRESS_MAP_CKSUMV5) {
+ } else if (data_format & RMNET_FLAGS_EGRESS_MAP_CKSUMV5) {
additional_header_len = sizeof(struct rmnet_map_v5_csum_header);
csum_type = RMNET_FLAGS_EGRESS_MAP_CKSUMV5;
}
@@ -161,7 +167,7 @@ static int rmnet_map_egress_handler(struct sk_buff *skb,
csum_type);
map_header = rmnet_map_add_map_header(skb, additional_header_len,
- port, 0);
+ data_format, 0);
if (!map_header)
return -ENOMEM;
diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_map.h b/drivers/net/ethernet/qualcomm/rmnet/rmnet_map.h
index 60ca8b780c88ad7d9a6334370c0ff73a83e02d2d..0977e495f5915539fc1154bb592a2648aef43cda 100644
--- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_map.h
+++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_map.h
@@ -41,12 +41,13 @@ enum rmnet_map_commands {
#define RMNET_MAP_ADD_PAD_BYTES 1
struct sk_buff *rmnet_map_deaggregate(struct sk_buff *skb,
- struct rmnet_port *port);
+ u32 data_format);
struct rmnet_map_header *rmnet_map_add_map_header(struct sk_buff *skb,
int hdrlen,
- struct rmnet_port *port,
+ u32 data_format,
int pad);
-void rmnet_map_command(struct sk_buff *skb, struct rmnet_port *port);
+void rmnet_map_command(struct sk_buff *skb, struct rmnet_port *port,
+ u32 data_format);
int rmnet_map_checksum_downlink_packet(struct sk_buff *skb, u16 len);
void rmnet_map_checksum_uplink_packet(struct sk_buff *skb,
struct rmnet_port *port,
@@ -59,6 +60,6 @@ void rmnet_map_tx_aggregate_init(struct rmnet_port *port);
void rmnet_map_tx_aggregate_exit(struct rmnet_port *port);
void rmnet_map_update_ul_agg_config(struct rmnet_port *port, u32 size,
u32 count, u32 time);
-u32 rmnet_map_validate_packet_len(struct sk_buff *skb, struct rmnet_port *port);
+u32 rmnet_map_validate_packet_len(struct sk_buff *skb, u32 data_format);
#endif /* _RMNET_MAP_H_ */
diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_map_command.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_map_command.c
index add0f5ade2e6174427abff532d160f89122059f7..d334727e1f5256201996fc56c419ec05f04dde4e 100644
--- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_map_command.c
+++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_map_command.c
@@ -48,13 +48,13 @@ static u8 rmnet_map_do_flow_control(struct sk_buff *skb,
static void rmnet_map_send_ack(struct sk_buff *skb,
unsigned char type,
- struct rmnet_port *port)
+ u32 data_format)
{
struct rmnet_map_header *map_header = (void *)skb->data;
struct rmnet_map_control_command *cmd;
struct net_device *dev = skb->dev;
- if (port->data_format & RMNET_FLAGS_INGRESS_MAP_CKSUMV4)
+ if (data_format & RMNET_FLAGS_INGRESS_MAP_CKSUMV4)
skb_trim(skb,
skb->len - sizeof(struct rmnet_map_dl_csum_trailer));
@@ -72,7 +72,8 @@ static void rmnet_map_send_ack(struct sk_buff *skb,
/* Process MAP command frame and send N/ACK message as appropriate. Message cmd
* name is decoded here and appropriate handler is called.
*/
-void rmnet_map_command(struct sk_buff *skb, struct rmnet_port *port)
+void rmnet_map_command(struct sk_buff *skb, struct rmnet_port *port,
+ u32 data_format)
{
struct rmnet_map_header *map_header = (void *)skb->data;
struct rmnet_map_control_command *cmd;
@@ -98,5 +99,5 @@ void rmnet_map_command(struct sk_buff *skb, struct rmnet_port *port)
break;
}
if (rc == RMNET_MAP_COMMAND_ACK)
- rmnet_map_send_ack(skb, rc, port);
+ rmnet_map_send_ack(skb, rc, data_format);
}
diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_map_data.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_map_data.c
index e6f48dc9fe64d7e563a0ba355c1ede35c73b4593..39d6d084e73f00f9fcd475ad98b16dde4cb266c6 100644
--- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_map_data.c
+++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_map_data.c
@@ -294,7 +294,7 @@ static void rmnet_map_v5_checksum_uplink_packet(struct sk_buff *skb,
*/
struct rmnet_map_header *rmnet_map_add_map_header(struct sk_buff *skb,
int hdrlen,
- struct rmnet_port *port,
+ u32 data_format,
int pad)
{
struct rmnet_map_header *map_header;
@@ -306,7 +306,7 @@ struct rmnet_map_header *rmnet_map_add_map_header(struct sk_buff *skb,
memset(map_header, 0, sizeof(struct rmnet_map_header));
/* Set next_hdr bit for csum offload packets */
- if (port->data_format & RMNET_FLAGS_EGRESS_MAP_CKSUMV5)
+ if (data_format & RMNET_FLAGS_EGRESS_MAP_CKSUMV5)
map_header->flags |= MAP_NEXT_HEADER_FLAG;
if (pad == RMNET_MAP_NO_PAD_BYTES) {
@@ -333,7 +333,7 @@ struct rmnet_map_header *rmnet_map_add_map_header(struct sk_buff *skb,
return map_header;
}
-u32 rmnet_map_validate_packet_len(struct sk_buff *skb, struct rmnet_port *port)
+u32 rmnet_map_validate_packet_len(struct sk_buff *skb, u32 data_format)
{
struct rmnet_map_v5_csum_header *next_hdr = NULL;
struct rmnet_map_header *maph;
@@ -351,9 +351,9 @@ u32 rmnet_map_validate_packet_len(struct sk_buff *skb, struct rmnet_port *port)
packet_len = ntohs(maph->pkt_len) + sizeof(*maph);
- if (port->data_format & RMNET_FLAGS_INGRESS_MAP_CKSUMV4) {
+ if (data_format & RMNET_FLAGS_INGRESS_MAP_CKSUMV4) {
packet_len += sizeof(struct rmnet_map_dl_csum_trailer);
- } else if ((port->data_format & RMNET_FLAGS_INGRESS_MAP_CKSUMV5) &&
+ } else if ((data_format & RMNET_FLAGS_INGRESS_MAP_CKSUMV5) &&
!(maph->flags & MAP_CMD_FLAG)) {
/* Mapv5 data pkt without csum hdr is invalid */
if (!(maph->flags & MAP_NEXT_HEADER_FLAG))
@@ -381,12 +381,12 @@ u32 rmnet_map_validate_packet_len(struct sk_buff *skb, struct rmnet_port *port)
* is responsible for freeing the original skb.
*/
struct sk_buff *rmnet_map_deaggregate(struct sk_buff *skb,
- struct rmnet_port *port)
+ u32 data_format)
{
struct sk_buff *skbn;
u32 packet_len;
- packet_len = rmnet_map_validate_packet_len(skb, port);
+ packet_len = rmnet_map_validate_packet_len(skb, data_format);
if (!packet_len)
return NULL;
--
2.55.0.1032.g73a4cd73de-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH net-next 2/3] net: rmnet: annotate data-races around mux_id
2026-09-16 13:22 [PATCH net-next 0/3] net: rmnet: lockless rmnet_fill_info() Eric Dumazet
2026-09-16 13:22 ` [PATCH net-next 1/3] net: rmnet: annotate data-races around port->data_format Eric Dumazet
@ 2026-09-16 13:22 ` Eric Dumazet
2026-09-17 13:22 ` netdev-bot+sashiko
2026-09-16 13:22 ` [PATCH net-next 3/3] net: rmnet: no longer rely on RTNL in rmnet_fill_info() Eric Dumazet
` (2 subsequent siblings)
4 siblings, 1 reply; 8+ messages in thread
From: Eric Dumazet @ 2026-09-16 13:22 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Kuniyuki Iwashima, Andrew Lunn, netdev,
eric.dumazet, Eric Dumazet, Subash Abhinov Kasiviswanathan,
Sean Tranchetti
priv->mux_id is written under RTNL from rmnet_vnd_newlink() and
rmnet_changelink(), but rmnet_egress_handler() reads it from the
transmit path without any lock.
Likewise ep->mux_id is written under RTNL from rmnet_changelink(),
while rmnet_get_endpoint() reads it from the receive path under
RCU only.
Add the missing READ_ONCE()/WRITE_ONCE() annotations.
While at it, publish ep->mux_id before inserting the endpoint in
its new bucket in rmnet_changelink(). Otherwise a concurrent
rmnet_get_endpoint() walking the new bucket could find the
endpoint still carrying its old mux_id, and drop the packet.
Signed-off-by: Eric Dumazet <edumazet@google.com>
Cc: Subash Abhinov Kasiviswanathan <subash.a.kasiviswanathan@oss.qualcomm.com>
Cc: Sean Tranchetti <sean.tranchetti@oss.qualcomm.com>
---
drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c | 6 +++---
drivers/net/ethernet/qualcomm/rmnet/rmnet_handlers.c | 2 +-
drivers/net/ethernet/qualcomm/rmnet/rmnet_vnd.c | 2 +-
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c
index 59ef8b4ce5321ebbd1d416fd9da0a820e53ecf9e..248a9d822409b7cbf3739c085487644b7025dd37 100644
--- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c
+++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c
@@ -331,11 +331,11 @@ static int rmnet_changelink(struct net_device *dev, struct nlattr *tb[],
}
hlist_del_init_rcu(&ep->hlnode);
+ WRITE_ONCE(ep->mux_id, mux_id);
hlist_add_head_rcu(&ep->hlnode,
&port->muxed_ep[mux_id]);
- ep->mux_id = mux_id;
- priv->mux_id = mux_id;
+ WRITE_ONCE(priv->mux_id, mux_id);
}
}
@@ -427,7 +427,7 @@ struct rmnet_endpoint *rmnet_get_endpoint(struct rmnet_port *port, u8 mux_id)
hlist_for_each_entry_rcu(ep, &port->muxed_ep[mux_id], hlnode,
lockdep_rtnl_is_held()) {
- if (ep->mux_id == mux_id)
+ if (READ_ONCE(ep->mux_id) == mux_id)
return ep;
}
diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_handlers.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_handlers.c
index d4fec2fff227cd93f14eb1802aff185e6d496786..aa5523f4618eb350ec3da20b7f085d955b3403a2 100644
--- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_handlers.c
+++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_handlers.c
@@ -261,7 +261,7 @@ void rmnet_egress_handler(struct sk_buff *skb)
orig_dev = skb->dev;
priv = netdev_priv(orig_dev);
skb->dev = priv->real_dev;
- mux_id = priv->mux_id;
+ mux_id = READ_ONCE(priv->mux_id);
port = rmnet_get_port_rcu(skb->dev);
if (!port)
diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_vnd.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_vnd.c
index 4f0ddcedfa9790dc5843fdea85b2da4bc8329775..9594c97c918565e93ac70ac73340f705b391bd18 100644
--- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_vnd.c
+++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_vnd.c
@@ -332,7 +332,7 @@ int rmnet_vnd_newlink(u8 id, struct net_device *rmnet_dev,
rmnet_dev->rtnl_link_ops = &rmnet_link_ops;
- priv->mux_id = id;
+ WRITE_ONCE(priv->mux_id, id);
netdev_dbg(rmnet_dev, "rmnet dev created\n");
}
--
2.55.0.1032.g73a4cd73de-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH net-next 3/3] net: rmnet: no longer rely on RTNL in rmnet_fill_info()
2026-09-16 13:22 [PATCH net-next 0/3] net: rmnet: lockless rmnet_fill_info() Eric Dumazet
2026-09-16 13:22 ` [PATCH net-next 1/3] net: rmnet: annotate data-races around port->data_format Eric Dumazet
2026-09-16 13:22 ` [PATCH net-next 2/3] net: rmnet: annotate data-races around mux_id Eric Dumazet
@ 2026-09-16 13:22 ` Eric Dumazet
2026-09-18 3:04 ` [PATCH net-next 0/3] net: rmnet: lockless rmnet_fill_info() subash.a.kasiviswanathan
2026-09-18 22:50 ` patchwork-bot+netdevbpf
4 siblings, 0 replies; 8+ messages in thread
From: Eric Dumazet @ 2026-09-16 13:22 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Kuniyuki Iwashima, Andrew Lunn, netdev,
eric.dumazet, Eric Dumazet, Subash Abhinov Kasiviswanathan,
Sean Tranchetti
rmnet_fill_info() used rmnet_get_port_rtnl(), and thus required
RTNL.
Now that priv->mux_id and port->data_format are properly
annotated, rmnet_fill_info() can simply fetch the port under
rcu_read_lock().
rmnet_get_port_rcu() was only used from the data path and thus
used rcu_dereference_bh(). Relax its lockdep condition so that it
can also be called from process context under a plain
rcu_read_lock(), and use it from rmnet_fill_info().
Note that rmnet_get_port_rcu() checks real_dev->rx_handler before
returning rx_handler_data: this is what makes the lockless lookup
safe against rmnet_unregister_real_device(), which clears
rx_handler, waits for a grace period in
netdev_rx_handler_unregister(), and only then frees the port.
While at it, add missing const qualifiers.
Signed-off-by: Eric Dumazet <edumazet@google.com>
Cc: Subash Abhinov Kasiviswanathan <subash.a.kasiviswanathan@oss.qualcomm.com>
Cc: Sean Tranchetti <sean.tranchetti@oss.qualcomm.com>
---
.../ethernet/qualcomm/rmnet/rmnet_config.c | 38 +++++++++----------
.../ethernet/qualcomm/rmnet/rmnet_config.h | 2 +-
2 files changed, 18 insertions(+), 22 deletions(-)
diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c
index 248a9d822409b7cbf3739c085487644b7025dd37..b930f638ec448de839806852a74fc4b2f8888e43 100644
--- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c
+++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c
@@ -371,32 +371,24 @@ static size_t rmnet_get_size(const struct net_device *dev)
static int rmnet_fill_info(struct sk_buff *skb, const struct net_device *dev)
{
- struct rmnet_priv *priv = netdev_priv(dev);
- struct net_device *real_dev;
+ const struct rmnet_priv *priv = netdev_priv(dev);
+ const struct rmnet_port *port;
struct ifla_rmnet_flags f;
- struct rmnet_port *port;
- real_dev = priv->real_dev;
+ if (nla_put_u16(skb, IFLA_RMNET_MUX_ID, READ_ONCE(priv->mux_id)))
+ return -EMSGSIZE;
- if (nla_put_u16(skb, IFLA_RMNET_MUX_ID, priv->mux_id))
- goto nla_put_failure;
-
- if (rmnet_is_real_dev_registered(real_dev)) {
- port = rmnet_get_port_rtnl(real_dev);
- f.flags = port->data_format;
- } else {
- f.flags = 0;
- }
+ rcu_read_lock();
+ port = rmnet_get_port_rcu(priv->real_dev);
+ f.flags = port ? READ_ONCE(port->data_format) : 0;
+ rcu_read_unlock();
f.mask = ~0;
if (nla_put(skb, IFLA_RMNET_FLAGS, sizeof(f), &f))
- goto nla_put_failure;
+ return -EMSGSIZE;
return 0;
-
-nla_put_failure:
- return -EMSGSIZE;
}
struct rtnl_link_ops rmnet_link_ops __read_mostly = {
@@ -413,12 +405,16 @@ struct rtnl_link_ops rmnet_link_ops __read_mostly = {
.fill_info = rmnet_fill_info,
};
-struct rmnet_port *rmnet_get_port_rcu(struct net_device *real_dev)
+/* Can be called from a RCU read-side critical section, with or
+ * without BH disabled.
+ */
+struct rmnet_port *rmnet_get_port_rcu(const struct net_device *real_dev)
{
- if (rmnet_is_real_dev_registered(real_dev))
- return rcu_dereference_bh(real_dev->rx_handler_data);
- else
+ if (!rmnet_is_real_dev_registered(real_dev))
return NULL;
+
+ return rcu_dereference_check(real_dev->rx_handler_data,
+ rcu_read_lock_bh_held());
}
struct rmnet_endpoint *rmnet_get_endpoint(struct rmnet_port *port, u8 mux_id)
diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.h b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.h
index f50fae1c6bdd5d4edfb18476e2ca44057921a472..5adda0323dda776d69d50d22bc75456b43442e3d 100644
--- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.h
+++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.h
@@ -90,7 +90,7 @@ struct rmnet_priv {
struct rmnet_priv_stats stats;
};
-struct rmnet_port *rmnet_get_port_rcu(struct net_device *real_dev);
+struct rmnet_port *rmnet_get_port_rcu(const struct net_device *real_dev);
struct rmnet_endpoint *rmnet_get_endpoint(struct rmnet_port *port, u8 mux_id);
int rmnet_add_bridge(struct net_device *rmnet_dev,
struct net_device *slave_dev,
--
2.55.0.1032.g73a4cd73de-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH net-next 1/3] net: rmnet: annotate data-races around port->data_format
2026-09-16 13:22 ` [PATCH net-next 1/3] net: rmnet: annotate data-races around port->data_format Eric Dumazet
@ 2026-09-17 13:22 ` netdev-bot+sashiko
0 siblings, 0 replies; 8+ messages in thread
From: netdev-bot+sashiko @ 2026-09-17 13:22 UTC (permalink / raw)
To: edumazet
Cc: davem, kuba, pabeni, horms, kuniyu, andrew+netdev, netdev,
eric.dumazet, subash.a.kasiviswanathan, sean.tranchetti
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 1 (2) · Low: 1
- [Medium] Even with a single per-packet snapshot of port->data_format,
the individual stages still resolve the checksum-version bits…
- [Low] The commit message of "net: rmnet: annotate data-races around
port->data_format" documents concrete, user-visible wire-format…
Pre-existing issues:
- [Medium] port->rmnet_mode and port->bridge_ep are written with plain
stores under RTNL (rmnet_newlink() at rmnet_config.c:163-164,…
- [Medium] rmnet_register_real_device() publishes the zero-initialized
struct rmnet_port through netdev_rx_handler_register()…
--
net: rmnet: annotate data-races around port->data_format
This adds READ_ONCE()/WRITE_ONCE() annotations around
port->data_format, which is written under RTNL from rmnet_newlink() and
rmnet_changelink() but read from the RX and TX data paths without any
lock. The value is now sampled once per packet in
rmnet_map_ingress_handler() and rmnet_map_egress_handler() and passed
down to the MAP helpers instead of being re-read by each of them.
A couple of questions below about the commit message and about the
remaining asymmetry between the stages that consume the sampled value.
[Severity: Low]
The changelog describes concrete wire-format failures rather than just a
KCSAN annotation cleanup:
> so a concurrent rmnet_changelink() could produce
> a MAP header announcing a v5 csum header that was neither reserved
> nor written.
and
> rmnet_map_send_ack() could trim a dl csum
> trailer that the ingress path never accounted for.
Should this carry a Fixes: tag then? Candidates would be
b6e5d27e32ef60 ("net: ethernet: rmnet: Add support for MAPv5 egress
packets"), which added the second read of port->data_format in
rmnet_map_add_map_header(), and 14452ca3b5ce ("net: qualcomm: rmnet:
Export mux_id and flags to netlink"), which made the flags runtime
configurable.
Without a tag, stable tooling will not pick this up. If the intent is
net-next hardening instead (the third patch in the series,
"net: rmnet: no longer rely on RTNL in rmnet_fill_info()", is clearly a
refactor), would it be better to word the two paragraphs above as
theoretical rather than as observed packet corruption?
> diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c
> index bed6f63facf25..59ef8b4ce5321 100644
> --- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c
> +++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c
[ ... ]
> @@ -342,14 +342,16 @@ static int rmnet_changelink(struct net_device *dev, struct nlattr *tb[],
> if (data[IFLA_RMNET_FLAGS]) {
> struct ifla_rmnet_flags *flags;
> u32 old_data_format;
> + u32 data_format;
>
> old_data_format = port->data_format;
> flags = nla_data(data[IFLA_RMNET_FLAGS]);
> - port->data_format &= ~flags->mask;
> - port->data_format |= flags->flags & flags->mask;
> + data_format = old_data_format & ~flags->mask;
> + data_format |= flags->flags & flags->mask;
> + WRITE_ONCE(port->data_format, data_format);
>
> if (rmnet_vnd_update_dev_mtu(port, real_dev)) {
> - port->data_format = old_data_format;
> + WRITE_ONCE(port->data_format, old_data_format);
> NL_SET_ERR_MSG_MOD(extack, "Invalid MTU on real dev");
> return -EINVAL;
> }
> diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_handlers.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_handlers.c
> index d055a2628d8c9..d4fec2fff227c 100644
> --- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_handlers.c
> +++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_handlers.c
[ ... ]
> @@ -82,7 +83,7 @@ __rmnet_map_ingress_handler(struct sk_buff *skb,
>
> skb->dev = ep->egress_dev;
>
> - if ((port->data_format & RMNET_FLAGS_INGRESS_MAP_CKSUMV5) &&
> + if ((data_format & RMNET_FLAGS_INGRESS_MAP_CKSUMV5) &&
> (map_header->flags & MAP_NEXT_HEADER_FLAG)) {
> if (rmnet_map_process_next_hdr_packet(skb, len))
> goto free_skb;
[ ... ]
> @@ -120,14 +122,16 @@ rmnet_map_ingress_handler(struct sk_buff *skb,
> skb_push(skb, ETH_HLEN);
> }
>
> - if (port->data_format & RMNET_FLAGS_INGRESS_DEAGGREGATION) {
> - while ((skbn = rmnet_map_deaggregate(skb, port)) != NULL)
> - __rmnet_map_ingress_handler(skbn, port);
> + data_format = READ_ONCE(port->data_format);
> +
> + if (data_format & RMNET_FLAGS_INGRESS_DEAGGREGATION) {
> + while ((skbn = rmnet_map_deaggregate(skb, data_format)) != NULL)
> + __rmnet_map_ingress_handler(skbn, port, data_format);
>
> consume_skb(skb);
> } else {
> - if (rmnet_map_validate_packet_len(skb, port))
> - __rmnet_map_ingress_handler(skb, port);
> + if (rmnet_map_validate_packet_len(skb, data_format))
> + __rmnet_map_ingress_handler(skb, port, data_format);
> else
> kfree_skb(skb);
> }
[Severity: Medium]
This isn't a bug introduced by this patch, but the caller of
rmnet_map_ingress_handler() reads two more fields of the same struct
with plain loads while RTNL writers store them plainly:
drivers/net/ethernet/qualcomm/rmnet/rmnet_handlers.c:rmnet_rx_handler() {
switch (port->rmnet_mode) {
case RMNET_EPMODE_VND:
rmnet_map_ingress_handler(skb, port);
break;
case RMNET_EPMODE_BRIDGE:
rmnet_bridge_handler(skb, port->bridge_ep);
break;
}
done:
return RX_HANDLER_CONSUMED;
}
The writers update the pair in two separate stores, in opposite orders.
rmnet_add_bridge() does:
port->rmnet_mode = RMNET_EPMODE_BRIDGE;
port->bridge_ep = slave_dev;
while rmnet_unregister_bridge() does:
port->bridge_ep = NULL;
port->rmnet_mode = RMNET_EPMODE_VND;
Can a concurrent receive observe rmnet_mode == RMNET_EPMODE_BRIDGE
together with bridge_ep == NULL? rmnet_bridge_handler() only acts
inside "if (bridge_dev)", yet rmnet_rx_handler() still returns
RX_HANDLER_CONSUMED, so does the skb leak in that window?
Since the RX path being annotated here is the same one that reads these
fields, would it make sense to annotate rmnet_mode and bridge_ep in the
same series? They are still plain accesses at the end of the series
(94ca9c4e3244).
[Severity: Medium]
This is a pre-existing issue, but it is closely related to the note in
the changelog that "the rx_handler is live and traffic can already be
flowing". rmnet_register_real_device() publishes the zero-filled port
through the rx_handler before any receive-visible state is set:
drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c:rmnet_register_real_device() {
port = kzalloc_obj(*port);
...
rc = netdev_rx_handler_register(real_dev, rmnet_rx_handler, port);
...
for (entry = 0; entry < RMNET_MAX_LOGICAL_EP; entry++)
INIT_HLIST_HEAD(&port->muxed_ep[entry]);
}
port->rmnet_mode is only assigned later, in rmnet_newlink(), after
rmnet_vnd_newlink() and netdev_upper_dev_link(). The valid values are
RMNET_EPMODE_VND (1) and RMNET_EPMODE_BRIDGE (2), so during that window
rmnet_mode is 0, the switch in rmnet_rx_handler() matches neither case
and there is no default, but RX_HANDLER_CONSUMED is still returned.
Is a packet arriving on the real_dev in that window leaked? Would it be
better to install a valid rmnet_mode before netdev_rx_handler_register()?
> @@ -139,14 +143,16 @@ static int rmnet_map_egress_handler(struct sk_buff *skb,
> {
> int required_headroom, additional_header_len, csum_type = 0;
> struct rmnet_map_header *map_header;
> + u32 data_format;
>
> additional_header_len = 0;
> required_headroom = sizeof(struct rmnet_map_header);
>
> - if (port->data_format & RMNET_FLAGS_EGRESS_MAP_CKSUMV4) {
> + data_format = READ_ONCE(port->data_format);
> + if (data_format & RMNET_FLAGS_EGRESS_MAP_CKSUMV4) {
> additional_header_len = sizeof(struct rmnet_map_ul_csum_header);
> csum_type = RMNET_FLAGS_EGRESS_MAP_CKSUMV4;
> - } else if (port->data_format & RMNET_FLAGS_EGRESS_MAP_CKSUMV5) {
> + } else if (data_format & RMNET_FLAGS_EGRESS_MAP_CKSUMV5) {
> additional_header_len = sizeof(struct rmnet_map_v5_csum_header);
> csum_type = RMNET_FLAGS_EGRESS_MAP_CKSUMV5;
> }
> @@ -161,7 +167,7 @@ static int rmnet_map_egress_handler(struct sk_buff *skb,
> csum_type);
>
> map_header = rmnet_map_add_map_header(skb, additional_header_len,
> - port, 0);
> + data_format, 0);
> if (!map_header)
> return -ENOMEM;
[Severity: Medium]
The snapshot removes the race, but the stages still resolve the
checksum-version bits differently, so is the symptom named in the
changelog ("a MAP header announcing a v5 csum header that was neither
reserved nor written") still reachable when both egress bits are set,
now without any race?
Nothing appears to reject that combination: rmnet_policy only checks the
size of struct ifla_rmnet_flags, rmnet_rtnl_validate() only validates
the MUX ID, and both rmnet_newlink() and rmnet_changelink() apply
flags->flags & flags->mask verbatim. RMNET_FLAGS_EGRESS_MAP_CKSUMV4
and RMNET_FLAGS_EGRESS_MAP_CKSUMV5 are independent uAPI bits.
On TX, this function picks V4 first, so
rmnet_map_checksum_uplink_packet() -> rmnet_map_v4_checksum_uplink_packet()
pushes a v4 header:
ul_header = (struct rmnet_map_ul_csum_header *)
skb_push(skb, sizeof(struct rmnet_map_ul_csum_header));
while rmnet_map_add_map_header() looks at the raw V5 bit of the same
sampled word:
/* Set next_hdr bit for csum offload packets */
if (data_format & RMNET_FLAGS_EGRESS_MAP_CKSUMV5)
map_header->flags |= MAP_NEXT_HEADER_FLAG;
Since the only caller passes pad == RMNET_MAP_NO_PAD_BYTES, the early
return keeps that flag. Would passing the already-resolved csum_type
(or rejecting both version bits at configuration time) be preferable to
threading the raw bitmask, which only closes the race?
The same asymmetry exists on RX: rmnet_map_validate_packet_len() gives
INGRESS_MAP_CKSUMV4 precedence and adds 8 bytes of dl csum trailer,
__rmnet_map_ingress_handler() gives V5 precedence when
MAP_NEXT_HEADER_FLAG is set and consumes a 4-byte v5 header, and
rmnet_map_send_ack() trims 8 bytes purely on the V4 bit. With both
ingress bits set, is a v5 frame rejected as 4 bytes short, and inside an
aggregate does rmnet_map_deaggregate() advance its cursor 4 bytes into
the following MAP header, breaking every later split point?
> diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_map_command.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_map_command.c
> index add0f5ade2e61..d334727e1f525 100644
> --- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_map_command.c
> +++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_map_command.c
> @@ -48,13 +48,13 @@ static u8 rmnet_map_do_flow_control(struct sk_buff *skb,
>
> static void rmnet_map_send_ack(struct sk_buff *skb,
> unsigned char type,
> - struct rmnet_port *port)
> + u32 data_format)
> {
> struct rmnet_map_header *map_header = (void *)skb->data;
> struct rmnet_map_control_command *cmd;
> struct net_device *dev = skb->dev;
>
> - if (port->data_format & RMNET_FLAGS_INGRESS_MAP_CKSUMV4)
> + if (data_format & RMNET_FLAGS_INGRESS_MAP_CKSUMV4)
> skb_trim(skb,
> skb->len - sizeof(struct rmnet_map_dl_csum_trailer));
>
> diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_map_data.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_map_data.c
> index e6f48dc9fe64d..39d6d084e73f0 100644
> --- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_map_data.c
> +++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_map_data.c
> @@ -306,7 +306,7 @@ struct rmnet_map_header *rmnet_map_add_map_header(struct sk_buff *skb,
> memset(map_header, 0, sizeof(struct rmnet_map_header));
>
> /* Set next_hdr bit for csum offload packets */
> - if (port->data_format & RMNET_FLAGS_EGRESS_MAP_CKSUMV5)
> + if (data_format & RMNET_FLAGS_EGRESS_MAP_CKSUMV5)
> map_header->flags |= MAP_NEXT_HEADER_FLAG;
>
> if (pad == RMNET_MAP_NO_PAD_BYTES) {
> @@ -351,9 +351,9 @@ u32 rmnet_map_validate_packet_len(struct sk_buff *skb, struct rmnet_port *port)
>
> packet_len = ntohs(maph->pkt_len) + sizeof(*maph);
>
> - if (port->data_format & RMNET_FLAGS_INGRESS_MAP_CKSUMV4) {
> + if (data_format & RMNET_FLAGS_INGRESS_MAP_CKSUMV4) {
> packet_len += sizeof(struct rmnet_map_dl_csum_trailer);
> - } else if ((port->data_format & RMNET_FLAGS_INGRESS_MAP_CKSUMV5) &&
> + } else if ((data_format & RMNET_FLAGS_INGRESS_MAP_CKSUMV5) &&
> !(maph->flags & MAP_CMD_FLAG)) {
> /* Mapv5 data pkt without csum hdr is invalid */
> if (!(maph->flags & MAP_NEXT_HEADER_FLAG))
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260916132231.1780091-1-edumazet%40google.com
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH net-next 2/3] net: rmnet: annotate data-races around mux_id
2026-09-16 13:22 ` [PATCH net-next 2/3] net: rmnet: annotate data-races around mux_id Eric Dumazet
@ 2026-09-17 13:22 ` netdev-bot+sashiko
0 siblings, 0 replies; 8+ messages in thread
From: netdev-bot+sashiko @ 2026-09-17 13:22 UTC (permalink / raw)
To: edumazet
Cc: davem, kuba, pabeni, horms, kuniyu, andrew+netdev, netdev,
eric.dumazet, subash.a.kasiviswanathan, sean.tranchetti
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 0 · Low: 1
- [Low] The patch "net: rmnet: annotate data-races around mux_id"
contains, besides the READ_ONCE()/WRITE_ONCE() annotations, a real…
--
net: rmnet: annotate data-races around mux_id
This adds READ_ONCE()/WRITE_ONCE() annotations around priv->mux_id and
ep->mux_id, which are written under RTNL but read from the transmit and
receive paths. It also reorders rmnet_changelink() so that ep->mux_id is
published before the endpoint is inserted into its new hash bucket.
> diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c
> index 59ef8b4ce5321..248a9d822409b 100644
> --- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c
> +++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c
> @@ -331,11 +331,11 @@ static int rmnet_changelink(struct net_device *dev, struct nlattr *tb[],
> }
>
> hlist_del_init_rcu(&ep->hlnode);
> + WRITE_ONCE(ep->mux_id, mux_id);
> hlist_add_head_rcu(&ep->hlnode,
> &port->muxed_ep[mux_id]);
>
> - ep->mux_id = mux_id;
> - priv->mux_id = mux_id;
> + WRITE_ONCE(priv->mux_id, mux_id);
[Severity: Low]
Beyond the annotations, the reordering here looks like a functional fix
rather than a pure cleanup. Should it carry a Fixes: tag?
With the old ordering the endpoint was visible in the new bucket while
still carrying its old mux_id, so a concurrent reader in
rmnet_get_endpoint() would reject it:
hlist_for_each_entry_rcu(ep, &port->muxed_ep[mux_id], hlnode,
lockdep_rtnl_is_held()) {
if (READ_ONCE(ep->mux_id) == mux_id)
return ep;
}
return NULL;
and the RX path then frees the skb in __rmnet_map_ingress_handler():
ep = rmnet_get_endpoint(port, mux_id);
if (!ep)
goto free_skb;
The reader is driven by inbound traffic in
rmnet_rx_handler() -> rmnet_map_ingress_handler() ->
__rmnet_map_ingress_handler(), while the writer side runs from
ip link set <rmnet dev> type rmnet mux_id N, so the observable effect is
occasional packet loss during a mux_id change.
git blame points at commit 2abb5792387e ("net: qualcomm: rmnet: Allow
configuration updates to existing devices") for the previous ordering, so
would the following trailer be appropriate to help stable pick this up?
Fixes: 2abb5792387e ("net: qualcomm: rmnet: Allow configuration updates to existing devices")
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260916132231.1780091-1-edumazet%40google.com
^ permalink raw reply [flat|nested] 8+ messages in thread
* RE: [PATCH net-next 0/3] net: rmnet: lockless rmnet_fill_info()
2026-09-16 13:22 [PATCH net-next 0/3] net: rmnet: lockless rmnet_fill_info() Eric Dumazet
` (2 preceding siblings ...)
2026-09-16 13:22 ` [PATCH net-next 3/3] net: rmnet: no longer rely on RTNL in rmnet_fill_info() Eric Dumazet
@ 2026-09-18 3:04 ` subash.a.kasiviswanathan
2026-09-18 22:50 ` patchwork-bot+netdevbpf
4 siblings, 0 replies; 8+ messages in thread
From: subash.a.kasiviswanathan @ 2026-09-18 3:04 UTC (permalink / raw)
To: 'Eric Dumazet', 'David S . Miller',
'Jakub Kicinski', 'Paolo Abeni'
Cc: 'Simon Horman', 'Kuniyuki Iwashima',
'Andrew Lunn', netdev, eric.dumazet,
'Sean Tranchetti'
> -----Original Message-----
> From: Eric Dumazet <edumazet@google.com>
> Sent: Wednesday, September 16, 2026 7:22 AM
> To: David S . Miller <davem@davemloft.net>; Jakub Kicinski
> <kuba@kernel.org>; Paolo Abeni <pabeni@redhat.com>
> Cc: Simon Horman <horms@kernel.org>; Kuniyuki Iwashima
> <kuniyu@google.com>; Andrew Lunn <andrew+netdev@lunn.ch>;
> netdev@vger.kernel.org; eric.dumazet@gmail.com; Eric Dumazet
> <edumazet@google.com>; Subash Abhinov Kasiviswanathan
> <subash.a.kasiviswanathan@oss.qualcomm.com>; Sean Tranchetti
> <sean.tranchetti@oss.qualcomm.com>
> Subject: [PATCH net-next 0/3] net: rmnet: lockless rmnet_fill_info()
>
> rmnet_fill_info() currently relies on RTNL being held, because it uses
> rmnet_get_port_rtnl() to reach the rmnet_port attached to the underlying real
> device.
>
> While auditing the fields exposed there, it appears that both
> priv->mux_id and port->data_format are written under RTNL but read
> from the data path without any lock. Note that rmnet_newlink() can update
> port->data_format of an already active port, and that
> rmnet_changelink() can change both fields while traffic is flowing.
>
> - Patch 1 annotates the data-races around port->data_format. It also
> samples the field only once per packet and passes the value down,
> so that both ends of a packet transformation agree on the format
> (TX used to size the MAP headroom and set MAP_NEXT_HEADER_FLAG
> from two separate reads), and prevents rmnet_changelink() from
> publishing an intermediate value to the data path.
>
> - Patch 2 annotates the data-races around priv->mux_id and
> ep->mux_id. It also publishes ep->mux_id before the endpoint is
> inserted into its new bucket, so that a concurrent lookup walking
> that bucket can no longer miss it and drop the packet.
>
> - Patch 3 converts rmnet_fill_info() to RCU. rmnet_get_port_rcu()
> was only called from the data path and thus used
> rcu_dereference_bh(); its lockdep condition is relaxed so that it
> can also be called from process context.
>
> The lockless lookup is safe because rmnet_get_port_rcu() checks real_dev-
> >rx_handler before returning rx_handler_data, while
> rmnet_unregister_real_device() clears rx_handler and waits for a grace period
> (in netdev_rx_handler_unregister()) before freeing the port.
>
> This is part of a larger effort to remove the RTNL dependency from
> rtnl_link_ops->fill_info().
>
> Assisted-by: LLM
> Cc: Subash Abhinov Kasiviswanathan
> <subash.a.kasiviswanathan@oss.qualcomm.com>
> Cc: Sean Tranchetti <sean.tranchetti@oss.qualcomm.com>
>
> Eric Dumazet (3):
> net: rmnet: annotate data-races around port->data_format
> net: rmnet: annotate data-races around mux_id
> net: rmnet: no longer rely on RTNL in rmnet_fill_info()
>
> .../ethernet/qualcomm/rmnet/rmnet_config.c | 54 +++++++++----------
> .../ethernet/qualcomm/rmnet/rmnet_config.h | 2 +-
> .../ethernet/qualcomm/rmnet/rmnet_handlers.c | 34 +++++++-----
> .../net/ethernet/qualcomm/rmnet/rmnet_map.h | 9 ++--
> .../qualcomm/rmnet/rmnet_map_command.c | 9 ++--
> .../ethernet/qualcomm/rmnet/rmnet_map_data.c | 14 ++---
> .../net/ethernet/qualcomm/rmnet/rmnet_vnd.c | 2 +-
> 7 files changed, 65 insertions(+), 59 deletions(-)
>
> --
> 2.55.0.1032.g73a4cd73de-goog
No concerns from our side. For the series -
Reviewed-by: Subash Abhinov Kasiviswanathan <subash.a.kasiviswanathan@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH net-next 0/3] net: rmnet: lockless rmnet_fill_info()
2026-09-16 13:22 [PATCH net-next 0/3] net: rmnet: lockless rmnet_fill_info() Eric Dumazet
` (3 preceding siblings ...)
2026-09-18 3:04 ` [PATCH net-next 0/3] net: rmnet: lockless rmnet_fill_info() subash.a.kasiviswanathan
@ 2026-09-18 22:50 ` patchwork-bot+netdevbpf
4 siblings, 0 replies; 8+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-09-18 22:50 UTC (permalink / raw)
To: Eric Dumazet
Cc: davem, kuba, pabeni, horms, kuniyu, andrew+netdev, netdev,
eric.dumazet, subash.a.kasiviswanathan, sean.tranchetti
Hello:
This series was applied to netdev/net-next.git (main)
by Jakub Kicinski <kuba@kernel.org>:
On Wed, 16 Sep 2026 13:22:28 +0000 you wrote:
> rmnet_fill_info() currently relies on RTNL being held, because it
> uses rmnet_get_port_rtnl() to reach the rmnet_port attached to the
> underlying real device.
>
> While auditing the fields exposed there, it appears that both
> priv->mux_id and port->data_format are written under RTNL but read
> from the data path without any lock. Note that rmnet_newlink() can
> update port->data_format of an already active port, and that
> rmnet_changelink() can change both fields while traffic is flowing.
>
> [...]
Here is the summary with links:
- [net-next,1/3] net: rmnet: annotate data-races around port->data_format
https://git.kernel.org/netdev/net-next/c/c863bf6a7d7d
- [net-next,2/3] net: rmnet: annotate data-races around mux_id
https://git.kernel.org/netdev/net-next/c/0067187dbe15
- [net-next,3/3] net: rmnet: no longer rely on RTNL in rmnet_fill_info()
https://git.kernel.org/netdev/net-next/c/d7c9ba103b06
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-09-18 22:51 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-16 13:22 [PATCH net-next 0/3] net: rmnet: lockless rmnet_fill_info() Eric Dumazet
2026-09-16 13:22 ` [PATCH net-next 1/3] net: rmnet: annotate data-races around port->data_format Eric Dumazet
2026-09-17 13:22 ` netdev-bot+sashiko
2026-09-16 13:22 ` [PATCH net-next 2/3] net: rmnet: annotate data-races around mux_id Eric Dumazet
2026-09-17 13:22 ` netdev-bot+sashiko
2026-09-16 13:22 ` [PATCH net-next 3/3] net: rmnet: no longer rely on RTNL in rmnet_fill_info() Eric Dumazet
2026-09-18 3:04 ` [PATCH net-next 0/3] net: rmnet: lockless rmnet_fill_info() subash.a.kasiviswanathan
2026-09-18 22:50 ` patchwork-bot+netdevbpf
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox