Netdev List
 help / color / mirror / Atom feed
* [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