From ad1320865b1826ffa2e81e37c49535751e81335c Mon Sep 17 00:00:00 2001 From: David Lee Date: Fri, 9 Oct 2026 15:42:31 +0800 Subject: [PATCH 01/94] sctp: hold shkey across socket migration sctp_sock_migrate() transfers queued DATA skbs from the old socket to the new one. skb_orphan() invokes sctp_wfree() during that transfer and drops the skb-owned shared-key reference. If userspace has removed that key from the association, this can be the final reference. The following sctp_set_owner_w() then dereferences the freed chunk->shkey while trying to take the new owner reference. Take a temporary shared-key reference before orphaning the skb and release it after the new owner has taken its reference. This preserves the selected authentication key throughout the ownership transfer. Bug found and triaged by OpenAI Security Research and validated by Trail of Bits. Fixes: 1b1e0bc99474 ("sctp: add refcnt support for sh_key") Signed-off-by: Kyle Zeng Signed-off-by: David Lee Acked-by: Xin Long Link: https://patch.msgid.link/20261009074244.3718-2-juny24602@gmail.com Signed-off-by: Jakub Kicinski --- net/sctp/socket.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/net/sctp/socket.c b/net/sctp/socket.c index 321898f22d80..696e67936251 100644 --- a/net/sctp/socket.c +++ b/net/sctp/socket.c @@ -147,9 +147,19 @@ static inline void sctp_set_owner_w(struct sctp_chunk *chunk) static void sctp_clear_owner_w(struct sctp_chunk *chunk) { + /* Keep the shkey alive until the new owner takes its reference. */ + if (chunk->shkey) + sctp_auth_shkey_hold(chunk->shkey); skb_orphan(chunk->skb); } +static void sctp_set_owner_w_migrate(struct sctp_chunk *chunk) +{ + sctp_set_owner_w(chunk); + if (chunk->shkey) + sctp_auth_shkey_release(chunk->shkey); +} + #define traverse_and_process() \ do { \ msg = chunk->msg; \ @@ -9637,7 +9647,7 @@ static int sctp_sock_migrate(struct sock *oldsk, struct sock *newsk, lock_sock_nested(newsk, SINGLE_DEPTH_NESTING); sctp_for_each_tx_datachunk(assoc, true, sctp_clear_owner_w); sctp_assoc_migrate(assoc, newsk); - sctp_for_each_tx_datachunk(assoc, false, sctp_set_owner_w); + sctp_for_each_tx_datachunk(assoc, false, sctp_set_owner_w_migrate); /* If the association on the newsk is already closed before accept() * is called, set RCV_SHUTDOWN flag. From 52ffeaa358d1f3d91aa87a26ad90d77769cff74b Mon Sep 17 00:00:00 2001 From: Jun Yang Date: Fri, 9 Oct 2026 15:42:32 +0800 Subject: [PATCH 02/94] sctp: re-point retained control chunks on association migration sctp_sock_migrate() transfers DATA chunk ownership but leaves retained control chunks pointing at the old socket. A later retransmission can therefore access the socket after it has been freed. Extend the migration walk to cover retained control chunks and use sctp_control_set_owner_w() to assign their new owner. Reuse the DATA ownership check so each chunk is processed once per pass, even when it is reachable through multiple lists or retained pointers. Preserve a control chunk's existing authentication key across migration so its final release can still generate SCTP_AUTH_FREE_KEY when needed. Fixes: d04adf1b3551 ("sctp: reset owner sk for data chunks on out queues when migrating a sock") Cc: stable@kernel.org Reported-by: TencentOS Corvus AI Signed-off-by: Jun Yang Acked-by: Xin Long Link: https://patch.msgid.link/20261009074244.3718-3-juny24602@gmail.com Signed-off-by: Jakub Kicinski --- include/net/sctp/sm.h | 1 + net/sctp/sm_make_chunk.c | 5 ++-- net/sctp/socket.c | 53 ++++++++++++++++++++++++++++++---------- 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/include/net/sctp/sm.h b/include/net/sctp/sm.h index 3bfd261a53cc..76605d1ee839 100644 --- a/include/net/sctp/sm.h +++ b/include/net/sctp/sm.h @@ -252,6 +252,7 @@ struct sctp_chunk *sctp_make_fwdtsn(const struct sctp_association *asoc, struct sctp_fwdtsn_skip *skiplist); struct sctp_chunk *sctp_make_auth(const struct sctp_association *asoc, __u16 key_id); +void sctp_control_set_owner_w(struct sctp_chunk *chunk); struct sctp_chunk *sctp_make_strreset_req(const struct sctp_association *asoc, __u16 stream_num, __be16 *stream_list, bool out, bool in); diff --git a/net/sctp/sm_make_chunk.c b/net/sctp/sm_make_chunk.c index 84a4c97d0f75..ddc31a5dbad3 100644 --- a/net/sctp/sm_make_chunk.c +++ b/net/sctp/sm_make_chunk.c @@ -94,7 +94,7 @@ static void sctp_control_release_owner(struct sk_buff *skb) } } -static void sctp_control_set_owner_w(struct sctp_chunk *chunk) +void sctp_control_set_owner_w(struct sctp_chunk *chunk) { struct sctp_association *asoc = chunk->asoc; struct sk_buff *skb = chunk->skb; @@ -107,7 +107,8 @@ static void sctp_control_set_owner_w(struct sctp_chunk *chunk) * For now don't do anything for now. */ if (chunk->auth) { - chunk->shkey = asoc->shkey; + if (!chunk->shkey) + chunk->shkey = asoc->shkey; sctp_auth_shkey_hold(chunk->shkey); } skb->sk = asoc ? asoc->base.sk : NULL; diff --git a/net/sctp/socket.c b/net/sctp/socket.c index 696e67936251..8cf3e955ae18 100644 --- a/net/sctp/socket.c +++ b/net/sctp/socket.c @@ -155,9 +155,24 @@ static void sctp_clear_owner_w(struct sctp_chunk *chunk) static void sctp_set_owner_w_migrate(struct sctp_chunk *chunk) { - sctp_set_owner_w(chunk); - if (chunk->shkey) - sctp_auth_shkey_release(chunk->shkey); + struct sctp_shared_key *shkey = chunk->shkey; + + if (chunk->msg) + sctp_set_owner_w(chunk); + else + sctp_control_set_owner_w(chunk); + + if (shkey) + sctp_auth_shkey_release(shkey); +} + +static void sctp_process_tx_chunk(struct sctp_association *asoc, + struct sctp_chunk *chunk, bool clear, + void (*cb)(struct sctp_chunk *)) +{ + if ((clear && asoc->base.sk == chunk->skb->sk) || + (!clear && asoc->base.sk != chunk->skb->sk)) + cb(chunk); } #define traverse_and_process() \ @@ -165,17 +180,14 @@ do { \ msg = chunk->msg; \ if (msg == prev_msg) \ continue; \ - list_for_each_entry(c, &msg->chunks, frag_list) { \ - if ((clear && asoc->base.sk == c->skb->sk) || \ - (!clear && asoc->base.sk != c->skb->sk)) \ - cb(c); \ - } \ + list_for_each_entry(c, &msg->chunks, frag_list) \ + sctp_process_tx_chunk(asoc, c, clear, cb); \ prev_msg = msg; \ } while (0) -static void sctp_for_each_tx_datachunk(struct sctp_association *asoc, - bool clear, - void (*cb)(struct sctp_chunk *)) +static void sctp_for_each_tx_chunk(struct sctp_association *asoc, + bool clear, + void (*cb)(struct sctp_chunk *)) { struct sctp_datamsg *msg, *prev_msg = NULL; @@ -198,6 +210,21 @@ static void sctp_for_each_tx_datachunk(struct sctp_association *asoc, list_for_each_entry(chunk, &q->out_chunk_list, list) traverse_and_process(); + + list_for_each_entry(chunk, &q->control_chunk_list, list) + sctp_process_tx_chunk(asoc, chunk, clear, cb); + + list_for_each_entry(chunk, &asoc->asconf_ack_list, transmitted_list) + sctp_process_tx_chunk(asoc, chunk, clear, cb); + + list_for_each_entry(chunk, &asoc->addip_chunk_list, list) + sctp_process_tx_chunk(asoc, chunk, clear, cb); + + if (asoc->strreset_chunk) + sctp_process_tx_chunk(asoc, asoc->strreset_chunk, clear, cb); + + if (asoc->addip_last_asconf) + sctp_process_tx_chunk(asoc, asoc->addip_last_asconf, clear, cb); } static void sctp_for_each_rx_skb(struct sctp_association *asoc, struct sock *sk, @@ -9645,9 +9672,9 @@ static int sctp_sock_migrate(struct sock *oldsk, struct sock *newsk, * paths won't try to lock it and then oldsk. */ lock_sock_nested(newsk, SINGLE_DEPTH_NESTING); - sctp_for_each_tx_datachunk(assoc, true, sctp_clear_owner_w); + sctp_for_each_tx_chunk(assoc, true, sctp_clear_owner_w); sctp_assoc_migrate(assoc, newsk); - sctp_for_each_tx_datachunk(assoc, false, sctp_set_owner_w_migrate); + sctp_for_each_tx_chunk(assoc, false, sctp_set_owner_w_migrate); /* If the association on the newsk is already closed before accept() * is called, set RCV_SHUTDOWN flag. From 2d36ec73836e83175deb467fd494c61006107e5d Mon Sep 17 00:00:00 2001 From: Yun Zhou Date: Thu, 8 Oct 2026 17:38:24 +0800 Subject: [PATCH 03/94] net: erspan: set lltx to avoid sch_direct_xmit deadlock The ERSPAN xmit path re-enters the network stack, causing nested acquisition of _xmit_lock on the underlay device while already holding the ERSPAN device's _xmit_lock, creating an ABBA deadlock. This happens for both IPv4 ERSPAN (erspan_xmit() -> ip_tunnel_xmit()) and IPv6 ERSPAN (ip6erspan_tunnel_xmit() -> ip6_tnl_xmit()): sch_direct_xmit [lock erspan] -> erspan_xmit -> ip_tunnel_xmit -> ip_output -> __dev_queue_xmit -> sch_direct_xmit [lock underlay] sch_direct_xmit [lock erspan] -> ip6erspan_tunnel_xmit -> ip6_tnl_xmit -> ip6_output -> __dev_queue_xmit -> sch_direct_xmit [lock underlay] Set dev->lltx = true so HARD_TX_LOCK() skips the spinlock for ERSPAN. This is safe as the ERSPAN xmit path has no shared mutable state: o_seqno is atomic, TX stats are per-CPU u64_stats, dst_cache is per-CPU, and o_flags is no longer modified in the xmit path since commit 9958e69b9893 ("gre: fix ERSPAN o_flags race/corruption in xmit and fill_info"). GRETAP, the sibling device with identical xmit structure, already sets lltx. Reported-by: syzbot+9bda1b9fbb7fbdf9b62b@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=9bda1b9fbb7fbdf9b62b Fixes: 84e54fe0a5ea ("gre: introduce native tunnel support for ERSPAN") Fixes: 5a963eb61b7c ("ip6_gre: Add ERSPAN native tunnel support") Cc: stable@vger.kernel.org # 9958e69b9893 ("gre: fix ERSPAN o_flags race/corruption in xmit and fill_info") Cc: stable@vger.kernel.org Signed-off-by: Yun Zhou Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20261008093824.119453-1-yun.zhou@windriver.com Signed-off-by: Jakub Kicinski --- net/ipv4/ip_gre.c | 1 + net/ipv6/ip6_gre.c | 1 + 2 files changed, 2 insertions(+) diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c index e4878e9aa636..149a4e83b692 100644 --- a/net/ipv4/ip_gre.c +++ b/net/ipv4/ip_gre.c @@ -1367,6 +1367,7 @@ static int erspan_tunnel_init(struct net_device *dev) dev->features |= GRE_FEATURES; dev->hw_features |= GRE_FEATURES; dev->priv_flags |= IFF_LIVE_ADDR_CHANGE; + dev->lltx = true; netif_keep_dst(dev); return ip_tunnel_init(dev); diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c index 04f7c70b7320..3429543ec77e 100644 --- a/net/ipv6/ip6_gre.c +++ b/net/ipv6/ip6_gre.c @@ -1864,6 +1864,7 @@ static int ip6erspan_tap_init(struct net_device *dev) dev->mtu -= 8; dev->priv_flags |= IFF_LIVE_ADDR_CHANGE; + dev->lltx = true; ip6erspan_tnl_link_config(tunnel, 1); netdev_hold(dev, &tunnel->dev_tracker, GFP_KERNEL); From 50f2103fce43922b6e562937b2d2fc62089ff7ec Mon Sep 17 00:00:00 2001 From: Henry Martin Date: Mon, 12 Oct 2026 10:53:36 +0800 Subject: [PATCH 04/94] rds: fix out-of-bounds conn_path walks via handshake exthdrs Both the lane and the connection-generation negotiation are driven by extension headers in handshake packets, and both are processed unconditionally, even on transports that are not multipath capable. A non-MP connection (t_mp_capable unset) owns exactly one rds_conn_path slot, so a peer claiming up to RDS_MPATH_WORKERS lanes makes every subsequent fan-out walk conn->c_path[0..7] at 504-byte strides past the single allocated slot (KASAN: "Read of size 8 in rds_conn_path_connect_if_down"); the generation negotiation opens a similar out-of-bounds walk on the same array. Only process handshake extension headers when the connection's transport is multipath capable, so a peer can never make us walk lanes that do not exist. Both call sites in rds_recv_incoming() are gated. This is a port of the Oracle UEK fix for the same issue, picked up where Paula's series of the same change left off. This issue was discovered by Tencent CodeBuddy Security. Cc: stable@vger.kernel.org Link: https://lore.kernel.org/netdev/20260825120132.51636-1-pamoutafpro@gmail.com/ Link: https://github.com/oracle/linux-uek/commit/2fdeb0513f18 Fixes: 840df162b3eb ("rds: reduce memory footprint for RDS when transport is RDMA") Fixes: 905dd4184e07 ("RDS: TCP: Track peer's connection generation number") Signed-off-by: Henry Martin Reviewed-by: Allison Henderson Link: https://patch.msgid.link/20261012025336.54756-1-bsdhenrymartin@gmail.com Signed-off-by: Jakub Kicinski --- net/rds/recv.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/net/rds/recv.c b/net/rds/recv.c index 6204e577a90a..93ee4546ae36 100644 --- a/net/rds/recv.c +++ b/net/rds/recv.c @@ -379,7 +379,8 @@ void rds_recv_incoming(struct rds_connection *conn, struct in6_addr *saddr, rds_stats_inc(s_recv_ping); rds_send_pong(cp, inc->i_hdr.h_sport); /* if this is a handshake ping, start multipath if necessary */ - if (RDS_HS_PROBE(be16_to_cpu(inc->i_hdr.h_sport), + if (conn->c_trans->t_mp_capable && + RDS_HS_PROBE(be16_to_cpu(inc->i_hdr.h_sport), be16_to_cpu(inc->i_hdr.h_dport))) { rds_recv_hs_exthdrs(&inc->i_hdr, cp->cp_conn); rds_start_mprds(cp->cp_conn); @@ -387,7 +388,8 @@ void rds_recv_incoming(struct rds_connection *conn, struct in6_addr *saddr, goto out; } - if (be16_to_cpu(inc->i_hdr.h_dport) == RDS_FLAG_PROBE_PORT && + if (conn->c_trans->t_mp_capable && + be16_to_cpu(inc->i_hdr.h_dport) == RDS_FLAG_PROBE_PORT && inc->i_hdr.h_sport == 0) { rds_recv_hs_exthdrs(&inc->i_hdr, cp->cp_conn); /* if this is a handshake pong, start multipath if necessary */ From 4fc1d5bb448dfab391a96f949d02ad03ad8cda5c Mon Sep 17 00:00:00 2001 From: George Melikov Date: Fri, 9 Oct 2026 20:30:18 +0300 Subject: [PATCH 05/94] bnx2x: fix header length calculation for encapsulated TSO Commit 80bfab79b835 ("net: adopt skb_network_offset() and similar helpers") replaced the difference between the inner transport header and the outer network header with skb_inner_transport_offset(). The helper is relative to skb->data, so the new calculation incorrectly includes the outer Ethernet header in fw_ip_hdr_to_payload_w. On a BCM57810 transmitting TCP over VXLAN with an outer MTU of 1500, hardware segmentation emits packets declaring an outer IPv4 total length of 1514 while the actual IP packet is only 1500 bytes long. The receiver drops these malformed packets, severely degrading TCP connectivity. Subtract skb_network_offset() to restore the original calculation of the length from the outer network header to the inner TCP payload. Reproduced with plain kernel VXLAN and one TCP flow on kernel 7.0.0-31-generic: 50 of 200 captured packets had an invalid outer IP length with the original driver, versus none with this fix. Disabling TSO/GSO on the test VXLAN device also eliminated the error with the original driver. Fixes: 80bfab79b835 ("net: adopt skb_network_offset() and similar helpers") Signed-off-by: George Melikov Reviewed-by: Simon Horman Link: https://patch.msgid.link/20261009-bnx2x-tso-fix-v1-1-1ebea422422d@gmelikov.ru Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnx2x/bnx2x_cmn.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/broadcom/bnx2x/bnx2x_cmn.c b/drivers/net/ethernet/broadcom/bnx2x/bnx2x_cmn.c index 5a9742fd3ddf..6fb0619731ae 100644 --- a/drivers/net/ethernet/broadcom/bnx2x/bnx2x_cmn.c +++ b/drivers/net/ethernet/broadcom/bnx2x/bnx2x_cmn.c @@ -3666,7 +3666,8 @@ static void bnx2x_update_pbds_gso_enc(struct sk_buff *skb, u8 outerip_off, outerip_len = 0; /* from outer IP to transport */ - hlen_w = skb_inner_transport_offset(skb) >> 1; + hlen_w = (skb_inner_transport_offset(skb) - + skb_network_offset(skb)) >> 1; /* transport len */ hlen_w += inner_tcp_hdrlen(skb) >> 1; From b2039a6dd36b5c89142bfb95bc0205be8abcc600 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Fri, 9 Oct 2026 09:49:05 +0200 Subject: [PATCH 06/94] ipv4: use RCU protection in inet_netconf_get_devconf() inet_netconf_get_devconf() runs without RTNL. It uses dev_get_by_index() and in_dev_get() to pin the device and its in_device while the reply is allocated and filled. in_dev_get() is not safe for a lockless caller. Since commit 9d40c84cf5bc ("net: devinet: Reduce refcount before grace period"), inetdev_destroy() drops the final reference before the RCU grace period. A reader can load dev->ip_ptr, then call refcount_inc() on a zero refcount after in_dev_free_rcu() has been queued, and keep using the in_device once it has been freed. None of these references are needed. An in_device is freed after an RCU grace period, ipv4_devconf is embedded in it, and inet_netconf_fill_devconf() does not sleep. Allocate the skb first, then perform the lookup and the fill under rcu_read_lock(), using dev_get_by_index_rcu() and __in_dev_get_rcu(), like inet_netconf_dump_devconf() already does. Fixes: bbcf91053bb6 ("inet: do not use RTNL in inet_netconf_get_devconf()") Reported-by: Baul Lee Closes: https://lore.kernel.org/netdev/20260815172032.79740-1-baul.lee@xbow.com/ Signed-off-by: Eric Dumazet Tested-by: Cen Zhang Reviewed-by: Ido Schimmel Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/CAAVpQUA5FEMWV1R4vy2+uzQc+_iLDaz4m-D6kfKYngGf2sE0Eg@mail.gmail.com Link: https://patch.msgid.link/20261009074906.729877-1-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- net/ipv4/devinet.c | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/net/ipv4/devinet.c b/net/ipv4/devinet.c index 5b6b11c943e4..f9a361621f74 100644 --- a/net/ipv4/devinet.c +++ b/net/ipv4/devinet.c @@ -2379,8 +2379,8 @@ static int inet_netconf_get_devconf(struct sk_buff *in_skb, struct net *net = sock_net(in_skb->sk); struct nlattr *tb[NETCONFA_MAX + 1]; const struct ipv4_devconf *devconf; - struct in_device *in_dev = NULL; - struct net_device *dev = NULL; + struct in_device *in_dev; + struct net_device *dev; struct sk_buff *skb; int ifindex; int err; @@ -2392,7 +2392,13 @@ static int inet_netconf_get_devconf(struct sk_buff *in_skb, if (!tb[NETCONFA_IFINDEX]) return -EINVAL; + skb = nlmsg_new(inet_netconf_msgsize_devconf(NETCONFA_ALL), GFP_KERNEL); + if (!skb) + return -ENOBUFS; + ifindex = nla_get_s32(tb[NETCONFA_IFINDEX]); + + rcu_read_lock(); switch (ifindex) { case NETCONFA_IFINDEX_ALL: devconf = net->ipv4.devconf_all; @@ -2402,36 +2408,29 @@ static int inet_netconf_get_devconf(struct sk_buff *in_skb, break; default: err = -ENODEV; - dev = dev_get_by_index(net, ifindex); - if (dev) - in_dev = in_dev_get(dev); + dev = dev_get_by_index_rcu(net, ifindex); + if (!dev) + goto out_unlock; + in_dev = __in_dev_get_rcu(dev); if (!in_dev) - goto errout; + goto out_unlock; devconf = &in_dev->cnf; break; } - err = -ENOBUFS; - skb = nlmsg_new(inet_netconf_msgsize_devconf(NETCONFA_ALL), GFP_KERNEL); - if (!skb) - goto errout; - err = inet_netconf_fill_devconf(skb, ifindex, devconf, NETLINK_CB(in_skb).portid, nlh->nlmsg_seq, RTM_NEWNETCONF, 0, NETCONFA_ALL); +out_unlock: + rcu_read_unlock(); if (err < 0) { /* -EMSGSIZE implies BUG in inet_netconf_msgsize_devconf() */ WARN_ON(err == -EMSGSIZE); kfree_skb(skb); - goto errout; + return err; } - err = rtnl_unicast(skb, net, NETLINK_CB(in_skb).portid); -errout: - if (in_dev) - in_dev_put(in_dev); - dev_put(dev); - return err; + return rtnl_unicast(skb, net, NETLINK_CB(in_skb).portid); } static int inet_netconf_dump_devconf(struct sk_buff *skb, From 5dfa91a5ec53cfbf907a1fca095ac3ac7a4e94c9 Mon Sep 17 00:00:00 2001 From: Zixuan Chai Date: Fri, 9 Oct 2026 00:52:31 +0800 Subject: [PATCH 07/94] ipv6: defrag: orphan skb before queuing IPv4 and nf_conntrack fragment queues orphan skbs before returning -EINPROGRESS, but ip6_frag_queue() leaves the socket association intact. Commit 18685451fc4e ("inet: inet_defrag: prevent sk release while still in use") moved skb_orphan() into ip_frag_queue() and nf_ct_frag6_queue(), but missed ip6_frag_queue(). Without orphaning, inet_frag_reasm_finish() clears fp->sk on frag_list skbs but leaves fp->destructor set to sock_pfree. Freeing the reassembled skb then calls sock_pfree() with a NULL socket. A socket using SOCK_RCU_FREE may also be released while its fragment is queued, leaving a stale socket pointer; this is a second consequence of the same missing orphan. Orphan the skb before returning -EINPROGRESS so the queued fragment no longer retains the socket association or destructor. Fixes: cf7fbe660f2d ("bpf: Add socket assign support") Cc: stable@vger.kernel.org Reported-by: Vega Reviewed-by: Eric Dumazet Reviewed-by: Fernando Fernandez Mancera Signed-off-by: Zixuan Chai Signed-off-by: Ren Wei Link: https://patch.msgid.link/5c396b7011168cd98b9812df4d6bbd00a96048e7.1791008591.git.petalzu987@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv6/reassembly.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/ipv6/reassembly.c b/net/ipv6/reassembly.c index 05c51f669754..a7964125c764 100644 --- a/net/ipv6/reassembly.c +++ b/net/ipv6/reassembly.c @@ -232,6 +232,7 @@ static int ip6_frag_queue(struct net *net, } skb_dst_drop(skb); + skb_orphan(skb); return -EINPROGRESS; insert_error: From 31b564f70d8819a1c11d0fd504fe582fc0e0f0cf Mon Sep 17 00:00:00 2001 From: Theodor Arsenij Larionov Trichkine Date: Mon, 12 Oct 2026 12:10:28 +0200 Subject: [PATCH 08/94] net: use the full socket in sk_mc_loop() sk_mc_loop() is called from ip_mc_output() and ip6_finish_output2() with skb->sk, which is not always a full socket: - Since commit ca6fb0651883 ("tcp: attach SYNACK messages to request sockets instead of listener"), a SYNACK is attached to its request socket. - Since commit 79636038d37e ("ipv4: tcp: give socket pointer to control skbs"), an ACK or RST sent by ip_send_unicast_reply() on behalf of a TIME_WAIT socket is attached to the timewait socket. When such a packet uses a multicast route, inet_test_bit(MC_LOOP, sk) reads inet_flags past the end of the request or timewait socket. This happens for instance when a SYN with a multicast source address reaches a listener, after being looped back with its dst attached, so that ip_rcv_finish_core() skips the martian source check. A raw IP_HDRINCL socket sending to a local address does this, as does re-injection by nft dup or the iptables TEE target. The destination can also come from a source route option, or from a route of type multicast covering a unicast prefix. BUG: KASAN: slab-out-of-bounds in sk_mc_loop+0x111/0x170 Read of size 8 at addr ffff88800f2b7f10 by task repro-raw/470 Call Trace: sk_mc_loop+0x111/0x170 ip_mc_output+0x355/0x930 ip_build_and_send_pkt+0xb87/0xc50 tcp_v4_send_synack+0x500/0x6f0 tcp_conn_request+0x2135/0x2d90 tcp_v4_conn_request+0xa5/0x210 tcp_rcv_state_process+0x136e/0x6920 tcp_v4_do_rcv+0x339/0xb10 tcp_v4_rcv+0x34ab/0x3ab0 ip_protocol_deliver_rcu+0x6e/0x3e0 ip_local_deliver_finish+0x34d/0x510 ip_local_deliver+0x1bc/0x310 ip_rcv+0x390/0x410 __netif_receive_skb_one_core+0x199/0x1e0 process_backlog+0x239/0x680 __napi_poll+0xb5/0x650 net_rx_action+0x980/0xd60 handle_softirqs+0x17f/0x590 do_softirq+0x3f/0x60 The buggy address belongs to the object at ffff88800f2b7d60 which belongs to the cache request_sock_TCP of size 312 The buggy address is located 120 bytes to the right of allocated 312-byte region [ffff88800f2b7d60, ffff88800f2b7e98) Use sk_const_to_full_sk(), which restores the behavior from before the two commits above: a request socket uses the setting of its listener, and a timewait socket gets the default (loop back), as the per-cpu control socket used before had. Fixes: ca6fb0651883 ("tcp: attach SYNACK messages to request sockets instead of listener") Fixes: 79636038d37e ("ipv4: tcp: give socket pointer to control skbs") Signed-off-by: Theodor Arsenij Larionov Trichkine [edumazet: rewrote the changelog, added the TIME_WAIT case] Signed-off-by: Eric Dumazet Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20261012101029.927296-2-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- net/core/sock.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/core/sock.c b/net/core/sock.c index e8551df8330f..1302c1e848b2 100644 --- a/net/core/sock.c +++ b/net/core/sock.c @@ -769,6 +769,7 @@ bool sk_mc_loop(const struct sock *sk) { if (dev_recursion_level()) return false; + sk = sk_const_to_full_sk(sk); if (!sk) return true; /* IPV6_ADDRFORM can change sk->sk_family under us. */ From 54d574d4afd9de820c8a017665c8095f0f5fdbaa Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 12 Oct 2026 12:10:29 +0200 Subject: [PATCH 09/94] tcp: use the echoed source route in cookie_v4_check() When the ACK carries a source route option, cookie_v4_check() routes the new socket to opt->faddr, where opt points to the options of the received packet. ip_options_compile() only fills faddr for options given by setsockopt() (when called without an skb), so on receive faddr is 0 and the lookup is done for daddr 0, which gives a local route. Use the options echoed by tcp_v4_save_options() instead, as inet_csk_route_req() and inet_csk_route_child_sock() do with ireq->ireq_opt. Read them after security_inet_conn_request(), since CIPSO can replace ireq->ireq_opt there. The route now goes to the first hop of the source route, so also refuse a strict source route through a gateway, as inet_csk_route_req() does. Found by sashiko while reviewing the sk_mc_loop() fix. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Eric Dumazet Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20261012101029.927296-3-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- net/ipv4/syncookies.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/net/ipv4/syncookies.c b/net/ipv4/syncookies.c index 73e129768184..8c9691569e01 100644 --- a/net/ipv4/syncookies.c +++ b/net/ipv4/syncookies.c @@ -404,12 +404,12 @@ static struct request_sock *cookie_tcp_check(struct net *net, struct sock *sk, */ struct sock *cookie_v4_check(struct sock *sk, struct sk_buff *skb) { - struct ip_options *opt = &TCP_SKB_CB(skb)->header.h4.opt; const struct tcphdr *th = tcp_hdr(skb); struct tcp_sock *tp = tcp_sk(sk); struct inet_request_sock *ireq; struct net *net = sock_net(sk); struct tcp_request_sock *treq; + struct ip_options_rcu *opt; struct request_sock *req; struct sock *ret = sk; struct flowi4 fl4; @@ -449,6 +449,8 @@ struct sock *cookie_v4_check(struct sock *sk, struct sk_buff *skb) SKB_DR_SET(reason, SECURITY_HOOK); goto out_free; } + /* Load ireq->ireq_opt after the LSM hook, which can replace it. */ + opt = rcu_dereference_protected(ireq->ireq_opt, true); tcp_ao_syncookie(sk, skb, req, AF_INET); @@ -461,7 +463,7 @@ struct sock *cookie_v4_check(struct sock *sk, struct sk_buff *skb) flowi4_init_output(&fl4, ireq->ir_iif, ireq->ir_mark, ip_sock_rt_tos(sk), ip_sock_rt_scope(sk), IPPROTO_TCP, inet_sk_flowi_flags(sk), - opt->srr ? opt->faddr : ireq->ir_rmt_addr, + (opt && opt->opt.srr) ? opt->opt.faddr : ireq->ir_rmt_addr, ireq->ir_loc_addr, th->source, th->dest, sk_uid(sk)); security_req_classify_flow(req, flowi4_to_flowi_common(&fl4)); @@ -470,6 +472,12 @@ struct sock *cookie_v4_check(struct sock *sk, struct sk_buff *skb) SKB_DR_SET(reason, IP_OUTNOROUTES); goto out_free; } + /* Same check as in inet_csk_route_req(). */ + if (opt && opt->opt.is_strictroute && rt->rt_uses_gateway) { + ip_rt_put(rt); + SKB_DR_SET(reason, IP_OUTNOROUTES); + goto out_free; + } /* Try to redo what tcp_v4_send_synack did. */ req->rsk_window_clamp = READ_ONCE(tp->window_clamp) ? : From 5bef8649bb9ac4b2a014bcc227202ebd2df824be Mon Sep 17 00:00:00 2001 From: Seung Min Shin Date: Sun, 11 Oct 2026 16:27:50 +0000 Subject: [PATCH 10/94] sctp: hold transport reference across connect wait sctp_sendmsg_to_asoc() selects a transport before waiting for a new interleaved association to become established. sctp_wait_for_connect() drops the socket lock while sleeping. While the lock is dropped, backlog processing can handle a COOKIE ACK and an ASCONF DEL-IP that removes the selected transport. sctp_assoc_rm_peer() then marks the transport dead and drops its association-owned reference. The transport can be freed through RCU before the send path resumes. After the wait, the stale pointer is assigned to newly created DATA or I-DATA chunks and later dereferenced by sctp_outq_select_transport(). Hold a reference to the selected transport across sctp_wait_for_connect(). After the socket lock is reacquired, abort the send if the transport was marked dead. Callers without a selected transport pass NULL and retain their existing behavior. Cc: stable@vger.kernel.org Fixes: 668c9beb9020 ("sctp: implement assign_number for sctp_stream_interleave") Acked-by: Xin Long Signed-off-by: Seung Min Shin Link: https://patch.msgid.link/20261011162750.1529631-1-guncraft2000@naver.com Signed-off-by: Jakub Kicinski --- net/sctp/socket.c | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/net/sctp/socket.c b/net/sctp/socket.c index 8cf3e955ae18..b5a57f61ee6d 100644 --- a/net/sctp/socket.c +++ b/net/sctp/socket.c @@ -75,7 +75,8 @@ static int sctp_wait_for_sndbuf(struct sctp_association *asoc, struct sctp_transport *transport, long *timeo_p, size_t msg_len); static int sctp_wait_for_packet(struct sock *sk, int *err, long *timeo_p); -static int sctp_wait_for_connect(struct sctp_association *, long *timeo_p); +static int sctp_wait_for_connect(struct sctp_association *, + struct sctp_transport *, long *timeo_p); static int sctp_wait_for_accept(struct sock *sk, long timeo); static void sctp_wait_for_close(struct sock *sk, long timeo); static void sctp_destruct_sock(struct sock *sk); @@ -1282,7 +1283,7 @@ static int __sctp_connect(struct sock *sk, struct sockaddr *kaddrs, *assoc_id = asoc->assoc_id; timeo = sock_sndtimeo(sk, flags & O_NONBLOCK); - return sctp_wait_for_connect(asoc, &timeo); + return sctp_wait_for_connect(asoc, NULL, &timeo); out_free: pr_debug("%s: took out_free path with asoc:%p kaddrs:%p err:%d\n", @@ -1884,7 +1885,7 @@ static int sctp_sendmsg_to_asoc(struct sctp_association *asoc, if (asoc->ep->intl_enable) { timeo = sock_sndtimeo(sk, 0); - err = sctp_wait_for_connect(asoc, &timeo); + err = sctp_wait_for_connect(asoc, transport, &timeo); if (err || sk->sk_shutdown & RCV_SHUTDOWN) { err = -ESRCH; goto err; @@ -1927,7 +1928,7 @@ static int sctp_sendmsg_to_asoc(struct sctp_association *asoc, if (unlikely(wait_connect)) { timeo = sock_sndtimeo(sk, msg->msg_flags & MSG_DONTWAIT); - sctp_wait_for_connect(asoc, &timeo); + sctp_wait_for_connect(asoc, NULL, &timeo); } err = msg_len; @@ -9427,7 +9428,9 @@ static bool sctp_writeable(const struct sock *sk) /* Wait for an association to go into ESTABLISHED state. If timeout is 0, * returns immediately with EINPROGRESS. */ -static int sctp_wait_for_connect(struct sctp_association *asoc, long *timeo_p) +static int sctp_wait_for_connect(struct sctp_association *asoc, + struct sctp_transport *transport, + long *timeo_p) { struct sock *sk = asoc->base.sk; int err = 0; @@ -9436,12 +9439,16 @@ static int sctp_wait_for_connect(struct sctp_association *asoc, long *timeo_p) pr_debug("%s: asoc:%p, timeo:%ld\n", __func__, asoc, *timeo_p); - /* Increment the association's refcnt. */ + /* Hold references while the socket lock is dropped. */ + if (transport) + sctp_transport_hold(transport); sctp_association_hold(asoc); for (;;) { prepare_to_wait_exclusive(&asoc->wait, &wait, TASK_INTERRUPTIBLE); + if (transport && transport->dead) + goto do_transport_dead; if (!*timeo_p) goto do_nonblock; if (sk->sk_shutdown & RCV_SHUTDOWN) @@ -9470,7 +9477,8 @@ static int sctp_wait_for_connect(struct sctp_association *asoc, long *timeo_p) out: finish_wait(&asoc->wait, &wait); - /* Release the association's refcnt. */ + if (transport) + sctp_transport_put(transport); sctp_association_put(asoc); return err; @@ -9489,6 +9497,10 @@ static int sctp_wait_for_connect(struct sctp_association *asoc, long *timeo_p) do_nonblock: err = -EINPROGRESS; goto out; + +do_transport_dead: + err = -EAGAIN; + goto out; } static int sctp_wait_for_accept(struct sock *sk, long timeo) From 65cf6863606b0507a7c3bd1711317e4f5d6b8b7e Mon Sep 17 00:00:00 2001 From: Shubham Antil Date: Tue, 13 Oct 2026 08:27:29 +0530 Subject: [PATCH 11/94] RDS/IB: validate receive fragment completions rds_ib_process_recv() accounts every receive fragment as contributing min(i_recv_data_rem, RDS_FRAG_SIZE) bytes, regardless of the completion length. When a completion is shorter than the header-declared amount, receive accounting can advance beyond the payload supplied by the completion. Only an IB_WC_RECV completion has the buffer layout expected by this receive path. Other successful completion opcodes must not be interpreted as ordinary receive completions. Reject successful completions with any other opcode before processing their length. For ordinary receive completions, require enough payload to cover the amount accounted to the fragment before linking its page into the incoming message. This is compatible with the current transmit paths. MSG_ZEROCOPY is restricted to RDS/TCP, while RDS/IB copied and page-vector messages use full-sized non-final scatterlist entries. Found during an LLM-assisted source audit and validated under KMSAN with a malformed short receive completion. Reported-by: sungbyeongchan Closes: https://lore.kernel.org/netdev/20261006205204.1322102-1-tjdqudcks0424@naver.com/ Reported-by: Giovanni Vignone Link: https://lore.kernel.org/netdev/8a6ee1d5336ae65b8ffdc8c40a1135657207e204.camel@kernel.org/ Reported-by: Robert van Eijk Link: https://lore.kernel.org/netdev/8a6ee1d5336ae65b8ffdc8c40a1135657207e204.camel@kernel.org/ Reported-by: Paolo Gentry Link: https://lore.kernel.org/netdev/8a6ee1d5336ae65b8ffdc8c40a1135657207e204.camel@kernel.org/ Fixes: 1e23b3ee0e94 ("RDS/IB: Receive datagrams via IB") Cc: stable@vger.kernel.org Signed-off-by: Shubham Antil Reviewed-by: Allison Henderson Link: https://patch.msgid.link/20261013025729.40815-1-shubham@octane.security Signed-off-by: Jakub Kicinski --- net/rds/ib_recv.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/net/rds/ib_recv.c b/net/rds/ib_recv.c index bd6cb3ffaa57..aa50b85b0f99 100644 --- a/net/rds/ib_recv.c +++ b/net/rds/ib_recv.c @@ -949,6 +949,12 @@ static void rds_ib_process_recv(struct rds_connection *conn, } } + if (data_len < min_t(u32, ic->i_recv_data_rem, RDS_FRAG_SIZE)) { + rds_ib_conn_error(conn, + "fragment shorter than header-declared length; forcing reconnect\n"); + goto done; + } + list_add_tail(&recv->r_frag->f_item, &ibinc->ii_frags); recv->r_frag = NULL; @@ -1004,7 +1010,12 @@ void rds_ib_recv_cqe_handler(struct rds_ib_connection *ic, * event is processed. */ if (wc->status == IB_WC_SUCCESS) { - rds_ib_process_recv(conn, recv, wc->byte_len, state); + if (wc->opcode != IB_WC_RECV) + rds_ib_conn_error(conn, + "recv completion had unexpected opcode %u, disconnecting and reconnecting\n", + wc->opcode); + else + rds_ib_process_recv(conn, recv, wc->byte_len, state); } else { /* We expect errors as the qp is drained during shutdown */ if (rds_conn_up(conn) || rds_conn_connecting(conn)) From 191aa41548b2d2b6af534a6d5cf84a552435c74c Mon Sep 17 00:00:00 2001 From: Bobby Eshleman Date: Mon, 12 Oct 2026 12:58:08 -0700 Subject: [PATCH 12/94] MAINTAINERS: add Bobby Eshleman as vsock reviewer I've been reviewing vsock via lore+lei for a while now. Finally adding myself as a formal reviewer, since vsock will continue being stuck with me for the foreseeable future. Signed-off-by: Bobby Eshleman Reviewed-by: Stefano Garzarella Acked-by: Michael S. Tsirkin Acked-by: Stefan Hajnoczi Link: https://patch.msgid.link/20261012-add-maintainers-v1-1-ef0488f460db@meta.com Signed-off-by: Jakub Kicinski --- MAINTAINERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 5d39ec6feb03..00b440be0d46 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -28838,6 +28838,7 @@ F: virt/lib/ VIRTIO AND VHOST VSOCK DRIVER M: Stefan Hajnoczi M: Stefano Garzarella +R: Bobby Eshleman L: kvm@vger.kernel.org L: virtualization@lists.linux.dev L: netdev@vger.kernel.org @@ -29143,6 +29144,7 @@ F: drivers/media/test-drivers/vivid/* VM SOCKETS (AF_VSOCK) M: Stefano Garzarella +R: Bobby Eshleman L: virtualization@lists.linux.dev L: netdev@vger.kernel.org S: Maintained From 00accbd9d5021b942a16078bd5717d7865f6971c Mon Sep 17 00:00:00 2001 From: Joas Antonio dos Santos Date: Thu, 8 Oct 2026 12:28:12 -0300 Subject: [PATCH 13/94] calipso: update payload_len when removing the CALIPSO option calipso_skbuff_delattr() removes the CALIPSO option from the hop-by-hop extension header, or the whole extension header if CALIPSO is its only option, and pulls the skb by the removed length. It never updates ipv6hdr->payload_len, so after a successful removal the header still claims the original length. calipso_skbuff_setattr() already adjusts payload_len by the length it adds; the delete path was missed. This is reached on the forward path when a packet carrying CALIPSO is sent to a destination mapped to an unlabeled NetLabel domain. The forwarded packet then has a payload_len larger than its real payload by the removed length (8 to 264 bytes), and the receiver drops it as truncated. So removing the label on the forward path has been broken for every packet that is not resegmented later (SYN, UDP, ICMPv6...). Adjust payload_len after the header is moved. BIG TCP packets carry payload_len == 0 (see ipv6_set_payload_len()), and ipv6_payload_len() then falls back to skb->len, so leave them alone. Tested with client, router and server network namespaces on one kernel (arm64, QEMU): SELinux permissive with a minimal mdp-generated policy (network_peer_controls=1), "netlabelctl calipso add pass doi:16", the default NetLabel mapping left unlabeled, IPv6 forwarding on the router. The client sends 5 UDP datagrams with a CALIPSO hop-by-hop option (DOI 16, set through IPV6_HOPOPTS) to the server through the router: before: router Ip6OutForwDatagrams 5, server receives 0, server Ip6InTruncatedPkts 5 after: router Ip6OutForwDatagrams 5, server receives 5, server Ip6InTruncatedPkts 0 Fixes: 2917f57b6bc1 ("calipso: Allow the lsm to label the skbuff directly.") Signed-off-by: Joas Antonio dos Santos Link: https://patch.msgid.link/179147329205.58079.5847298721173675285@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv6/calipso.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/ipv6/calipso.c b/net/ipv6/calipso.c index a0192b4fef33..2ec4c43d332a 100644 --- a/net/ipv6/calipso.c +++ b/net/ipv6/calipso.c @@ -1428,6 +1428,10 @@ static int calipso_skbuff_delattr(struct sk_buff *skb) skb_pull(skb, delta); memmove((char *)ip6_hdr + delta, ip6_hdr, size); skb_reset_network_header(skb); + ip6_hdr = ipv6_hdr(skb); + /* BIG TCP packets have payload_len == 0 */ + if (ip6_hdr->payload_len) + be16_add_cpu(&ip6_hdr->payload_len, -delta); } return 0; From 86a30c4348497bcddcf2796258955bbaaf58c9a3 Mon Sep 17 00:00:00 2001 From: Vincent Jardin Date: Fri, 9 Oct 2026 02:00:56 +0200 Subject: [PATCH 14/94] dpaa2-ptp: fix support for PTP_EXTTS_REQUEST2 ioctl Commit 7c571ac57d9d ("net: ptp: introduce .supported_extts_flags to ptp_clock_info") moved the validation of the external timestamp request flags into the PTP core. A driver that does not set .supported_extts_flags still accepts the legacy PTP_EXTTS_REQUEST ioctl, but every PTP_EXTTS_REQUEST2 request is rejected with -EOPNOTSUPP. On an LX2160A board, linuxptp ts2phc arms the external timestamp channel of the 1588 timer with PTP_EXTTS_REQUEST2, gets -EOPNOTSUPP and exits, so the PHC cannot be disciplined from an external 1PPS. ptp_qoriq_init() programs TMR_CTRL with ETEP1 and ETEP2 cleared, so the timer timestamps the rising edge of its external trigger inputs. Declare that, with PTP_STRICT_FLAGS, so that a falling edge request is rejected instead of being timestamped on the rising edge. This also makes the legacy PTP_EXTTS_REQUEST ioctl strict: a request for the falling edge, or for both edges, now fails with -EOPNOTSUPP instead of being timestamped on the rising edge. That cannot be avoided: the core adds PTP_STRICT_FLAGS to every PTP_EXTTS_REQUEST2 request, so a driver has to declare it to accept them, and once declared it applies to both ioctls. Commit 3200fdd4021d ("broadcom: fix support for PTP_EXTTS_REQUEST2 ioctl") made the same change for bcm-phy-ptp. Fixes: 7c571ac57d9d ("net: ptp: introduce .supported_extts_flags to ptp_clock_info") Reviewed-by: Jacob Keller Reviewed-by: Kory Maincent Signed-off-by: Vincent Jardin Link: https://patch.msgid.link/20261009-for-upstream-qoriq-ptp-extts-flags-v2-1-0f4eeff2295d@free.fr Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/freescale/dpaa2/dpaa2-ptp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-ptp.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-ptp.c index 4497e3c0456d..ac2b873dcf8e 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-ptp.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-ptp.c @@ -78,6 +78,7 @@ static const struct ptp_clock_info dpaa2_ptp_caps = { .n_per_out = 3, .n_pins = 0, .pps = 1, + .supported_extts_flags = PTP_RISING_EDGE | PTP_STRICT_FLAGS, .adjfine = ptp_qoriq_adjfine, .adjtime = ptp_qoriq_adjtime, .gettime64 = ptp_qoriq_gettime, From 8e96c4d393168661704d850bcf818e46efb8ef24 Mon Sep 17 00:00:00 2001 From: Vincent Jardin Date: Fri, 9 Oct 2026 02:00:57 +0200 Subject: [PATCH 15/94] net: enetc: fix support for PTP_EXTTS_REQUEST2 ioctl Commit 7c571ac57d9d ("net: ptp: introduce .supported_extts_flags to ptp_clock_info") moved the validation of the external timestamp request flags into the PTP core. A driver that does not set .supported_extts_flags still accepts the legacy PTP_EXTTS_REQUEST ioctl, but every PTP_EXTTS_REQUEST2 request is rejected with -EOPNOTSUPP. The ENETC timer is the QorIQ 1588 timer, set up by ptp_qoriq_init(), which programs TMR_CTRL with ETEP1 and ETEP2 cleared, so it timestamps the rising edge of its external trigger inputs. Declare that, with PTP_STRICT_FLAGS, so that a falling edge request is rejected instead of being timestamped on the rising edge. This also makes the legacy PTP_EXTTS_REQUEST ioctl strict: a request for the falling edge, or for both edges, now fails with -EOPNOTSUPP instead of being timestamped on the rising edge. That cannot be avoided: the core adds PTP_STRICT_FLAGS to every PTP_EXTTS_REQUEST2 request, so a driver has to declare it to accept them, and once declared it applies to both ioctls. Commit 3200fdd4021d ("broadcom: fix support for PTP_EXTTS_REQUEST2 ioctl") made the same change for bcm-phy-ptp. Fixes: 7c571ac57d9d ("net: ptp: introduce .supported_extts_flags to ptp_clock_info") Reviewed-by: Jacob Keller Reviewed-by: Kory Maincent Signed-off-by: Vincent Jardin Reviewed-by: Wei Fang Link: https://patch.msgid.link/20261009-for-upstream-qoriq-ptp-extts-flags-v2-2-0f4eeff2295d@free.fr Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/freescale/enetc/enetc_ptp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/freescale/enetc/enetc_ptp.c b/drivers/net/ethernet/freescale/enetc/enetc_ptp.c index 162cbc801730..5ffee882d624 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_ptp.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_ptp.c @@ -16,6 +16,7 @@ static struct ptp_clock_info enetc_ptp_caps = { .n_per_out = 0, .n_pins = 0, .pps = 1, + .supported_extts_flags = PTP_RISING_EDGE | PTP_STRICT_FLAGS, .adjfine = ptp_qoriq_adjfine, .adjtime = ptp_qoriq_adjtime, .gettime64 = ptp_qoriq_gettime, From d347ab5cf809365e7b3749dc91d77e89e4b7ec3f Mon Sep 17 00:00:00 2001 From: Vincent Jardin Date: Fri, 9 Oct 2026 02:00:58 +0200 Subject: [PATCH 16/94] ptp: qoriq: fix support for PTP_EXTTS_REQUEST2 ioctl Commit 7c571ac57d9d ("net: ptp: introduce .supported_extts_flags to ptp_clock_info") moved the validation of the external timestamp request flags into the PTP core. A driver that does not set .supported_extts_flags still accepts the legacy PTP_EXTTS_REQUEST ioctl, but every PTP_EXTTS_REQUEST2 request is rejected with -EOPNOTSUPP, since the core always adds PTP_STRICT_FLAGS to it. ptp_qoriq_init() programs TMR_CTRL with ETEP1 and ETEP2 cleared, so the timer timestamps the rising edge of its external trigger inputs. Declare that, with PTP_STRICT_FLAGS, so that a falling edge request is rejected instead of being timestamped on the rising edge. This also makes the legacy PTP_EXTTS_REQUEST ioctl strict: a request for the falling edge, or for both edges, now fails with -EOPNOTSUPP instead of being timestamped on the rising edge. That cannot be avoided: the core adds PTP_STRICT_FLAGS to every PTP_EXTTS_REQUEST2 request, so a driver has to declare it to accept them, and once declared it applies to both ioctls. Commit 3200fdd4021d ("broadcom: fix support for PTP_EXTTS_REQUEST2 ioctl") made the same change for bcm-phy-ptp. Fixes: 7c571ac57d9d ("net: ptp: introduce .supported_extts_flags to ptp_clock_info") Reviewed-by: Jacob Keller Reviewed-by: Kory Maincent Signed-off-by: Vincent Jardin Reviewed-by: Wei Fang Link: https://patch.msgid.link/20261009-for-upstream-qoriq-ptp-extts-flags-v2-3-0f4eeff2295d@free.fr Signed-off-by: Jakub Kicinski --- drivers/ptp/ptp_qoriq.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/ptp/ptp_qoriq.c b/drivers/ptp/ptp_qoriq.c index 76272e5ac5b8..bcd29104effe 100644 --- a/drivers/ptp/ptp_qoriq.c +++ b/drivers/ptp/ptp_qoriq.c @@ -355,6 +355,7 @@ static const struct ptp_clock_info ptp_qoriq_caps = { .n_per_out = 0, .n_pins = 0, .pps = 1, + .supported_extts_flags = PTP_RISING_EDGE | PTP_STRICT_FLAGS, .adjfine = ptp_qoriq_adjfine, .adjtime = ptp_qoriq_adjtime, .gettime64 = ptp_qoriq_gettime, From 29afe67082b2d33ab2e90c56c337235d725f023f Mon Sep 17 00:00:00 2001 From: Quanye Yang Date: Tue, 13 Oct 2026 16:29:19 +0200 Subject: [PATCH 17/94] mptcp: reset msk state on early connect failure mptcp_connect() can fall back before the subflow SYN is sent, for example when the netns is in an MPTCP blackhole, token allocation fails, or MD5SIG is in use. If the subsequent subflow connect() fails immediately (EAFNOSUPPORT, ENETUNREACH, ...), __inet_stream_connect() returns while the socket is still SS_UNCONNECTED and never calls ->disconnect(). This was reported by Clashiko [1]. The error path only dropped the token and moved the msk back to TCP_CLOSE. MPTCP_FALLBACK_DONE, allow_subflows and request_mptcp were left as after early fallback, so a later connect() on the same fd stayed TCP-only. Closes: https://github.com/multipath-tcp/mptcp_net-next/issues/633 [1] Fixes: 0235d075a592 ("mptcp: mark as fallback even early ones") Cc: stable@vger.kernel.org Signed-off-by: Quanye Yang Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20261013-net-mptcp-misc-fixes-7-3-rc8-v1-1-932c9b182fc7@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/protocol.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index e89a69ab927c..e6f73a5ee630 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -4212,9 +4212,16 @@ static int mptcp_connect(struct sock *sk, struct sockaddr_unsized *uaddr, * subflow_finish_connect() */ if (unlikely(err)) { - /* avoid leaving a dangling token in an unconnected socket */ mptcp_token_destroy(msk); mptcp_set_state(sk, TCP_CLOSE); + + spin_lock_bh(&msk->fallback_lock); + msk->allow_subflows = true; + msk->allow_infinite_fallback = true; + clear_bit(MPTCP_FALLBACK_DONE, &msk->flags); + spin_unlock_bh(&msk->fallback_lock); + + mptcp_subflow_ctx_reset(mptcp_subflow_ctx(ssk)); return err; } From 7538fa33bb2f66b8b809ff39d58308be48e197a0 Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Tue, 13 Oct 2026 16:29:20 +0200 Subject: [PATCH 18/94] mptcp: fix subflow bitfield misuse The subflow status bitfield is protected by the subflow socket lock, with one notable exception: the `close_event_done` bit is actually under the msk socket lock protection. As reported by Clashiko [1], flipping the latter bit may corrupt other entries in the same bitfield. Move the `close_event_done` outside the bitfield, in a 32bit hole. Link: https://lore.kernel.org/178959153492.22033.1944011998010756543@kernel.org Fixes: d82809b6c5f2 ("mptcp: avoid duplicated SUB_CLOSED events") Cc: stable@vger.kernel.org Signed-off-by: Paolo Abeni Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20261013-net-mptcp-misc-fixes-7-3-rc8-v1-2-932c9b182fc7@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/protocol.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h index 0384d6a023f9..c006110b1c56 100644 --- a/net/mptcp/protocol.h +++ b/net/mptcp/protocol.h @@ -583,14 +583,16 @@ struct mptcp_subflow_context { closing : 1, /* must not pass rx data to msk anymore */ valid_csum_seen : 1, /* at least one csum validated */ is_mptfo : 1, /* subflow is doing TFO */ - close_event_done : 1, /* has done the post-closed part */ mpc_drop : 1, /* the MPC option has been dropped in a rtx */ resetting : 1, /* subflow is resetting */ - __unused : 8; + __unused : 9; bool data_avail; bool scheduled; bool pm_listener; /* a listener managed by the kernel PM? */ bool fully_established; /* path validated */ + bool close_event_done; /* has done the post-closed part, + * protected by msk socket lock + */ u32 lent_mem_frag; u32 remote_nonce; u64 thmac; From c9c90cf437a9e6597611b6d3aad6e255fad22867 Mon Sep 17 00:00:00 2001 From: Ruide Cao Date: Tue, 13 Oct 2026 16:29:21 +0200 Subject: [PATCH 19/94] mptcp: hold MP_JOIN msk ref when cloning reqsk TCP request migration clones pending request sockets with inet_reqsk_clone(). For MPTCP MP_JOIN requests this byte-copies subflow_req->msk, but the clone does not own a reference. The original and cloned requests can consequently drop the same msk reference, leaving one request with a dangling pointer. This manifests as a KASAN slab-use-after-free in subflow_req_destructor(): BUG: KASAN: slab-use-after-free in subflow_req_destructor (net/mptcp/subflow.c:45) Write of size 4 at addr ff1100010e008d40 by task swapper/0/0 CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Not tainted 7.2.0-rc5-00353-gc27e36054537 #10 PREEMPT(full) Call Trace: subflow_req_destructor (net/mptcp/subflow.c:45) subflow_v4_req_destructor (net/mptcp/subflow.c:694) __reqsk_free (net/ipv4/inet_connection_sock.c:906) reqsk_timer_handler (include/net/request_sock.h:137 net/ipv4/inet_connection_sock.c:1147) Allocated by task 10014: sk_prot_alloc (net/core/sock.c:2246) sk_clone (net/core/sock.c:2488) mptcp_sk_clone_init (include/net/sock.h:1848 net/mptcp/protocol.c:3564) subflow_syn_recv_sock (net/mptcp/subflow.c:883) tcp_check_req (net/ipv4/tcp_minisocks.c:934) Freed by task 0: slab_free_after_rcu_debug (include/linux/kasan.h:235 mm/slub.c:2677 mm/slub.c:6439) rcu_core (kernel/rcu/tree.c:2645 kernel/rcu/tree.c:2897) Last potentially related work creation: kmem_cache_free (mm/slub.c:2638 mm/slub.c:6377 mm/slub.c:6504) __sk_destruct (net/core/sock.c:2289 net/core/sock.c:2391) sk_destruct (net/core/sock.c:2419) __sk_free (net/core/sock.c:2430) sk_free (net/core/sock.c:2441) mptcp_close (include/net/sock.h:2020 net/mptcp/protocol.c:3399) inet_release (net/ipv4/af_inet.c:442) The buggy address belongs to the cache MPTCP of size 2968 The buggy address is 128 bytes inside a freed 2968-byte region A non-NULL subflow_req->msk means that the request owns one reference. The third ACK can concurrently transfer the original request reference to the child and release it, so taking an unconditional hold on the copied pointer is unsafe. MPTCP sockets use SLAB_TYPESAFE_BY_RCU and all current inet_reqsk_clone() callers run in an RCU read-side critical section. Document and lockdep-check that calling-context requirement. Read the pointer from the original request and acquire a reference only if it is still live. Use refcount_inc_not_zero_acquire() so the subsequent ownership validation cannot be reordered before the try-get, then re-read the original request to verify that it still owns the same msk. If either check fails, clear the clone pointer; otherwise its normal destructor balances the new reference. Mark the ownership-transfer store with WRITE_ONCE() to match the lockless reads. The following patch completes the clone fixup for MP_CAPABLE token ownership. Both patches carry the same Fixes tag and are required for stable backports. Fixes: c905dee62232 ("tcp: Migrate TCP_NEW_SYN_RECV requests at retransmitting SYN+ACKs.") Cc: stable@vger.kernel.org Reported-by: Kyle Zeng Reported-by: David Lee Reported-by: Vega Closes: https://lore.kernel.org/all/20260804095051.715355-1-david.lee@trailofbits.com/ Signed-off-by: Ruide Cao Signed-off-by: Ren Wei Reviewed-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20261013-net-mptcp-misc-fixes-7-3-rc8-v1-3-932c9b182fc7@kernel.org Signed-off-by: Jakub Kicinski --- include/net/mptcp.h | 9 +++++++++ net/ipv4/inet_connection_sock.c | 4 ++++ net/mptcp/subflow.c | 35 ++++++++++++++++++++++++++++++++- 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/include/net/mptcp.h b/include/net/mptcp.h index 485d55b66ea6..ad7005b574bb 100644 --- a/include/net/mptcp.h +++ b/include/net/mptcp.h @@ -219,6 +219,9 @@ int mptcp_subflow_init_cookie_req(struct request_sock *req, struct request_sock *mptcp_subflow_reqsk_alloc(const struct request_sock_ops *ops, struct sock *sk_listener, bool attach_listener); +/* Caller must hold an RCU read-side lock. */ +void mptcp_subflow_reqsk_clone(struct request_sock *req, + struct request_sock *new_req); __be32 mptcp_get_reset_option(const struct sk_buff *skb); @@ -305,6 +308,12 @@ static inline struct request_sock *mptcp_subflow_reqsk_alloc(const struct reques return NULL; } +/* Caller must hold an RCU read-side lock. */ +static inline void mptcp_subflow_reqsk_clone(struct request_sock *req, + struct request_sock *new_req) +{ +} + static inline __be32 mptcp_reset_option(const struct sk_buff *skb) { return htonl(0u); } static inline void mptcp_active_detect_blackhole(struct sock *sk, bool expired) { } diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c index 6a30f1138454..c6f8011200a0 100644 --- a/net/ipv4/inet_connection_sock.c +++ b/net/ipv4/inet_connection_sock.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -961,6 +962,9 @@ static struct request_sock *inet_reqsk_clone(struct request_sock *req, rcu_assign_pointer(tcp_sk(nreq->sk)->fastopen_rsk, nreq); } + if (rsk_is_mptcp(req)) + mptcp_subflow_reqsk_clone(req, nreq); + return nreq; } diff --git a/net/mptcp/subflow.c b/net/mptcp/subflow.c index f0a6725d2c37..3139a9ab2853 100644 --- a/net/mptcp/subflow.c +++ b/net/mptcp/subflow.c @@ -47,6 +47,39 @@ static void subflow_req_destructor(struct request_sock *req) mptcp_token_destroy_request(req); } +void mptcp_subflow_reqsk_clone(struct request_sock *req, + struct request_sock *new_req) +{ + struct mptcp_subflow_request_sock *subflow_req = mptcp_subflow_rsk(req); + struct mptcp_subflow_request_sock *new_subflow_req; + struct mptcp_sock *msk; + + new_subflow_req = mptcp_subflow_rsk(new_req); + RCU_LOCKDEP_WARN(!rcu_read_lock_any_held(), + "MPTCP reqsk clone called without RCU"); + + /* A non-NULL ->msk means the request owns one reference. The clone + * copied only the pointer, while the original request can concurrently + * transfer its reference to the child. Acquire a reference for the + * clone, then verify that the original request still owns the same msk. + * MPTCP sockets use SLAB_TYPESAFE_BY_RCU and all clone callers run in + * an RCU read-side critical section, keeping the memory stable here. + */ + msk = READ_ONCE(subflow_req->msk); + if (msk) { + struct sock *sk = (struct sock *)msk; + + if (!refcount_inc_not_zero_acquire(&sk->sk_refcnt)) { + msk = NULL; + } else if (READ_ONCE(subflow_req->msk) != msk) { + sock_put(sk); + msk = NULL; + } + } + + new_subflow_req->msk = msk; +} + static void subflow_generate_hmac(u64 key1, u64 key2, u32 nonce1, u32 nonce2, void *hmac) { @@ -927,7 +960,7 @@ static struct sock *subflow_syn_recv_sock(const struct sock *sk, } /* move the msk reference ownership to the subflow */ - subflow_req->msk = NULL; + WRITE_ONCE(subflow_req->msk, NULL); ctx->conn = (struct sock *)owner; if (subflow_use_different_sport(owner, sk)) { From 2093bc7fa50056fef8620d2b82bcb2e9a0a5575d Mon Sep 17 00:00:00 2001 From: Ruide Cao Date: Tue, 13 Oct 2026 16:29:22 +0200 Subject: [PATCH 20/94] mptcp: fix MP_CAPABLE token migration when cloning reqsk TCP request migration clones pending request sockets with inet_reqsk_clone(). For MPTCP MP_CAPABLE requests this byte-copies the token_node hlist state into the clone even though the token table still names the original request, and the following warning can be seen: WARNING: net/mptcp/token.c:364 at mptcp_token_destroy_request+0x2b0/0x330, CPU#2: swapper/2/0 CPU: 2 UID: 0 PID: 0 Comm: swapper/2 Not tainted 7.2.0-rc5-00353-gc27e36054537 #10 PREEMPT(full) RIP: 0010:mptcp_token_destroy_request (net/mptcp/token.c:364 (discriminator 1)) Call Trace: subflow_v4_req_destructor (net/mptcp/subflow.c:694) __reqsk_free (net/ipv4/inet_connection_sock.c:906) reqsk_timer_handler (include/net/request_sock.h:137 net/ipv4/inet_connection_sock.c:1147) call_timer_fn (kernel/time/timer.c:1748) Move token request ownership from the original request to the clone under the token bucket lock. Make mptcp_token_accept() and mptcp_token_destroy_request() re-check token_node under the same lock and treat an already moved or removed request as a normal race instead of warning. This also keeps token bucket chain_len accounting balanced. If a passive MP_CAPABLE socket cannot claim the token, destroy the provisional MPTCP socket and let the subflow fall back rather than installing a socket with mismatched token ownership. Mark that path as a passive handshake fallback so MPCapableFallbackACK records the downgrade. If the speculative clone later loses the inet_ehash_insert() ownership arbitration, the original request may find that its token was already moved and fall back to plain TCP. The clone destructor then releases the token reservation and keeps chain_len balanced. This trades an exceptionally rare fallback for eliminating the warning and persistent accounting drift. Fixes: c905dee62232 ("tcp: Migrate TCP_NEW_SYN_RECV requests at retransmitting SYN+ACKs.") Cc: stable@vger.kernel.org Reported-by: Vega Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/86e2514b533bf4d55d4aa2fdbf1404022e8c9430.1776149210.git.caoruide123%40gmail.com Signed-off-by: Ruide Cao Signed-off-by: Ren Wei Reviewed-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20261013-net-mptcp-misc-fixes-7-3-rc8-v1-4-932c9b182fc7@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/protocol.c | 31 +++++++++++++++---- net/mptcp/protocol.h | 4 ++- net/mptcp/subflow.c | 6 +++- net/mptcp/token.c | 68 +++++++++++++++++++++++++++++++++++++----- net/mptcp/token_test.c | 4 +-- 5 files changed, 97 insertions(+), 16 deletions(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index e6f73a5ee630..05c38564e8fd 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -3706,6 +3706,24 @@ static void mptcp_copy_ip_options(struct sock *newsk, const struct sock *sk) rcu_read_unlock(); } +static void mptcp_sk_clone_destroy(struct sock *nsk) +{ + struct mptcp_sock *msk = mptcp_sk(nsk); + + mptcp_release_sched(msk); + mptcp_set_state(nsk, TCP_CLOSE); + /* inet_csk_prepare_forced_close() clears TCP sock_ops state via + * tcp_sk(), but nsk is an MPTCP master socket; keep the inet-level + * destroy preparation here. + */ + bh_unlock_sock(nsk); + sock_put(nsk); + sock_set_flag(nsk, SOCK_DEAD); + tcp_orphan_count_inc(); + inet_sk(nsk)->inet_num = 0; + inet_csk_destroy_sock(nsk); +} + struct sock *mptcp_sk_clone_init(const struct sock *sk, const struct mptcp_options_received *mp_opt, struct sock *ssk, @@ -3765,11 +3783,6 @@ struct sock *mptcp_sk_clone_init(const struct sock *sk, list_add(&subflow->node, &msk->conn_list); sock_hold(ssk); - /* new mpc subflow takes ownership of the newly - * created mptcp socket - */ - mptcp_token_accept(subflow_req, msk); - /* set msk addresses early to ensure mptcp_pm_get_local_id() * uses the correct data */ @@ -3778,6 +3791,14 @@ struct sock *mptcp_sk_clone_init(const struct sock *sk, mptcp_rcv_space_init(msk, ssk); msk->rcvq_space.time = mptcp_stamp(); + if (!mptcp_token_accept(subflow_req, msk)) { + list_del_init(&subflow->node); + WRITE_ONCE(msk->first, NULL); + sock_put(ssk); + mptcp_sk_clone_destroy(nsk); + return NULL; + } + if (mp_opt->suboptions & OPTION_MPTCP_MPC_ACK) __mptcp_subflow_fully_established(msk, subflow, mp_opt); bh_unlock_sock(nsk); diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h index c006110b1c56..4ed598febbf0 100644 --- a/net/mptcp/protocol.h +++ b/net/mptcp/protocol.h @@ -1083,9 +1083,11 @@ static inline void mptcp_token_init_request(struct request_sock *req) } int mptcp_token_new_request(struct request_sock *req); +void mptcp_token_move_request(struct request_sock *req, + struct request_sock *new_req); void mptcp_token_destroy_request(struct request_sock *req); int mptcp_token_new_connect(struct sock *ssk); -void mptcp_token_accept(struct mptcp_subflow_request_sock *r, +bool mptcp_token_accept(struct mptcp_subflow_request_sock *r, struct mptcp_sock *msk); bool mptcp_token_exists(u32 token); struct mptcp_sock *mptcp_token_get_sock(struct net *net, u32 token); diff --git a/net/mptcp/subflow.c b/net/mptcp/subflow.c index 3139a9ab2853..2c72cb50fbf3 100644 --- a/net/mptcp/subflow.c +++ b/net/mptcp/subflow.c @@ -78,6 +78,8 @@ void mptcp_subflow_reqsk_clone(struct request_sock *req, } new_subflow_req->msk = msk; + + mptcp_token_move_request(req, new_req); } static void subflow_generate_hmac(u64 key1, u64 key2, u32 nonce1, u32 nonce2, @@ -922,8 +924,10 @@ static struct sock *subflow_syn_recv_sock(const struct sock *sk, if (ctx->mp_capable) { ctx->conn = mptcp_sk_clone_init(listener->conn, &mp_opt, child, req); - if (!ctx->conn) + if (!ctx->conn) { + fallback = true; goto fallback; + } ctx->subflow_id = 1; owner = mptcp_sk(ctx->conn); diff --git a/net/mptcp/token.c b/net/mptcp/token.c index f1a50f367add..e0f2823f92ef 100644 --- a/net/mptcp/token.c +++ b/net/mptcp/token.c @@ -180,6 +180,43 @@ int mptcp_token_new_connect(struct sock *ssk) return 0; } +/** + * mptcp_token_move_request - move request token ownership to a clone + * @req: original request socket + * @new_req: cloned request socket + * + * Move the token hash entry from the original request to its clone. + */ +void mptcp_token_move_request(struct request_sock *req, + struct request_sock *new_req) +{ + struct mptcp_subflow_request_sock *subflow_req = mptcp_subflow_rsk(req); + struct mptcp_subflow_request_sock *new_subflow_req; + struct mptcp_subflow_request_sock *pos; + struct token_bucket *bucket; + + new_subflow_req = mptcp_subflow_rsk(new_req); + + if (hlist_nulls_unhashed_lockless(&subflow_req->token_node)) { + mptcp_token_init_request(new_req); + return; + } + + bucket = token_bucket(subflow_req->token); + spin_lock_bh(&bucket->lock); + if (hlist_nulls_unhashed(&subflow_req->token_node)) { + mptcp_token_init_request(new_req); + } else { + pos = __token_lookup_req(bucket, subflow_req->token); + if (pos == subflow_req) + hlist_nulls_replace_init_rcu(&subflow_req->token_node, + &new_subflow_req->token_node); + else + mptcp_token_init_request(new_req); + } + spin_unlock_bh(&bucket->lock); +} + /** * mptcp_token_accept - replace a req sk with full sock in token hash * @req: the request socket to be removed @@ -187,24 +224,36 @@ int mptcp_token_new_connect(struct sock *ssk) * * Called when a SYN packet creates a new logical connection, i.e. * is not a join request. + * + * Return: true on success. */ -void mptcp_token_accept(struct mptcp_subflow_request_sock *req, +bool mptcp_token_accept(struct mptcp_subflow_request_sock *req, struct mptcp_sock *msk) { struct mptcp_subflow_request_sock *pos; struct sock *sk = (struct sock *)msk; struct token_bucket *bucket; + bool ret = false; - sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1); bucket = token_bucket(req->token); spin_lock_bh(&bucket->lock); + if (hlist_nulls_unhashed(&req->token_node)) + goto unlock; - /* pedantic lookup check for the moved token */ pos = __token_lookup_req(bucket, req->token); - if (!WARN_ON_ONCE(pos != req)) - hlist_nulls_del_init_rcu(&req->token_node); + if (pos != req) + goto unlock; + + hlist_nulls_del_init_rcu(&req->token_node); __sk_nulls_add_node_rcu((struct sock *)msk, &bucket->msk_chain); + ret = true; + +unlock: spin_unlock_bh(&bucket->lock); + if (ret) + sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1); + + return ret; } bool mptcp_token_exists(u32 token) @@ -355,16 +404,21 @@ void mptcp_token_destroy_request(struct request_sock *req) struct mptcp_subflow_request_sock *pos; struct token_bucket *bucket; - if (hlist_nulls_unhashed(&subflow_req->token_node)) + if (hlist_nulls_unhashed_lockless(&subflow_req->token_node)) return; bucket = token_bucket(subflow_req->token); spin_lock_bh(&bucket->lock); + if (hlist_nulls_unhashed(&subflow_req->token_node)) + goto unlock; + pos = __token_lookup_req(bucket, subflow_req->token); - if (!WARN_ON_ONCE(pos != subflow_req)) { + if (pos == subflow_req) { hlist_nulls_del_init_rcu(&pos->token_node); bucket->chain_len--; } + +unlock: spin_unlock_bh(&bucket->lock); } diff --git a/net/mptcp/token_test.c b/net/mptcp/token_test.c index 4fc39fa2e262..be9acce8a567 100644 --- a/net/mptcp/token_test.c +++ b/net/mptcp/token_test.c @@ -99,7 +99,7 @@ static void mptcp_token_test_accept(struct kunit *test) KUNIT_ASSERT_EQ(test, 0, mptcp_token_new_request((struct request_sock *)req)); msk->token = req->token; - mptcp_token_accept(req, msk); + KUNIT_EXPECT_TRUE(test, mptcp_token_accept(req, msk)); KUNIT_EXPECT_PTR_EQ(test, msk, mptcp_token_get_sock(&init_net, msk->token)); /* this is now a no-op */ @@ -122,7 +122,7 @@ static void mptcp_token_test_destroyed(struct kunit *test) KUNIT_ASSERT_EQ(test, 0, mptcp_token_new_request((struct request_sock *)req)); msk->token = req->token; - mptcp_token_accept(req, msk); + KUNIT_EXPECT_TRUE(test, mptcp_token_accept(req, msk)); /* simulate race on removal */ refcount_set(&sk->sk_refcnt, 0); From c2124f80be4ceb6ec566ca3b5fc87f84ea9c36e0 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Tue, 13 Oct 2026 13:50:22 +0200 Subject: [PATCH 21/94] vlan: restore HW VLAN tag insertion with reorder_hdr off MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 447cbe95ebb9 ("vlan: fix skb_under_panic and races when toggling HW VLAN offload") replaced vlan_passthru_header_ops with vlan_header_ops unconditionally to avoid lockless data races when modifying hard_header_len dynamically under RTNL. Prior to that commit, vlan devices on top of a real device capable of HW VLAN TX offload used vlan_passthru_header_ops, which never inserted the 802.1Q header in the frame. Now, with reorder_hdr=off (as done by Home Assistant OS / NetworkManager when flags are omitted over D-Bus), vlan_dev_hard_header() always pushes an in-band 802.1Q header, and vlan_dev_hard_start_xmit() hands the frame to the real device as is, even if the real device supports HW VLAN TX offload. This is a regression for r8169 users (Realtek RTL8168h), who report transmit queue timeouts (NETDEV WATCHDOG): this NIC stalls when asked to offload checksum and/or TSO for frames carrying an in-band tag. Users of r8152 based USB adapters were affected as well. Reverting the blamed commit, disabling TX checksum offload (which also disables TSO), or setting reorder_hdr=on were all reported to work around the issue. Keep vlan_dev_hard_header() as is: the in-band header keeps the frame layout seen at the vlan device egress (taps, tc, netfilter) unchanged, and is relied upon by stacked vlans (Q-in-Q) and by AF_PACKET SOCK_DGRAM users sending ETH_P_8021Q frames. Instead, when vlan_dev_hard_start_xmit() gets a frame carrying an in-band tag with our protocol and the real device can insert tags, move that tag to skb->vlan_tci with __skb_vlan_pop(), so that the real device inserts it. Frames sent on the wire are unchanged, except that frames shorter than 64 bytes with their tag now get up to 4 bytes of extra padding, as with reorder_hdr=on and before 447cbe95ebb9. skb_reset_mac_header() is needed first, because not all ndo_start_xmit() callers set the mac header (e.g. AF_XDP copy mode, which uses __dev_direct_xmit()). vlan_set_encap_proto() may read the two bytes after the vlan header, so frames whose linear part is shorter than VLAN_ETH_HLEN + 2 are sent unchanged, as before. This also makes sure h_vlan_proto, read earlier in the function, was in the linear part. Shared skbs (e.g. from pktgen) are also sent unchanged, since popping the tag modifies the skb, as are skbs already carrying a hwaccel tag (e.g. from generic XDP redirect), whose tag must not be overwritten, skbs carrying XDP metadata, which the pop would have to move, and CHECKSUM_PARTIAL skbs whose csum_start lies within the first VLAN_ETH_HLEN bytes (only possible with bogus offsets, e.g. from AF_XDP tx metadata), which the pop would leave before skb->data. If popping the header fails (memory allocation failure), the frame is dropped and counted in tx_dropped, rather than handing the real device a frame with an in-band tag. tx_bytes is now sampled before the pop, so the vlan device statistics do not change. This also applies to frames provided by the sender with our tag (AF_PACKET SOCK_RAW, or frames redirected from another reorder_hdr=off vlan): the real device now inserts their tag too. Fixes: 447cbe95ebb9 ("vlan: fix skb_under_panic and races when toggling HW VLAN offload") Reported-by: Jan Čermák Closes: https://lore.kernel.org/netdev/CAPa5EdCj3v17tB-SF2JNecq5Q8s1Pranr-XzAFWNpNTBPgPG7w@mail.gmail.com/ Reported-by: Stefan Agner Reported-by: hitchin999 Closes: https://github.com/home-assistant/operating-system/issues/5019 Tested-by: Jan Čermák Signed-off-by: Eric Dumazet Cc: Heiner Kallweit Link: https://patch.msgid.link/20261013115022.1043024-1-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- net/8021q/vlan_dev.c | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/net/8021q/vlan_dev.c b/net/8021q/vlan_dev.c index c949c6a82945..2d775b4c84ce 100644 --- a/net/8021q/vlan_dev.c +++ b/net/8021q/vlan_dev.c @@ -110,6 +110,8 @@ static netdev_tx_t vlan_dev_hard_start_xmit(struct sk_buff *skb, unsigned int len; int ret; + len = skb->len; + /* Handle non-VLAN frames if they are sent to us, for example by DHCP. * * NOTE: THIS ASSUMES DIX ETHERNET, SPECIFICALLY NOT SUPPORTING @@ -121,10 +123,39 @@ static netdev_tx_t vlan_dev_hard_start_xmit(struct sk_buff *skb, vlan_tci = vlan->vlan_id; vlan_tci |= vlan_dev_get_egress_qos_mask(dev, skb->priority); __vlan_hwaccel_put_tag(skb, vlan->vlan_proto, vlan_tci); + } else if (skb_headlen(skb) >= VLAN_ETH_HLEN + sizeof(__be16) && + !skb_shared(skb) && !skb_vlan_tag_present(skb) && + !skb_metadata_len(skb) && + (skb->ip_summed != CHECKSUM_PARTIAL || + skb_checksum_start_offset(skb) >= VLAN_ETH_HLEN) && + vlan_hw_offload_capable(READ_ONCE(vlan->real_dev->features), + vlan->vlan_proto)) { + u16 vlan_tci; + + /* The frame carries its outer tag in-band, either inserted + * by vlan_dev_hard_header() or provided by the sender. + * Let the real device insert it: some NICs can not offload + * checksum or TSO for frames carrying an in-band tag. + * Not all ndo_start_xmit() callers set the mac header. + * vlan_set_encap_proto() may read two bytes after the vlan + * header, so frames with a shorter linear part are sent + * unchanged, as are shared skbs (e.g. from pktgen), skbs + * already carrying a hwaccel tag (e.g. from generic XDP + * redirect), skbs carrying XDP metadata, and CHECKSUM_PARTIAL + * skbs whose checksum starts within the header to be popped. + * On allocation failure, drop the frame rather than handing + * it to the real device with an in-band tag. + */ + skb_reset_mac_header(skb); + if (unlikely(__skb_vlan_pop(skb, &vlan_tci))) { + dev_kfree_skb_any(skb); + this_cpu_inc(vlan->vlan_pcpu_stats->tx_dropped); + return NETDEV_TX_OK; + } + __vlan_hwaccel_put_tag(skb, vlan->vlan_proto, vlan_tci); } skb->dev = vlan->real_dev; - len = skb->len; if (unlikely(netpoll_tx_running(dev))) return vlan_netpoll_send_skb(vlan, skb); From 58f7989da6e715ce834ac861b7ba6ae9f9bdb098 Mon Sep 17 00:00:00 2001 From: Yuhang Zheng Date: Mon, 12 Oct 2026 09:07:19 +0800 Subject: [PATCH 22/94] net/sched: cls_flower: avoid stale mask references after delete Flower releases a mask reference when a filter is removed from the classifier. The filter object can remain alive while dump, walk, or hardware reoffload paths hold a reference to it. Those paths can then dereference f->mask after the mask has already been released. Keep the mask reference until the filter reference count reaches zero. Release the mask synchronously from the final __fl_put() while the caller still holds the tcf_proto reference, and leave the existing RCU work to free the mask. This keeps the mask available to every reader that still holds the filter. The filter is still removed from the classifier indexes immediately. Therefore, a deleted filter can temporarily retain an otherwise empty mask; fl_delete() uses the handle IDR, rather than the mask list, to determine whether the classifier has become empty. Failed mask assignment can leave fnew->mask as an error or borrowed pointer without an owned mask reference. Clear it before the common failure path so the final put cannot release an unowned reference. This fix does not add fields, snapshots, or per-filter memory. Fixes: 92149190067d ("net: sched: flower: set unlocked flag for flower proto ops") Cc: stable@vger.kernel.org Reported-by: Xin Liu Tested-by: Yucheng Lu Signed-off-by: Yuhang Zheng Signed-off-by: Ren Wei Link: https://patch.msgid.link/20261012010720.2248662-2-n05ec@lzu.edu.cn Signed-off-by: Jakub Kicinski --- net/sched/cls_flower.c | 50 +++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/net/sched/cls_flower.c b/net/sched/cls_flower.c index 1cefea571efd..92a7f2d6efc0 100644 --- a/net/sched/cls_flower.c +++ b/net/sched/cls_flower.c @@ -518,15 +518,22 @@ static void fl_hw_update_stats(struct tcf_proto *tp, struct cls_fl_filter *f, tcf_exts_hw_stats_update(&f->exts, &cls_flower.stats, cls_flower.use_act_stats); } -static void __fl_put(struct cls_fl_filter *f) +static bool __fl_put(struct tcf_proto *tp, struct cls_fl_filter *f) { + bool last = false; + if (!refcount_dec_and_test(&f->refcnt)) - return; + return false; + + if (f->mask) + last = fl_mask_put(fl_head_dereference(tp), f->mask); if (tcf_exts_get_net(&f->exts)) tcf_queue_work(&f->rwork, fl_destroy_filter_work); else __fl_destroy_filter(f); + + return last; } static struct cls_fl_filter *__fl_get(struct cls_fl_head *head, u32 handle) @@ -572,11 +579,10 @@ static int __fl_delete(struct tcf_proto *tp, struct cls_fl_filter *f, list_del_rcu(&f->list); spin_unlock(&tp->lock); - *last = fl_mask_put(head, f->mask); if (!tc_skip_hw(f->flags)) fl_hw_destroy_filter(tp, f, rtnl_held, extack); tcf_unbind_filter(tp, &f->res); - __fl_put(f); + *last = __fl_put(tp, f); return 0; } @@ -617,7 +623,7 @@ static void fl_put(struct tcf_proto *tp, void *arg) { struct cls_fl_filter *f = arg; - __fl_put(f); + __fl_put(tp, f); } static void *fl_get(struct tcf_proto *tp, u32 handle) @@ -2488,12 +2494,14 @@ static int fl_change(struct net *net, struct sk_buff *in_skb, } err = fl_check_assign_mask(head, fnew, fold, mask); - if (err) + if (err) { + fnew->mask = NULL; goto unbind_filter; + } err = fl_ht_insert_unique(fnew, fold, &in_ht); if (err) - goto errout_mask; + goto unbind_filter; if (!tc_skip_hw(fnew->flags)) { err = fl_hw_replace_filter(tp, fnew, rtnl_held, extack); @@ -2547,7 +2555,6 @@ static int fl_change(struct net *net, struct sk_buff *in_skb, spin_unlock(&tp->lock); - fl_mask_put(head, fold->mask); if (!tc_skip_hw(fold->flags)) fl_hw_destroy_filter(tp, fold, rtnl_held, NULL); tcf_unbind_filter(tp, &fold->res); @@ -2555,7 +2562,7 @@ static int fl_change(struct net *net, struct sk_buff *in_skb, * after this. */ refcount_dec(&fold->refcnt); - __fl_put(fold); + __fl_put(tp, fold); } else { idr_replace(&head->handle_idr, fnew, fnew->handle); @@ -2580,9 +2587,6 @@ static int fl_change(struct net *net, struct sk_buff *in_skb, if (in_ht) rhashtable_remove_fast(&fnew->mask->ht, &fnew->ht_node, fnew->mask->filter_ht_params); -errout_mask: - fl_mask_put(head, fnew->mask); - unbind_filter: if (bound_to_filter) { if (flags & TCA_ACT_FLAGS_NO_RTNL) @@ -2598,14 +2602,14 @@ static int fl_change(struct net *net, struct sk_buff *in_skb, idr_remove(&head->handle_idr, fnew->handle); spin_unlock(&tp->lock); } - __fl_put(fnew); + __fl_put(tp, fnew); errout_tb: kfree(tb); errout_mask_alloc: tcf_queue_work(&mask->rwork, fl_uninit_mask_free_work); errout_fold: if (fold) - __fl_put(fold); + __fl_put(tp, fold); return err; } @@ -2618,8 +2622,10 @@ static int fl_delete(struct tcf_proto *tp, void *arg, bool *last, int err = 0; err = __fl_delete(tp, f, &last_on_mask, rtnl_held, extack); - *last = list_empty(&head->masks); - __fl_put(f); + spin_lock(&tp->lock); + *last = idr_is_empty(&head->handle_idr); + spin_unlock(&tp->lock); + __fl_put(tp, f); return err; } @@ -2641,12 +2647,12 @@ static void fl_walk(struct tcf_proto *tp, struct tcf_walker *arg, rcu_read_unlock(); if (arg->fn(tp, f, arg) < 0) { - __fl_put(f); + __fl_put(tp, f); arg->stop = 1; rcu_read_lock(); break; } - __fl_put(f); + __fl_put(tp, f); arg->count++; rcu_read_lock(); } @@ -2697,7 +2703,7 @@ static int fl_reoffload(struct tcf_proto *tp, bool add, flow_setup_cb_t *cb, cls_flower.rule = flow_rule_alloc(tcf_exts_num_actions(&f->exts)); if (!cls_flower.rule) { - __fl_put(f); + __fl_put(tp, f); return -ENOMEM; } @@ -2715,7 +2721,7 @@ static int fl_reoffload(struct tcf_proto *tp, bool add, flow_setup_cb_t *cb, if (err) { kfree(cls_flower.rule); if (tc_skip_sw(f->flags)) { - __fl_put(f); + __fl_put(tp, f); return err; } goto next_flow; @@ -2731,11 +2737,11 @@ static int fl_reoffload(struct tcf_proto *tp, bool add, flow_setup_cb_t *cb, kfree(cls_flower.rule); if (err) { - __fl_put(f); + __fl_put(tp, f); return err; } next_flow: - __fl_put(f); + __fl_put(tp, f); } return 0; From 718f364a20ecee4f25986789c25550cf4da73b4c Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Sun, 11 Oct 2026 01:41:27 +0000 Subject: [PATCH 23/94] gre: keep the UDP tunnel GSO type when completing GRO gre_gro_complete() assigns SKB_GSO_GRE to gso_type rather than OR-ing it in. When GRE is carried over UDP by FOU or GUE, udp_gro_complete() has already set SKB_GSO_UDP_TUNNEL{,_CSUM} before calling into the FOU or GUE gro_complete handler and then gre_gro_complete(), so that bit is lost. That has broken forwarding of such packets since FOU gained GRO: if a host with a FOU or GUE socket on the tunnel's port merges transit packets and then forwards them out a device that can't segment them itself, udp4_ufo_fragment() finds neither tunnel bit and fails, and the packets are dropped. Since commit 3d010c8031e3 ("udp: do not accept non-tunnel GSO skbs landing in a tunnel"), local receive is broken too: udp_unexpected_gso() considers the merged skb, which reaches the encapsulation socket without either UDP tunnel bit, unexpected and hands it to udp_rcv_segment(). Segmenting fails, so the packet is freed and counted in UdpInErrors. Either way, only packets GRO didn't merge get through, so TCP over FOU or GUE GRE tunnels slows to about one segment per round trip whenever the receiving device has GRO enabled. OR in SKB_GSO_GRE instead, as ipip_gro_complete() and sit_gro_complete() do for SKB_GSO_IPXIP4. For plain GRE, gso_type is still zero at this point unless the driver set it, so nothing changes there in the usual case. The assignment dates to commit bf5a755f5e91 ("net-gre-gro: Add GRE support to the GRO stack"), but nothing set gso_type before it until FOU GRO came along, so the Fixes tag points there. Tested under virtme-ng with bulk TCP over a GRE tunnel between two network namespaces joined by a veth, with GRO on the receiving veth and segmentation off on the sending one, so GRO sees wire-sized packets. 32 MiB took: before after FOU over IPv4 26.0s, 9658 UDP errors 0.28s, 0 errors GUE over IPv4 26.1s, 9686 UDP errors 0.39s, 0 errors FOU over IPv6 26.4s, 9860 UDP errors 0.16s, 0 errors plain GRE 0.17s 0.15s Forwarding the same FOU traffic through a third namespace with its own FOU socket on the port, GRO on its ingress and segmentation off on its egress, took 26.0s with 4826 egress drops before and 0.31s with none after. On v6.8, before 3d010c8031e3, local receive was fine but forwarding failed the same way (25.7s, 4764 drops). With GRO off on the receiver, all took under 0.2s both before and after. The gre_gso.sh and fou_mcast_encap.sh selftests give the same results before and after. Fixes: afe93325bc02 ("fou: Add GRO support") Signed-off-by: Brad Fitzpatrick Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20261011-bradfitz-gre-fou-gro-b4-v1-1-df713aad1531@tailscale.com Signed-off-by: Jakub Kicinski --- net/ipv4/gre_offload.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ipv4/gre_offload.c b/net/ipv4/gre_offload.c index 5028c72d494a..c8129782d39d 100644 --- a/net/ipv4/gre_offload.c +++ b/net/ipv4/gre_offload.c @@ -242,7 +242,7 @@ static int gre_gro_complete(struct sk_buff *skb, int nhoff) __be16 type; skb->encapsulation = 1; - skb_shinfo(skb)->gso_type = SKB_GSO_GRE; + skb_shinfo(skb)->gso_type |= SKB_GSO_GRE; type = greh->protocol; if (greh->flags & GRE_KEY) From d0eb9632fd987bf3029fc0c845b65ba6b661a41f Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Sun, 11 Oct 2026 01:41:28 +0000 Subject: [PATCH 24/94] selftests: net: add a test for GRO of GRE over FOU and GUE Send bulk TCP through GRE tunnels carried over UDP by FOU and by GUE, each over IPv4 and IPv6, with GRO on at the receiving veth and segmentation off at the sender so the receiver gets wire-sized packets to merge. Check that the receiver counts no UDP errors. Verified under virtme-ng: without the previous patch, all four cases fail, with the encapsulation socket counting every merged packet in UdpInErrors or Udp6InErrors. With it, they pass in a few seconds. Signed-off-by: Brad Fitzpatrick Link: https://patch.msgid.link/20261011-bradfitz-gre-fou-gro-b4-v1-2-df713aad1531@tailscale.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/Makefile | 1 + tools/testing/selftests/net/fou_gre_gro.sh | 164 +++++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100755 tools/testing/selftests/net/fou_gre_gro.sh diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile index 590b33e16d9f..55101ed41c52 100644 --- a/tools/testing/selftests/net/Makefile +++ b/tools/testing/selftests/net/Makefile @@ -42,6 +42,7 @@ TEST_PROGS := \ fib_rule_tests.sh \ fib_tests.sh \ fin_ack_lat.sh \ + fou_gre_gro.sh \ fou_mcast_encap.sh \ fq_band_pktlimit.sh \ gre_gso.sh \ diff --git a/tools/testing/selftests/net/fou_gre_gro.sh b/tools/testing/selftests/net/fou_gre_gro.sh new file mode 100755 index 000000000000..21ed50f647ae --- /dev/null +++ b/tools/testing/selftests/net/fou_gre_gro.sh @@ -0,0 +1,164 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Test that GRE packets carried over UDP (FOU or GUE) get through when the +# receiver merges them with GRO. +# +# Send bulk TCP through such tunnels with GRO on at the receiver, and +# segmentation off at the sender so the receiver gets wire-sized packets +# to merge, and check that the receiver counts no UDP errors (UdpInErrors +# or Udp6InErrors). Also check that GRO really merged packets, by comparing +# the UDP datagrams the receiver delivered with the packets its veth +# received, so that a setup where GRO doesn't engage skips rather than +# passes. + +source lib.sh + +NSEND="" +NRECV="" + +FOU_PORT=5555 +TCP_PORT=9000 +SIZE_MB=4 + +TUN_S=192.168.99.1 +TUN_R=192.168.99.2 + +cleanup() { + cleanup_all_ns +} + +trap cleanup EXIT + +# setup_tunnel creates the namespaces, the veth between them and a GRE +# tunnel over UDP encapsulation $1 (fou or gue) over IPv$2. It returns +# $ksft_skip if the kernel or iproute2 can't set it up. +setup_tunnel() { + local encap=$1 + local family=$2 + local type=gre + local addr_s=10.0.0.1 + local addr_r=10.0.0.2 + local fou_args="ipproto 47" + + if [ "$encap" = gue ]; then + fou_args=gue + fi + if [ "$family" = 6 ]; then + type=ip6gre + addr_s=2001:db8::1 + addr_r=2001:db8::2 + fou_args="$fou_args -6" + fi + + setup_ns NSEND NRECV || return "$ksft_skip" + + ip link add veth_s netns "$NSEND" type veth \ + peer name veth_r netns "$NRECV" || return "$ksft_skip" + ip -n "$NSEND" addr add 10.0.0.1/24 dev veth_s + ip -n "$NRECV" addr add 10.0.0.2/24 dev veth_r + ip -n "$NSEND" addr add 2001:db8::1/64 dev veth_s nodad + ip -n "$NRECV" addr add 2001:db8::2/64 dev veth_r nodad + ip -n "$NSEND" link set veth_s up + ip -n "$NRECV" link set veth_r up + + ip netns exec "$NRECV" ethtool -K veth_r gro on || return "$ksft_skip" + ip netns exec "$NSEND" ethtool -K veth_s tso off gso off \ + tx-gre-segmentation off tx-gre-csum-segmentation off \ + tx-udp_tnl-segmentation off tx-udp_tnl-csum-segmentation off || + return "$ksft_skip" + + # shellcheck disable=SC2086 # $fou_args is several arguments + ip netns exec "$NSEND" ip fou add port "$FOU_PORT" $fou_args || + return "$ksft_skip" + # shellcheck disable=SC2086 + ip netns exec "$NRECV" ip fou add port "$FOU_PORT" $fou_args || + return "$ksft_skip" + ip -n "$NSEND" link add gre_t type "$type" \ + local "$addr_s" remote "$addr_r" \ + encap "$encap" encap-sport auto encap-dport "$FOU_PORT" || + return "$ksft_skip" + ip -n "$NRECV" link add gre_t type "$type" \ + local "$addr_r" remote "$addr_s" \ + encap "$encap" encap-sport auto encap-dport "$FOU_PORT" || + return "$ksft_skip" + + ip -n "$NSEND" addr add "$TUN_S/30" dev gre_t + ip -n "$NRECV" addr add "$TUN_R/30" dev gre_t + ip -n "$NSEND" link set gre_t up + ip -n "$NRECV" link set gre_t up +} + +# recv_counter prints the sum of the receiver's nstat counters "$@". +recv_counter() { + ip netns exec "$NRECV" nstat -az "$@" | + awk '!/^#/ { n += $2 } END { print n + 0 }' +} + +run_test() { + local encap=$1 + local family=$2 + local name="GRO of GRE in ${encap^^} over IPv$family" + local datagrams + local packets + local errors + local pid + local rc + + RET=0 + + if ! setup_tunnel "$encap" "$family"; then + log_test_skip "$name" + cleanup_all_ns + return + fi + + ip netns exec "$NRECV" socat -u \ + TCP4-LISTEN:"$TCP_PORT",bind="$TUN_R",reuseaddr OPEN:/dev/null & + pid=$! + wait_local_port_listen "$NRECV" "$TCP_PORT" tcp + + # The timeout only bounds failing runs; passing ones take under a second. + dd if=/dev/zero bs=1M count="$SIZE_MB" 2>/dev/null | + ip netns exec "$NSEND" timeout 20 \ + socat -u STDIN TCP4:"$TUN_R":"$TCP_PORT" + rc=$? + + # Check the UDP error count rather than the duration, which depends + # on the machine. + errors=$(recv_counter UdpInErrors Udp6InErrors) + check_err "$errors" "$errors UDP errors at the receiver" + check_err "$rc" "transfer failed" + + # Without errors, a pass only means something if GRO merged packets: + # then the encapsulation socket sees far fewer datagrams than the + # veth received packets. + datagrams=$(recv_counter UdpInDatagrams Udp6InDatagrams) + packets=$(ip netns exec "$NRECV" \ + cat /sys/class/net/veth_r/statistics/rx_packets) + if [ "$RET" -eq 0 ] && [ $((datagrams * 2)) -gt "$packets" ]; then + ret_set_ksft_status "$ksft_skip" \ + "GRO didn't merge: $datagrams UDP datagrams from $packets packets" + fi + + log_test "$name" + kill "$pid" 2>/dev/null + wait "$pid" 2>/dev/null + cleanup_all_ns +} + +require_command socat +require_command ethtool +require_command nstat + +modprobe -q fou +modprobe -q ip_gre +modprobe -q fou6 +modprobe -q ip6_gre + +run_test fou 4 +run_test gue 4 +run_test fou 6 +run_test gue 6 + +exit "$EXIT_STATUS" From 07bd391eb9572e74f073c49acef327611234f9bb Mon Sep 17 00:00:00 2001 From: Alok Tiwari Date: Sun, 11 Oct 2026 05:54:59 -0700 Subject: [PATCH 25/94] octeontx2-af: Fix CQ enable state tracking after AQ writes Use the CQ context's ena field when updating the CQ enable bitmap. The current code reads rq.ena, which overlaps bit 0 of the CQ base address and can leave the bitmap out of sync with the hardware context. This can cause enabled CQs to be skipped during teardown. Use sq.ena for the SQ bitmap update as well, so each queue type reads its own context. Fixes: 557dd485eaeb ("octeontx2-af: Support for disabling NIX RQ/SQ/CQ contexts") Signed-off-by: Alok Tiwari Reviewed-by: Ratheesh Kannoth Reviewed-by: Sunil Goutham Link: https://patch.msgid.link/20261011125508.534618-1-alok.a.tiwari@oracle.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c index 153eb57bad06..95f782d47927 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c @@ -1239,7 +1239,7 @@ static int rvu_nix_blk_aq_enq_inst(struct rvu *rvu, struct nix_hw *nix_hw, __clear_bit(req->qidx, pfvf->rq_bmap); } if (req->ctype == NIX_AQ_CTYPE_SQ) { - ena = (req->rq.ena & req->sq_mask.ena) | + ena = (req->sq.ena & req->sq_mask.ena) | (test_bit(req->qidx, pfvf->sq_bmap) & ~req->sq_mask.ena); if (ena) @@ -1248,7 +1248,7 @@ static int rvu_nix_blk_aq_enq_inst(struct rvu *rvu, struct nix_hw *nix_hw, __clear_bit(req->qidx, pfvf->sq_bmap); } if (req->ctype == NIX_AQ_CTYPE_CQ) { - ena = (req->rq.ena & req->cq_mask.ena) | + ena = (req->cq.ena & req->cq_mask.ena) | (test_bit(req->qidx, pfvf->cq_bmap) & ~req->cq_mask.ena); if (ena) From 48d522b65fb0ce2bd54f8f5838d77b48afcf1f3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Sat, 22 Aug 2026 21:40:13 +0000 Subject: [PATCH 26/94] netfilter: nf_conntrack: avoid truncating IPv6 nexthdr offset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit find_prev_fhdr() returns the offset of the Next Header field before an IPv6 Fragment header through an int pointer, but stores the offset in a u8 local. An extension header chain can put this field beyond byte 255, causing the offset to wrap before it is saved in the fragment queue. Reassembly later uses the wrapped value to update the preceding Next Header field. This overwrites another byte in the IPv6 header chain and can make conntrack parse a different transport tuple from the one used by IPv6 local delivery. A two-fragment packet can, for example, be recorded as UDP destination port 53 while its payload is delivered to UDP port 9999. Use int for the working offset, matching the output argument and the other offset variables in this path. Fixes: 9fb9cbb1082d ("[NETFILTER]: Add nf_conntrack subsystem.") Assisted-by: Codex:gpt-5 Signed-off-by: Jérémy Jean Signed-off-by: Pablo Neira Ayuso --- net/ipv6/netfilter/nf_conntrack_reasm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ipv6/netfilter/nf_conntrack_reasm.c b/net/ipv6/netfilter/nf_conntrack_reasm.c index 599c49bf0a0a..be72c4346f8b 100644 --- a/net/ipv6/netfilter/nf_conntrack_reasm.c +++ b/net/ipv6/netfilter/nf_conntrack_reasm.c @@ -398,7 +398,7 @@ find_prev_fhdr(struct sk_buff *skb, u8 *prevhdrp, int *prevhoff, int *fhoff) { u8 nexthdr = ipv6_hdr(skb)->nexthdr; const int netoff = skb_network_offset(skb); - u8 prev_nhoff = netoff + offsetof(struct ipv6hdr, nexthdr); + int prev_nhoff = netoff + offsetof(struct ipv6hdr, nexthdr); int start = netoff + sizeof(struct ipv6hdr); int len = skb->len - start; u8 prevhdr = NEXTHDR_IPV6; From c2226f0c7fe90b67732e5ae36dc5c3cc01ddf888 Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Sat, 5 Sep 2026 07:01:50 +0200 Subject: [PATCH 27/94] netfilter: nft_payload: restrict checksum offsets to known values We need to prevent userspace from corrupting e.g. tcp->doff, because many locations in conntrack and conntrack helpers rely on nf_conntrack_in() having validated the packet headers. nft_payload allows to alter headers later which invalidates this assumption. The 'Fixes' commit restricts writes to safe fields, but there is another side channel: the checksum location. Restrict this too. Reported via sashiko/gemini. Fixes: 112e447d17f7 ("netfilter: validate L4 headers after userspace packet writes") Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Florian Westphal Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nft_payload.c | 40 ++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/net/netfilter/nft_payload.c b/net/netfilter/nft_payload.c index e315d35f73d4..5b76af68b704 100644 --- a/net/netfilter/nft_payload.c +++ b/net/netfilter/nft_payload.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -1008,11 +1009,14 @@ static bool nft_payload_validate_inet_csum_offset(const struct nft_ctx *ctx, if (priv->csum_flags) /* makes no sense, asks for "re-update" of L4 checksum */ return false; - /* no further check here; offset can't be negative so bogus - * offsets can corrupt L4 or payload but not l3 headers. - * We already allow arbitrary l4/inner payload writes. - */ - return true; + /* Validate csum_offset is one of the supported transport header checksums */ + if (priv->csum_offset == offsetof(struct tcphdr, check) || + priv->csum_offset == offsetof(struct udphdr, check) || + priv->csum_offset == offsetof(struct icmphdr, checksum) || + priv->csum_offset == offsetof(struct icmp6hdr, icmp6_cksum)) + return true; + + return false; case NFT_PAYLOAD_INNER_HEADER: return true; case NFT_PAYLOAD_TUN_HEADER: @@ -1046,6 +1050,27 @@ static bool nft_payload_csum_nh_write_ok(const struct nft_payload_set *priv, return false; } +static bool nft_payload_csum_th_write_ok(const struct nft_payload_set *priv, + const struct nft_pktinfo *pkt) +{ + if (!(pkt->flags & NFT_PKTINFO_L4PROTO)) + return false; + + switch (pkt->tprot) { + case IPPROTO_TCP: + return priv->csum_offset == offsetof(struct tcphdr, check); + case IPPROTO_UDP: + case IPPROTO_UDPLITE: + return priv->csum_offset == offsetof(struct udphdr, check); + case IPPROTO_ICMPV6: + return priv->csum_offset == offsetof(struct icmp6hdr, icmp6_cksum); + case IPPROTO_ICMP: + return priv->csum_offset == offsetof(struct icmphdr, checksum); + } + + return false; +} + static bool nft_payload_csum_write_ok(const struct nft_pktinfo *pkt, const struct nft_payload_set *priv) { @@ -1055,9 +1080,10 @@ static bool nft_payload_csum_write_ok(const struct nft_pktinfo *pkt, case NFT_PAYLOAD_NETWORK_HEADER: return nft_payload_csum_nh_write_ok(priv, pkt); case NFT_PAYLOAD_TRANSPORT_HEADER: + return nft_payload_csum_th_write_ok(priv, pkt); case NFT_PAYLOAD_INNER_HEADER: - /* neither offsets are validated, offsets cannot be - * negative so real l3 headers cannot be mangled. + /* offset is not validated, offset cannot be + * negative so real l3/l4 headers cannot be mangled. */ return true; case NFT_PAYLOAD_TUN_HEADER: From 3ba20137287f4f34ddbf0afb53c5d1ba03b6a8c2 Mon Sep 17 00:00:00 2001 From: Julian Anastasov Date: Mon, 7 Sep 2026 21:34:53 +0300 Subject: [PATCH 28/94] ipvs: fix more races around the overload flag Sashiko warns that we still have races when the dest overload thresholds are updated. If we narrow the range between thresholds the lockless readers (ip_vs_bind_dest and ip_vs_unbind_dest) may use old threshold values and as result to skip the required flag change. To solve the problem ip_vs_bind_dest() and ip_vs_unbind_dest() will have the chance as before to change the overload state not only at the exact threshold positions but at any conn count below the lower and above the upper thresholds. To make it work without entering the locked region for every connection, we introduce l_threshold_val. Together with u_threshold_val we will switch between two states: 1. not overloaded: where l_threshold_val is set to 0 to close the door for more ip_vs_unbind_dest() calls to clear the overload state. 2. overloaded: where u_threshold_val is set to INT_MAX to close the door for more ip_vs_bind_dest() calls to set the overload state. If a race occurs, multiple CPUs can repeat the state change but this should be for short time window. With the introduction of l_threshold_val the IP_VS_DEST_F_OVERLOAD flag and the 'flags' field are not needed anymore. We will use the ip_vs_dest_is_overloaded() helper for all places that read the overload state. Do not check IP_VS_DEST_CF_AVAILABLE in ip_vs_dh.c because we do not hold reference to dest after it is removed and rely on RCU to access the available dests. To reduce the cacheline accesses while selecting destination, keep the mostly used fields (weight and l_threshold_val) at the first read-mostly cacheline together with the hash nodes. Fixes: 8f843441c4e7 ("ipvs: properly update the overload flag on dest edit") Link: https://sashiko.dev/#/patchset/20260810190621.894119-1-pablo%40netfilter.org Signed-off-by: Julian Anastasov Signed-off-by: Pablo Neira Ayuso --- include/net/ip_vs.h | 50 ++++++++++++++++++++------------ net/netfilter/ipvs/ip_vs_conn.c | 8 ++--- net/netfilter/ipvs/ip_vs_ctl.c | 38 ++++++++++++------------ net/netfilter/ipvs/ip_vs_dh.c | 16 ++-------- net/netfilter/ipvs/ip_vs_fo.c | 2 +- net/netfilter/ipvs/ip_vs_lblc.c | 4 +-- net/netfilter/ipvs/ip_vs_lblcr.c | 8 ++--- net/netfilter/ipvs/ip_vs_lc.c | 2 +- net/netfilter/ipvs/ip_vs_mh.c | 2 +- net/netfilter/ipvs/ip_vs_nq.c | 2 +- net/netfilter/ipvs/ip_vs_ovf.c | 2 +- net/netfilter/ipvs/ip_vs_rr.c | 2 +- net/netfilter/ipvs/ip_vs_sed.c | 4 +-- net/netfilter/ipvs/ip_vs_sh.c | 2 +- net/netfilter/ipvs/ip_vs_twos.c | 4 +-- net/netfilter/ipvs/ip_vs_wlc.c | 4 +-- net/netfilter/ipvs/ip_vs_wrr.c | 2 +- 17 files changed, 76 insertions(+), 76 deletions(-) diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h index 32fde731bceb..9a83bff32569 100644 --- a/include/net/ip_vs.h +++ b/include/net/ip_vs.h @@ -36,9 +36,6 @@ #define IP_VS_HDR_INVERSE 1 #define IP_VS_HDR_ICMP 2 -/* Destination Server Flags */ -#define IP_VS_DEST_F_OVERLOAD 0x0002 /* server is overloaded */ - /* Destination Server Config Flags */ #define IP_VS_DEST_CF_AVAILABLE 0x0001 /* server is available */ @@ -973,33 +970,44 @@ struct ip_vs_dest_dst { * and so on. */ struct ip_vs_dest { + /* Cacheline for hash table nodes - read-mostly */ + struct list_head n_list; /* for the dests in the service */ struct hlist_node d_list; /* for table with all the dests */ - u16 af; /* address family */ - __be16 port; /* port number of the server */ - union nf_inet_addr addr; /* IP address of the server */ - volatile unsigned int flags; /* dest status flags */ - atomic_t conn_flags; /* flags to copy to conn */ atomic_t weight; /* server weight */ - unsigned long cflags; /* config flags */ atomic_t last_weight; /* server latest weight */ + + /* connection thresholds */ + u32 l_threshold_val;/* used lower threshold */ + u32 u_threshold_val;/* used upper threshold */ + /* 32/48 */ + u32 l_threshold; /* lower threshold */ + u32 u_threshold; /* upper threshold */ + + unsigned long cflags; /* config flags */ + + /* 44/64 */ + atomic_t conn_flags; /* flags to copy to conn */ + __u16 tun_type; /* tunnel type */ __be16 tun_port; /* tunnel port */ __u16 tun_flags; /* tunnel flags */ + u16 af; /* address family */ + __be16 port; /* port number of the server */ + /* 60/80 */ + union nf_inet_addr addr; /* IP address of the server */ + + /* connection counters */ + atomic_t totalconns; /* total connections */ + atomic_t activeconns; /* active connections */ + atomic_t persistconns; /* persistent connections */ + refcount_t refcnt; /* reference counter */ struct ip_vs_stats stats; /* statistics */ unsigned long idle_start; /* start time, jiffies */ - /* connection counters and thresholds */ - atomic_t activeconns; /* active connections */ - atomic_t totalconns; /* total connections */ - atomic_t persistconns; /* persistent connections */ - __u32 u_threshold; /* upper threshold */ - __u32 l_threshold; /* lower threshold */ - __u32 l_threshold_val;/* used lower threshold */ - /* for destination cache */ spinlock_t dst_lock; /* lock of dst_cache */ struct ip_vs_dest_dst __rcu *dest_dst; /* cached dst info */ @@ -1915,7 +1923,13 @@ static inline void ip_vs_dest_put_and_free(struct ip_vs_dest *dest) kfree(dest); } -void ip_vs_dest_update_overload(struct ip_vs_dest *dest, int mode); +void ip_vs_dest_update_overload(struct ip_vs_dest *dest, bool overload); + +/* Check if dest is in overloaded state */ +static inline bool ip_vs_dest_is_overloaded(const struct ip_vs_dest *dest) +{ + return READ_ONCE(dest->l_threshold_val); +} /* IPVS sync daemon data and function prototypes * (from ip_vs_sync.c) diff --git a/net/netfilter/ipvs/ip_vs_conn.c b/net/netfilter/ipvs/ip_vs_conn.c index cb009208826f..94f25fb7d87e 100644 --- a/net/netfilter/ipvs/ip_vs_conn.c +++ b/net/netfilter/ipvs/ip_vs_conn.c @@ -1153,8 +1153,8 @@ ip_vs_bind_dest(struct ip_vs_conn *cp, struct ip_vs_dest *dest) if (!(flags & IP_VS_CONN_F_INACTIVE)) atomic_inc(&dest->activeconns); tc = atomic_inc_return(&dest->totalconns); - if (tc == READ_ONCE(dest->u_threshold)) - ip_vs_dest_update_overload(dest, 1); + if (tc >= READ_ONCE(dest->u_threshold_val)) + ip_vs_dest_update_overload(dest, true); } else { /* It is a persistent connection/template, so increase the persistent connection counter */ @@ -1246,8 +1246,8 @@ static inline void ip_vs_unbind_dest(struct ip_vs_conn *cp) if (!(cp->flags & IP_VS_CONN_F_INACTIVE)) atomic_dec(&dest->activeconns); tc = atomic_fetch_dec(&dest->totalconns); - if (tc == READ_ONCE(dest->l_threshold_val)) - ip_vs_dest_update_overload(dest, -1); + if (tc <= READ_ONCE(dest->l_threshold_val)) + ip_vs_dest_update_overload(dest, false); } else { /* It is a persistent connection/template, so decrease the persistent connection counter */ diff --git a/net/netfilter/ipvs/ip_vs_ctl.c b/net/netfilter/ipvs/ip_vs_ctl.c index 4c1c739446b7..3003547dcabe 100644 --- a/net/netfilter/ipvs/ip_vs_ctl.c +++ b/net/netfilter/ipvs/ip_vs_ctl.c @@ -1309,32 +1309,32 @@ void ip_vs_stats_free(struct ip_vs_stats *stats) * - conns reach u_threshold and exceed it: set the flag * - conns go below l_threshold (or 75% of u_threshold): clear the flag */ -static void __ip_vs_dest_update_overload(struct ip_vs_dest *dest, int mode) +static void __ip_vs_dest_update_overload(struct ip_vs_dest *dest, bool overload) { int conns; u32 l, u; lockdep_assert_held(&dest->dst_lock); u = READ_ONCE(dest->u_threshold); - if (!u) - goto unset; - l = READ_ONCE(dest->l_threshold_val); - conns = atomic_read(&dest->totalconns); - if (conns >= (mode > 0 ? l : u)) { - dest->flags |= IP_VS_DEST_F_OVERLOAD; - return; + if (u) { + /* Low threshold defaults to 75% of upper threshold */ + l = READ_ONCE(dest->l_threshold) ? : (u - (u >> 2)); + conns = atomic_read(&dest->totalconns); + if (conns >= (overload ? l : u)) { + WRITE_ONCE(dest->u_threshold_val, INT_MAX); + WRITE_ONCE(dest->l_threshold_val, l); + return; + } } - if (conns >= (mode < 0 ? u : l)) - return; -unset: - dest->flags &= ~IP_VS_DEST_F_OVERLOAD; + WRITE_ONCE(dest->u_threshold_val, u ? : INT_MAX); + WRITE_ONCE(dest->l_threshold_val, 0); } -void ip_vs_dest_update_overload(struct ip_vs_dest *dest, int mode) +void ip_vs_dest_update_overload(struct ip_vs_dest *dest, bool overload) { spin_lock_bh(&dest->dst_lock); - __ip_vs_dest_update_overload(dest, mode); + __ip_vs_dest_update_overload(dest, overload); spin_unlock_bh(&dest->dst_lock); } @@ -1406,15 +1406,13 @@ __ip_vs_update_dest(struct ip_vs_service *svc, struct ip_vs_dest *dest, if (READ_ONCE(dest->u_threshold) != udest->u_threshold || READ_ONCE(dest->l_threshold) != udest->l_threshold) { + bool overload; + spin_lock_bh(&dest->dst_lock); WRITE_ONCE(dest->u_threshold, udest->u_threshold); WRITE_ONCE(dest->l_threshold, udest->l_threshold); - /* Low threshold defaults to 75% of upper threshold */ - WRITE_ONCE(dest->l_threshold_val, - udest->l_threshold ? : - (udest->u_threshold - - (udest->u_threshold >> 2))); - __ip_vs_dest_update_overload(dest, 0); + overload = ip_vs_dest_is_overloaded(dest); + __ip_vs_dest_update_overload(dest, overload); spin_unlock_bh(&dest->dst_lock); } diff --git a/net/netfilter/ipvs/ip_vs_dh.c b/net/netfilter/ipvs/ip_vs_dh.c index 43abed7a26a6..95e52950adbb 100644 --- a/net/netfilter/ipvs/ip_vs_dh.c +++ b/net/netfilter/ipvs/ip_vs_dh.c @@ -195,16 +195,6 @@ static int ip_vs_dh_dest_changed(struct ip_vs_service *svc, } -/* - * If the dest flags is set with IP_VS_DEST_F_OVERLOAD, - * consider that the server is overloaded here. - */ -static inline int is_overloaded(struct ip_vs_dest *dest) -{ - return dest->flags & IP_VS_DEST_F_OVERLOAD; -} - - /* * Destination hashing scheduling */ @@ -219,10 +209,8 @@ ip_vs_dh_schedule(struct ip_vs_service *svc, const struct sk_buff *skb, s = (struct ip_vs_dh_state *) svc->sched_data; dest = ip_vs_dh_get(svc->af, s, &iph->daddr); - if (!dest || - !(dest->cflags & IP_VS_DEST_CF_AVAILABLE) - || atomic_read(&dest->weight) <= 0 - || is_overloaded(dest)) { + if (!dest || atomic_read(&dest->weight) <= 0 || + ip_vs_dest_is_overloaded(dest)) { ip_vs_scheduler_err(svc, "no destination available"); return NULL; } diff --git a/net/netfilter/ipvs/ip_vs_fo.c b/net/netfilter/ipvs/ip_vs_fo.c index d657b47c6511..e07fa33f6d52 100644 --- a/net/netfilter/ipvs/ip_vs_fo.c +++ b/net/netfilter/ipvs/ip_vs_fo.c @@ -29,7 +29,7 @@ ip_vs_fo_schedule(struct ip_vs_service *svc, const struct sk_buff *skb, * Find virtual server with highest weight and send it traffic */ list_for_each_entry_rcu(dest, &svc->destinations, n_list) { - if (!(dest->flags & IP_VS_DEST_F_OVERLOAD) && + if (!ip_vs_dest_is_overloaded(dest) && atomic_read(&dest->weight) > hw) { hweight = dest; hw = atomic_read(&dest->weight); diff --git a/net/netfilter/ipvs/ip_vs_lblc.c b/net/netfilter/ipvs/ip_vs_lblc.c index 55af77a56929..e1111c3b2721 100644 --- a/net/netfilter/ipvs/ip_vs_lblc.c +++ b/net/netfilter/ipvs/ip_vs_lblc.c @@ -418,7 +418,7 @@ __ip_vs_lblc_schedule(struct ip_vs_service *svc) * new connection. */ list_for_each_entry_rcu(dest, &svc->destinations, n_list) { - if (dest->flags & IP_VS_DEST_F_OVERLOAD) + if (ip_vs_dest_is_overloaded(dest)) continue; if (atomic_read(&dest->weight) > 0) { least = dest; @@ -433,7 +433,7 @@ __ip_vs_lblc_schedule(struct ip_vs_service *svc) */ nextstage: list_for_each_entry_continue_rcu(dest, &svc->destinations, n_list) { - if (dest->flags & IP_VS_DEST_F_OVERLOAD) + if (ip_vs_dest_is_overloaded(dest)) continue; doh = ip_vs_dest_conn_overhead(dest); diff --git a/net/netfilter/ipvs/ip_vs_lblcr.c b/net/netfilter/ipvs/ip_vs_lblcr.c index 858393b1d2d1..dfb5bf83750d 100644 --- a/net/netfilter/ipvs/ip_vs_lblcr.c +++ b/net/netfilter/ipvs/ip_vs_lblcr.c @@ -166,7 +166,7 @@ static inline struct ip_vs_dest *ip_vs_dest_set_min(struct ip_vs_dest_set *set) /* select the first destination server, whose weight > 0 */ list_for_each_entry_rcu(e, &set->list, list) { least = e->dest; - if (least->flags & IP_VS_DEST_F_OVERLOAD) + if (ip_vs_dest_is_overloaded(least)) continue; if ((atomic_read(&least->weight) > 0) && @@ -181,7 +181,7 @@ static inline struct ip_vs_dest *ip_vs_dest_set_min(struct ip_vs_dest_set *set) nextstage: list_for_each_entry_continue_rcu(e, &set->list, list) { dest = e->dest; - if (dest->flags & IP_VS_DEST_F_OVERLOAD) + if (ip_vs_dest_is_overloaded(dest)) continue; doh = ip_vs_dest_conn_overhead(dest); @@ -580,7 +580,7 @@ __ip_vs_lblcr_schedule(struct ip_vs_service *svc) * new connection. */ list_for_each_entry_rcu(dest, &svc->destinations, n_list) { - if (dest->flags & IP_VS_DEST_F_OVERLOAD) + if (ip_vs_dest_is_overloaded(dest)) continue; if (atomic_read(&dest->weight) > 0) { @@ -596,7 +596,7 @@ __ip_vs_lblcr_schedule(struct ip_vs_service *svc) */ nextstage: list_for_each_entry_continue_rcu(dest, &svc->destinations, n_list) { - if (dest->flags & IP_VS_DEST_F_OVERLOAD) + if (ip_vs_dest_is_overloaded(dest)) continue; doh = ip_vs_dest_conn_overhead(dest); diff --git a/net/netfilter/ipvs/ip_vs_lc.c b/net/netfilter/ipvs/ip_vs_lc.c index 18b976a7c4d2..9002f491bff6 100644 --- a/net/netfilter/ipvs/ip_vs_lc.c +++ b/net/netfilter/ipvs/ip_vs_lc.c @@ -38,7 +38,7 @@ ip_vs_lc_schedule(struct ip_vs_service *svc, const struct sk_buff *skb, */ list_for_each_entry_rcu(dest, &svc->destinations, n_list) { - if ((dest->flags & IP_VS_DEST_F_OVERLOAD) || + if (ip_vs_dest_is_overloaded(dest) || atomic_read(&dest->weight) == 0) continue; doh = ip_vs_dest_conn_overhead(dest); diff --git a/net/netfilter/ipvs/ip_vs_mh.c b/net/netfilter/ipvs/ip_vs_mh.c index 020863047562..d70b23aec5fe 100644 --- a/net/netfilter/ipvs/ip_vs_mh.c +++ b/net/netfilter/ipvs/ip_vs_mh.c @@ -80,7 +80,7 @@ static inline void generate_hash_secret(hsiphash_key_t *hash1, static inline bool is_unavailable(struct ip_vs_dest *dest) { return atomic_read(&dest->weight) <= 0 || - dest->flags & IP_VS_DEST_F_OVERLOAD; + ip_vs_dest_is_overloaded(dest); } /* Returns hash value for IPVS MH entry */ diff --git a/net/netfilter/ipvs/ip_vs_nq.c b/net/netfilter/ipvs/ip_vs_nq.c index ada158c610ce..7cc28902086e 100644 --- a/net/netfilter/ipvs/ip_vs_nq.c +++ b/net/netfilter/ipvs/ip_vs_nq.c @@ -72,7 +72,7 @@ ip_vs_nq_schedule(struct ip_vs_service *svc, const struct sk_buff *skb, list_for_each_entry_rcu(dest, &svc->destinations, n_list) { - if (dest->flags & IP_VS_DEST_F_OVERLOAD || + if (ip_vs_dest_is_overloaded(dest) || !atomic_read(&dest->weight)) continue; diff --git a/net/netfilter/ipvs/ip_vs_ovf.c b/net/netfilter/ipvs/ip_vs_ovf.c index c5c67df80a0b..eaa3a7ae6efa 100644 --- a/net/netfilter/ipvs/ip_vs_ovf.c +++ b/net/netfilter/ipvs/ip_vs_ovf.c @@ -33,7 +33,7 @@ ip_vs_ovf_schedule(struct ip_vs_service *svc, const struct sk_buff *skb, */ list_for_each_entry_rcu(dest, &svc->destinations, n_list) { w = atomic_read(&dest->weight); - if ((dest->flags & IP_VS_DEST_F_OVERLOAD) || + if (ip_vs_dest_is_overloaded(dest) || atomic_read(&dest->activeconns) > w || w == 0) continue; diff --git a/net/netfilter/ipvs/ip_vs_rr.c b/net/netfilter/ipvs/ip_vs_rr.c index 4125ee561cdc..bd17cdd092f5 100644 --- a/net/netfilter/ipvs/ip_vs_rr.c +++ b/net/netfilter/ipvs/ip_vs_rr.c @@ -66,7 +66,7 @@ ip_vs_rr_schedule(struct ip_vs_service *svc, const struct sk_buff *skb, list_for_each_entry_continue_rcu(dest, &svc->destinations, n_list) { - if (!(dest->flags & IP_VS_DEST_F_OVERLOAD) && + if (!ip_vs_dest_is_overloaded(dest) && atomic_read(&dest->weight) > 0) /* HIT */ goto out; diff --git a/net/netfilter/ipvs/ip_vs_sed.c b/net/netfilter/ipvs/ip_vs_sed.c index 245a323c84cd..7925f4d28fce 100644 --- a/net/netfilter/ipvs/ip_vs_sed.c +++ b/net/netfilter/ipvs/ip_vs_sed.c @@ -75,7 +75,7 @@ ip_vs_sed_schedule(struct ip_vs_service *svc, const struct sk_buff *skb, */ list_for_each_entry_rcu(dest, &svc->destinations, n_list) { - if (!(dest->flags & IP_VS_DEST_F_OVERLOAD) && + if (!ip_vs_dest_is_overloaded(dest) && atomic_read(&dest->weight) > 0) { least = dest; loh = ip_vs_sed_dest_overhead(least); @@ -90,7 +90,7 @@ ip_vs_sed_schedule(struct ip_vs_service *svc, const struct sk_buff *skb, */ nextstage: list_for_each_entry_continue_rcu(dest, &svc->destinations, n_list) { - if (dest->flags & IP_VS_DEST_F_OVERLOAD) + if (ip_vs_dest_is_overloaded(dest)) continue; doh = ip_vs_sed_dest_overhead(dest); if ((__s64)loh * atomic_read(&dest->weight) > diff --git a/net/netfilter/ipvs/ip_vs_sh.c b/net/netfilter/ipvs/ip_vs_sh.c index cd67066e3b26..c24dd5cb7e47 100644 --- a/net/netfilter/ipvs/ip_vs_sh.c +++ b/net/netfilter/ipvs/ip_vs_sh.c @@ -73,7 +73,7 @@ struct ip_vs_sh_state { static inline bool is_unavailable(struct ip_vs_dest *dest) { return atomic_read(&dest->weight) <= 0 || - dest->flags & IP_VS_DEST_F_OVERLOAD; + ip_vs_dest_is_overloaded(dest); } /* diff --git a/net/netfilter/ipvs/ip_vs_twos.c b/net/netfilter/ipvs/ip_vs_twos.c index dbb7f5fd4688..86186122a1f0 100644 --- a/net/netfilter/ipvs/ip_vs_twos.c +++ b/net/netfilter/ipvs/ip_vs_twos.c @@ -52,7 +52,7 @@ static struct ip_vs_dest *ip_vs_twos_schedule(struct ip_vs_service *svc, /* Generate a random weight between [0,sum of all weights) */ list_for_each_entry_rcu(dest, &svc->destinations, n_list) { - if (!(dest->flags & IP_VS_DEST_F_OVERLOAD)) { + if (!ip_vs_dest_is_overloaded(dest)) { weight = atomic_read(&dest->weight); if (weight > 0) { total_weight += weight; @@ -75,7 +75,7 @@ static struct ip_vs_dest *ip_vs_twos_schedule(struct ip_vs_service *svc, /* Pick two weighted servers */ list_for_each_entry_rcu(dest, &svc->destinations, n_list) { - if (dest->flags & IP_VS_DEST_F_OVERLOAD) + if (ip_vs_dest_is_overloaded(dest)) continue; weight = atomic_read(&dest->weight); diff --git a/net/netfilter/ipvs/ip_vs_wlc.c b/net/netfilter/ipvs/ip_vs_wlc.c index 9da445ca09a1..08e9e22f72bf 100644 --- a/net/netfilter/ipvs/ip_vs_wlc.c +++ b/net/netfilter/ipvs/ip_vs_wlc.c @@ -47,7 +47,7 @@ ip_vs_wlc_schedule(struct ip_vs_service *svc, const struct sk_buff *skb, */ list_for_each_entry_rcu(dest, &svc->destinations, n_list) { - if (!(dest->flags & IP_VS_DEST_F_OVERLOAD) && + if (!ip_vs_dest_is_overloaded(dest) && atomic_read(&dest->weight) > 0) { least = dest; loh = ip_vs_dest_conn_overhead(least); @@ -62,7 +62,7 @@ ip_vs_wlc_schedule(struct ip_vs_service *svc, const struct sk_buff *skb, */ nextstage: list_for_each_entry_continue_rcu(dest, &svc->destinations, n_list) { - if (dest->flags & IP_VS_DEST_F_OVERLOAD) + if (ip_vs_dest_is_overloaded(dest)) continue; doh = ip_vs_dest_conn_overhead(dest); if ((__s64)loh * atomic_read(&dest->weight) > diff --git a/net/netfilter/ipvs/ip_vs_wrr.c b/net/netfilter/ipvs/ip_vs_wrr.c index 2dcff1040da5..fad69bae5a22 100644 --- a/net/netfilter/ipvs/ip_vs_wrr.c +++ b/net/netfilter/ipvs/ip_vs_wrr.c @@ -176,7 +176,7 @@ ip_vs_wrr_schedule(struct ip_vs_service *svc, const struct sk_buff *skb, list_for_each_entry_continue_rcu(dest, &svc->destinations, n_list) { - if (!(dest->flags & IP_VS_DEST_F_OVERLOAD) && + if (!ip_vs_dest_is_overloaded(dest) && atomic_read(&dest->weight) >= mark->cw) goto found; if (dest == stop) From 7f1a04254a05e14845ff3d377931f941f62e41d5 Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Tue, 22 Sep 2026 11:08:13 +0200 Subject: [PATCH 29/94] ipvs: skip committing sync_threshold when proc_dointvec fails Forward rc value and skip updating sysctl_sync_threshold when proc_dointvec fails. This targets a write such as "5 x" which would fail with -EINVAL but would store the first element anyway. Signed-off-by: Joel Granados Acked-by: Julian Anastasov Signed-off-by: Pablo Neira Ayuso --- net/netfilter/ipvs/ip_vs_ctl.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/netfilter/ipvs/ip_vs_ctl.c b/net/netfilter/ipvs/ip_vs_ctl.c index 3003547dcabe..ce9fe20d6106 100644 --- a/net/netfilter/ipvs/ip_vs_ctl.c +++ b/net/netfilter/ipvs/ip_vs_ctl.c @@ -2452,6 +2452,8 @@ proc_do_sync_threshold(const struct ctl_table *table, int write, mutex_lock(&ipvs->sync_mutex); memcpy(val, valp, sizeof(val)); rc = proc_dointvec(&tmp, write, buffer, lenp, ppos); + if (rc) + goto out; if (write) { if (val[0] < 0 || val[1] < 0 || (val[0] >= val[1] && val[1])) @@ -2459,6 +2461,7 @@ proc_do_sync_threshold(const struct ctl_table *table, int write, else memcpy(valp, val, sizeof(val)); } +out: mutex_unlock(&ipvs->sync_mutex); return rc; } From c203ecf905745d979de5df28fa657121f2e9342d Mon Sep 17 00:00:00 2001 From: Shuangfeng He Date: Fri, 2 Oct 2026 16:42:53 +0800 Subject: [PATCH 30/94] netfilter: flowtable: use the vlan id, not the full tci, in the tuple key nf_flow_tuple_encap() stores the skb vlan tag in the encap tuple id using skb_vlan_tag_get(), which returns the unmasked TCI including the 3 PCP priority bits. The flowtable entry, however, is programmed by nf_dev_path_info() with vlan_dev_vlan_id(), a plain 12-bit VID. When a NIC delivers RX vlan offload skbs with a non-zero priority (an RTL9617C EPON ONU was observed handing over vid 818 skbs with tci 0x6332, PCP 3), the runtime lookup key never matches the programmed entry, so every packet misses the flow table and software flow offload is silently defeated. Mask both parse sites to the 12-bit VID so the lookup key matches for any priority. The in-header variant has the same problem for frames received with PCP != 0 on a non-offload path. The egress side is unaffected: nf_flow_encap_push() restores tags from the programmed tuple id, which never carried priority bits. Measured on an RTL9617C ONU (PPPoE over VLAN 818 WAN, NATed 6-flow download over a 1G EPON line), before -> after: download throughput 377 Mbps -> 936 Mbps CPU0 %soft at that rate 91.5% -> 70.9% Fixes: 4cd91f7c290f ("netfilter: flowtable: add vlan support") Signed-off-by: Shuangfeng He Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_flow_table_ip.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/netfilter/nf_flow_table_ip.c b/net/netfilter/nf_flow_table_ip.c index c8c29a9a1684..2dec15d402ca 100644 --- a/net/netfilter/nf_flow_table_ip.c +++ b/net/netfilter/nf_flow_table_ip.c @@ -170,14 +170,14 @@ static void nf_flow_tuple_encap(struct nf_flowtable_ctx *ctx, int i = 0; if (skb_vlan_tag_present(skb)) { - tuple->encap[i].id = skb_vlan_tag_get(skb); + tuple->encap[i].id = skb_vlan_tag_get_id(skb); tuple->encap[i].proto = skb->vlan_proto; i++; } switch (skb->protocol) { case htons(ETH_P_8021Q): veth = (struct vlan_ethhdr *)skb_mac_header(skb); - tuple->encap[i].id = ntohs(veth->h_vlan_TCI); + tuple->encap[i].id = ntohs(veth->h_vlan_TCI) & VLAN_VID_MASK; tuple->encap[i].proto = skb->protocol; offset += VLAN_HLEN; break; From 73a73d442f9986353fe284bd1b504d0313d0f153 Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Wed, 7 Oct 2026 23:01:00 +0200 Subject: [PATCH 31/94] netfilter: nf_reject: initialize IPCB at inet ingress The inet ingress hook runs before ip_rcv_core() and ip6_rcv_core(), so neither IPCB nor IP6CB has been initialized when nft_reject_inet passes a packet to nf_send_unreach() or nf_send_unreach6(). skb->cb can therefore retain metadata from an earlier protocol layer. For IPv4, icmp_send() treats IPCB(skb)->opt as parsed IPv4 options. Stale option offsets can make __ip_options_echo() copy an attacker-controlled length into its 40-byte reply option buffer and overflow the stack. On an unpatched v7.2-rc5 kernel, the IPv4 reproducer produced: BUG: KASAN: stack-out-of-bounds in __ip_options_echo() Write of size 255 For IPv6, icmpv6_send() consumes IP6CB(skb) as parsed IPv6 metadata. It always uses iif for source selection and, when MIP6 is enabled, uses dsthao as an extension-header offset. These fields must not be stale. For NF_INET_INGRESS, clear the address-family-specific control block and restore its input interface before constructing the ICMP error. This matches similar initialization normally performed by ip_rcv_core() and ip6_rcv_core(). Fixes: 117ca1f8920c ("netfilter: nft_reject_inet: allow to use reject from inet ingress") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6-sol Codex:gpt-5.5-cyber Signed-off-by: Kyle Zeng Co-developed-by: David Lee Signed-off-by: David Lee Signed-off-by: Pablo Neira Ayuso --- net/ipv4/netfilter/nf_reject_ipv4.c | 5 +++++ net/ipv6/netfilter/nf_reject_ipv6.c | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/net/ipv4/netfilter/nf_reject_ipv4.c b/net/ipv4/netfilter/nf_reject_ipv4.c index 59ec465a9df9..7cb33af671e2 100644 --- a/net/ipv4/netfilter/nf_reject_ipv4.c +++ b/net/ipv4/netfilter/nf_reject_ipv4.c @@ -357,6 +357,11 @@ void nf_send_unreach(struct sk_buff *skb_in, int code, int hook) if (!skb_valid_dst(skb_in) && nf_reject_fill_skb_dst(skb_in) < 0) return; + if (hook == NF_INET_INGRESS) { + memset(IPCB(skb_in), 0, sizeof(*IPCB(skb_in))); + IPCB(skb_in)->iif = skb_in->skb_iif; + } + if (skb_csum_unnecessary(skb_in) || !nf_reject_verify_csum(skb_in, dataoff, proto)) { icmp_send(skb_in, ICMP_DEST_UNREACH, code, 0); diff --git a/net/ipv6/netfilter/nf_reject_ipv6.c b/net/ipv6/netfilter/nf_reject_ipv6.c index 07cdaa10da0d..5e08d8529cc8 100644 --- a/net/ipv6/netfilter/nf_reject_ipv6.c +++ b/net/ipv6/netfilter/nf_reject_ipv6.c @@ -444,6 +444,11 @@ void nf_send_unreach6(struct net *net, struct sk_buff *skb_in, if (hooknum == NF_INET_LOCAL_OUT && skb_in->dev == NULL) skb_in->dev = net->loopback_dev; + if (hooknum == NF_INET_INGRESS) { + memset(IP6CB(skb_in), 0, sizeof(*IP6CB(skb_in))); + IP6CB(skb_in)->iif = skb_in->skb_iif; + } + if (!skb_valid_dst(skb_in) && nf_reject6_fill_skb_dst(skb_in) < 0) return; From 86d47d4c84a1b8470ba3fc16df13ea2a220b74ff Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Wed, 7 Oct 2026 23:51:44 +0200 Subject: [PATCH 32/94] netfilter: nft_set_rbtree: narrow down range completion to anonymous sets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For anonymous sets, when adjacent ranges are found, the end element is not added to the set to pack the set representation. Use next start element to complete this interval. Jérémy Jean says: Consider two ranges in a netfilter verdict map: * keys 10-19 accept packets, and * keys 30-39 jump to chain 'victim'. Now, delete the end marker at 20. The lookup array now uses element 30 as the first range's end, so an INTERVAL_END query for key 15 returns element 30, including its jump verdict and chain pointer. This can make KASAN reports a UAF after the GC releases reference chain for 30 that has expired: BUG: KASAN: slab-use-after-free in nft_verdict_dump+0xdd/0x110 Read of size 8 at addr ff110000009dfb58 by task repro/50 ... Call Trace: ... nft_verdict_dump+0xdd/0x110 nft_data_dump+0x4d/0x80 nf_tables_fill_setelem+0x30b/0x8a0 ? __asan_memcpy+0x3c/0x60 nf_tables_fill_setelem_info+0x1b0/0x200 nft_get_set_elem+0x3fd/0x4b0 ... Freed by task 24: ... kfree+0x1a5/0x280 nf_tables_trans_destroy_work+0x2cb/0x520 Fixes: 2aa34191f06f ("netfilter: nft_set_rbtree: use binary search array in get command") Reported-by: Jérémy Jean Link: https://patch.msgid.link/netfilter-devel/20261005200404.3544062-2-Jeremy.Jean@oss.cyber.gouv.fr/ Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nft_set_rbtree.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/netfilter/nft_set_rbtree.c b/net/netfilter/nft_set_rbtree.c index 12431b55752f..fbe1750f1ca2 100644 --- a/net/netfilter/nft_set_rbtree.c +++ b/net/netfilter/nft_set_rbtree.c @@ -1091,7 +1091,8 @@ static void nft_rbtree_commit(struct nft_set *set) * representation. Use next start element to complete this * interval. */ - if (nft_rbtree_interval_start(rbe) && + if (nft_set_is_anonymous(set) && + nft_rbtree_interval_start(rbe) && nft_rbtree_interval_start(prev_rbe) && priv->array_next->intervals[num_intervals].from) priv->array_next->intervals[num_intervals++].to = &prev_rbe->ext; From 2edda78aa4eb04cd8f112a81897510ee0cdd7099 Mon Sep 17 00:00:00 2001 From: Sung Byeongchan Date: Fri, 9 Oct 2026 03:38:14 +0900 Subject: [PATCH 33/94] netfilter: xt_dccp: handle reserved options as single-byte The DCCP UAPI defines option numbers 3 through 31 as reserved. Like Padding and Mandatory, these options occupy one byte. dccp_find_option() only treats values below 2 as single-byte and interprets the byte after a reserved option as a TLV length. This can skip a later option and invert an XT_DCCP_OPTION rule result. A local production-hook fixture installed the same DROP rule for IPv4 and IPv6. In three fresh boots per kernel, a target-only packet was dropped 20/20 times, while adding reserved option 3 before the same target let 20/20 packets reach the protected receiver. The fixed kernel dropped all trigger packets in three fresh boots and preserved the normal controls. Treat every option up to DCCPO_MAX_RESERVED as single-byte, matching the protocol definition. Triggering requires CONFIG_NETFILTER_XTABLES_LEGACY, CONFIG_NETFILTER_XT_MATCH_DCCP, an option-based rule, and remote protocol-33 reachability. The touched source is unchanged at 0c2669a9f4a1d607e7591ae50ccf3c432a0aff08, where the patch applies and builds cleanly. Fixes: 1d3de414eb20 ("[NETFILTER]: New iptables DCCP protocol header match") Assisted-by: LLM Signed-off-by: Sung Byeongchan Signed-off-by: Pablo Neira Ayuso --- net/netfilter/xt_dccp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/netfilter/xt_dccp.c b/net/netfilter/xt_dccp.c index 3db81e041af9..23a4e3e8df7d 100644 --- a/net/netfilter/xt_dccp.c +++ b/net/netfilter/xt_dccp.c @@ -62,7 +62,7 @@ dccp_find_option(u_int8_t option, return true; } - if (op[i] < 2 || i == optlen - 1) + if (op[i] <= DCCPO_MAX_RESERVED || i == optlen - 1) i++; else i += op[i + 1] ? : 1; From 08f7882637f2db5b98f521498eeaf94770383a6e Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Mon, 19 Oct 2026 11:54:03 +0200 Subject: [PATCH 34/94] netfilter: flowtable: reset transport header after nf_flow_encap_pop() nf_flow_encap_pop() pulls vlan/pppoe or ip tunnel headers and resets the network header, but leaves the transport header pointing into the removed outer header. Reset the transport header to the inner L4 header, otherwise later consumers (e.g. GSO segmentation) parse a bogus header. Fixes: 4cd91f7c290f ("netfilter: flowtable: add vlan support") Signed-off-by: Lorenzo Bianconi Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_flow_table_ip.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/netfilter/nf_flow_table_ip.c b/net/netfilter/nf_flow_table_ip.c index 2dec15d402ca..bb894c4f2b4a 100644 --- a/net/netfilter/nf_flow_table_ip.c +++ b/net/netfilter/nf_flow_table_ip.c @@ -502,6 +502,7 @@ static int nf_flow_offload_forward(struct nf_flowtable_ctx *ctx, nf_flow_encap_pop(ctx, skb, tuplehash); thoff -= ctx->offset; + skb_set_transport_header(skb, thoff); iph = ip_hdr(skb); nf_flow_nat_ip(flow, skb, thoff, dir, iph); @@ -1097,6 +1098,7 @@ static int nf_flow_offload_ipv6_forward(struct nf_flowtable_ctx *ctx, flow_offload_refresh(flow_table, flow, false); nf_flow_encap_pop(ctx, skb, tuplehash); + skb_set_transport_header(skb, sizeof(*ip6h)); ip6h = ipv6_hdr(skb); nf_flow_nat_ipv6(flow, skb, dir, ip6h); From cd37ab22a74e05aae5c16c67295cbf5188b16268 Mon Sep 17 00:00:00 2001 From: Liu Chao Date: Mon, 19 Oct 2026 15:56:26 +0800 Subject: [PATCH 35/94] netfilter: nf_conntrack_netlink: fix inverted IPv6 tuple filter ctnetlink_filter_match_tuple() returns 0 (no match) when the IPv6 source or destination address differs from the filter, because the !ipv6_addr_cmp() test is inverted. ipv6_addr_cmp() returns 0 when the addresses are equal, so the negation makes the branch trigger on a match instead of a mismatch, which is the opposite of the IPv4 path above. Drop the '!' so the IPv6 branch returns 0 only when the addresses differ, matching the IPv4 semantics and the caller's expectation. Fixes: cb8aa9a3affb ("netfilter: ctnetlink: add kernel side filtering for dump") Cc: stable@vger.kernel.org Signed-off-by: Liu Chao Reviewed-by: Weibin Liu Reviewed-by: Florian Westphal Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_conntrack_netlink.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c index 4e5d7c701436..cc8695d5356d 100644 --- a/net/netfilter/nf_conntrack_netlink.c +++ b/net/netfilter/nf_conntrack_netlink.c @@ -1098,13 +1098,13 @@ static int ctnetlink_filter_match_tuple(struct nf_conntrack_tuple *filter_tuple, break; case NFPROTO_IPV6: if ((flags & CTA_FILTER_FLAG(CTA_IP_SRC)) && - !ipv6_addr_cmp(&filter_tuple->src.u3.in6, - &ct_tuple->src.u3.in6)) + ipv6_addr_cmp(&filter_tuple->src.u3.in6, + &ct_tuple->src.u3.in6)) return 0; if ((flags & CTA_FILTER_FLAG(CTA_IP_DST)) && - !ipv6_addr_cmp(&filter_tuple->dst.u3.in6, - &ct_tuple->dst.u3.in6)) + ipv6_addr_cmp(&filter_tuple->dst.u3.in6, + &ct_tuple->dst.u3.in6)) return 0; break; } From 78731b46967e3e7102b5cd5062bef185f3404b5c Mon Sep 17 00:00:00 2001 From: Vaibhav Nagare Date: Mon, 12 Oct 2026 12:58:02 +0530 Subject: [PATCH 36/94] bnxt_en: don't leave an XDP program installed when the open fails bnxt_xdp_set() stores the new program and drops the reference on the old one before reopening the NIC. If bnxt_open_nic() then fails, ndo_bpf() returns an error with the new program still in bp->xdp_prog. The caller treats the error as "nothing was installed" and drops its own reference, so bp->xdp_prog is left pointing at a freed program and the next XDP update dereferences it: BUG: unable to handle page fault for address: ff78ecc80ddd1038 RIP: 0010:__bpf_prog_put+0x5/0x80 Call Trace: bnxt_xdp_set+0xad/0x1b0 [bnxt_en] dev_xdp_propagate+0x36/0xa0 bond_xdp_set+0xeb/0x2d0 [bonding] dev_xdp_install+0x1b1/0x350 bpf_xdp_link_update+0xc5/0x1b0 link_update+0x104/0x1e0 __sys_bpf+0x662/0xcf0 Seen on a 6.12 based kernel after bnxt_alloc_mem() failed an order-4 allocation on a fragmented host: bnxt_en 0000:a0:00.1 ens4f1np1: nic open fail (rc: fffffff4) bond1: (slave ens4f1np1): Error -12 calling ndo_bpf Bonding is not required to hit this; a plain XDP attach on a bnxt interface takes the same path. Restore the previous program when an attach or a replace fails, and recompute the page-mode and XDP TX ring configuration from it. The configuration matters because bnxt_init_one_rx_ring() only assigns rxr->xdp_prog in page mode, and __bnxt_set_rx_skb_mode() derives dev->max_mtu from the installed program. Release the old program only once the change has been committed. A detach is not undone. dev_xdp_detach_link() clears the core's record of the program even when the driver returns an error, so putting the program back would leave the driver running a program the core has already dropped. If the reopen fails during a detach, bnxt_xdp_set() therefore returns 0 with the device closed; bnxt_open_nic() has already logged the failure. Found in a crash dump; the change was not tested on hardware. Fixes: c6d30e8391b8 ("bnxt_en: Add basic XDP support.") Cc: stable@vger.kernel.org Signed-off-by: Vaibhav Nagare Reviewed-by: Simon Horman Link: https://patch.msgid.link/20261012072802.2153706-1-vnagare@redhat.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.c | 49 +++++++++++++------ 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.c b/drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.c index 9e5009be8e98..96845c8646e0 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.c @@ -381,11 +381,31 @@ int bnxt_xdp_xmit(struct net_device *dev, int num_frames, return nxmit; } +static void bnxt_xdp_apply_cfg(struct bnxt *bp, int tx_xdp) +{ + struct net_device *dev = bp->dev; + int tc = bp->num_tc ? : 1; + + if (bp->xdp_prog) { + bnxt_set_rx_skb_mode(bp, true); + xdp_features_set_redirect_target_locked(dev, true); + } else { + xdp_features_clear_redirect_target_locked(dev); + bnxt_set_rx_skb_mode(bp, false); + } + bp->tx_nr_rings_xdp = tx_xdp; + bp->tx_nr_rings = bp->tx_nr_rings_per_tc * tc + tx_xdp; + bnxt_set_cp_rings(bp, true); + bnxt_set_tpa_flags(bp); + bnxt_set_ring_params(bp); +} + static int bnxt_xdp_set(struct bnxt *bp, struct bpf_prog *prog) { struct net_device *dev = bp->dev; int tx_xdp = 0, rc, tc; struct bpf_prog *old; + int old_tx_xdp; netdev_assert_locked(dev); @@ -418,26 +438,23 @@ static int bnxt_xdp_set(struct bnxt *bp, struct bpf_prog *prog) if (netif_running(dev)) bnxt_close_nic(bp, true, false); + old_tx_xdp = bp->tx_nr_rings_xdp; old = xchg(&bp->xdp_prog, prog); + bnxt_xdp_apply_cfg(bp, tx_xdp); + + if (netif_running(dev)) { + rc = bnxt_open_nic(bp, true, false); + /* a detach cannot be undone, only an attach */ + if (rc && prog) { + WRITE_ONCE(bp->xdp_prog, old); + bnxt_xdp_apply_cfg(bp, old_tx_xdp); + return rc; + } + } + if (old) bpf_prog_put(old); - if (prog) { - bnxt_set_rx_skb_mode(bp, true); - xdp_features_set_redirect_target_locked(dev, true); - } else { - xdp_features_clear_redirect_target_locked(dev); - bnxt_set_rx_skb_mode(bp, false); - } - bp->tx_nr_rings_xdp = tx_xdp; - bp->tx_nr_rings = bp->tx_nr_rings_per_tc * tc + tx_xdp; - bnxt_set_cp_rings(bp, true); - bnxt_set_tpa_flags(bp); - bnxt_set_ring_params(bp); - - if (netif_running(dev)) - return bnxt_open_nic(bp, true, false); - return 0; } From 5820ca58b409402505b9e904d1ded4908e0b6b81 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Sat, 10 Oct 2026 17:02:48 -0400 Subject: [PATCH 37/94] netdevsim: reject cls_bpf programs bound to another device The issue: nsim_bpf_setup_tc_block_cb() installs any device-bound SCHED_CLS program without checking that its offload device matches the target netdev. The core cls_bpf attach path rejects a mismatching program, but a netdev that joins a shared block later is replayed the stored filter by cls_bpf_reoffload() before it is inserted into block->ports, so the attach-time check cannot see it. The driver callback is the authoritative per-device acceptance boundary and must enforce the match itself, as nfp already does in nfp_net_bpf_offload(). Without the check, replaying a device-bound program onto a different bpf_offload_dev installs it into that device's netdevsim state, which trips the WARN in nsim_bpf_uninit() (ns->bpf_offloaded set on a foreign device) and can dereference a NULL prog->aux->offload in nsim_prog_set_loaded(). The Fix: Reject a device-bound program whose offload device does not match the target netdev, with -EINVAL and an extack so the rejected replay is not silent. The check is intentionally narrower than nfp's: it fires only for a dev-bound program, so nsim's bpf_tc_non_bound_accept mode (which admits an unbound program onto the device) is unaffected. Teardown is never blocked: the guard is on the program being installed and an unbind passes cls_bpf.prog = NULL. Conditions to recreate the bug: - CONFIG_NETDEVSIM=y, CONFIG_NET_CLS_BPF=y - load and pin a dev-bound SCHED_CLS program for netdevsim device B - install the program on a shared block owned by B - add an independently backed netdevsim device C to that block; the bind replays the filter to C's callback, and C's ports/0/bpf_offloaded_id takes the B-bound program id. The reported Sashiko review is at https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260809094418.901607-1-jhs@mojatatu.com Fixes: 31d3ad832948 ("netdevsim: add bpf offload support") Reported-by: Sashiko (nipa) Link: https://lore.kernel.org/netdev/20260809094418.901607-1-jhs@mojatatu.com/ Link: https://lore.kernel.org/netdev/QDISC-PYC7.v1.20261003103747@mojatatu.com/ Cc: stable@vger.kernel.org Reviewed-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Reviewed-by: Simon Horman Link: https://patch.msgid.link/QDISC-PYC7.v2.20261010165947-2@mojatatu.com Signed-off-by: Jakub Kicinski --- drivers/net/netdevsim/bpf.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/net/netdevsim/bpf.c b/drivers/net/netdevsim/bpf.c index 16aa88278398..f45c1c8f0584 100644 --- a/drivers/net/netdevsim/bpf.c +++ b/drivers/net/netdevsim/bpf.c @@ -144,6 +144,17 @@ int nsim_bpf_setup_tc_block_cb(enum tc_setup_type type, return -EOPNOTSUPP; } + /* A dev-bound program can be replayed here when a netdev joins + * a shared block, after the core attach-time check has run; + * enforce the device match as nfp does. + */ + if (prog && bpf_prog_is_dev_bound(prog->aux) && + !bpf_offload_dev_match(prog, ns->netdev)) { + NSIM_EA(cls_bpf->common.extack, + "program bound to a different device"); + return -EINVAL; + } + if (cls_bpf->command != TC_CLSBPF_OFFLOAD) return -EOPNOTSUPP; From d305547d1632a242818c5ebb430c85121a2ed434 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Sat, 10 Oct 2026 17:02:49 -0400 Subject: [PATCH 38/94] net/sched: cls_bpf: accept dev-bound programs on shared blocks The issue: Follow-up to commit 120977e2c096 ("net/sched: cls_bpf: reject dev-bound programs bound to a different device"), which derived the target device from block->q: dev = block->q ? qdisc_dev(block->q) : NULL; tcf_block_create() leaves block->q NULL for shared blocks, so a device-bound program attached through a shared block is rejected with -EINVAL even when it is bound to a netdev that owns the block. The offload path (cls_bpf_offload_cmd()) never consults block->q and drives the per-device callbacks for shared blocks as before. The Fix: Accept the attach on a shared block when bpf_offload_dev_match() matches every netdev bound to the block, and reject an empty block. Every member must match because the offload path broadcasts the program to every callback registered on the block, so an any-member check would re-admit the wrong-device attach on a mixed block. Every dev-bound SCHED_CLS attach is skip_sw, which becomes err_stop and aborts the offload on the first failing callback, so the all-members rule is the offload path's own semantics. Iterate the public block->ports xarray (913b47d3424e, v6.8-rc1) rather than block->owner_list, which is private to cls_api.c. The shared-block rejection reports "Program is not bound to every device in the block", distinct from the non-shared message, because a member can exist without a registered callback (netdevsim registers its block callback ingress-only). Context note: This patch is not an independent fix: it re-exposes the shared-block replay path that commit 120977e2c096 ("net/sched: cls_bpf: reject dev-bound programs bound to a different device") had closed, so patch 1 must be applied first, including in stable. A stable tree taking this patch alone would re-enable shared-block attaches on a kernel whose netdevsim callback is still unchecked. Conditions to recreate the bug: - CONFIG_NETDEVSIM=y, CONFIG_NET_CLS_BPF=y - load a dev-bound SCHED_CLS program for netdevsim device B (prog_ifindex=B), pin it in bpffs - tc qdisc add dev B ingress_block 22 clsact - tc filter add block 22 ingress bpf da object-pinned skip_sw Unfixed, the filter add fails with -EINVAL ("Program is bound to a different device"); fixed, it succeeds. Adding a second, independently backed netdevsim device to the same block makes the attach fail again. Fixes: 120977e2c096 ("net/sched: cls_bpf: reject dev-bound programs bound to a different device") Reported-by: Sashiko (nipa) Link: https://lore.kernel.org/netdev/20260809094418.901607-1-jhs@mojatatu.com/ Link: https://lore.kernel.org/netdev/QDISC-PYC7.v1.20261003103747@mojatatu.com/ Cc: stable@vger.kernel.org Reviewed-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Reviewed-by: Simon Horman Link: https://patch.msgid.link/QDISC-PYC7.v2.20261010165947-3@mojatatu.com Signed-off-by: Jakub Kicinski --- net/sched/cls_bpf.c | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/net/sched/cls_bpf.c b/net/sched/cls_bpf.c index 188cf0f949dd..07ad9556889d 100644 --- a/net/sched/cls_bpf.c +++ b/net/sched/cls_bpf.c @@ -392,11 +392,31 @@ static int cls_bpf_prog_from_efd(struct nlattr **tb, struct cls_bpf_prog *prog, if (bpf_prog_is_dev_bound(fp->aux)) { struct tcf_block *block = tp->chain->block; struct net_device *dev; + unsigned long ifindex; + bool match = false; - dev = block->q ? qdisc_dev(block->q) : NULL; - if (!dev || !bpf_offload_dev_match(fp, dev)) { - NL_SET_ERR_MSG(extack, - "Program is bound to a different device"); + /* A shared block has no qdisc of its own, but it may bind + * several netdevs and the program is offloaded to all of them; + * require a match with every member. + */ + if (!tcf_block_shared(block)) { + dev = qdisc_dev(tcf_block_q(block)); + match = bpf_offload_dev_match(fp, dev); + if (!match) + NL_SET_ERR_MSG(extack, + "Program is bound to a different device"); + } else { + xa_for_each(&block->ports, ifindex, dev) { + match = bpf_offload_dev_match(fp, dev); + if (!match) + break; + } + if (!match) + NL_SET_ERR_MSG(extack, + "Program is not bound to every device in the block"); + } + + if (!match) { bpf_prog_put(fp); return -EINVAL; } From 866363c3cc40147d0501a1a6dae485e13852d150 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Sat, 10 Oct 2026 17:02:50 -0400 Subject: [PATCH 39/94] selftests: net: exercise dev-bound cls_bpf on shared blocks The issue: bpf_offload.py only exercises per-device ingress qdiscs today, so neither the shared-block accept path nor the late-join replay is tested. The Fix: Add a test for a device-bound SCHED_CLS program attached through a shared block. It loads a dev-bound SCHED_CLS program for a netdevsim device and checks: - the program attaches to a shared block owned by its device, and the filter teardown leaves the device unloaded; - a block that also holds an independently backed device rejects the attach, because the offload path broadcasts the program to all members; - an independently backed device that joins a populated block is not loaded by the bind/replay path, and the driver's extack is asserted; - a second port of the same netdevsim device (same offload device) is accepted together with the bound port. The program is loaded from the existing sample_ret0.bpf.o with an explicit `type classifier` override, so no new sample object is needed. Reviewed-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Reviewed-by: Simon Horman Link: https://patch.msgid.link/QDISC-PYC7.v2.20261010165947-4@mojatatu.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/bpf_offload.py | 139 +++++++++++++++++++++ 1 file changed, 139 insertions(+) diff --git a/tools/testing/selftests/net/bpf_offload.py b/tools/testing/selftests/net/bpf_offload.py index c856d266c8f3..8c1da9b51be5 100755 --- a/tools/testing/selftests/net/bpf_offload.py +++ b/tools/testing/selftests/net/bpf_offload.py @@ -452,6 +452,38 @@ class BpfNetdevSim(NetdevSim): self.tc_add_ingress() self.wait_for_flush(bound=bound, total=total) + def tc_add_block(self, block, fail=True, include_stderr=False): + return tc("qdisc add dev %s ingress_block %d clsact" % + (self['ifname'], block), fail=fail, + include_stderr=include_stderr) + + def tc_del_block(self, block): + tc("qdisc del dev %s clsact" % (self['ifname']), fail=False) + + def tc_add_block_filter(self, block, bpf, op="add", prio=None, handle=None, + da=False, verbose=False, skip_sw=False, + skip_hw=False, fail=True, include_stderr=False): + spec = "" + if prio is not None: + spec += " prio %d" % (prio) + if handle: + spec += " handle %s" % (handle) + + params = "" + if da: + params += " da" + if verbose: + params += " verbose" + if skip_sw: + params += " skip_sw" + if skip_hw: + params += " skip_hw" + + return tc("filter {op} block {block} {spec} bpf {bpf} {params}" + .format(op=op, block=block, spec=spec, bpf=bpf, + params=params), + fail=fail, include_stderr=include_stderr) + def tc_show_ingress(self, expected=None): # No JSON support, oh well... flags = ["skip_sw", "skip_hw", "in_hw"] @@ -629,6 +661,111 @@ def test_spurios_extack(sim, obj, skip_hw, needle): include_stderr=True) check_no_extack(res, needle) +def test_tc_shared_block(): + """ + Device-bound cls_bpf program attached through a shared block. + """ + # A shared block has no qdisc, so the target device is derived from the + # block's bound netdevs and the program must match every member, since + # the offload path broadcasts it to every callback on the block. A + # netdev joining a populated block later is replayed the stored filter + # before it is inserted into block->ports, so the driver callback must + # enforce the device match as well. + simdev = BpfNetdevSimDev() + sim, = simdev.nsims + sim.set_ethtool_tc_offloads(True) + + start_test("Test TC shared-block load dev-bound program...") + bpftool_prog_load("sample_ret0.bpf.o", "/sys/fs/bpf/tc_shared", + prog_type="classifier", dev=sim['ifname']) + + start_test("Test TC shared-block dev-bound attach...") + # The pinned program is dev-bound to this device; attaching it through + # a shared block must succeed even though block->q is NULL. + sim.tc_add_block(22) + # err is kept (include_stderr) for post-mortem value on a failure. + ret, _, err = sim.tc_add_block_filter(22, bpf_pinned("/sys/fs/bpf/tc_shared"), + da=True, skip_sw=True, + fail=False, include_stderr=True) + fail(ret != 0, "dev-bound program rejected on a matching shared block") + fail(sim.dfs_read("bpf_offloaded_id") == "0", + "dev-bound program not offloaded on a matching shared block") + sim.tc_del_block(22) + + start_test("Test TC shared-block broadcast to a foreign member...") + # Add an independently backed device to the SAME block before the + # attach. The program is bound to sim's device only, and the offload + # path fans it out to every member, so the attach must be rejected. + simdev2 = BpfNetdevSimDev() + sim2, = simdev2.nsims + sim2.set_ethtool_tc_offloads(True) + sim.tc_add_block(40) + sim2.tc_add_block(40) + ret, _, err = sim.tc_add_block_filter(40, bpf_pinned("/sys/fs/bpf/tc_shared"), + da=True, skip_sw=True, + fail=False, include_stderr=True) + fail(ret == 0, "dev-bound program broadcast to a foreign shared-block member") + check_extack(err, "Program is not bound to every device in the block.", args) + sim2.tc_del_block(40) + sim.tc_del_block(40) + simdev2.remove() + + start_test("Test TC shared-block late join replay...") + # Build a block owned by sim's device with the matching program + # installed, then let an independently backed device join it. The + # join replays the stored filter to the joining device's callback + # before the device is recorded in block->ports; the driver must + # reject the replay so the foreign device is never loaded. + # sim2 is created here (not inherited) so the TC-offload state it + # depends on is explicit. + simdev2 = BpfNetdevSimDev() + sim2, = simdev2.nsims + sim2.set_ethtool_tc_offloads(True) + sim.tc_add_block(30) + ret, _, err = sim.tc_add_block_filter(30, bpf_pinned("/sys/fs/bpf/tc_shared"), + da=True, skip_sw=True, + fail=False, include_stderr=True) + fail(ret != 0, "dev-bound program rejected on a matching shared block") + ret, _, err = sim2.tc_add_block(30, fail=False, include_stderr=True) + fail(ret == 0, "foreign device joined a populated dev-bound shared block") + check_extack_nsim(err, "program bound to a different device.", args) + fail(sim2.dfs_read("bpf_offloaded_id") != "0", + "replayed program loaded onto a foreign device") + # The add above was expected to fail, so the qdisc may not exist; the + # delete is harmless and only cleans up when it does. + sim2.tc_del_block(30) + sim.tc_del_block(30) + simdev2.remove() + + start_test("Test TC shared-block same-offload-device member...") + # A second port of the same netdevsim device shares the offload device, + # so a program bound to one port may legitimately be offloaded to both. + simdev3 = BpfNetdevSimDev(2) + sim3, sim3b = simdev3.nsims + sim3.set_ethtool_tc_offloads(True) + sim3b.set_ethtool_tc_offloads(True) + bpftool_prog_load("sample_ret0.bpf.o", "/sys/fs/bpf/tc_shared_multi", + prog_type="classifier", dev=sim3['ifname']) + sim3.tc_add_block(50) + sim3b.tc_add_block(50) + # err kept for post-mortem value on a failure. + ret, _, err = sim3.tc_add_block_filter(50, bpf_pinned("/sys/fs/bpf/tc_shared_multi"), + da=True, skip_sw=True, + fail=False, include_stderr=True) + fail(ret != 0, "same-offload-device member rejected on a shared block") + fail(sim3.dfs_read("bpf_offloaded_id") == "0", + "program not offloaded to the bound port") + fail(sim3b.dfs_read("bpf_offloaded_id") == "0", + "program not offloaded to the same-offload-device port") + sim3b.tc_del_block(50) + sim3.tc_del_block(50) + + simdev3.remove() + simdev.remove() + rm("/sys/fs/bpf/tc_shared_multi") + rm("/sys/fs/bpf/tc_shared") + bpftool_prog_list_wait(expected=0) + def test_multi_prog(simdev, sim, obj, modename, modeid): start_test("Test multi-attachment XDP - %s + offload..." % (modename or "default", )) @@ -1349,6 +1486,8 @@ try: ret, out = bpftool("prog show %s" % (progB), fail=False) fail(ret != 0, "couldn't get information about orphaned program") + test_tc_shared_block() + print("%s: OK" % (os.path.basename(__file__))) finally: From f67b3ed1308d7a282e2d6202d61af052ab0da39e Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Mon, 12 Oct 2026 14:09:25 -0400 Subject: [PATCH 40/94] net/sched: act_api: reject duplicate actions in a batch tca_action_gd() builds actions[] with one tcf_action_get_1() per nested TCA_ACT_TAB entry. Each successful lookup takes its own reference to the resolved action, so repeated TCA_ACT_INDEX entries in one request yield the same pointer in multiple slots. For RTM_DELACTION, tcf_action_delete() then consumes two references per slot: one in tcf_action_put() and one in tcf_idr_delete_index(). A duplicate therefore drives the refcount to zero mid-walk -- the delete frees the action and removes its IDR slot -- and the next slot calls tcf_action_put() on the freed action: refcount_t: underflow; use-after-free. WARNING: lib/refcount.c:87 at refcount_dec_not_one refcount_dec_and_mutex_lock __tcf_action_put tca_action_gd Three duplicate entries are enough. Reject a repeated action while a delete batch is built, dropping the reference the extra lookup took, and return -EINVAL. RTM_GETACTION balances its own references and keeps accepting duplicate entries. Conditions to recreate the bug: tc actions add action gact index 100 tc actions delete action gact index 100 action gact index 100 \ action gact index 100 Fixes: 16af6067392c ("net: sched: implement reference counted action release") Reported-by: Sashiko (gemini) Link: https://sashiko.dev/#/patchset/20260824153903.4143642-1-victor@mojatatu.com Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-K1TH.v1.r3.20261012134653-1@mojatatu.com Signed-off-by: Jakub Kicinski --- net/sched/act_api.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/net/sched/act_api.c b/net/sched/act_api.c index 9eb1dec14cb1..42b5e40b52d0 100644 --- a/net/sched/act_api.c +++ b/net/sched/act_api.c @@ -2025,7 +2025,7 @@ static int tca_action_gd(struct net *net, struct nlattr *nla, struct nlmsghdr *n, u32 portid, int event, struct netlink_ext_ack *extack) { - int i, ret; + int i, j, ret; struct nlattr *tb[TCA_ACT_MAX_PRIO + 1]; struct tc_action *act; size_t attr_size = 0; @@ -2050,6 +2050,25 @@ tca_action_gd(struct net *net, struct nlattr *nla, struct nlmsghdr *n, ret = PTR_ERR(act); goto err; } + + /* A delete consumes two references per slot (tcf_action_put() + * and tcf_idr_delete_index()) but each entry takes one, so a + * repeated action would hit zero mid-walk. GET balances its + * own references and keeps accepting duplicates. + */ + if (event == RTM_DELACTION) { + for (j = 0; j < i - 1; j++) { + if (actions[j] != act) + continue; + + tcf_action_put(act); + NL_SET_ERR_MSG(extack, + "Duplicate TC action in a delete batch"); + ret = -EINVAL; + goto err; + } + } + attr_size += tcf_action_fill_size(act); actions[i - 1] = act; } From 044ad3c90d9d2df2c5cb3325ae17b4e9603a311f Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Mon, 12 Oct 2026 14:09:26 -0400 Subject: [PATCH 41/94] selftests: tc-testing: add duplicate action delete batch test tca_action_gd() takes one reference per nested TCA_ACT_TAB entry while tcf_action_delete() consumes two per slot, so three duplicate TCA_ACT_INDEX entries in one RTM_DELACTION free the action mid-walk and the next slot dereferences it. Assert a duplicate batch is rejected and the action survives. Conditions to recreate the bug: tc actions add action pass index 1 tc actions delete action gact index 1 action gact index 1 \ action gact index 1 Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-K1TH.v1.r3.20261012134653-2@mojatatu.com Signed-off-by: Jakub Kicinski --- .../tc-tests/actions/batch-delete.json | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tools/testing/selftests/tc-testing/tc-tests/actions/batch-delete.json b/tools/testing/selftests/tc-testing/tc-tests/actions/batch-delete.json index ef7ca4a6775b..7620e927ae2c 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/actions/batch-delete.json +++ b/tools/testing/selftests/tc-testing/tc-tests/actions/batch-delete.json @@ -111,5 +111,38 @@ 255 ] ] + }, + { + "id": "8135", + "name": "Reject duplicate action in a delete batch", + "category": [ + "actions", + "gact" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + [ + "$TC actions flush action gact", + 0, + 1, + 255 + ], + "$TC actions add action pass index 1" + ], + "cmdUnderTest": "$TC actions del action gact index 1 action gact index 1 action gact index 1", + "expExitCode": "255", + "verifyCmd": "$TC actions ls action gact", + "matchPattern": "total acts 1\\b.*index 1 ref 1 bind 0\\b", + "matchCount": "1", + "teardown": [ + [ + "$TC actions flush action gact", + 0, + 1, + 255 + ] + ] } ] From 305deb54c6c287e586bed92da2375ae1220e8dde Mon Sep 17 00:00:00 2001 From: Pengfei Zhang Date: Tue, 13 Oct 2026 10:47:47 +0800 Subject: [PATCH 42/94] net: stmmac: xgmac: do not treat TBU as a TX event Commit 8d07a7930434 ("net: stmmac: xgmac: Do not enable TBU interrupt") removed TBUE from the default DMA interrupt mask, but did not remove the TBU check from dwxgmac2_dma_interrupt(). TBU is set whenever a TX channel runs out of descriptors. Since TBUE is not enabled and only enabled bits are cleared, the TBU status bit is never cleared. As a result, TBU stays set on every idle TX channel, regardless of whether there is any TX traffic. With a shared interrupt, stmmac_dma_interrupt() checks all channels, so every interrupt from any source schedules TX NAPI on every idle TX channel. Only use TI to trigger TX processing. Fixes: 8d07a7930434 ("net: stmmac: xgmac: Do not enable TBU interrupt") Signed-off-by: Pengfei Zhang Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20261013024747.2688984-1-zhangfeionline@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c index ff83858ebc1f..0a6e82a155c0 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c @@ -346,7 +346,7 @@ static int dwxgmac2_dma_interrupt(struct stmmac_priv *priv, u64_stats_update_end(&stats->syncp); ret |= handle_rx; } - if (likely(intr_status & (XGMAC_TI | XGMAC_TBU))) { + if (likely(intr_status & XGMAC_TI)) { u64_stats_update_begin(&stats->syncp); u64_stats_inc(&stats->tx_normal_irq_n[chan]); u64_stats_update_end(&stats->syncp); From 679f6929a66db9f5a6611dad37545fbd705962d5 Mon Sep 17 00:00:00 2001 From: Sorah Fukumori Date: Tue, 13 Oct 2026 22:02:48 +0900 Subject: [PATCH 43/94] net: gro: don't chain linear parts larger than gso_size into frag_list skb_gro_receive() chains an skb into the frag_list of the held packet when its payload cannot be attached as page frags: when its head is not a page fragment and still holds payload, or when its head and frags would overflow the frags[] array of the last packet. Since commit 5eddb24901ee ("gro: add support of (hw)gro packets to gro stack"), that skb may itself be a GSO packet whose linear part holds several gso_size segments. skb_segment() cannot split the linear part of a frag_list member across segments, so segmenting the aggregate in software, e.g. when forwarding it to a device without NETIF_F_FRAGLIST, hits BUG_ON(skb_headlen(list_skb) > len); Fully linear GSO packets come from generic XDP. When the program does not support frags, netif_skb_check_for_xdp() linearizes any nonlinear skb with pskb_expand_head() and skb_linearize() and keeps its GSO metadata. GRO on the device runs before generic XDP, so each aggregate passed by the program carries all of its payload in a kmalloc'ed head. A tunnel that decapsulates these packets and runs GRO again through gro_cells then builds the bad frag_list. This was first seen on a router that forwards TCP received over VXLAN, with a non-frags XDP program attached in SKB mode to the underlying bond because one slave lacks native XDP: kernel BUG at net/core/skbuff.c:4917! RIP: 0010:skb_segment+0xbb6/0xf40 Call Trace: tcp_gso_segment ipv6_gso_segment skb_mac_gso_segment __skb_gso_segment validate_xmit_skb __dev_queue_xmit ip6_output ip6_sublist_rcv_finish ipv6_list_rcv netif_receive_skb_list_internal gro_complete dev_gro_receive gro_receive_skb gro_cell_poll __napi_poll net_rx_action It reproduces with three network namespaces: TCP sent over VXLAN on an IPv6 veth link to a router namespace that has an XDP_PASS program without frags support attached to the underlay veth in generic mode and GRO enabled on its VXLAN device, and forwards to a veth with tx-scatter-gather-fraglist disabled. A program with frags support in generic mode gives similar packets. skb_pp_cow_data() rebuilds the skb with up to about a page of linear data in a page_pool head_frag and keeps the GSO metadata, so once frags[] is full the next such packet is chained through frag_list with more than gso_size bytes in its head as well. Refuse the merge when the linear part to be chained exceeds the held packet's gso_size. tcp_gro_receive() then flushes the held packet and the skb starts a new GRO packet as its head, whose linear length skb_segment() handles. Fixes: 5eddb24901ee ("gro: add support of (hw)gro packets to gro stack") Cc: stable@vger.kernel.org Assisted-by: claude-opus-5-5 Signed-off-by: Sorah Fukumori Tested-by: Chase Hiltz Reviewed-by: Eric Dumazet Reviewed-by: Jason Xing Link: https://patch.msgid.link/20261013130250.3643916-1-her@sorah.jp Signed-off-by: Paolo Abeni --- net/core/gro.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/core/gro.c b/net/core/gro.c index 29b4d02bf519..ff828a3dce3c 100644 --- a/net/core/gro.c +++ b/net/core/gro.c @@ -187,6 +187,10 @@ int skb_gro_receive(struct sk_buff *p, struct sk_buff *skb) } merge: + /* skb_segment() cannot split the linear part of a frag_list member. */ + if (unlikely(headlen > offset + skb_shinfo(p)->gso_size)) + return -E2BIG; + /* sk ownership - if any - completely transferred to the aggregated packet */ skb->destructor = NULL; skb->sk = NULL; From b6ef0d965fe8ce0ee4b4dcb58b3511f83003e42d Mon Sep 17 00:00:00 2001 From: Ivan Vecera Date: Wed, 14 Oct 2026 14:14:12 +0200 Subject: [PATCH 44/94] selftests: tc-testing: use $IP in hfsc test 8c39 setup Test 8c39 brings up lo with a bare "ip" command. nsPlugin runs it as "$IP netns exec ip ...", and as tdc runs commands with an empty environment, "ip netns exec" searches only the default execvp() path (/bin:/usr/bin). On systems that install ip only in /sbin, like Fedora, the setup fails: # ./tdc.py -e 8c39 Test 8c39: HFSC classify walk still reaches leaf after lateral drift -----> prepare stage *** Could not execute: "ip link set lo up" -----> prepare stage *** Error message: "exec of "ip" failed: No such file or directory Use $IP like all other tdc tests do. Fixes: 1e24c4f2ee44 ("selftests: tc-testing: add a lateral-drift hfsc classify-walk test") Signed-off-by: Ivan Vecera Reviewed-by: Victor Nogueira Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20261014121414.2373985-1-ivecera@redhat.com Signed-off-by: Paolo Abeni --- tools/testing/selftests/tc-testing/tc-tests/qdiscs/hfsc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/hfsc.json b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/hfsc.json index 4f6bbb8b57f9..557730eef055 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/hfsc.json +++ b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/hfsc.json @@ -181,7 +181,7 @@ "requires": "nsPlugin" }, "setup": [ - "ip link set lo up", + "$IP link set lo up", "$TC qdisc add dev lo handle 1: root hfsc default 30", "$TC class add dev lo parent 1: classid 1:1 hfsc rt m2 100kbit", "$TC class add dev lo parent 1:1 classid 1:10 hfsc rt m2 50kbit", From d06ae66516e1aaf023808912af6292aef09d46c3 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Tue, 13 Oct 2026 17:05:00 +0800 Subject: [PATCH 45/94] l2tp: snapshot sequencing state when building transmit headers Updates to send_seq from netlink, PPP setsockopt and receive-side negotiation race with packet transmission. The header length is updated separately, while transmit reads both fields repeatedly without synchronization. For example, transmit can push a ten-byte L2TPv2 header, then a concurrent writer clears send_seq before the header builder reads it. The builder writes only six bytes, leaving four bytes of uninitialized skb headroom in the outgoing packet. The reverse mismatch overwrites the beginning of the payload. A later hdr_len read can also make the UDP length disagree with the skb length. KCSAN reported: BUG: KCSAN: data-race in l2tp_session_set_header_len / l2tp_xmit_skb write to 0xffff888100d0944a of 2 bytes by task 116 on cpu 1: l2tp_session_set_header_len+0x81/0xc0 l2tp_nl_cmd_session_modify+0x122/0x2c0 genl_family_rcv_msg_doit+0x170/0x1d0 read to 0xffff888100d0944a of 2 bytes by task 115 on cpu 0: l2tp_xmit_skb+0x9c/0xc80 pppol2tp_sendmsg+0x21c/0x2a0 __sys_sendto+0x28c/0x2a0 The bug can lead to leak kernel base address for KASLR bypass, the corresponding reproducer can be provided per request. Initialize hdr_len to the maximum header size and leave it unchanged when sequencing is toggled. Snapshot send_seq once per packet and use that snapshot for the actual header length and header construction, including the sequence flag. Use the same length for headroom, the header push and the UDP length. PPP callers can safely reserve the maximum size; L2TPv3 header sizes do not depend on sequencing. Make send_seq a separate bool so READ_ONCE() and WRITE_ONCE() can be used without sharing a bitfield storage unit with other configuration flags. Preserve the netlink attribute's existing low-bit truncation. Fixes: 3557baabf280 ("[L2TP]: PPP over L2TP driver core") Cc: stable@vger.kernel.org Assisted-by: GPT-6 Astra Signed-off-by: Chengfeng Ye Link: https://patch.msgid.link/20261013090500.1180376-1-nicoyip.dev@gmail.com Signed-off-by: Paolo Abeni --- net/l2tp/l2tp_core.c | 51 +++++++++++++++++++---------------------- net/l2tp/l2tp_core.h | 6 ++--- net/l2tp/l2tp_debugfs.c | 4 ++-- net/l2tp/l2tp_netlink.c | 11 ++++----- net/l2tp/l2tp_ppp.c | 8 +++---- 5 files changed, 35 insertions(+), 45 deletions(-) diff --git a/net/l2tp/l2tp_core.c b/net/l2tp/l2tp_core.c index 4712cc41881a..7e042a3e648c 100644 --- a/net/l2tp/l2tp_core.c +++ b/net/l2tp/l2tp_core.c @@ -915,11 +915,9 @@ void l2tp_recv_common(struct l2tp_session *session, struct sk_buff *skb, * check if we sre sending sequence numbers and if not, * configure it so. */ - if (!session->lns_mode && !session->send_seq) { + if (!session->lns_mode && !READ_ONCE(session->send_seq)) { trace_session_seqnum_lns_enable(session); - session->send_seq = 1; - l2tp_session_set_header_len(session, tunnel->version, - tunnel->encap); + WRITE_ONCE(session->send_seq, true); } } else { /* No sequence numbers. @@ -937,12 +935,10 @@ void l2tp_recv_common(struct l2tp_session *session, struct sk_buff *skb, * If we're the LNS and we're sending sequence numbers, the * LAC is broken. Discard the frame. */ - if (!session->lns_mode && session->send_seq) { + if (!session->lns_mode && READ_ONCE(session->send_seq)) { trace_session_seqnum_lns_disable(session); - session->send_seq = 0; - l2tp_session_set_header_len(session, tunnel->version, - tunnel->encap); - } else if (session->send_seq) { + WRITE_ONCE(session->send_seq, false); + } else if (READ_ONCE(session->send_seq)) { pr_debug_ratelimited("%s: recv data has no seq numbers when required. Discarding.\n", session->name); atomic_long_inc(&session->stats.rx_seq_discards); @@ -1138,7 +1134,8 @@ static void l2tp_udp_encap_err_recv(struct sock *sk, struct sk_buff *skb, int er /* Build an L2TP header for the session into the buffer provided. */ -static int l2tp_build_l2tpv2_header(struct l2tp_session *session, void *buf) +static int l2tp_build_l2tpv2_header(struct l2tp_session *session, void *buf, + bool send_seq) { struct l2tp_tunnel *tunnel = session->tunnel; __be16 *bufp = buf; @@ -1147,14 +1144,14 @@ static int l2tp_build_l2tpv2_header(struct l2tp_session *session, void *buf) u32 tunnel_id = tunnel->peer_tunnel_id; u32 session_id = session->peer_session_id; - if (session->send_seq) + if (send_seq) flags |= L2TP_HDRFLAG_S; /* Setup L2TP header. */ *bufp++ = htons(flags); *bufp++ = htons(tunnel_id); *bufp++ = htons(session_id); - if (session->send_seq) { + if (send_seq) { *bufp++ = htons(session->ns); *bufp++ = 0; session->ns++; @@ -1165,7 +1162,8 @@ static int l2tp_build_l2tpv2_header(struct l2tp_session *session, void *buf) return bufp - optr; } -static int l2tp_build_l2tpv3_header(struct l2tp_session *session, void *buf) +static int l2tp_build_l2tpv3_header(struct l2tp_session *session, void *buf, + bool send_seq) { struct l2tp_tunnel *tunnel = session->tunnel; char *bufp = buf; @@ -1191,7 +1189,7 @@ static int l2tp_build_l2tpv3_header(struct l2tp_session *session, void *buf) if (session->l2specific_type == L2TP_L2SPECTYPE_DEFAULT) { u32 l2h = 0; - if (session->send_seq) { + if (send_seq) { l2h = 0x40000000 | session->ns; session->ns++; session->ns &= 0xffffff; @@ -1225,19 +1223,24 @@ static int l2tp_xmit_queue(struct l2tp_tunnel *tunnel, struct sk_buff *skb, stru static int l2tp_xmit_core(struct l2tp_session *session, struct sk_buff *skb, unsigned int *len) { struct l2tp_tunnel *tunnel = session->tunnel; + bool send_seq = READ_ONCE(session->send_seq); unsigned int data_len = skb->len; struct sock *sk = tunnel->sock; + u16 hdr_len = session->hdr_len; int headroom, uhlen, udp_len; int ret = NET_XMIT_SUCCESS; struct inet_sock *inet; struct udphdr *uh; + if (tunnel->version == L2TP_HDR_VER_2 && !send_seq) + hdr_len -= 4; + /* Check that there's enough headroom in the skb to insert IP, * UDP and L2TP headers. If not enough, expand it to * make room. Adjust truesize. */ uhlen = (tunnel->encap == L2TP_ENCAPTYPE_UDP) ? sizeof(*uh) : 0; - headroom = NET_SKB_PAD + sizeof(struct iphdr) + uhlen + session->hdr_len; + headroom = NET_SKB_PAD + sizeof(struct iphdr) + uhlen + hdr_len; if (skb_cow_head(skb, headroom)) { kfree_skb(skb); return NET_XMIT_DROP; @@ -1245,9 +1248,9 @@ static int l2tp_xmit_core(struct l2tp_session *session, struct sk_buff *skb, uns /* Setup L2TP header */ if (tunnel->version == L2TP_HDR_VER_2) - l2tp_build_l2tpv2_header(session, __skb_push(skb, session->hdr_len)); + l2tp_build_l2tpv2_header(session, __skb_push(skb, hdr_len), send_seq); else - l2tp_build_l2tpv3_header(session, __skb_push(skb, session->hdr_len)); + l2tp_build_l2tpv3_header(session, __skb_push(skb, hdr_len), send_seq); /* Reset control buffer */ memset(skb->cb, 0, sizeof(skb->cb)); @@ -1290,7 +1293,7 @@ static int l2tp_xmit_core(struct l2tp_session *session, struct sk_buff *skb, uns uh = udp_hdr(skb); uh->source = inet->inet_sport; uh->dest = inet->inet_dport; - udp_len = uhlen + session->hdr_len + data_len; + udp_len = uhlen + hdr_len + data_len; if (udp_len > U16_MAX) { kfree_skb(skb); ret = NET_XMIT_DROP; @@ -1759,16 +1762,11 @@ static void l2tp_session_del_work(struct work_struct *work) l2tp_session_put(session); } -/* We come here whenever a session's send_seq, cookie_len or - * l2specific_type parameters are set. - */ -void l2tp_session_set_header_len(struct l2tp_session *session, int version, - enum l2tp_encap_type encap) +static void l2tp_session_set_header_len(struct l2tp_session *session, int version, + enum l2tp_encap_type encap) { if (version == L2TP_HDR_VER_2) { - session->hdr_len = 6; - if (session->send_seq) - session->hdr_len += 4; + session->hdr_len = 10; } else { session->hdr_len = 4 + session->cookie_len; session->hdr_len += l2tp_get_l2specific_len(session); @@ -1776,7 +1774,6 @@ void l2tp_session_set_header_len(struct l2tp_session *session, int version, session->hdr_len += 4; } } -EXPORT_SYMBOL_GPL(l2tp_session_set_header_len); struct l2tp_session *l2tp_session_create(int priv_size, struct l2tp_tunnel *tunnel, u32 session_id, u32 peer_session_id, struct l2tp_session_cfg *cfg) diff --git a/net/l2tp/l2tp_core.h b/net/l2tp/l2tp_core.h index ffd8ced3a51f..25ae4c25d3ef 100644 --- a/net/l2tp/l2tp_core.h +++ b/net/l2tp/l2tp_core.h @@ -76,7 +76,7 @@ struct l2tp_session { u8 peer_cookie[8]; int peer_cookie_len; u16 l2specific_type; - u16 hdr_len; + u16 hdr_len; /* maximum L2TP header length */ u32 nr; /* session NR state (receive) */ u32 ns; /* session NR state (send) */ struct sk_buff_head reorder_q; /* receive reorder queue */ @@ -94,8 +94,8 @@ struct l2tp_session { char name[L2TP_SESSION_NAME_MAX]; /* for logging */ char ifname[IFNAMSIZ]; + bool send_seq; /* send packets with sequence numbers? */ unsigned int recv_seq:1; /* expect receive packets with sequence numbers? */ - unsigned int send_seq:1; /* send packets with sequence numbers? */ unsigned int lns_mode:1; /* behave as LNS? * LAC enables sequence numbers under LNS control. */ @@ -257,8 +257,6 @@ void l2tp_recv_common(struct l2tp_session *session, struct sk_buff *skb, int l2tp_udp_encap_recv(struct sock *sk, struct sk_buff *skb); /* Transmit path helpers for sending packets over the tunnel socket. */ -void l2tp_session_set_header_len(struct l2tp_session *session, int version, - enum l2tp_encap_type encap); int l2tp_xmit_skb(struct l2tp_session *session, struct sk_buff *skb); /* Pseudowire management. diff --git a/net/l2tp/l2tp_debugfs.c b/net/l2tp/l2tp_debugfs.c index c12d5e1ee550..58d1d6a7768e 100644 --- a/net/l2tp/l2tp_debugfs.c +++ b/net/l2tp/l2tp_debugfs.c @@ -182,12 +182,12 @@ static void l2tp_dfs_seq_session_show(struct seq_file *m, void *v) session->pwtype == L2TP_PWTYPE_ETH ? "ETH" : session->pwtype == L2TP_PWTYPE_PPP ? "PPP" : ""); - if (session->send_seq || session->recv_seq) + if (READ_ONCE(session->send_seq) || session->recv_seq) seq_printf(m, " nr %u, ns %u\n", session->nr, session->ns); seq_printf(m, " refcnt %d\n", refcount_read(&session->ref_count)); seq_printf(m, " config 0/0/%c/%c/-/%s %08x %u\n", session->recv_seq ? 'R' : '-', - session->send_seq ? 'S' : '-', + READ_ONCE(session->send_seq) ? 'S' : '-', session->lns_mode ? "LNS" : "LAC", 0, jiffies_to_msecs(session->reorder_timeout)); diff --git a/net/l2tp/l2tp_netlink.c b/net/l2tp/l2tp_netlink.c index 38aac59d052c..89ef04c78787 100644 --- a/net/l2tp/l2tp_netlink.c +++ b/net/l2tp/l2tp_netlink.c @@ -700,12 +700,9 @@ static int l2tp_nl_cmd_session_modify(struct sk_buff *skb, struct genl_info *inf if (info->attrs[L2TP_ATTR_RECV_SEQ]) session->recv_seq = nla_get_u8(info->attrs[L2TP_ATTR_RECV_SEQ]); - if (info->attrs[L2TP_ATTR_SEND_SEQ]) { - struct l2tp_tunnel *tunnel = session->tunnel; - - session->send_seq = nla_get_u8(info->attrs[L2TP_ATTR_SEND_SEQ]); - l2tp_session_set_header_len(session, tunnel->version, tunnel->encap); - } + if (info->attrs[L2TP_ATTR_SEND_SEQ]) + WRITE_ONCE(session->send_seq, + nla_get_u8(info->attrs[L2TP_ATTR_SEND_SEQ]) & 1); if (info->attrs[L2TP_ATTR_LNS_MODE]) session->lns_mode = nla_get_u8(info->attrs[L2TP_ATTR_LNS_MODE]); @@ -748,7 +745,7 @@ static int l2tp_nl_session_send(struct sk_buff *skb, u32 portid, u32 seq, int fl (session->peer_cookie_len && nla_put(skb, L2TP_ATTR_PEER_COOKIE, session->peer_cookie_len, session->peer_cookie)) || nla_put_u8(skb, L2TP_ATTR_RECV_SEQ, session->recv_seq) || - nla_put_u8(skb, L2TP_ATTR_SEND_SEQ, session->send_seq) || + nla_put_u8(skb, L2TP_ATTR_SEND_SEQ, READ_ONCE(session->send_seq)) || nla_put_u8(skb, L2TP_ATTR_LNS_MODE, session->lns_mode) || (l2tp_tunnel_uses_xfrm(tunnel) && nla_put_u8(skb, L2TP_ATTR_USING_IPSEC, 1)) || diff --git a/net/l2tp/l2tp_ppp.c b/net/l2tp/l2tp_ppp.c index ef8fdfaf051d..4f311d473a64 100644 --- a/net/l2tp/l2tp_ppp.c +++ b/net/l2tp/l2tp_ppp.c @@ -1195,9 +1195,7 @@ static int pppol2tp_session_setsockopt(struct sock *sk, err = -EINVAL; break; } - session->send_seq = !!val; - l2tp_session_set_header_len(session, session->tunnel->version, - session->tunnel->encap); + WRITE_ONCE(session->send_seq, !!val); break; case PPPOL2TP_SO_LNSMODE: @@ -1307,7 +1305,7 @@ static int pppol2tp_session_getsockopt(struct sock *sk, break; case PPPOL2TP_SO_SENDSEQ: - *val = session->send_seq; + *val = READ_ONCE(session->send_seq); break; case PPPOL2TP_SO_LNSMODE: @@ -1546,7 +1544,7 @@ static void pppol2tp_seq_session_show(struct seq_file *m, void *v) state, user_data_ok); seq_printf(m, " 0/0/%c/%c/%s %08x %u\n", session->recv_seq ? 'R' : '-', - session->send_seq ? 'S' : '-', + READ_ONCE(session->send_seq) ? 'S' : '-', session->lns_mode ? "LNS" : "LAC", 0, jiffies_to_msecs(session->reorder_timeout)); From 42a91cbfc7a3ab99d05cb2ed0de5a24b9652cfed Mon Sep 17 00:00:00 2001 From: Yongzhao Chen Date: Tue, 13 Oct 2026 21:03:39 +0200 Subject: [PATCH 46/94] net: phy: qca83xx: read resolved QCA8337 link status After a QCA8337 SmartSpeed downshift, genphy_read_status() can report 1000 Mbit/s from the advertised modes when the PHY is running at 100 Mbit/s. On a Redmi AX5400 running downstream OpenWrt Linux 6.18.52, this caused qca8k to program the user-port MAC for 1000 Mbit/s and traffic failed. An earlier prototype that read the resolved speed from the PHY restored traffic. Wrap genphy_read_status() and use at803x_read_specific_status() to replace its speed and duplex result when an autonegotiated link comes up. Resolve pause for the resulting duplex. Until the PHY reports a resolved speed and duplex, report the link as down so that the next poll reads them again, as marvell_read_status_page_an() does. In that case also clear the duplex, master/slave state and link partner modes that genphy_read_status() read for an up link. QCA8337's register layout matches the helper (data sheet 80-Y0619-3 Rev. D, pages 327-328). Keep the generic master/slave read order and the early return for a steady autonegotiated link. This avoids additional reads of the clear-on-read idle error count in MII_STAT1000. Forced mode continues to use BMCR. Report the MDI-X state read at link-up and invalidate it when the link goes down or autonegotiation is disabled. Clear mdix_ctrl because this driver does not implement MDI-X configuration. The QCA8327 entries keep genphy_read_status(). The AR8327 data sheet (MKG-15791 Rev. 1.0) lists the QCA8327-A PHY ID and documents the same PHY-specific status layout, with SmartSpeed enabled after a hardware reset, so those PHYs may have the same problem. That is untested on QCA8327 hardware, so limit this change to QCA8337. Fixes: b3591c2a3661 ("net: dsa: qca8k: Switch to PHYLINK instead of PHYLIB") Suggested-by: Andrew Lunn Signed-off-by: Yongzhao Chen Assisted-by: LLM Link: https://patch.msgid.link/20261013190339.51909-1-yongzhao.derek@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/phy/qcom/qca83xx.c | 58 ++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/drivers/net/phy/qcom/qca83xx.c b/drivers/net/phy/qcom/qca83xx.c index bc70ed8efd86..2c4d085bacdb 100644 --- a/drivers/net/phy/qcom/qca83xx.c +++ b/drivers/net/phy/qcom/qca83xx.c @@ -92,6 +92,63 @@ static int qca83xx_probe(struct phy_device *phydev) return 0; } +static int qca8337_read_status(struct phy_device *phydev) +{ + struct at803x_ss_mask ss_mask = { + .speed_mask = AT803X_SS_SPEED_MASK, + .speed_shift = __bf_shf(AT803X_SS_SPEED_MASK), + }; + int ret, old_link = phydev->link; + + ret = genphy_read_status(phydev); + if (ret) + return ret; + + /* Keep the state resolved at link-up while the link stays up */ + if (phydev->autoneg == AUTONEG_ENABLE && old_link && phydev->link) + goto out; + + phydev->mdix = ETH_TP_MDI_INVALID; + + /* genphy_read_status() derives speed and duplex from the + * advertisements, which is wrong after a SmartSpeed downshift. + * Use what the PHY resolved instead. + */ + if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete && + phydev->link) { + phydev->speed = SPEED_UNKNOWN; + phydev->duplex = DUPLEX_UNKNOWN; + phydev->pause = false; + phydev->asym_pause = false; + + ret = at803x_read_specific_status(phydev, ss_mask); + if (ret) + return ret; + + /* Report the link down until speed and duplex are resolved, + * so that the next poll reads them again. Also clear what + * genphy_read_status() read for an up link, as the + * PHY_HALTED path of the state machine does. + */ + if (phydev->speed == SPEED_UNKNOWN || + phydev->duplex == DUPLEX_UNKNOWN) { + phydev->link = 0; + phydev->duplex = DUPLEX_UNKNOWN; + phydev->mdix = ETH_TP_MDI_INVALID; + phydev->master_slave_state = MASTER_SLAVE_STATE_UNKNOWN; + linkmode_zero(phydev->lp_advertising); + } else { + phy_resolve_aneg_pause(phydev); + } + } + +out: + /* MDI-X configuration is not implemented */ + phydev->mdix_ctrl = ETH_TP_MDI_INVALID; + + return 0; +} + static int qca83xx_config_init(struct phy_device *phydev) { u8 switch_revision; @@ -220,6 +277,7 @@ static struct phy_driver qca83xx_driver[] = { .flags = PHY_IS_INTERNAL, .config_init = qca83xx_config_init, .soft_reset = genphy_soft_reset, + .read_status = qca8337_read_status, .get_sset_count = qca83xx_get_sset_count, .get_strings = qca83xx_get_strings, .get_stats = qca83xx_get_stats, From fece8420e76fe53ee84f0439f18d026b4c7641da Mon Sep 17 00:00:00 2001 From: James Hilliard Date: Wed, 14 Oct 2026 12:33:48 -0600 Subject: [PATCH 47/94] xsk: freeze deferred pool teardown without blocking unregister MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deferred pool destruction calls ndo_bpf() under RTNL. system_wq is not frozen during system sleep, so that callback can run after a device has suspended and gated its clocks. Use system_freezable_wq so running destruction finishes before device suspend and new work waits until process thaw. Keep assigned pools visible to NETDEV_UNREGISTER independently of the socket list. A released socket has already left that list, but its final pool put can queue destruction after workqueues freeze. A resume-time unregister would then wait for a device reference whose release cannot run until the resume completes. Track assigned pools per netdev with netdev operations locking and detach remaining pools after the notifier socket walk, including copy-mode pools. This also covers leased queues without scanning pools from unrelated devices or network namespaces. Remove the entry on assignment failure and normal teardown. The deferred worker still owns the pool and later observes the cleared device pointer, avoiding a second driver detach or put. The lifetime problem was identified by code inspection of the deferred release and system-sleep paths. Fixes: 1c1efc2af158 ("xsk: Create and free buffer pool independently from umem") Signed-off-by: James Hilliard Reviewed-by: Björn Töpel Reviewed-by: Jason Xing Acked-by: Stanislav Fomichev Link: https://patch.msgid.link/20261014-xsk-suspend-teardown-v4-1-27795582e4a3@gmail.com Signed-off-by: Paolo Abeni --- include/linux/netdevice.h | 5 ++++ include/net/xsk_buff_pool.h | 3 +++ net/xdp/xsk.c | 5 ++++ net/xdp/xsk_buff_pool.c | 46 ++++++++++++++++++++++++++++++------- 4 files changed, 51 insertions(+), 8 deletions(-) diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 3cff2174dc03..ac257221c7c5 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -2545,6 +2545,11 @@ struct net_device { /* protected by rtnl_lock */ struct bpf_xdp_entity xdp_state[__MAX_XDP_MODE]; +#ifdef CONFIG_XDP_SOCKETS + /** @xsk_pools: assigned AF_XDP pools, ops protected */ + struct hlist_head xsk_pools; +#endif + u8 dev_addr_shadow[MAX_ADDR_LEN]; netdevice_tracker linkwatch_dev_tracker; netdevice_tracker watchdog_dev_tracker; diff --git a/include/net/xsk_buff_pool.h b/include/net/xsk_buff_pool.h index a7df573784fd..02e89350faf9 100644 --- a/include/net/xsk_buff_pool.h +++ b/include/net/xsk_buff_pool.h @@ -48,6 +48,8 @@ struct xsk_buff_pool { struct device *dev; struct net_device *netdev; struct list_head xsk_tx_list; + /* Assigned pools, including deferred releases; ops protected. */ + struct hlist_node dev_node; /* Protects modifications to the xsk_tx_list */ spinlock_t xsk_tx_list_lock; refcount_t users; @@ -119,6 +121,7 @@ bool xp_put_pool(struct xsk_buff_pool *pool); void xp_clear_dev(struct xsk_buff_pool *pool); void xp_add_xsk(struct xsk_buff_pool *pool, struct xdp_sock *xs); void xp_del_xsk(struct xsk_buff_pool *pool, struct xdp_sock *xs); +void xp_clear_dev_all(struct net_device *dev); /* AF_XDP, and XDP core. */ void xp_free(struct xdp_buff_xsk *xskb); diff --git a/net/xdp/xsk.c b/net/xdp/xsk.c index 33475b180ea6..781ba46eb152 100644 --- a/net/xdp/xsk.c +++ b/net/xdp/xsk.c @@ -2145,6 +2145,11 @@ static int xsk_notifier(struct notifier_block *this, mutex_unlock(&xs->mutex); } mutex_unlock(&net->xdp.lock); + /* + * A released socket is no longer on xdp.list. Its pool can still + * hold a device reference on the frozen release workqueue. + */ + xp_clear_dev_all(dev); break; } return NOTIFY_DONE; diff --git a/net/xdp/xsk_buff_pool.c b/net/xdp/xsk_buff_pool.c index 9d2d94f1fb75..0ebd4d46f7ab 100644 --- a/net/xdp/xsk_buff_pool.c +++ b/net/xdp/xsk_buff_pool.c @@ -172,7 +172,7 @@ int xp_assign_dev(struct xsk_buff_pool *pool, u32 frame_size; int err = 0; - ASSERT_RTNL(); + netdev_assert_locked_ops_compat(netdev); force_zc = flags & XDP_ZEROCOPY; force_copy = flags & XDP_COPY; @@ -204,6 +204,11 @@ int xp_assign_dev(struct xsk_buff_pool *pool, pool->cached_need_wakeup = XDP_WAKEUP_TX; dev_hold(netdev); + /* + * Socket removal precedes the final pool put. Keep the pool visible to + * NETDEV_UNREGISTER even while release work is waiting for process thaw. + */ + hlist_add_head(&pool->dev_node, &netdev->xsk_pools); if (force_copy) /* For copy-mode, we are done. */ @@ -244,7 +249,6 @@ int xp_assign_dev(struct xsk_buff_pool *pool, bpf.xsk.pool = pool; bpf.xsk.queue_id = queue_id; - netdev_assert_locked_ops_compat(netdev); err = netdev->netdev_ops->ndo_bpf(netdev, &bpf); if (err) goto err_unreg_pool; @@ -265,6 +269,8 @@ int xp_assign_dev(struct xsk_buff_pool *pool, err = 0; /* fallback to copy mode */ if (err) { xsk_clear_pool_at_qid(netdev, queue_id); + hlist_del(&pool->dev_node); + pool->netdev = NULL; dev_put(netdev); } return err; @@ -287,19 +293,39 @@ int xp_assign_dev_shared(struct xsk_buff_pool *pool, struct xdp_sock *umem_xs, return xp_assign_dev(pool, dev, queue_id, flags); } +static void __xp_clear_dev(struct xsk_buff_pool *pool) +{ + struct net_device *netdev = pool->netdev; + + netdev_assert_locked_ops_compat(netdev); + xp_disable_drv_zc(pool); + xsk_clear_pool_at_qid(netdev, pool->queue_id); + hlist_del(&pool->dev_node); + pool->netdev = NULL; + dev_put(netdev); +} + void xp_clear_dev(struct xsk_buff_pool *pool) { struct net_device *netdev = pool->netdev; - if (!pool->netdev) + if (!netdev) return; netdev_lock_ops(netdev); - xp_disable_drv_zc(pool); - xsk_clear_pool_at_qid(pool->netdev, pool->queue_id); - pool->netdev = NULL; + __xp_clear_dev(pool); netdev_unlock_ops(netdev); - dev_put(netdev); +} + +void xp_clear_dev_all(struct net_device *dev) +{ + struct xsk_buff_pool *pool; + struct hlist_node *next; + + netdev_lock_ops(dev); + hlist_for_each_entry_safe(pool, next, &dev->xsk_pools, dev_node) + __xp_clear_dev(pool); + netdev_unlock_ops(dev); } static void xp_release_deferred(struct work_struct *work) @@ -337,7 +363,11 @@ bool xp_put_pool(struct xsk_buff_pool *pool) if (refcount_dec_and_test(&pool->users)) { INIT_WORK(&pool->work, xp_release_deferred); - schedule_work(&pool->work); + /* + * Teardown calls ndo_bpf(), which may need powered hardware. + * RTNL alone does not exclude the device's system PM callbacks. + */ + queue_work(system_freezable_wq, &pool->work); return true; } From d730ca15cf3e4806e38fa8fc6f2af8d7405ef2a4 Mon Sep 17 00:00:00 2001 From: Alexandre Cassen Date: Mon, 12 Oct 2026 12:05:53 +0200 Subject: [PATCH 48/94] net/mlx5: HWS, fix reformat pool creation race mlx5_fs_get_pr_encap_pool() inserts a new pool when the lookup misses. Two callers allocating the first reformat of a size at once both create one and the second fails with -EBUSY. TC serializes these allocations under encap_tbl_lock while RDMA flow actions on the FDB don't. Sashiko AI review of an earlier series spotted this issue. Tested for regressions on ConnectX-7 with firmware 28.48.1000. Fixes: aecd9d1020e3 ("net/mlx5: fs, add HWS packet reformat API function") Reviewed-by: Moshe Shemesh Signed-off-by: Alexandre Cassen Reviewed-by: Simon Horman Link: https://patch.msgid.link/20261012100555.1317878-2-acassen@corp.free.fr Acked-by: Tariq Toukan Signed-off-by: Paolo Abeni --- .../mellanox/mlx5/core/steering/hws/fs_hws.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/fs_hws.c b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/fs_hws.c index 54761754e657..fcef2b64f62c 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/fs_hws.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/fs_hws.c @@ -1229,7 +1229,7 @@ static struct mlx5_fs_pool * mlx5_fs_get_pr_encap_pool(struct mlx5_core_dev *dev, struct xarray *pr_pools, enum mlx5hws_action_type reformat_type, size_t size) { - struct mlx5_fs_pool *pr_pool; + struct mlx5_fs_pool *pr_pool, *old; unsigned long index = size; int err; @@ -1243,13 +1243,14 @@ mlx5_fs_get_pr_encap_pool(struct mlx5_core_dev *dev, struct xarray *pr_pools, err = mlx5_fs_hws_pr_pool_init(pr_pool, dev, size, reformat_type); if (err) goto free_pr_pool; - err = xa_insert(pr_pools, index, pr_pool, GFP_KERNEL); - if (err) - goto cleanup_pr_pool; - return pr_pool; + old = xa_cmpxchg(pr_pools, index, NULL, pr_pool, GFP_KERNEL); + if (!old) + return pr_pool; -cleanup_pr_pool: mlx5_fs_hws_pr_pool_cleanup(pr_pool); + kfree(pr_pool); + return xa_is_err(old) ? ERR_PTR(xa_err(old)) : old; + free_pr_pool: kfree(pr_pool); return ERR_PTR(err); From e4ab8ab27ab1c9f0cd1589bee471d93319e20c95 Mon Sep 17 00:00:00 2001 From: Alexandre Cassen Date: Mon, 12 Oct 2026 12:05:54 +0200 Subject: [PATCH 49/94] net/mlx5: HWS, fix modify header pool creation race mlx5_cmd_hws_modify_header_alloc() creates a pool at the index after the last one if no pool matches the pattern. TC and CT allocate modify headers concurrently, meaning two callers can take the same index and the second call fails with -EBUSY. Its error path also destroys the pool it just created although another caller may already use it. Keep such a pool since cleanup releases it. Pools now live until cleanup so the lookup needs no lock. A miss looks again under a mutex before creating the pool. Tested for regressions on ConnectX-7 with firmware 28.48.1000. Fixes: b36315ca69cb ("net/mlx5: fs, add HWS modify header API function") Signed-off-by: Alexandre Cassen Reviewed-by: Moshe Shemesh Reviewed-by: Simon Horman Link: https://patch.msgid.link/20261012100555.1317878-3-acassen@corp.free.fr Acked-by: Tariq Toukan Signed-off-by: Paolo Abeni --- .../mellanox/mlx5/core/steering/hws/fs_hws.c | 53 +++++++++++-------- .../mellanox/mlx5/core/steering/hws/fs_hws.h | 1 + 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/fs_hws.c b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/fs_hws.c index fcef2b64f62c..d6958ba5dcc2 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/fs_hws.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/fs_hws.c @@ -63,6 +63,7 @@ static int mlx5_fs_init_hws_actions_pool(struct mlx5_core_dev *dev, xa_init(&hws_pool->el2tol3tnl_pools); xa_init(&hws_pool->el2tol2tnl_pools); xa_init(&hws_pool->mh_pools); + mutex_init(&hws_pool->mh_pools_lock); xa_init(&hws_pool->table_dests); xa_init(&hws_pool->vport_dests); xa_init(&hws_pool->vport_vhca_dests); @@ -111,6 +112,7 @@ static void mlx5_fs_cleanup_hws_actions_pool(struct mlx5_fs_hws_context *fs_ctx) xa_for_each(&hws_pool->mh_pools, i, pool) mlx5_fs_destroy_mh_pool(pool, &hws_pool->mh_pools, i); xa_destroy(&hws_pool->mh_pools); + mutex_destroy(&hws_pool->mh_pools_lock); xa_for_each(&hws_pool->el2tol2tnl_pools, i, pool) mlx5_fs_destroy_pr_pool(pool, &hws_pool->el2tol2tnl_pools, i); xa_destroy(&hws_pool->el2tol2tnl_pools); @@ -1458,6 +1460,23 @@ mlx5_fs_destroy_mh_pool(struct mlx5_fs_pool *pool, struct xarray *mh_pools, kfree(pool); } +static struct mlx5_fs_pool * +mlx5_fs_find_mh_pool(struct xarray *mh_pools, + struct mlx5hws_action_mh_pattern *pattern, + unsigned long *cnt) +{ + struct mlx5_fs_pool *pool; + unsigned long i; + + *cnt = 0; + xa_for_each(mh_pools, i, pool) { + if (mlx5_fs_hws_mh_pool_match(pool, pattern)) + return pool; + (*cnt)++; + } + return NULL; +} + static int mlx5_cmd_hws_modify_header_alloc(struct mlx5_flow_root_namespace *ns, u8 namespace, u8 num_actions, void *modify_actions, @@ -1468,33 +1487,28 @@ static int mlx5_cmd_hws_modify_header_alloc(struct mlx5_flow_root_namespace *ns, struct mlx5_fs_hws_mh *mh_data = NULL; struct mlx5hws_action *hws_action; struct mlx5_fs_pool *pool; - unsigned long i, cnt = 0; - bool known_pattern; + unsigned long cnt; int err; pattern.sz = MLX5_UN_SZ_BYTES(set_add_copy_action_in_auto) * num_actions; pattern.data = modify_actions; - known_pattern = false; - xa_for_each(&hws_pool->mh_pools, i, pool) { - if (mlx5_fs_hws_mh_pool_match(pool, &pattern)) { - known_pattern = true; - break; - } - cnt++; - } - - if (!known_pattern) { - pool = mlx5_fs_create_mh_pool(ns->dev, &pattern, - &hws_pool->mh_pools, cnt); + pool = mlx5_fs_find_mh_pool(&hws_pool->mh_pools, &pattern, &cnt); + if (!pool) { + mutex_lock(&hws_pool->mh_pools_lock); + pool = mlx5_fs_find_mh_pool(&hws_pool->mh_pools, &pattern, + &cnt); + if (!pool) + pool = mlx5_fs_create_mh_pool(ns->dev, &pattern, + &hws_pool->mh_pools, cnt); + mutex_unlock(&hws_pool->mh_pools_lock); if (IS_ERR(pool)) return PTR_ERR(pool); } + mh_data = mlx5_fs_hws_mh_pool_acquire_mh(pool); - if (IS_ERR(mh_data)) { - err = PTR_ERR(mh_data); - goto destroy_pool; - } + if (IS_ERR(mh_data)) + return PTR_ERR(mh_data); hws_action = mh_data->bulk->hws_action; mh_data->data = kmemdup(pattern.data, pattern.sz, GFP_KERNEL); if (!mh_data->data) { @@ -1510,9 +1524,6 @@ static int mlx5_cmd_hws_modify_header_alloc(struct mlx5_flow_root_namespace *ns, release_mh: mlx5_fs_hws_mh_pool_release_mh(pool, mh_data); -destroy_pool: - if (!known_pattern) - mlx5_fs_destroy_mh_pool(pool, &hws_pool->mh_pools, cnt); return err; } diff --git a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/fs_hws.h b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/fs_hws.h index 20cdacd8f12e..cf068fb5bdbf 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/fs_hws.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/fs_hws.h @@ -19,6 +19,7 @@ struct mlx5_fs_hws_actions_pool { struct xarray el2tol3tnl_pools; struct xarray el2tol2tnl_pools; struct xarray mh_pools; + struct mutex mh_pools_lock; /* serializes pool creation */ struct xarray table_dests; struct xarray vport_vhca_dests; struct xarray vport_dests; From daac93975b8ff0a0a036a5e3562236c0b40c3e36 Mon Sep 17 00:00:00 2001 From: James Hilliard Date: Tue, 13 Oct 2026 14:35:42 -0600 Subject: [PATCH 50/94] net: stmmac: avoid duplicate XSK TX completions stmmac_tx_clean() reports completed AF_XDP TX descriptors through xsk_tx_completed(), but leaves their buffer type as STMMAC_TXBUF_T_XSK_TX. The slots retain that type until they are reused for another buffer type. On interface close or XDP teardown, dma_free_tx_skbufs() walks the entire TX ring, not just the dirty_tx..cur_tx range. stmmac_free_tx_buffer() counts the stale XSK_TX slots again, causing xsk_tx_completed() to advance the completion-ring producer for buffers that have already been returned. This publishes completion entries without matching outstanding TX reservations and corrupts completion accounting for a socket which stays bound across the teardown. Reset the XSK buffer type after counting the completion in both stmmac_tx_clean() and stmmac_free_tx_buffer(). XSK TX slots have a NULL skb pointer and no per-slot DMA mapping, so the SKB type represents an empty slot. Pending XSK descriptors keep their type until they are completed or released, and a released slot can no longer be counted again by a later cleanup. Fixes: 132c32ee5bc0 ("net: stmmac: Add TX via XDP zero-copy socket") Signed-off-by: James Hilliard Link: https://patch.msgid.link/20261013-submit-stmmac-xsk-tx-completion-v1-v1-1-f21b9d098e62@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index b837e8e27a35..1527e03711cc 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -1742,8 +1742,10 @@ static void stmmac_free_tx_buffer(struct stmmac_priv *priv, tx_q->xdpf[i] = NULL; } - if (tx_q->tx_skbuff_dma[i].buf_type == STMMAC_TXBUF_T_XSK_TX) + if (tx_q->tx_skbuff_dma[i].buf_type == STMMAC_TXBUF_T_XSK_TX) { tx_q->xsk_frames_done++; + tx_q->tx_skbuff_dma[i].buf_type = STMMAC_TXBUF_T_SKB; + } if (tx_q->tx_skbuff[i] && tx_q->tx_skbuff_dma[i].buf_type == STMMAC_TXBUF_T_SKB) { @@ -2926,8 +2928,11 @@ static int stmmac_tx_clean(struct stmmac_priv *priv, int budget, u32 queue, tx_q->xdpf[entry] = NULL; } - if (tx_q->tx_skbuff_dma[entry].buf_type == STMMAC_TXBUF_T_XSK_TX) + if (tx_q->tx_skbuff_dma[entry].buf_type == STMMAC_TXBUF_T_XSK_TX) { tx_q->xsk_frames_done++; + /* Teardown must not count this slot again. */ + tx_q->tx_skbuff_dma[entry].buf_type = STMMAC_TXBUF_T_SKB; + } if (tx_q->tx_skbuff_dma[entry].buf_type == STMMAC_TXBUF_T_SKB) { if (likely(skb)) { From 0edb8222b852f430e5b5e0475cc2650d409214a0 Mon Sep 17 00:00:00 2001 From: Carolina Jubran Date: Thu, 15 Oct 2026 10:40:35 +0300 Subject: [PATCH 51/94] net/mlx5: DPLL, fix fractional frequency offset scaling The frequency_diff is reported by firmware in parts-per-trillion, which is exactly what the DPLL core now expects from the ffo_get callback. Commit bc443c253fcd ("dpll: expose fractional frequency offset in ppt") switched the callback contract to ppt but assumed frequency_diff was in ppm and scaled it by 1000000 to "convert" it. Since the value was already ppt, this makes both exported attributes wrong. Report frequency_diff as is and let the core divide it down for the legacy ppm attribute. Fixes: f035dca34ede ("net/mlx5: DPLL, Implement fractional frequency offset get pin op") Signed-off-by: Carolina Jubran Reviewed-by: Jiri Pirko Signed-off-by: Tariq Toukan Reviewed-by: Daniel Machon Reviewed-by: Ivan Vecera Link: https://patch.msgid.link/20261015074035.1092340-1-tariqt@nvidia.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/mellanox/mlx5/core/dpll.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/dpll.c b/drivers/net/ethernet/mellanox/mlx5/core/dpll.c index 7c69d9029bfa..54ba7c330d66 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/dpll.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/dpll.c @@ -138,7 +138,7 @@ mlx5_dpll_pin_ffo_get(struct mlx5_dpll_synce_status *synce_status, { if (!synce_status->oper_freq_measure) return -ENODATA; - *ffo = 1000000LL * synce_status->frequency_diff; + *ffo = synce_status->frequency_diff; return 0; } From 0df412012a26389582b120844d098e1e961f10a6 Mon Sep 17 00:00:00 2001 From: Carolina Jubran Date: Thu, 15 Oct 2026 11:02:18 +0300 Subject: [PATCH 52/94] net/mlx5e: Fix NULL deref in PTP TX timestamp recovery warn PTP SQs are allocated via mlx5e_ptp_alloc_txqsq(), which never sets sq->channel. The excessive port timestamp delta recovery path incorrectly used sq->channel->netdev for netdev_warn(), causing a NULL pointer dereference. Use sq->netdev instead. Fixes: 391dad2e686f ("net/mlx5e: Recover SQ on excessive PTP TX timestamp delta") Signed-off-by: Carolina Jubran Reviewed-by: Alex Lazar Signed-off-by: Tariq Toukan Reviewed-by: Kalesh AP Reviewed-by: Vadim Fedorenko Link: https://patch.msgid.link/20261015080218.1102172-1-tariqt@nvidia.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/mellanox/mlx5/core/en/ptp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/ptp.c b/drivers/net/ethernet/mellanox/mlx5/core/en/ptp.c index 723f66a6bd63..2e1c3bb5a959 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/ptp.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/ptp.c @@ -98,7 +98,7 @@ static void mlx5e_skb_cb_hwtstamp_tx(struct sk_buff *skb, ptpsq->cq_stats->abort_abs_diff_ns += diff; if (diff > (NSEC_PER_SEC >> 1) && !test_and_set_bit(MLX5E_SQ_STATE_RECOVERING, &sq->state)) { - netdev_warn(sq->channel->netdev, + netdev_warn(sq->netdev, "PTP TX timestamp difference between CQE and port exceeds threshold: %lld ns, recovering SQ %u\n", (s64)diff, sq->sqn); queue_work(sq->priv->wq, &ptpsq->report_unhealthy_work); From 6ace56ed7b13a9c6d4cb7ef0d82d3a3d53324a63 Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Thu, 15 Oct 2026 14:06:48 +0900 Subject: [PATCH 53/94] ipv6: remove ifaddr from hash during ifdown list cleanup addrconf_ifdown() clears the address hash before snapshotting the per-device address list. A concurrent ipv6_add_addr() can publish an address after the hash scan and before the list snapshot. The ifdown path then removes the address from the device list and drops the list-owned reference while it is still linked in the hash. Once the remaining references are released, inet6_ifa_finish_destroy() warns, but a DEAD address continues to kfree_rcu(). The hash therefore points to freed memory that later RCU walkers can dereference. This follow-on was found by source review and was not separately triggered at runtime. Remove each non-kept address from the hash immediately before removing it from the per-device list. Keeping it hashed through the delete notification blocks same-address publication until NETDEV_DOWN has been delivered. hlist_del_init_rcu() is safe when the earlier hash scan already removed the address. This handles a late publisher captured by the list snapshot. The next change serializes publication with teardown so an add cannot miss both observations. Both changes are required when backporting the complete fix. Fixes: 73a8bd74e261 ("ipv6: Revert 'administrative down' address handling changes.") Cc: stable@vger.kernel.org Reported-by: Ido Schimmel Link: https://lore.kernel.org/r/20261004135117.GA206930@shredder Link: https://lore.kernel.org/r/179161099539.434549.2238120015372268173@kernel.org Suggested-by: Ido Schimmel Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20261015050651.1841557-2-4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv6/addrconf.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c index c90ee6dd7446..72d7df23f796 100644 --- a/net/ipv6/addrconf.c +++ b/net/ipv6/addrconf.c @@ -4019,6 +4019,10 @@ static int addrconf_ifdown(struct net_device *dev, bool unregister) } if (!keep) { + spin_lock_bh(&net->ipv6.addrconf_hash_lock); + hlist_del_init_rcu(&ifa->addr_lst); + spin_unlock_bh(&net->ipv6.addrconf_hash_lock); + write_lock_bh(&idev->lock); list_del_rcu(&ifa->if_list); write_unlock_bh(&idev->lock); From 06227034e2d93942119b922cf05f2de9da0ad848 Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Thu, 15 Oct 2026 14:06:49 +0900 Subject: [PATCH 54/94] ipv6: serialize address publication with device teardown ipv6_add_addr() checks idev state before allocating an ifaddr, but publishes the object later. addrconf_ifdown() can mark and detach the idev between the check and publication. This happens when a non-loopback device MTU falls below IPV6_MIN_MTU. A forced interleaving published an address on a dead idev, and ipv6_get_ifaddr() still returned it after teardown. Later device deletion remained blocked with: unregister_netdevice: waiting for ac0 to become free. Usage count = 3 Protect the dead indication with idev->lock and keep that lock across both hash and device-list publication. Recheck dead before publishing; if teardown wins the lock, reject the unpublished object. Non-unregister teardown does not set dead. The preceding change covers an add that misses the initial hash scan but reaches the per-device list snapshot. Both changes are required when backporting this fix. Fixes: 8814c4b53381 ("[IPV6] ADDRCONF: Convert addrconf_lock to RCU.") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/179161099408.434549.14449036924552933828@kernel.org Link: https://lore.kernel.org/r/20261012145306.GB779501@shredder Suggested-by: Ido Schimmel Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20261015050651.1841557-3-4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv6/addrconf.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c index 72d7df23f796..3bede30d4e98 100644 --- a/net/ipv6/addrconf.c +++ b/net/ipv6/addrconf.c @@ -1168,14 +1168,18 @@ ipv6_add_addr(struct inet6_dev *idev, struct ifa6_config *cfg, rcu_read_lock(); - err = ipv6_add_addr_hash(idev->dev, ifa); + write_lock_bh(&idev->lock); + + if (idev->dead) + err = -ENODEV; + else + err = ipv6_add_addr_hash(idev->dev, ifa); if (err < 0) { + write_unlock_bh(&idev->lock); rcu_read_unlock(); goto out; } - write_lock_bh(&idev->lock); - /* Add to inet6_dev unicast addr list. */ ipv6_link_dev_addr(idev, ifa); @@ -3897,7 +3901,9 @@ static int addrconf_ifdown(struct net_device *dev, bool unregister) * Do not dev_put! */ if (unregister) { + write_lock_bh(&idev->lock); WRITE_ONCE(idev->dead, 1); + write_unlock_bh(&idev->lock); /* protected by rtnl_lock */ RCU_INIT_POINTER(dev->ip6_ptr, NULL); From ed7f278812e540db507f683e5bf9b6d0a1f5bc1e Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Thu, 15 Oct 2026 14:06:50 +0900 Subject: [PATCH 55/94] ipv6: initialize temporary ifaddr before publication ipv6_create_tempaddr() holds a reference to the public ifaddr, publishes the new temporary address through ipv6_add_addr(), and only then stores the reference and final timestamps. addrconf_ifdown() can remove the temporary address before ifpub is stored, leaking the public ifaddr reference. Publishing ifpub alone is insufficient: manage_tempaddrs() can then find the object before cstamp and tstamp are final, update its lifetimes, and have the constructor overwrite its timestamp with an older value. This follow-on was found by source review and was not separately triggered at runtime. Pass the public ifaddr and both timestamps in ifa6_config. Initialize all three fields before adding the temporary address to either the hash or per-device lists. On success the existing public-ifaddr reference transfers to the temporary object. On error it remains owned and released by ipv6_create_tempaddr(). Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Reported-by: Sashiko Closes: https://lore.kernel.org/r/179122559913.434549.12720841717630168470@kernel.org Link: https://lore.kernel.org/r/179161099665.434549.5858601179492551889@kernel.org Link: https://lore.kernel.org/r/20261007164548.GA1153540@shredder Link: https://lore.kernel.org/r/20261012145240.GA779501@shredder Suggested-by: Ido Schimmel Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20261015050651.1841557-4-4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- include/net/addrconf.h | 3 +++ net/ipv6/addrconf.c | 20 +++++++++++++------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/include/net/addrconf.h b/include/net/addrconf.h index e6764245995f..6818057cf727 100644 --- a/include/net/addrconf.h +++ b/include/net/addrconf.h @@ -81,6 +81,9 @@ struct ifa6_config { u8 ifa_proto; const struct in6_addr *peer_pfx; + struct inet6_ifaddr *ifpub; + unsigned long cstamp; + unsigned long tstamp; u32 rt_priority; u32 ifa_flags; diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c index 3bede30d4e98..df5666812261 100644 --- a/net/ipv6/addrconf.c +++ b/net/ipv6/addrconf.c @@ -1155,10 +1155,19 @@ ipv6_add_addr(struct inet6_dev *idev, struct ifa6_config *cfg, ifa->flags |= IFA_F_TENTATIVE; ifa->valid_lft = cfg->valid_lft; ifa->prefered_lft = cfg->preferred_lft; - ifa->cstamp = ifa->tstamp = jiffies; ifa->tokenized = false; ifa->rt = f6i; + ifa->ifpub = cfg->ifpub; + if (cfg->ifpub) { + ifa->cstamp = cfg->cstamp; + ifa->tstamp = cfg->tstamp; + } else { + unsigned long now = jiffies; + + ifa->cstamp = now; + ifa->tstamp = now; + } ifa->idev = idev; in6_dev_hold(idev); @@ -1491,6 +1500,9 @@ static int ipv6_create_tempaddr(struct inet6_ifaddr *ifp, bool block) cfg.pfx = &addr; cfg.scope = ipv6_addr_scope(cfg.pfx); + cfg.ifpub = ifp; + cfg.cstamp = now; + cfg.tstamp = tmp_tstamp; ift = ipv6_add_addr(idev, &cfg, block, NULL); if (IS_ERR(ift)) { @@ -1501,12 +1513,6 @@ static int ipv6_create_tempaddr(struct inet6_ifaddr *ifp, bool block) goto retry; } - spin_lock_bh(&ift->lock); - ift->ifpub = ifp; - ift->cstamp = now; - ift->tstamp = tmp_tstamp; - spin_unlock_bh(&ift->lock); - addrconf_dad_start(ift); in6_ifa_put(ift); in6_dev_put(idev); From ffb668669ce4144347973d8b2fad449e9be0563d Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Thu, 15 Oct 2026 14:06:51 +0900 Subject: [PATCH 56/94] ipv6: do not restart DAD for a dead ifaddr addrconf_dad_failure() can create a stable-privacy replacement without RTNL. After ipv6_add_addr() publishes it, addrconf_ifdown() can mark the new ifaddr DEAD, clear its route, and unlink it before the recovery path takes ifp2->lock. The recovery path then unconditionally changes the state back to PREDAD and queues delayed DAD work. The MTU teardown path can leave IF_READY set. For an optimistic address, addrconf_dad_begin() can consequently pass the cleared NULL route to ip6_ins_rt(). This follow-on was found by source review and was not separately triggered at runtime. The new ifaddr is already initialized in PREDAD. Under ifp2->lock, record the retry and queue DAD only if teardown has not changed the state to DEAD. If teardown wins after this check, the work observes DEAD and exits without reviving the address. This complements the existing lock_errdad check that preserves the original ifaddr's DEAD state after the same function reacquires its lock. Fixes: 5f40ef77adb2 ("ipv6: do retries on stable privacy addresses") Cc: stable@vger.kernel.org Reported-by: Sashiko Closes: https://lore.kernel.org/r/179161099408.434549.14449036924552933828@kernel.org Link: https://lore.kernel.org/r/20260513025509.3776405-1-lilinmao@kylinos.cn Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20261015050651.1841557-5-4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv6/addrconf.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c index df5666812261..a2d3fb187f76 100644 --- a/net/ipv6/addrconf.c +++ b/net/ipv6/addrconf.c @@ -2198,6 +2198,7 @@ void addrconf_dad_failure(struct sk_buff *skb, struct inet6_ifaddr *ifp) ifp->idev->dev->name, &ifp->addr, eth_hdr(skb)->h_source); if (ifp->flags & IFA_F_STABLE_PRIVACY) { + bool start_dad = false; struct in6_addr new_addr; struct inet6_ifaddr *ifp2; int retries = ifp->stable_privacy_retry + 1; @@ -2236,11 +2237,14 @@ void addrconf_dad_failure(struct sk_buff *skb, struct inet6_ifaddr *ifp) goto lock_errdad; spin_lock_bh(&ifp2->lock); - ifp2->stable_privacy_retry = retries; - ifp2->state = INET6_IFADDR_STATE_PREDAD; + if (ifp2->state != INET6_IFADDR_STATE_DEAD) { + ifp2->stable_privacy_retry = retries; + start_dad = true; + } spin_unlock_bh(&ifp2->lock); - addrconf_mod_dad_work(ifp2, net->ipv6.sysctl.idgen_delay); + if (start_dad) + addrconf_mod_dad_work(ifp2, net->ipv6.sysctl.idgen_delay); in6_ifa_put(ifp2); lock_errdad: spin_lock_bh(&ifp->lock); From 504bdcb8c1566a6f887f9accebe1aa99b502ae97 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Fri, 16 Oct 2026 10:48:02 +0200 Subject: [PATCH 57/94] tcp: tcp_metrics: use the socket netns, not the dst device netns tcp_get_metrics(), __tcp_get_metrics_req() and tcpm_new() look up and create metrics entries in the netns of the cached dst device rather than in the netns of the socket. These usually match, but not after the device has been moved to another netns or unregistered: a socket can keep a stale dst in sk_dst_cache until it is checked again, and tcp_update_metrics() reads it with __sk_dst_get() without any dst_check(). Once dst_dev_put() has run, the dst device is blackhole_netdev and the entry is created in init_net. Before that, for instance for routes through nexthop objects whose cached dsts are released from a nested RCU callback, it is the netns the device has just been moved to. A TCP socket of netns A that enters TIME_WAIT in that window then creates or updates an entry carrying A's addresses and RTT in another netns, where TCP_METRICS_CMD_GET can dump it and where new connections to the same peer inherit those metrics. The metrics belong to the netns of the socket, so use sock_net() for full sockets and request sockets, and pass it to tcpm_new(). Kernel sockets that do not hold a netns reference can still reach tcp_time_wait() after tcp_net_metrics_exit_batch() has flushed the entries of their dismantling netns, and an entry created then would never be flushed and would keep a dangling tcpm_net. Make tcpm_new() refuse to create entries when check_net() fails. Its callers are in an RCU read-side section, and cleanup_net() waits for a grace period between the refcount reaching zero and the flush, so an entry added after a successful check is always seen by the flush. Fixes: 51c5d0c4b169 ("tcp: Maintain dynamic metrics in local cache.") Signed-off-by: Eric Dumazet Cc: Qiliang Yuan Reviewed-by: Neal Cardwell Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20261016084802.2725187-1-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- net/ipv4/tcp_metrics.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/net/ipv4/tcp_metrics.c b/net/ipv4/tcp_metrics.c index dc0c081fc1f3..d58f99c67d4d 100644 --- a/net/ipv4/tcp_metrics.c +++ b/net/ipv4/tcp_metrics.c @@ -161,16 +161,26 @@ static void tcpm_check_stamp(struct tcp_metrics_block *tm, rcu_dereference_protected(p, lockdep_is_held(&tcp_metrics_lock)) static struct tcp_metrics_block *tcpm_new(struct dst_entry *dst, + struct net *net, struct inetpeer_addr *saddr, struct inetpeer_addr *daddr, unsigned int hash) { struct tcp_metrics_block *tm; bool reclaim = false; - struct net *net; spin_lock_bh(&tcp_metrics_lock); - net = dst_dev_net_rcu(dst); + + /* Do not add entries to a dismantling netns: they would not be + * flushed again. Callers are in an RCU read-side section, and + * cleanup_net() waits for a grace period after the netns refcount + * reached zero and before tcp_metrics_flush_all(NULL) runs, so an + * entry added after a successful check_net() is seen by the flush. + */ + if (unlikely(!check_net(net))) { + tm = NULL; + goto out_unlock; + } /* While waiting for the spin-lock the cache might have been populated * with this entry and so we have to check again. @@ -273,7 +283,7 @@ static struct tcp_metrics_block *__tcp_get_metrics_req(struct request_sock *req, return NULL; } - net = dst_dev_net_rcu(dst); + net = sock_net(req_to_sk(req)); hash ^= net_hash_mix(net); hash = hash_32(hash, tcp_metrics_hash_log); @@ -318,7 +328,7 @@ static struct tcp_metrics_block *tcp_get_metrics(struct sock *sk, else return NULL; - net = dst_dev_net_rcu(dst); + net = sock_net(sk); hash ^= net_hash_mix(net); hash = hash_32(hash, tcp_metrics_hash_log); @@ -326,7 +336,7 @@ static struct tcp_metrics_block *tcp_get_metrics(struct sock *sk, if (tm == TCP_METRICS_RECLAIM_PTR) tm = NULL; if (!tm && create) - tm = tcpm_new(dst, &saddr, &daddr, hash); + tm = tcpm_new(dst, net, &saddr, &daddr, hash); else tcpm_check_stamp(tm, dst); From 106e562966a02f61917c2e0b83909aa2b088447f Mon Sep 17 00:00:00 2001 From: Anirudh Virdi Date: Tue, 13 Oct 2026 16:24:51 +0530 Subject: [PATCH 58/94] net: qede: initialize extack in qede_flow_spec_to_rule() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit qede_flow_spec_to_rule() declares a netlink_ext_ack on the stack without initializing it. qede_parse_flow_attr() and qede_flow_spec_validate() only set extack._msg on error paths via NL_SET_ERR_MSG_MOD(), so on a successful parse the err_out block reads an uninitialized pointer. If that garbage happens to be non-NULL, DP_NOTICE("%s", extack._msg) dereferences it inside vsnprintf, causing a page fault and kernel panic. This manifests when configuring ethtool ntuple flow rules (ethtool -U flow-type tcp4/udp4) on kernels built with CONFIG_INIT_STACK_NONE=y. With CONFIG_INIT_STACK_ALL_ZERO=y the bug is masked because the compiler zero-initializes extack, making _msg NULL. Reproduced on RHEL 9.8 (kernel-5.14.0-687.47.1.el9_8) and mainline (v7.3-rc6) with the following reproducer: IFACE=ens2f0 ip link set $IFACE up ethtool -K $IFACE ntuple on for i in $(seq 1 100); do ethtool -U $IFACE flow-type udp4 src-ip 192.168.2.$i action -1 done The loop panics reliably after 10-15 iterations with: BUG: unable to handle page fault RIP: string+0x49/0xd0 Call Trace: vsnprintf qede_flow_spec_to_rule+0x2b6/0x4b0 [qede] qede_add_cls_rule ethtool_set_rxnfc Zero-initialize extack so _msg is NULL unless explicitly set by an error path. Fixes: eb705d734525 ("net: qede: use faked extack in qede_flow_spec_to_rule()") Signed-off-by: Anirudh Virdi Reviewed-by: Asbjørn Sloth Tønnesen Reviewed-by: Daniel Machon Link: https://patch.msgid.link/20261013105451.201211-1-avirdi@redhat.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/qlogic/qede/qede_filter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/qlogic/qede/qede_filter.c b/drivers/net/ethernet/qlogic/qede/qede_filter.c index ab8b818588e2..5bf32f1034e0 100644 --- a/drivers/net/ethernet/qlogic/qede/qede_filter.c +++ b/drivers/net/ethernet/qlogic/qede/qede_filter.c @@ -1991,8 +1991,8 @@ static int qede_flow_spec_to_rule(struct qede_dev *edev, struct ethtool_rx_flow_spec *fs) { struct ethtool_rx_flow_spec_input input = {}; + struct netlink_ext_ack extack = {}; struct ethtool_rx_flow_rule *flow; - struct netlink_ext_ack extack; __be16 proto; int err; From db45c15c98134f5ebd13c4dc5b6be5d1cd3fc706 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Tue, 13 Oct 2026 13:44:34 -0700 Subject: [PATCH 59/94] net: bcmasp: fix mib counters struct alignment with ethtool stats When EDPKT stats were removed in commit e9f31435ee7d ("net: bcmasp: Add support for asp-v3.0"), the corresponding entries were removed from bcmasp_gstrings_stats in bcmasp_ethtool.c, but the edpkt_* fields were left at the beginning of struct bcmasp_mib_counters in bcmasp.h. Because bcmasp_update_mib_counters() and bcmasp_get_ethtool_stats() index into struct bcmasp_mib_counters sequentially based on the order of bcmasp_gstrings_stats, this 16-byte offset caused ethtool to read from shifted offsets: the first four software counters read unused fields and reported 0, the next four software counters reported the values of alloc_rx_skb_failed, tx_dma_failed, mc_filters_full_cnt, and uc_filters_full_cnt, while filters_combine_cnt, promisc_filters_cnt, tx_realloc_offload_failed, and tx_timeout_cnt were never reported. Remove the obsolete edpkt_* fields from struct bcmasp_mib_counters so that it aligns with bcmasp_gstrings_stats. Fixes: e9f31435ee7d ("net: bcmasp: Add support for asp-v3.0") Reviewed-by: Nicolai Buchwitz Reviewed-by: Justin Chen Signed-off-by: Florian Fainelli Link: https://patch.msgid.link/20261013204439.3494046-2-florian.fainelli@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/asp2/bcmasp.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/net/ethernet/broadcom/asp2/bcmasp.h b/drivers/net/ethernet/broadcom/asp2/bcmasp.h index 8c8ffaeadc79..9c9721da1662 100644 --- a/drivers/net/ethernet/broadcom/asp2/bcmasp.h +++ b/drivers/net/ethernet/broadcom/asp2/bcmasp.h @@ -250,10 +250,6 @@ struct bcmasp_intf_stats64 { }; struct bcmasp_mib_counters { - u32 edpkt_ts; - u32 edpkt_rx_pkt_cnt; - u32 edpkt_hdr_ext_cnt; - u32 edpkt_hdr_out_cnt; u32 umac_frm_cnt; u32 fb_frm_cnt; u32 fb_rx_fifo_depth; From a3cf9877558ab2c51182af558815dde64fc17cb9 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Tue, 13 Oct 2026 13:44:35 -0700 Subject: [PATCH 60/94] net: bcmasp: unmap previous DMA mappings on TX map failure When mapping an skb with fragments for transmission in bcmasp_xmit(), if mapping fails on fragment i > 0, the error handler calls bcmasp_clean_txcb() for previous iterations j < i. However, bcmasp_clean_txcb() only zeroes the control block fields without unmapping the DMA buffers, leaking the DMA mappings allocated for the head and earlier fragments. Call dma_unmap_single() before calling bcmasp_clean_txcb() in the error cleanup loop. Fixes: 490cb412007d ("net: bcmasp: Add support for ASP2.0 Ethernet controller") Reviewed-by: Nicolai Buchwitz Reviewed-by: Justin Chen Signed-off-by: Florian Fainelli Link: https://patch.msgid.link/20261013204439.3494046-3-florian.fainelli@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c b/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c index f2176ef3a127..9ad5a982542f 100644 --- a/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c +++ b/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c @@ -285,6 +285,11 @@ static netdev_tx_t bcmasp_xmit(struct sk_buff *skb, struct net_device *dev) intf->mib.tx_dma_failed++; spb_index = intf->tx_spb_index; for (j = 0; j < i; j++) { + txcb = &intf->tx_cbs[spb_index]; + dma_unmap_single(kdev, + dma_unmap_addr(txcb, dma_addr), + dma_unmap_len(txcb, dma_len), + DMA_TO_DEVICE); bcmasp_clean_txcb(intf, spb_index); spb_index = incr_ring(spb_index, DESC_RING_COUNT); From 07b624d2f5a4517d7d0022fbd4aef17e884b5686 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Tue, 13 Oct 2026 13:44:36 -0700 Subject: [PATCH 61/94] net: bcmasp: validate minimum RX packet size in bcmasp_rx_poll() In bcmasp_rx_poll(), the driver removes a 2-byte alignment pad and optionally strips the ETH_FCS_LEN CRC from received packets before passing them to eth_type_trans(). If the hardware reports a descriptor size smaller than the sum of the 2-byte pad, the Ethernet header (ETH_HLEN), and optional CRC (ETH_FCS_LEN), subtracting the pad and CRC lengths underflows u32 len. This adds ~4 GiB to rx_bytes statistics, while the undersized frame reaches eth_type_trans(), which reads past skb->len into stale buffer data. Check that desc->size is at least (2 + ETH_HLEN + (crc_fwd ? ETH_FCS_LEN : 0)) before proceeding to process the descriptor. Fixes: 490cb412007d ("net: bcmasp: Add support for ASP2.0 Ethernet controller") Reviewed-by: Justin Chen Signed-off-by: Florian Fainelli Link: https://patch.msgid.link/20261013204439.3494046-4-florian.fainelli@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c b/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c index 9ad5a982542f..d679c796c8c2 100644 --- a/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c +++ b/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c @@ -523,6 +523,12 @@ static int bcmasp_rx_poll(struct napi_struct *napi, int budget) DMA_FROM_DEVICE); len = desc->size; + if (unlikely(len < 2 + ETH_HLEN + (intf->crc_fwd ? ETH_FCS_LEN : 0))) { + u64_stats_update_begin(&stats->syncp); + u64_stats_inc(&stats->rx_dropped); + u64_stats_update_end(&stats->syncp); + goto next; + } /* Allocate a page pool page as the SKB data area so the * kernel can recycle it efficiently after the packet is From a31623d91c6e48b08c74dc95567aeb62f1f35938 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Tue, 13 Oct 2026 13:44:37 -0700 Subject: [PATCH 62/94] net: bcmasp: fix OF node reference leak for phy_dn In bcmasp_interface_create(), intf->phy_dn is obtained via of_parse_phandle() or assigned ndev_dn. of_parse_phandle() returns a node reference with its refcount incremented, but of_node_put() was never called on intf->phy_dn in bcmasp_interface_destroy() or the error unwind path in bcmasp_interface_create(). Acquire a reference on ndev_dn for the fixed-link case as well so that intf->phy_dn consistently holds a reference, and release it with of_node_put() on teardown and error. Fixes: 490cb412007d ("net: bcmasp: Add support for ASP2.0 Ethernet controller") Reviewed-by: Nicolai Buchwitz Reviewed-by: Justin Chen Signed-off-by: Florian Fainelli Link: https://patch.msgid.link/20261013204439.3494046-5-florian.fainelli@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c b/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c index d679c796c8c2..693e8cdfe960 100644 --- a/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c +++ b/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c @@ -1298,7 +1298,7 @@ struct bcmasp_intf *bcmasp_interface_create(struct bcmasp_priv *priv, ndev_dn->name); goto err_free_netdev; } - intf->phy_dn = ndev_dn; + intf->phy_dn = of_node_get(ndev_dn); } /* Map resource */ @@ -1338,6 +1338,7 @@ struct bcmasp_intf *bcmasp_interface_create(struct bcmasp_priv *priv, err_deregister_fixed_link: if (of_phy_is_fixed_link(ndev_dn)) of_phy_deregister_fixed_link(ndev_dn); + of_node_put(intf->phy_dn); err_free_netdev: free_netdev(ndev); err: @@ -1350,6 +1351,7 @@ void bcmasp_interface_destroy(struct bcmasp_intf *intf) unregister_netdev(intf->ndev); if (of_phy_is_fixed_link(intf->ndev_dn)) of_phy_deregister_fixed_link(intf->ndev_dn); + of_node_put(intf->phy_dn); free_netdev(intf->ndev); } From 74667a55122ad2fad88af4fd7672e4c8175c2558 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Tue, 13 Oct 2026 13:44:38 -0700 Subject: [PATCH 63/94] net: bcmasp: account for offload header in TX short packet padding When hardware checksum offload is enabled for an skb, bcmasp_csum_offload() prepends a struct bcmasp_pkt_offload header (20 bytes) to the skb via skb_push(). This increases skb->len and skb_headlen(skb) by sizeof(struct bcmasp_pkt_offload). Because the hardware descriptor processor strips this offload header before transmitting the packet on the wire, the wire packet length is smaller by sizeof(struct bcmasp_pkt_offload). The padding calculation must account for this extra header; otherwise, short frames are sent on the wire smaller than the minimum Ethernet frame size (ETH_ZLEN + ETH_FCS_LEN). Furthermore, small frames with payload in page fragments (such as small TCP segments with CHECKSUM_PARTIAL) have nr_frags > 0 and were skipping padding entirely because the padding check was restricted to nr_frags == 0. Perform the padding check on skb->len before the descriptor loop using skb_put_padto(), adding sizeof(struct bcmasp_pkt_offload) to min_size when checksum offload is enabled, and re-read nr_frags after padding in case the skb was linearized. Fixes: 490cb412007d ("net: bcmasp: Add support for ASP2.0 Ethernet controller") Reviewed-by: Justin Chen Signed-off-by: Florian Fainelli Link: https://patch.msgid.link/20261013204439.3494046-6-florian.fainelli@broadcom.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/broadcom/asp2/bcmasp_intf.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c b/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c index 693e8cdfe960..a855e637b6f0 100644 --- a/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c +++ b/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c @@ -236,7 +236,7 @@ static struct sk_buff *bcmasp_csum_offload(struct net_device *dev, static netdev_tx_t bcmasp_xmit(struct sk_buff *skb, struct net_device *dev) { struct bcmasp_intf *intf = netdev_priv(dev); - unsigned int total_bytes, size; + unsigned int total_bytes, size, min_size; int spb_index, nr_frags, i, j; struct bcmasp_tx_cb *txcb; dma_addr_t mapping, valid; @@ -262,16 +262,21 @@ static netdev_tx_t bcmasp_xmit(struct sk_buff *skb, struct net_device *dev) if (!skb) return NETDEV_TX_OK; + min_size = ETH_ZLEN + ETH_FCS_LEN; + if (csum_hw) + min_size += sizeof(struct bcmasp_pkt_offload); + + if (skb->len < min_size) { + if (skb_put_padto(skb, min_size)) + return NETDEV_TX_OK; + nr_frags = skb_shinfo(skb)->nr_frags; + } + spb_index = intf->tx_spb_index; valid = intf->tx_spb_dma_valid; for (i = 0; i <= nr_frags; i++) { if (!i) { size = skb_headlen(skb); - if (!nr_frags && size < (ETH_ZLEN + ETH_FCS_LEN)) { - if (skb_put_padto(skb, ETH_ZLEN + ETH_FCS_LEN)) - return NETDEV_TX_OK; - size = skb->len; - } mapping = dma_map_single(kdev, skb->data, size, DMA_TO_DEVICE); } else { From fa1459375794f14fb65039cd19c9398d759b491e Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Tue, 13 Oct 2026 13:44:39 -0700 Subject: [PATCH 64/94] net: bcmasp: fix network filter lookup and wake filter pair allocation In bcmasp_netfilt_get_init(), when looking up an existing filter (!init) for a specified location, if the filter at loc was not claimed, the previous loop continued searching higher indices and could return an arbitrary unrelated filter belonging to the port. This caused flow get or delete operations on an empty rule location to return or delete an unintended filter. Fix this by checking only the requested location on lookup and rejecting RX_CLS_LOC_ANY when !init. In addition, harden wake filter allocation and release by ensuring wake filter pair searches stay on even boundaries where both entries are free, checking that loc + 1 is free for positioned wake filters, and validating parity and bounds on release. Fixes: c5d511c49587 ("net: bcmasp: Add support for wake on net filters") Signed-off-by: Florian Fainelli Link: https://patch.msgid.link/20261013204439.3494046-7-florian.fainelli@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/asp2/bcmasp.c | 121 +++++++++++--------- 1 file changed, 70 insertions(+), 51 deletions(-) diff --git a/drivers/net/ethernet/broadcom/asp2/bcmasp.c b/drivers/net/ethernet/broadcom/asp2/bcmasp.c index 972474893a6b..f41a79529f10 100644 --- a/drivers/net/ethernet/broadcom/asp2/bcmasp.c +++ b/drivers/net/ethernet/broadcom/asp2/bcmasp.c @@ -511,6 +511,13 @@ static int bcmasp_netfilt_wr_to_hw(struct bcmasp_priv *priv, return 0; } +static bool bcmasp_netfilt_is_companion(struct bcmasp_priv *priv, int i) +{ + return i > 0 && (i % 2) && + priv->net_filters[i].wake_filter && + priv->net_filters[i - 1].wake_filter; +} + void bcmasp_netfilt_suspend(struct bcmasp_intf *intf) { struct bcmasp_priv *priv = intf->parent; @@ -524,9 +531,7 @@ void bcmasp_netfilt_suspend(struct bcmasp_intf *intf) priv->net_filters[i].port != intf->port) continue; - if (i > 0 && (i % 2) && - priv->net_filters[i].wake_filter && - priv->net_filters[i - 1].wake_filter) + if (bcmasp_netfilt_is_companion(priv, i)) continue; ret = bcmasp_netfilt_wr_to_hw(priv, &priv->net_filters[i]); @@ -556,9 +561,7 @@ int bcmasp_netfilt_get_all_active(struct bcmasp_intf *intf, u32 *rule_locs, priv->net_filters[i].port != intf->port) continue; - if (i > 0 && (i % 2) && - priv->net_filters[i].wake_filter && - priv->net_filters[i - 1].wake_filter) + if (bcmasp_netfilt_is_companion(priv, i)) continue; if (j == *rule_cnt) @@ -583,9 +586,7 @@ int bcmasp_netfilt_get_active(struct bcmasp_intf *intf) continue; /* Skip over a wake filter pair */ - if (i > 0 && (i % 2) && - priv->net_filters[i].wake_filter && - priv->net_filters[i - 1].wake_filter) + if (bcmasp_netfilt_is_companion(priv, i)) continue; cnt++; @@ -607,6 +608,9 @@ bool bcmasp_netfilt_check_dup(struct bcmasp_intf *intf, priv->net_filters[i].port != intf->port) continue; + if (bcmasp_netfilt_is_companion(priv, i)) + continue; + cur = &priv->net_filters[i].fs; if (cur->flow_type != fs->flow_type || @@ -659,7 +663,7 @@ bool bcmasp_netfilt_check_dup(struct bcmasp_intf *intf, } /* If no network filter found, return open filter. - * If no more open filters return NULL + * If no more open filters return error. */ struct bcmasp_net_filter *bcmasp_netfilt_get_init(struct bcmasp_intf *intf, u32 loc, bool wake_filter, @@ -669,45 +673,55 @@ struct bcmasp_net_filter *bcmasp_netfilt_get_init(struct bcmasp_intf *intf, struct bcmasp_priv *priv = intf->parent; int i, open_index = -1; - /* Check whether we exceed the filter table capacity */ + if (!init) { + if (loc == RX_CLS_LOC_ANY || loc >= priv->num_net_filters) + return ERR_PTR(-EINVAL); + + if (priv->net_filters[loc].claimed && + priv->net_filters[loc].port == intf->port && + !bcmasp_netfilt_is_companion(priv, loc)) + return &priv->net_filters[loc]; + + return ERR_PTR(-ENOENT); + } + if (loc != RX_CLS_LOC_ANY && loc >= priv->num_net_filters) return ERR_PTR(-EINVAL); /* If the filter location is busy (already claimed) and we are initializing * the filter (insertion), return a busy error code. */ - if (loc != RX_CLS_LOC_ANY && init && priv->net_filters[loc].claimed) - return ERR_PTR(-EBUSY); + if (loc != RX_CLS_LOC_ANY) { + if (priv->net_filters[loc].claimed) + return ERR_PTR(-EBUSY); - /* We need two filters for wake-up, so we cannot use an odd filter */ - if (wake_filter && loc != RX_CLS_LOC_ANY && (loc % 2)) - return ERR_PTR(-EINVAL); - - /* Initialize the loop index based on the desired location or from 0 */ - i = loc == RX_CLS_LOC_ANY ? 0 : loc; - - for ( ; i < priv->num_net_filters; i++) { - /* Found matching network filter */ - if (!init && - priv->net_filters[i].claimed && - priv->net_filters[i].hw_index == i && - priv->net_filters[i].port == intf->port) - return &priv->net_filters[i]; - - /* If we don't need a new filter or new filter already found */ - if (!init || open_index >= 0) - continue; - - /* Wake filter conslidates two filters to cover more bytes - * Wake filter is open if... - * 1. It is an even filter - * 2. The current and next filter is not claimed - */ - if (wake_filter && !(i % 2) && !priv->net_filters[i].claimed && - !priv->net_filters[i + 1].claimed) - open_index = i; - else if (!priv->net_filters[i].claimed) - open_index = i; + /* We need two filters for wake-up, so we cannot use an odd filter */ + if (wake_filter) { + if ((loc % 2) || loc + 1 >= priv->num_net_filters) + return ERR_PTR(-EINVAL); + if (priv->net_filters[loc + 1].claimed) + return ERR_PTR(-EBUSY); + } + open_index = loc; + } else { + for (i = 0; i < priv->num_net_filters; i++) { + /* Wake filter consolidates two filters to cover more bytes. + * Wake filter is open if: + * 1. It is an even filter + * 2. The current and next filter is not claimed + */ + if (wake_filter) { + if (!(i % 2) && (i + 1 < priv->num_net_filters) && + !priv->net_filters[i].claimed && + !priv->net_filters[i + 1].claimed) { + open_index = i; + break; + } + } else if (!priv->net_filters[i].claimed) { + open_index = i; + break; + } + } } if (open_index >= 0) { @@ -716,16 +730,20 @@ struct bcmasp_net_filter *bcmasp_netfilt_get_init(struct bcmasp_intf *intf, nfilter->port = intf->port; nfilter->ch = intf->channel + priv->tx_chan_offset; nfilter->hw_index = open_index; + + if (wake_filter) { + /* Claim next filter */ + priv->net_filters[open_index + 1].claimed = true; + priv->net_filters[open_index + 1].wake_filter = true; + priv->net_filters[open_index + 1].hw_index = open_index + 1; + priv->net_filters[open_index + 1].port = intf->port; + priv->net_filters[open_index + 1].ch = intf->channel + + priv->tx_chan_offset; + nfilter->wake_filter = true; + } } - if (wake_filter && open_index >= 0) { - /* Claim next filter */ - priv->net_filters[open_index + 1].claimed = true; - priv->net_filters[open_index + 1].wake_filter = true; - nfilter->wake_filter = true; - } - - return nfilter ? nfilter : ERR_PTR(-EINVAL); + return nfilter ? nfilter : ERR_PTR(-ENOSPC); } void bcmasp_netfilt_release(struct bcmasp_intf *intf, @@ -733,7 +751,8 @@ void bcmasp_netfilt_release(struct bcmasp_intf *intf, { struct bcmasp_priv *priv = intf->parent; - if (nfilt->wake_filter) { + if (nfilt->wake_filter && !(nfilt->hw_index % 2) && + nfilt->hw_index + 1 < priv->num_net_filters) { memset(&priv->net_filters[nfilt->hw_index + 1], 0, sizeof(struct bcmasp_net_filter)); } From 1d0590f96fa4133578b7370ea9ce1d871f6bfc21 Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Fri, 16 Oct 2026 13:14:06 +0300 Subject: [PATCH 65/94] bridge: mcast: Delete empty MDB entry when port group creation fails When a port group cannot be created for a new group, the MDB entry that was created for it is left with no ports and is not joined by the host. Its timer is never armed, so the entry is never deleted, but it is counted towards mcast_hash_max. When this maximum is reached, the bridge disables multicast snooping. Since commit b57e8d870d52 ("net: bridge: Maintain number of MDB entries in net_bridge_mcast_port"), port group creation also fails when the port reached its mcast_max_groups limit. A host behind such a port can therefore disable snooping by sending reports for new groups, which is what the limit is meant to prevent. The RTM_NEWMDB path has the same problem. Fix by deleting the entry after a failure if it is unused. Delete it immediately rather than arming its timer, so that it stops counting towards the maximum right away. Fixes: b57e8d870d52 ("net: bridge: Maintain number of MDB entries in net_bridge_mcast_port") Reported-by: Yunlong Ma Closes: https://lore.kernel.org/netdev/2f5b61e2.1e71a.1a11fcb3809.Coremail.yunlong_ma@buaa.edu.cn/ Acked-by: Nikolay Aleksandrov Signed-off-by: Ido Schimmel Tested-by: Gabriel Goller Reviewed-by: Gabriel Goller Link: https://patch.msgid.link/20261016101407.2030133-2-idosch@nvidia.com Signed-off-by: Jakub Kicinski --- net/bridge/br_mdb.c | 16 +++++++++++++--- net/bridge/br_multicast.c | 9 +++++++++ net/bridge/br_private.h | 1 + 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/net/bridge/br_mdb.c b/net/bridge/br_mdb.c index a01bd280c722..0adcd1524ab2 100644 --- a/net/bridge/br_mdb.c +++ b/net/bridge/br_mdb.c @@ -819,6 +819,7 @@ static int br_mdb_add_group_src_fwd(const struct br_mdb_config *cfg, struct br_mdb_config sg_cfg; struct br_ip sg_ip; u8 flags = 0; + int err; sg_ip = cfg->group; sg_ip.src = src_ip->src; @@ -842,7 +843,11 @@ static int br_mdb_add_group_src_fwd(const struct br_mdb_config *cfg, sg_cfg.filter_mode = MCAST_INCLUDE; sg_cfg.rt_protocol = cfg->rt_protocol; sg_cfg.nlflags = cfg->nlflags; - return br_mdb_add_group_sg(&sg_cfg, sgmp, brmctx, flags, extack); + err = br_mdb_add_group_sg(&sg_cfg, sgmp, brmctx, flags, extack); + if (err) + br_multicast_del_group(sgmp); + + return err; } static int br_mdb_add_group_src(const struct br_mdb_config *cfg, @@ -1047,6 +1052,7 @@ static int br_mdb_add_group(const struct br_mdb_config *cfg, struct net_bridge_mcast *brmctx; struct br_ip group = cfg->group; unsigned char flags = 0; + int err; brmctx = __br_mdb_choose_context(br, entry, extack); if (!brmctx) @@ -1073,9 +1079,13 @@ static int br_mdb_add_group(const struct br_mdb_config *cfg, flags |= MDB_PG_FLAGS_PERMANENT; if (br_multicast_is_star_g(&group)) - return br_mdb_add_group_star_g(cfg, mp, brmctx, flags, extack); + err = br_mdb_add_group_star_g(cfg, mp, brmctx, flags, extack); else - return br_mdb_add_group_sg(cfg, mp, brmctx, flags, extack); + err = br_mdb_add_group_sg(cfg, mp, brmctx, flags, extack); + if (err) + br_multicast_del_group(mp); + + return err; } static int __br_mdb_add(const struct br_mdb_config *cfg, diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c index 3fbad7b59769..ecc74b73a3e1 100644 --- a/net/bridge/br_multicast.c +++ b/net/bridge/br_multicast.c @@ -647,6 +647,14 @@ static void br_multicast_del_mdb_entry(struct net_bridge_mdb_entry *mp) queue_work(system_long_wq, &br->mcast_gc_work); } +void br_multicast_del_group(struct net_bridge_mdb_entry *mp) +{ + if (mp->ports || mp->host_joined) + return; + + br_multicast_del_mdb_entry(mp); +} + static void br_multicast_group_expired(struct timer_list *t) { struct net_bridge_mdb_entry *mp = timer_container_of(mp, t, timer); @@ -1554,6 +1562,7 @@ __br_multicast_add_group(struct net_bridge_mcast *brmctx, p = br_multicast_new_port_group(pmctx->port, group, *pp, 0, src, filter_mode, RTPROT_KERNEL, NULL); if (unlikely(!p)) { + br_multicast_del_group(mp); p = ERR_PTR(-ENOMEM); goto out; } diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h index b01997ea9508..ff746f036f54 100644 --- a/net/bridge/br_private.h +++ b/net/bridge/br_private.h @@ -998,6 +998,7 @@ struct net_bridge_mdb_entry * br_mdb_ip_get(struct net_bridge *br, struct br_ip *dst); struct net_bridge_mdb_entry * br_multicast_new_group(struct net_bridge *br, struct br_ip *group); +void br_multicast_del_group(struct net_bridge_mdb_entry *mp); struct net_bridge_port_group * br_multicast_new_port_group(struct net_bridge_port *port, const struct br_ip *group, From 335ae22c2e9db0514fd27615b9bf0c9e2a8302cf Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Fri, 16 Oct 2026 13:14:07 +0300 Subject: [PATCH 66/94] selftests: forwarding: bridge_mdb_max: Add a test for rejected MDB entries Add a test that checks that MDB entries that are rejected because the port reached its maximum number of MDB entries (mcast_max_groups) do not count towards the maximum number of MDB entries in the bridge (mcast_hash_max). The test lowers mcast_hash_max so that only three more entries can be added. It then configures the port's maximum so that new (*, G) entries are rejected and tries to add four of them. Next, it raises the maximum by one so that a new (*, G) entry is accepted, but its (S, G) entry is rejected, and tries this with four groups. Finally, it checks that multicast snooping was not disabled because the bridge ran out of MDB entries. Like the other tests, the test is run for entries added both by the control path (IGMP / MLD reports) and by the configuration path (RTM_NEWMDB), and for both the port and port-VLAN maximums. Without "bridge: mcast: Delete empty MDB entry when port group creation fails", the new tests fail (other tests omitted): # ./bridge_mdb_max.sh INFO: 802.1d tests TEST: cfg4: port maxgroups: rejected entries do not exhaust MDB [FAIL] Multicast snooping disabled by rejected MDB entries TEST: ctl4: port maxgroups: rejected entries do not exhaust MDB [FAIL] Multicast snooping disabled by rejected MDB entries TEST: cfg6: port maxgroups: rejected entries do not exhaust MDB [FAIL] Multicast snooping disabled by rejected MDB entries TEST: ctl6: port maxgroups: rejected entries do not exhaust MDB [FAIL] Multicast snooping disabled by rejected MDB entries INFO: 802.1q tests TEST: cfg4: port maxgroups: rejected entries do not exhaust MDB [FAIL] Multicast snooping disabled by rejected MDB entries TEST: ctl4: port maxgroups: rejected entries do not exhaust MDB [FAIL] Multicast snooping disabled by rejected MDB entries TEST: cfg6: port maxgroups: rejected entries do not exhaust MDB [FAIL] Multicast snooping disabled by rejected MDB entries TEST: ctl6: port maxgroups: rejected entries do not exhaust MDB [FAIL] Multicast snooping disabled by rejected MDB entries INFO: 802.1q mcast_vlan_snooping 1 tests TEST: cfg4: port_vlan maxgroups: rejected entries do not exhaust MDB [FAIL] Multicast snooping disabled by rejected MDB entries TEST: ctl4: port_vlan maxgroups: rejected entries do not exhaust MDB [FAIL] Multicast snooping disabled by rejected MDB entries TEST: cfg6: port_vlan maxgroups: rejected entries do not exhaust MDB [FAIL] Multicast snooping disabled by rejected MDB entries TEST: ctl6: port_vlan maxgroups: rejected entries do not exhaust MDB [FAIL] Multicast snooping disabled by rejected MDB entries Acked-by: Nikolay Aleksandrov Signed-off-by: Ido Schimmel Tested-by: Gabriel Goller Reviewed-by: Gabriel Goller Link: https://patch.msgid.link/20261016101407.2030133-3-idosch@nvidia.com Signed-off-by: Jakub Kicinski --- .../net/forwarding/bridge_mdb_max.sh | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) diff --git a/tools/testing/selftests/net/forwarding/bridge_mdb_max.sh b/tools/testing/selftests/net/forwarding/bridge_mdb_max.sh index 625162fd7e8b..4ebffe4588a2 100755 --- a/tools/testing/selftests/net/forwarding/bridge_mdb_max.sh +++ b/tools/testing/selftests/net/forwarding/bridge_mdb_max.sh @@ -399,6 +399,24 @@ bridge_port_vlan_maxgroups_set() bridge vlan set $locus mcast_max_groups $max } +bridge_mcast_snooping_get() +{ + ip -j -d link show dev br0 | + jq '.[].linkinfo.info_data.mcast_snooping' +} + +bridge_mcast_hash_max_get() +{ + ip -j -d link show dev br0 | + jq '.[].linkinfo.info_data.mcast_hash_max' +} + +bridge_mdb_entries_count() +{ + bridge -j mdb show dev br0 | + jq '[.[].mdb[] | [.grp, .src, .vid]] | unique | length' +} + test_ngroups_reporting() { local CFG=$1; shift @@ -938,6 +956,114 @@ test_8021qvs_maxgroups_too_many_entries_ctl6() test_maxgroups_too_many_entries ctl6 port_vlan "dev $swp1 vid 10" } +test_maxgroups_hash_max() +{ + local CFG=$1; shift + local context=$1; shift + local locus=$1; shift + + local hash_max n i + + RET=0 + + hash_max=$(bridge_mcast_hash_max_get) + + # Make sure the port has entries, so that max == n is not 0 (no limit). + ${CFG}_entries_add "$locus" temp 2 120 + check_err $? "$locus: Couldn't add MDB entries" + n=$(bridge_${context}_ngroups_get "$locus") + + # Leave room for three more MDB entries. + ip link set dev br0 type bridge \ + mcast_hash_max $(($(bridge_mdb_entries_count) + 3)) + check_err $? "Couldn't set mcast_hash_max" + + # Port at its maximum: (*, G) entries are rejected. + bridge_${context}_maxgroups_set "$locus" "$n" + check_err $? "$locus: Couldn't set maximum" + for i in $(seq 121 124); do + ${CFG}_entries_add "$locus" temp 2 "$i" &> /dev/null + done + + # One more slot: (*, G) is accepted, (S, G) is rejected. + bridge_${context}_maxgroups_set "$locus" $((n + 1)) + check_err $? "$locus: Couldn't set maximum" + for i in $(seq 125 128); do + ${CFG}_entries_add "$locus" temp 2 "$i" &> /dev/null + done + + [[ $(bridge_mcast_snooping_get) == 1 ]] + check_err $? "Multicast snooping disabled by rejected MDB entries" + + bridge mdb flush dev br0 + ip link set dev br0 type bridge mcast_snooping 1 + ip link set dev br0 type bridge mcast_hash_max "$hash_max" + bridge_${context}_maxgroups_set "$locus" 0 + check_err $? "$locus: Couldn't set maximum to 0" + + log_test "$CFG: $context maxgroups: rejected entries do not exhaust MDB" +} + +test_8021d_maxgroups_hash_max_cfg4() +{ + test_maxgroups_hash_max cfg4 port "dev $swp1" +} + +test_8021d_maxgroups_hash_max_ctl4() +{ + test_maxgroups_hash_max ctl4 port "dev $swp1" +} + +test_8021d_maxgroups_hash_max_cfg6() +{ + test_maxgroups_hash_max cfg6 port "dev $swp1" +} + +test_8021d_maxgroups_hash_max_ctl6() +{ + test_maxgroups_hash_max ctl6 port "dev $swp1" +} + +test_8021q_maxgroups_hash_max_cfg4() +{ + test_maxgroups_hash_max cfg4 port "dev $swp1 vid 10" +} + +test_8021q_maxgroups_hash_max_ctl4() +{ + test_maxgroups_hash_max ctl4 port "dev $swp1 vid 10" +} + +test_8021q_maxgroups_hash_max_cfg6() +{ + test_maxgroups_hash_max cfg6 port "dev $swp1 vid 10" +} + +test_8021q_maxgroups_hash_max_ctl6() +{ + test_maxgroups_hash_max ctl6 port "dev $swp1 vid 10" +} + +test_8021qvs_maxgroups_hash_max_cfg4() +{ + test_maxgroups_hash_max cfg4 port_vlan "dev $swp1 vid 10" +} + +test_8021qvs_maxgroups_hash_max_ctl4() +{ + test_maxgroups_hash_max ctl4 port_vlan "dev $swp1 vid 10" +} + +test_8021qvs_maxgroups_hash_max_cfg6() +{ + test_maxgroups_hash_max cfg6 port_vlan "dev $swp1 vid 10" +} + +test_8021qvs_maxgroups_hash_max_ctl6() +{ + test_maxgroups_hash_max ctl6 port_vlan "dev $swp1 vid 10" +} + test_maxgroups_too_many_cross_vlan() { local CFG=$1; shift @@ -1177,6 +1303,7 @@ test_8021d_maxgroups_cfg4() test_8021d_maxgroups_zero_cfg4 test_8021d_maxgroups_too_low_cfg4 test_8021d_maxgroups_too_many_entries_cfg4 + test_8021d_maxgroups_hash_max_cfg4 } test_8021d_maxgroups_ctl4() @@ -1184,6 +1311,7 @@ test_8021d_maxgroups_ctl4() test_8021d_maxgroups_zero_ctl4 test_8021d_maxgroups_too_low_ctl4 test_8021d_maxgroups_too_many_entries_ctl4 + test_8021d_maxgroups_hash_max_ctl4 } test_8021d_maxgroups_cfg6() @@ -1191,6 +1319,7 @@ test_8021d_maxgroups_cfg6() test_8021d_maxgroups_zero_cfg6 test_8021d_maxgroups_too_low_cfg6 test_8021d_maxgroups_too_many_entries_cfg6 + test_8021d_maxgroups_hash_max_cfg6 } test_8021d_maxgroups_ctl6() @@ -1198,6 +1327,7 @@ test_8021d_maxgroups_ctl6() test_8021d_maxgroups_zero_ctl6 test_8021d_maxgroups_too_low_ctl6 test_8021d_maxgroups_too_many_entries_ctl6 + test_8021d_maxgroups_hash_max_ctl6 } test_8021q_maxgroups_cfg4() @@ -1205,6 +1335,7 @@ test_8021q_maxgroups_cfg4() test_8021q_maxgroups_zero_cfg4 test_8021q_maxgroups_too_low_cfg4 test_8021q_maxgroups_too_many_entries_cfg4 + test_8021q_maxgroups_hash_max_cfg4 } test_8021q_maxgroups_ctl4() @@ -1212,6 +1343,7 @@ test_8021q_maxgroups_ctl4() test_8021q_maxgroups_zero_ctl4 test_8021q_maxgroups_too_low_ctl4 test_8021q_maxgroups_too_many_entries_ctl4 + test_8021q_maxgroups_hash_max_ctl4 } test_8021q_maxgroups_cfg6() @@ -1219,6 +1351,7 @@ test_8021q_maxgroups_cfg6() test_8021q_maxgroups_zero_cfg6 test_8021q_maxgroups_too_low_cfg6 test_8021q_maxgroups_too_many_entries_cfg6 + test_8021q_maxgroups_hash_max_cfg6 } test_8021q_maxgroups_ctl6() @@ -1226,6 +1359,7 @@ test_8021q_maxgroups_ctl6() test_8021q_maxgroups_zero_ctl6 test_8021q_maxgroups_too_low_ctl6 test_8021q_maxgroups_too_many_entries_ctl6 + test_8021q_maxgroups_hash_max_ctl6 } test_8021qvs_maxgroups_cfg4() @@ -1234,6 +1368,7 @@ test_8021qvs_maxgroups_cfg4() test_8021qvs_maxgroups_zero_cross_vlan_cfg4 test_8021qvs_maxgroups_too_low_cfg4 test_8021qvs_maxgroups_too_many_entries_cfg4 + test_8021qvs_maxgroups_hash_max_cfg4 test_8021qvs_maxgroups_too_many_cross_vlan_cfg4 } @@ -1243,6 +1378,7 @@ test_8021qvs_maxgroups_ctl4() test_8021qvs_maxgroups_zero_cross_vlan_ctl4 test_8021qvs_maxgroups_too_low_ctl4 test_8021qvs_maxgroups_too_many_entries_ctl4 + test_8021qvs_maxgroups_hash_max_ctl4 test_8021qvs_maxgroups_too_many_cross_vlan_ctl4 } @@ -1252,6 +1388,7 @@ test_8021qvs_maxgroups_cfg6() test_8021qvs_maxgroups_zero_cross_vlan_cfg6 test_8021qvs_maxgroups_too_low_cfg6 test_8021qvs_maxgroups_too_many_entries_cfg6 + test_8021qvs_maxgroups_hash_max_cfg6 test_8021qvs_maxgroups_too_many_cross_vlan_cfg6 } @@ -1261,6 +1398,7 @@ test_8021qvs_maxgroups_ctl6() test_8021qvs_maxgroups_zero_cross_vlan_ctl6 test_8021qvs_maxgroups_too_low_ctl6 test_8021qvs_maxgroups_too_many_entries_ctl6 + test_8021qvs_maxgroups_hash_max_ctl6 test_8021qvs_maxgroups_too_many_cross_vlan_ctl6 } From 8ff017b891e89276c15f06dfd7155981d572af77 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Fri, 16 Oct 2026 11:15:56 +0200 Subject: [PATCH 67/94] ipv6: account route exceptions to the netns of their dst rt6_insert_exception() and rt6_remove_exception() update rt6_stats->fib_rt_cache of dev_net(rt6i->dst.dev), but the device of an exception dst can move to another netns while the exception is still in its bucket. This happens when the bucket is flushed from an RCU callback, for instance from nexthop_free_rcu() for the nh_info of a replaced nexthop, whose exceptions still point to live routes. If the device moves to another netns before that callback runs, the decrement hits the other netns, whose u32 counter underflows in /proc/net/rt6_stats, while the counter of the original netns never goes back down. Account exceptions to the netns their dst was allocated in, derived from dst->ops as ip6_dst_gc() already does, which does not depend on the device. The dst already relies on that netns' ip6_dst_ops being alive when it is destroyed, which happens after the exception is removed. Fixes: 81eb8447daae ("ipv6: take care of rt6_stats") Signed-off-by: Eric Dumazet Reviewed-by: Kuniyuki Iwashima Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20261016091556.2750902-1-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- net/ipv6/route.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/net/ipv6/route.c b/net/ipv6/route.c index a5e955e56e79..2d5b58319808 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -1503,6 +1503,15 @@ static struct rt6_info *rt6_make_pcpu_route(struct net *net, */ static DEFINE_SPINLOCK(rt6_exception_lock); +/* Exceptions are accounted to the netns their dst was allocated in. + * Unlike dev_net(dst->dev), it does not change when the device moves to + * another netns or when dst_dev_put() points the dst to blackhole_netdev. + */ +static struct net *rt6_exception_net(const struct rt6_info *rt) +{ + return container_of(rt->dst.ops, struct net, ipv6.ip6_dst_ops); +} + /* Remove rt6_ex from hash table and free the memory * Caller must hold rt6_exception_lock */ @@ -1514,7 +1523,7 @@ static void rt6_remove_exception(struct rt6_exception_bucket *bucket, if (!bucket || !rt6_ex) return; - net = dev_net(rt6_ex->rt6i->dst.dev); + net = rt6_exception_net(rt6_ex->rt6i); net->ipv6.rt6_stats->fib_rt_cache--; /* purge completely the exception to allow releasing the held resources: @@ -1783,7 +1792,7 @@ static int rt6_insert_exception(struct rt6_info *nrt, rt6_ex->stamp = jiffies; hlist_add_head_rcu(&rt6_ex->hlist, &bucket->chain); bucket->depth++; - net->ipv6.rt6_stats->fib_rt_cache++; + rt6_exception_net(nrt)->ipv6.rt6_stats->fib_rt_cache++; /* Randomize max depth to avoid some side channels attacks. */ max_depth = FIB6_MAX_DEPTH + get_random_u32_below(FIB6_MAX_DEPTH); From f434191f93884c9de58c38f18ac7fc7f1fdf9e42 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Wed, 14 Oct 2026 12:48:18 -0700 Subject: [PATCH 68/94] net: phy: marvell: keep WOL_EIE across interrupt reconfiguration marvell_config_intr() rewrites the whole interrupt enable register, so the config_intr call from phy_init_hw() on resume silently cleared WOL_EIE on the 88E1318S and 88E1510: m88e1318_get_wol() still reported WAKE_MAGIC, but a matched magic packet was no longer routed to INTn and the board did not wake. Bit 7 of that register is WOL_EIE only on these two PHYs; on others, such as the 88E1111, it enables a FIFO error interrupt. So give the 88E1318S and 88E1510 their own config_intr and handle_interrupt, and leave the shared callbacks unchanged. m88e1318_set_wol() now records whether WoL is enabled, and both it and the new config_intr derive WOL_EIE from that state under the MDIO bus lock. WOL_EIE stays set across config_intr while WoL is enabled, except when the PHY has an interrupt line and interrupts are being disabled, as in phy_free_interrupt(): INTn is then the PHY IRQ, and nothing would read IEVENT after free_irq() to release it. The bit is set again once interrupts are re-enabled. phy_probe() still clears a WOL_EIE left by a previous boot, since WoL starts out disabled. The new handler also claims WoL events, so they do not go unacknowledged when the PHY IRQ is in use. Helios4 (Armada 388, 88E1512), 7.2.9. With this patch WOL_EIE stays set over 3 link down/up cycles, "wol d" clears it, and a magic packet on a running board is a handled interrupt. Without it, WOL_EIE is lost on the first down/up, survives "wol d", and the magic packet is an unhandled interrupt. Fixes: 3871c3876f80 ("mv643xx_eth with 88E1318S: support Wake on LAN") Signed-off-by: Rosen Penev Tested-by: Igor Velkov Link: https://patch.msgid.link/20261014194818.11640-2-rosenp@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/phy/marvell.c | 97 ++++++++++++++++++++++++++++++--------- 1 file changed, 75 insertions(+), 22 deletions(-) diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c index f71cffa88406..94e8dbe9a859 100644 --- a/drivers/net/phy/marvell.c +++ b/drivers/net/phy/marvell.c @@ -55,6 +55,8 @@ #define MII_M1011_IMASK 0x12 #define MII_M1011_IMASK_INIT 0x6400 #define MII_M1011_IMASK_CLEAR 0x0000 +/* WOL Event Interrupt Enable, 88E1318S and 88E1510 only */ +#define MII_M1011_IMASK_WOL BIT(7) #define MII_M1011_PHY_SCR 0x10 #define MII_M1011_PHY_SCR_DOWNSHIFT_EN BIT(11) @@ -139,11 +141,6 @@ #define MII_88E1318S_PHY_MSCR1_REG 16 #define MII_88E1318S_PHY_MSCR1_PAD_ODD BIT(6) -/* Copper Specific Interrupt Enable Register */ -#define MII_88E1318S_PHY_CSIER 0x12 -/* WOL Event Interrupt Enable */ -#define MII_88E1318S_PHY_CSIER_WOL_EIE BIT(7) - #define MII_88E1318S_PHY_LED_FUNC 0x10 #define MII_88E1318S_PHY_LED_FUNC_OFF (0x8) #define MII_88E1318S_PHY_LED_FUNC_ON (0x9) @@ -354,6 +351,7 @@ struct marvell_priv { u32 step; s8 pair; u8 vct_phase; + bool wol_enabled; }; static int marvell_read_page(struct phy_device *phydev) @@ -407,7 +405,8 @@ static int marvell_config_intr(struct phy_device *phydev) return err; } -static irqreturn_t marvell_handle_interrupt(struct phy_device *phydev) +static irqreturn_t __marvell_handle_interrupt(struct phy_device *phydev, + u16 mask) { int irq_status; @@ -417,7 +416,7 @@ static irqreturn_t marvell_handle_interrupt(struct phy_device *phydev) return IRQ_NONE; } - if (!(irq_status & MII_M1011_IMASK_INIT)) + if (!(irq_status & mask)) return IRQ_NONE; phy_trigger_machine(phydev); @@ -425,6 +424,57 @@ static irqreturn_t marvell_handle_interrupt(struct phy_device *phydev) return IRQ_HANDLED; } +static irqreturn_t marvell_handle_interrupt(struct phy_device *phydev) +{ + return __marvell_handle_interrupt(phydev, MII_M1011_IMASK_INIT); +} + +/* WOL_EIE also routes WoL events to INTn. Keep it set while WoL is enabled, + * unless INTn is the PHY interrupt and that interrupt is being disabled: + * nothing would read IEVENT after free_irq() to release the line. + */ +static u16 m88e1318_wol_imask(struct phy_device *phydev) +{ + struct marvell_priv *priv = phydev->priv; + + if (priv->wol_enabled && + (phydev->interrupts == PHY_INTERRUPT_ENABLED || + !phy_interrupt_is_valid(phydev))) + return MII_M1011_IMASK_WOL; + + return 0; +} + +static int m88e1318_config_intr(struct phy_device *phydev) +{ + bool enable = phydev->interrupts == PHY_INTERRUPT_ENABLED; + u16 imask; + int err; + + if (enable) { + err = marvell_ack_interrupt(phydev); + if (err) + return err; + } + + /* Serialise against m88e1318_set_wol() updating wol_enabled */ + phy_lock_mdio_bus(phydev); + imask = enable ? MII_M1011_IMASK_INIT : MII_M1011_IMASK_CLEAR; + imask |= m88e1318_wol_imask(phydev); + err = __phy_write(phydev, MII_M1011_IMASK, imask); + phy_unlock_mdio_bus(phydev); + if (err || enable) + return err; + + return marvell_ack_interrupt(phydev); +} + +static irqreturn_t m88e1318_handle_interrupt(struct phy_device *phydev) +{ + return __marvell_handle_interrupt(phydev, MII_M1011_IMASK_INIT | + MII_M1011_IMASK_WOL); +} + static int marvell_set_polarity(struct phy_device *phydev, int polarity) { u16 val; @@ -1969,18 +2019,27 @@ static void m88e1318_get_wol(struct phy_device *phydev, static int m88e1318_set_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol) { + struct marvell_priv *priv = phydev->priv; int err = 0, oldpage; oldpage = phy_save_page(phydev); if (oldpage < 0) goto error; - if (wol->wolopts & (WAKE_MAGIC | WAKE_PHY)) { - /* Explicitly switch to page 0x00, just to be sure */ - err = marvell_write_page(phydev, MII_MARVELL_COPPER_PAGE); - if (err < 0) - goto error; + priv->wol_enabled = !!(wol->wolopts & (WAKE_MAGIC | WAKE_PHY)); + /* Explicitly switch to page 0x00, just to be sure */ + err = marvell_write_page(phydev, MII_MARVELL_COPPER_PAGE); + if (err < 0) + goto error; + + /* Update the WOL interrupt enable */ + err = __phy_modify(phydev, MII_M1011_IMASK, MII_M1011_IMASK_WOL, + m88e1318_wol_imask(phydev)); + if (err < 0) + goto error; + + if (priv->wol_enabled) { /* If WOL event happened once, the LED[2] interrupt pin * will not be cleared unless we reading the interrupt status * register. If interrupts are in use, the normal interrupt @@ -1990,12 +2049,6 @@ static int m88e1318_set_wol(struct phy_device *phydev, if (!phy_interrupt_is_valid(phydev)) __phy_read(phydev, MII_M1011_IEVENT); - /* Enable the WOL interrupt */ - err = __phy_set_bits(phydev, MII_88E1318S_PHY_CSIER, - MII_88E1318S_PHY_CSIER_WOL_EIE); - if (err < 0) - goto error; - err = marvell_write_page(phydev, MII_MARVELL_LED_PAGE); if (err < 0) goto error; @@ -3817,8 +3870,8 @@ static struct phy_driver marvell_drivers[] = { .config_init = m88e1318_config_init, .config_aneg = m88e1318_config_aneg, .read_status = marvell_read_status, - .config_intr = marvell_config_intr, - .handle_interrupt = marvell_handle_interrupt, + .config_intr = m88e1318_config_intr, + .handle_interrupt = m88e1318_handle_interrupt, .get_wol = m88e1318_get_wol, .set_wol = m88e1318_set_wol, .resume = genphy_resume, @@ -3925,8 +3978,8 @@ static struct phy_driver marvell_drivers[] = { .config_init = m88e1510_config_init, .config_aneg = m88e1510_config_aneg, .read_status = marvell_read_status, - .config_intr = marvell_config_intr, - .handle_interrupt = marvell_handle_interrupt, + .config_intr = m88e1318_config_intr, + .handle_interrupt = m88e1318_handle_interrupt, .get_wol = m88e1318_get_wol, .set_wol = m88e1318_set_wol, .resume = m88e1510_resume, From 23dd5be7e19ceab8c1c79110ee3552bcabc463ef Mon Sep 17 00:00:00 2001 From: Dimitri Daskalakis Date: Fri, 16 Oct 2026 17:54:01 -0700 Subject: [PATCH 69/94] net/mlx5e: Update netdev features only after a successful ringparam set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mlx5 couples HW-GRO with tcp-data-split. If a user sets rx-gro-hw on -> tcp-data-split on -> rx-gro-hw off, the driver will leave HW-GRO on. mlx5e_ethtool_set_tcp_data_split() calls netdev_update_features() when tcp-data-split goes from enabled to disabled in case HW-GRO needs to be released. mlx5e_fix_features() reads the new HDS config from dev->cfg_pending, so HW-GRO is released before all ring parameters are validated. If the subsequent call to mlx5e_ethtool_set_ringparam() fails, the ethtool core discards the pending change and reports tcp-data-split enabled. The device is no longer splitting, and subsequent ring changes are blocked. Move the call to netdev_update_features() after mlx5e_ethtool_set_ringparam() exits successfully. The issue was discovered by manual code inspection during review of a change that stops mlx5 users from disabling tcp-data-split. It was reproduced on a ConnectX-7 with ethtool alone. Steps to reproduce: ethtool -K eth0 rx-gro-hw on ethtool -G eth0 tcp-data-split on ethtool -K eth0 rx-gro-hw off # HW-GRO kept on for tcp-data-split ethtool -G eth0 tcp-data-split off rx 1 # de-sync driver & kernel state netlink error: mlx5_core: rx (1) < min (16) netlink error: Invalid argument ethtool -G eth0 rx 1024 # valid ring changes are now blocked netlink error: mlx5_core: TCP-data-split is not supported when GRO HW is disabled netlink error: Invalid argument This is not a terminal state, users can recover by setting rx-gro-hw on. With the fix, the failing ringparam set leaves rx-gro-hw on and later ring changes succeed. Fixes: 46bcce5dfd33 ("net/mlx5e: Support ethtool tcp-data-split settings") Reported-by: Björn Töpel Closes: https://lore.kernel.org/87se2grw3d.fsf@all.your.base.are.belong.to.us/ Signed-off-by: Dimitri Daskalakis Reviewed-by: Björn Töpel Reviewed-by: Nimrod Oren Acked-by: Tariq Toukan Link: https://patch.msgid.link/20261017005401.1582755-1-dimitri.daskalakis1@gmail.com Signed-off-by: Jakub Kicinski --- .../ethernet/mellanox/mlx5/core/en_ethtool.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c b/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c index 3ed59ced0407..f38e033ab8fa 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c @@ -403,11 +403,6 @@ static bool mlx5e_ethtool_set_tcp_data_split(struct mlx5e_priv *priv, return false; } - /* Might need to disable HW-GRO if it was kept on due to hds. */ - if (tcp_data_split == ETHTOOL_TCP_DATA_SPLIT_DISABLED && - dev->cfg->hds_config == ETHTOOL_TCP_DATA_SPLIT_ENABLED) - netdev_update_features(priv->netdev); - return true; } @@ -468,13 +463,23 @@ static int mlx5e_set_ringparam(struct net_device *dev, struct netlink_ext_ack *extack) { struct mlx5e_priv *priv = netdev_priv(dev); + int err; if (!mlx5e_ethtool_set_tcp_data_split(priv, kernel_param->tcp_data_split, extack)) return -EINVAL; - return mlx5e_ethtool_set_ringparam(priv, param, extack); + err = mlx5e_ethtool_set_ringparam(priv, param, extack); + if (err) + return err; + + /* Might need to disable HW-GRO if it was kept on due to hds. */ + if (kernel_param->tcp_data_split == ETHTOOL_TCP_DATA_SPLIT_DISABLED && + dev->cfg->hds_config == ETHTOOL_TCP_DATA_SPLIT_ENABLED) + netdev_update_features(priv->netdev); + + return 0; } void mlx5e_ethtool_get_channels(struct mlx5e_priv *priv, From 1a4e7bc6e3457ad95515a9d2c042452c3161bb2c Mon Sep 17 00:00:00 2001 From: Yingjie Wang <1075151112@qq.com> Date: Fri, 16 Oct 2026 11:02:58 +0800 Subject: [PATCH 70/94] net: unix: reject negative max_dgram_qlen values net.unix.max_dgram_qlen is parsed as a signed int and copied to the u32 sock::sk_max_ack_backlog when an AF_UNIX socket is created. Consequently, a negative value such as -1 is accepted and converted to UINT_MAX. Since the receive queue length is also u32, its fullness check cannot become true for that limit. max_dgram_qlen has no documented negative sentinel. Its existing signed interface represents nonnegative values only through INT_MAX; negative bit patterns are not a supported encoding for the upper half of the u32 consumer range. Switch to proc_dointvec_minmax and make the established 0..INT_MAX input range explicit. This rejects negative values without adding new accepted values or changing the default and zero-value behavior. Very large valid values can still make the queue limit impractical to reach; this change corrects the input domain rather than imposing a new resource ceiling. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Yingjie Wang <1075151112@qq.com> Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/tencent_A643419B56079E9A448D97922763FF069F08@qq.com Signed-off-by: Jakub Kicinski --- Documentation/networking/ip-sysctl.rst | 3 ++- net/unix/sysctl_net_unix.c | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Documentation/networking/ip-sysctl.rst b/Documentation/networking/ip-sysctl.rst index f7af0286341c..8edd2eb2ac49 100644 --- a/Documentation/networking/ip-sysctl.rst +++ b/Documentation/networking/ip-sysctl.rst @@ -3823,5 +3823,6 @@ l3mdev_accept - BOOLEAN max_dgram_qlen - INTEGER The maximum length of dgram socket receive queue - Default: 10 + Possible values: 0 - INT_MAX + Default: 10 diff --git a/net/unix/sysctl_net_unix.c b/net/unix/sysctl_net_unix.c index 47660d5726bb..fa1fbaec3aaf 100644 --- a/net/unix/sysctl_net_unix.c +++ b/net/unix/sysctl_net_unix.c @@ -19,7 +19,9 @@ static const struct ctl_table unix_table[] = { .data = &init_net.unx.sysctl_max_dgram_qlen, .maxlen = sizeof(int), .mode = 0644, - .proc_handler = proc_dointvec + .proc_handler = proc_dointvec_minmax, + .extra1 = SYSCTL_ZERO, + .extra2 = SYSCTL_INT_MAX, }, }; From 15b7a86a8888dc6a8fd19e181757e32389de9064 Mon Sep 17 00:00:00 2001 From: Til Kaiser Date: Wed, 14 Oct 2026 19:31:17 +0200 Subject: [PATCH 71/94] mlxsw: Initialize register query payloads MFGD register queries can exhaust EMAD retries and leave Mellanox Spectrum switches stuck during boot or shutdown when running OpenWrt built with GCC 15. An example failure is shown below with the second line wrapped: EMAD retries (5/5) (tid=9b544fc100001f35) EMAD reg access failed (tid=9b544fc100001f35,reg_id=90f0(mfgd), type=query,status=0(operation performed)) On an SN2100, the failure occurs during driver teardown on reboot. On an SN3420, it occurs while probing mlxsw_spectrum during boot. The register query API transmits the supplied payload to the device before replacing it with the response. Both MFGD callers pass an uninitialized stack buffer, so the request contains arbitrary register field values and reserved bits. This makes the request depend on stack contents and compiler code generation. Initialize both MFGD query buffers in the firmware fatal event configuration and reporter test paths. The initialization has been confirmed to eliminate the boot and shutdown hangs on both switches. The subsequent read-modify-write still preserves the other fields returned by the device. Also initialize the FORE fan fault query and the MOGCR mirroring policer base query, which pass entirely uninitialized payloads. Restore the MTMP query initialization in the temperature history reset path, where only the slot and sensor index fields are set before querying the register. This follows commit 5b0907407e7f ("mlxsw: core: Zero payload buffers for couple of registers"), which initialized other query payloads after a firmware field length bug caused firmware to consume uninitialized data. Tested with firmware 2020.1148 and with 2016.3900 from linux-firmware. Fixes: 7d83ee111008 ("mlxsw: core: Introduce fw_fatal health reporter") Fixes: 2c6a33cd334b ("mlxsw: core: Extend hwmon interface with fan fault attribute") Fixes: 4039504e6a0c ("mlxsw: spectrum_span: Allow setting policer on a SPAN agent") Fixes: b890ad418e1f ("mlxsw: core_hwmon: Extend internal structures to support multi hwmon objects") Cc: stable@vger.kernel.org Signed-off-by: Til Kaiser Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20261014173117.26305-1-mail@tk154.de Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlxsw/core.c | 4 ++-- drivers/net/ethernet/mellanox/mlxsw/core_hwmon.c | 4 ++-- drivers/net/ethernet/mellanox/mlxsw/spectrum_span.c | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlxsw/core.c b/drivers/net/ethernet/mellanox/mlxsw/core.c index 308d8a94865f..42e9689d7f35 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/core.c +++ b/drivers/net/ethernet/mellanox/mlxsw/core.c @@ -2001,7 +2001,7 @@ mlxsw_core_health_fw_fatal_test(struct devlink_health_reporter *reporter, struct netlink_ext_ack *extack) { struct mlxsw_core *mlxsw_core = devlink_health_reporter_priv(reporter); - char mfgd_pl[MLXSW_REG_MFGD_LEN]; + char mfgd_pl[MLXSW_REG_MFGD_LEN] = {0}; int err; /* Read the register first to make sure no other bits are changed. */ @@ -2022,7 +2022,7 @@ mlxsw_core_health_fw_fatal_ops = { static int mlxsw_core_health_fw_fatal_config(struct mlxsw_core *mlxsw_core, bool enable) { - char mfgd_pl[MLXSW_REG_MFGD_LEN]; + char mfgd_pl[MLXSW_REG_MFGD_LEN] = {0}; int err; /* Read the register first to make sure no other bits are changed. */ diff --git a/drivers/net/ethernet/mellanox/mlxsw/core_hwmon.c b/drivers/net/ethernet/mellanox/mlxsw/core_hwmon.c index 30f172ce822c..a5e7aee730d6 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/core_hwmon.c +++ b/drivers/net/ethernet/mellanox/mlxsw/core_hwmon.c @@ -120,7 +120,7 @@ static ssize_t mlxsw_hwmon_temp_rst_store(struct device *dev, container_of(attr, struct mlxsw_hwmon_attr, dev_attr); struct mlxsw_hwmon_dev *mlxsw_hwmon_dev = mlxsw_hwmon_attr->mlxsw_hwmon_dev; struct mlxsw_hwmon *mlxsw_hwmon = mlxsw_hwmon_dev->hwmon; - char mtmp_pl[MLXSW_REG_MTMP_LEN]; + char mtmp_pl[MLXSW_REG_MTMP_LEN] = {0}; unsigned long val; int index; int err; @@ -177,7 +177,7 @@ static ssize_t mlxsw_hwmon_fan_fault_show(struct device *dev, container_of(attr, struct mlxsw_hwmon_attr, dev_attr); struct mlxsw_hwmon_dev *mlxsw_hwmon_dev = mlxsw_hwmon_attr->mlxsw_hwmon_dev; struct mlxsw_hwmon *mlxsw_hwmon = mlxsw_hwmon_dev->hwmon; - char fore_pl[MLXSW_REG_FORE_LEN]; + char fore_pl[MLXSW_REG_FORE_LEN] = {0}; bool fault; int err; diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_span.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_span.c index ae63d549b542..d54fe5e61296 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_span.c +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_span.c @@ -1720,7 +1720,7 @@ static int mlxsw_sp2_span_init(struct mlxsw_sp *mlxsw_sp) static int mlxsw_sp2_span_policer_id_base_set(struct mlxsw_sp *mlxsw_sp, u16 policer_id_base) { - char mogcr_pl[MLXSW_REG_MOGCR_LEN]; + char mogcr_pl[MLXSW_REG_MOGCR_LEN] = {0}; int err; err = mlxsw_reg_query(mlxsw_sp->core, MLXSW_REG(mogcr), mogcr_pl); From 7c9cb1edaf8fd8e1af25e39741e603500986ef66 Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Mon, 19 Oct 2026 17:03:36 +0300 Subject: [PATCH 72/94] ipv6: Hold RCU when emitting route hardware flags notification fib6_info_hw_flags_set() is called by drivers from process context without RTNL or RCU. When the route uses a nexthop object, rt6_nlmsg_size() and rt6_fill_node() access the nexthop's RCU protected nexthop info and group pointers via rcu_dereference_rtnl(), resulting in suspicious RCU usage splats [1]. Fix by holding RCU while sizing and filling the notification, like fib_alias_hw_flags_set() already does. Switch to GFP_ATOMIC for the skb allocation accordingly. Reproducer: # echo "10 0" > /sys/bus/netdevsim/new_device # sysctl -wq net.ipv6.fib_notify_on_flag_change=1 # sysctl -wq net.ipv4.nexthop_compat_mode=1 # ip -6 nexthop add id 1 blackhole # ip -6 route add 2001:db8:1::1/128 nhid 1 [1] WARNING: suspicious RCU usage 7.3.0-rc7-virtme-g191aa41548b2 #38 Not tainted net/ipv4/nexthop.c:1539 suspicious rcu_dereference_check() usage! [...] Workqueue: events nsim_fib_event_work Call Trace: dump_stack_lvl (lib/dump_stack.c:94 lib/dump_stack.c:120) lockdep_rcu_suspicious.cold (kernel/locking/lockdep.c:6972) nexthop_for_each_fib6_nh (net/ipv4/nexthop.c:1539 (discriminator 16)) rt6_nlmsg_size (net/ipv6/route.c:5728) fib6_info_hw_flags_set (net/ipv6/route.c:6503) nsim_fib6_rt_add (drivers/net/netdevsim/fib.c:671 drivers/net/netdevsim/fib.c:694) nsim_fib6_rt_insert (drivers/net/netdevsim/fib.c:759) nsim_fib_event_work (drivers/net/netdevsim/fib.c:856 drivers/net/netdevsim/fib.c:889 drivers/net/netdevsim/fib.c:1493) process_one_work (kernel/workqueue.c:3396) worker_thread (kernel/workqueue.c:3479 kernel/workqueue.c:3560) kthread (kernel/kthread.c:436) ret_from_fork (arch/x86/kernel/process.c:158) ret_from_fork_asm (arch/x86/entry/entry_64.S:245) Fixes: 907eea486888 ("net: ipv6: Emit notification when fib hardware flags are changed") Reported-by: Sashiko Closes: https://lore.kernel.org/netdev/179152182668.434549.18232968419539498055@kernel.org/ Signed-off-by: Ido Schimmel Reviewed-by: Eric Dumazet Reviewed-by: David Ahern Link: https://patch.msgid.link/20261019140337.2886875-2-idosch@nvidia.com Signed-off-by: Jakub Kicinski --- net/ipv6/route.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 2d5b58319808..d95baec6b5d3 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -6509,7 +6509,9 @@ void fib6_info_hw_flags_set(struct net *net, struct fib6_info *f6i, if (!fib_notify_on_flag_change) return; - skb = nlmsg_new(rt6_nlmsg_size(f6i), GFP_KERNEL); + rcu_read_lock(); + + skb = nlmsg_new(rt6_nlmsg_size(f6i), GFP_ATOMIC); if (!skb) { err = -ENOBUFS; goto errout; @@ -6522,10 +6524,13 @@ void fib6_info_hw_flags_set(struct net *net, struct fib6_info *f6i, goto errout; } + rcu_read_unlock(); + rtnl_notify(skb, net, 0, RTNLGRP_IPV6_ROUTE, NULL, GFP_KERNEL); return; errout: + rcu_read_unlock(); rtnl_set_sk_err(net, RTNLGRP_IPV6_ROUTE, err); } EXPORT_SYMBOL(fib6_info_hw_flags_set); From b5dc3fde8b92286a0cb06bf08e2ec0141cf867bb Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Mon, 19 Oct 2026 17:03:37 +0300 Subject: [PATCH 73/94] selftests: netdevsim: Add FIB notification tests for nexthop objects Add IPv4 and IPv6 test cases that program a route that uses a nexthop object and check that two notifications are emitted for it, with the second one carrying the RTM_F_TRAP flag. The new route_nexthop_check() helper follows the same pattern as the existing route_{addition,deletion,replacement}_check() helpers. Without "ipv6: Hold RCU when emitting route hardware flags notification", the IPv6 test case passes, but triggers suspicious RCU usage splats. The IPv4 test case is added for completeness. Signed-off-by: Ido Schimmel Reviewed-by: Eric Dumazet Reviewed-by: David Ahern Link: https://patch.msgid.link/20261019140337.2886875-3-idosch@nvidia.com Signed-off-by: Jakub Kicinski --- .../net/netdevsim/fib_notifications.sh | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/tools/testing/selftests/drivers/net/netdevsim/fib_notifications.sh b/tools/testing/selftests/drivers/net/netdevsim/fib_notifications.sh index 9896580c3d85..c49d6deb621a 100755 --- a/tools/testing/selftests/drivers/net/netdevsim/fib_notifications.sh +++ b/tools/testing/selftests/drivers/net/netdevsim/fib_notifications.sh @@ -8,10 +8,12 @@ ALL_TESTS=" ipv4_route_deletion_test ipv4_route_replacement_test ipv4_route_offload_failed_test + ipv4_route_nexthop_test ipv6_route_addition_test ipv6_route_deletion_test ipv6_route_replacement_test ipv6_route_offload_failed_test + ipv6_route_nexthop_test " NETDEVSIM_PATH=/sys/bus/netdevsim/ @@ -269,6 +271,50 @@ ipv4_route_offload_failed_test() log_test "IPv4 route offload failed" } +route_nexthop_check() +{ + local ip=$1; shift + local route=$1; shift + local nhid=$1; shift + local expected_num_notifications=$1; shift + local outfile + + ip netns exec testns1 sysctl -qw net.$ip.fib_notify_on_flag_change=1 + + outfile=$(mktemp) + + $IP monitor route &> $outfile & + sleep 1 + $IP route add $route nhid $nhid + sleep 1 + kill_process %% + + route_notify_check $outfile $expected_num_notifications + rm -f $outfile + + $IP route del $route nhid $nhid +} + +ipv4_route_nexthop_test() +{ + RET=0 + + local ip="ipv4" + local route=192.0.2.0/24 + local nhid=1 + + $IP -4 nexthop add id $nhid dev dummy1 + + # Make sure two notifications will be emitted for a programmed route + # that uses a nexthop object. + local expected_num_notifications=2 + route_nexthop_check $ip $route $nhid $expected_num_notifications + + $IP nexthop del id $nhid + + log_test "IPv4 route with nexthop object" +} + ipv6_route_addition_test() { RET=0 @@ -385,6 +431,26 @@ ipv6_route_offload_failed_test() log_test "IPv6 route offload failed" } +ipv6_route_nexthop_test() +{ + RET=0 + + local ip="ipv6" + local route=2001:db8:1::/64 + local nhid=1 + + $IP -6 nexthop add id $nhid dev dummy1 + + # Make sure two notifications will be emitted for a programmed route + # that uses a nexthop object. + local expected_num_notifications=2 + route_nexthop_check $ip $route $nhid $expected_num_notifications + + $IP nexthop del id $nhid + + log_test "IPv6 route with nexthop object" +} + setup_prepare() { modprobe netdevsim &> /dev/null From 9043be310c6a4c5e57f7bba4b9870554eac7aa96 Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Mon, 19 Oct 2026 17:54:07 +0200 Subject: [PATCH 74/94] team: reject CAN, IEEE 802.15.4 and 6LoWPAN devices in team_port_add When the first port has a non-Ethernet type, team_setup_by_port() copies port_dev->type to the team device while netdev_priv() of the team device still holds a struct team. Several L2 stacks identify their devices by dev->type alone and then access type specific data that the team driver never sets up: - CAN protocols rely on the CAN mid-layer private data (ml_priv) - IEEE 802.15.4 code dereferences ieee802154_ptr and the mlme ops in ml_priv - the 6LoWPAN netdev notifier writes through lowpan_dev(dev) on NETDEV_DOWN and reads it on NETDEV_UP/NETDEV_CHANGE Enslaving such a device (e.g. vxcan, wpan0, lowpan0) therefore leads to NULL pointer dereferences or to writes into struct team. This is the same problem that syzbot reported for the bonding driver with a vxcan device enslaved to a bond master, which crashed in can_rx_unregister() and was fixed by commit 8ba68464e478 ("bonding: refuse to enslave CAN devices"). The team driver has the same gap. Reject ARPHRD_CAN, ARPHRD_IEEE802154, ARPHRD_IEEE802154_MONITOR and ARPHRD_6LOWPAN devices in team_port_add(). Link: https://lore.kernel.org/all/b58d98e0-8fe3-4e4e-b8a5-c1c7e647ccca@hartkopp.net/ Link: https://syzkaller.appspot.com/bug?extid=8ed98cbd0161632bce95 # bonding splat Fixes: 1d76efe1577b ("team: add support for non-ethernet devices") Signed-off-by: Jiale Yao Signed-off-by: Oliver Hartkopp Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20261019155407.61487-1-socketcan@hartkopp.net Signed-off-by: Jakub Kicinski --- drivers/net/team/team_core.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/net/team/team_core.c b/drivers/net/team/team_core.c index ca75f08e3c59..957cc7885d2f 100644 --- a/drivers/net/team/team_core.c +++ b/drivers/net/team/team_core.c @@ -1225,6 +1225,17 @@ static int team_port_add(struct team *team, struct net_device *port_dev, return -EINVAL; } + if (port_dev->type == ARPHRD_CAN || + port_dev->type == ARPHRD_IEEE802154 || + port_dev->type == ARPHRD_IEEE802154_MONITOR || + port_dev->type == ARPHRD_6LOWPAN) { + NL_SET_ERR_MSG(extack, + "CAN, IEEE 802.15.4 and 6LoWPAN devices can't be added as a team port"); + netdev_err(dev, "Device %s is CAN, IEEE 802.15.4 or 6LoWPAN. These device types can't be added as a team port\n", + portname); + return -EINVAL; + } + if (netif_is_team_port(port_dev)) { NL_SET_ERR_MSG(extack, "Device is already a port of a team device"); netdev_err(dev, "Device %s is already a port " From c0703534b223a5c6993e0f000fd1c3abe621fb65 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Tue, 13 Oct 2026 15:45:01 -0500 Subject: [PATCH 75/94] net/smc: queue the copied chunk when smc_tx_sendmsg() faults When a send wraps the end of the send buffer, smc_tx_sendmsg() copies the data in two chunks. If memcpy_from_msg() faults on the second one, smc_tx_sendmsg() returns send_done, which already counts the first chunk, but leaves tx_curs_prep and sndbuf_space as they were. send() reports bytes the peer never receives, and the next send() overwrites them. Any caller can hit this on its own stream with a buffer whose tail is unreadable. On a fault in the second chunk, queue the first one. The next pass retries the rest and returns send_done if it faults again, so send() reports only what it queued, as TCP does. Fixes: e6727f39004b ("smc: send data (through RDMA)") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Link: https://patch.msgid.link/20261013-b4-disp-872a3689-v1-1-2a4c63b04761@proton.me Signed-off-by: Jakub Kicinski --- net/smc/smc_tx.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/net/smc/smc_tx.c b/net/smc/smc_tx.c index 3144b4b1fe29..495fb1354728 100644 --- a/net/smc/smc_tx.c +++ b/net/smc/smc_tx.c @@ -249,6 +249,13 @@ int smc_tx_sendmsg(struct smc_sock *smc, struct msghdr *msg, size_t len) rc = memcpy_from_msg(sndbuf_base + chunk_off, msg, chunk_len); if (rc) { + if (chunk) { + /* send_done counts the first chunk: + * queue it and retry the rest + */ + copylen -= chunk_len; + break; + } smc_sndbuf_sync_sg_for_device(conn); if (send_done) return send_done; From 746d0e7e851edefce9656ce744d4ec5e94de6b5f Mon Sep 17 00:00:00 2001 From: Michael Chan Date: Tue, 20 Oct 2026 15:53:58 -0700 Subject: [PATCH 76/94] MAINTAINERS: Update Michael Chan's email for tg3 My old email address doesn't work anymore. Update it to the current working one. Signed-off-by: Michael Chan Link: https://patch.msgid.link/20261020225358.1130051-1-michael.chan@broadcom.com Signed-off-by: Jakub Kicinski --- .mailmap | 1 + MAINTAINERS | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index 0a0be49bc4de..16c3e6599082 100644 --- a/.mailmap +++ b/.mailmap @@ -638,6 +638,7 @@ Mayuresh Janorkar Md Sadre Alam Miaoqing Pan Michael Buesch +Michael Chan Michael Grzeschik Michael Grzeschik Michael Riesch diff --git a/MAINTAINERS b/MAINTAINERS index 00b440be0d46..4941eeec4a0a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5623,7 +5623,7 @@ F: drivers/net/ethernet/broadcom/unimac.h BROADCOM TG3 GIGABIT ETHERNET DRIVER M: Pavan Chebbi -M: Michael Chan +M: Michael Chan L: netdev@vger.kernel.org S: Maintained F: drivers/net/ethernet/broadcom/tg3.* From 78d4bd22ec9df490135303c00e190ac43d5c334a Mon Sep 17 00:00:00 2001 From: Carolina Jubran Date: Mon, 19 Oct 2026 20:41:52 +0300 Subject: [PATCH 77/94] net: skbuff: don't copy alloc_cpu in skb header copies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 68822bdf76f1 ("net: generalize skb freeing deferral to per-cpu lists") added alloc_cpu to struct sk_buff and placed it in the headers group copied by __copy_skb_header(). This is wrong for newly allocated skbs. For example, skb_segment() allocates a new skb and records the current CPU in alloc_cpu, but __copy_skb_header() overwrites it with the value from the original skb. If the original skb was allocated on another CPU, the segment may later be treated as remotely allocated and unnecessarily deferred there when it is freed. Move alloc_cpu out of the headers group so it is preserved across header copies. Copy it explicitly in skb_clone(), which allocates the skb object directly instead of going through __alloc_skb(). This was discovered while investigating a throughput regression with software IPsec. The load used 512 IPsec tunnels carrying traffic over VXLAN, with 512 parallel iperf3 flows and an MTU of 4500. Before the fix: 16.09% queued_spin_lock_slowpath | --15.90%--_raw_spin_lock_irqsave | --9.37%--__slab_free kfree skb_release_data napi_consume_skb mlx5e_poll_tx_cq After the fix: 6.84% queued_spin_lock_slowpath | --6.57%--_raw_spin_lock_irqsave | --5.00%--__slab_free kfree skb_release_data napi_consume_skb mlx5e_poll_tx_cq Fixes: 68822bdf76f1 ("net: generalize skb freeing deferral to per-cpu lists") Signed-off-by: Carolina Jubran Reviewed-by: Dragos Tatulea Reviewed-by: Björn Töpel Reviewed-by: Jason Xing Reviewed-by: Eric Dumazet Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20261019174152.2932829-1-cjubran@nvidia.com Signed-off-by: Jakub Kicinski --- include/linux/skbuff.h | 4 ++-- net/core/skbuff.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h index a3ff380d43f0..d6894f463bf4 100644 --- a/include/linux/skbuff.h +++ b/include/linux/skbuff.h @@ -1036,8 +1036,6 @@ struct sk_buff { __u16 tc_index; /* traffic control index */ #endif - u16 alloc_cpu; - union { __wsum csum; struct { @@ -1088,6 +1086,8 @@ struct sk_buff { ); /* end headers group */ + u16 alloc_cpu; + /* These elements must be at the end, see alloc_skb() for details. */ sk_buff_data_t tail; sk_buff_data_t end; diff --git a/net/core/skbuff.c b/net/core/skbuff.c index 41beaf625421..ab178af02a22 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -1593,7 +1593,6 @@ static void __copy_skb_header(struct sk_buff *new, const struct sk_buff *old) #ifdef CONFIG_NET_RX_BUSY_POLL CHECK_SKB_FIELD(napi_id); #endif - CHECK_SKB_FIELD(alloc_cpu); #ifdef CONFIG_XPS CHECK_SKB_FIELD(sender_cpu); #endif @@ -1625,6 +1624,7 @@ static struct sk_buff *__skb_clone(struct sk_buff *n, struct sk_buff *skb) C(pfmemalloc); C(pp_recycle); n->destructor = NULL; + C(alloc_cpu); C(tail); C(end); C(head); From 82e31923c0553fdbfcddcd910ca07952880ca9b9 Mon Sep 17 00:00:00 2001 From: Dmitry Antipov Date: Mon, 19 Oct 2026 11:22:14 +0300 Subject: [PATCH 78/94] tcp_bpf: propagate error code in tcp_bpf_sendmsg() In tcp_bpf_sendmsg(), the value returned by sk_msg_memcopy_from_iter() is checked for error but not propagated to 'out_err' label, so error handling may not work as expected. Fix this by assigning error value to 'err', thus making sure that error won't be silently ignored. Reported-by: Sashiko Fixes: 5153a75ef34b ("tcp_bpf: Fix copied value in tcp_bpf_sendmsg") Signed-off-by: Dmitry Antipov Reviewed-by: Kuniyuki Iwashima Reviewed-by: Jason Xing Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20261019082214.102398-1-dmantipov@yandex.ru Signed-off-by: Jakub Kicinski --- net/ipv4/tcp_bpf.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/ipv4/tcp_bpf.c b/net/ipv4/tcp_bpf.c index 2e234d155b5e..0ce583f68d3b 100644 --- a/net/ipv4/tcp_bpf.c +++ b/net/ipv4/tcp_bpf.c @@ -581,6 +581,7 @@ static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size) copy); if (ret < 0) { sk_msg_trim(sk, msg_tx, osize); + err = ret; goto out_err; } From 1234707482053f1d1ac7c5756207d2c29057974c Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Sat, 17 Oct 2026 04:42:38 -0700 Subject: [PATCH 79/94] virtio_net: atomically disable rx mode work virtio-net uses a boolean to gate rx_mode_work during device teardown. The flag is changed under RTNL, but ndo_set_rx_mode checks it under the address-list lock. A callback can therefore pass the check and enqueue work after teardown has flushed it. The work is also enabled before late probe operations that can fail, allowing probe unwind to free the netdev with work still queued. KASAN reports the worker using a virtqueue that probe unwind already freed: BUG: KASAN: slab-use-after-free in virtqueue_add_split Workqueue: events virtnet_rx_mode_work Call Trace: virtqueue_add_split (drivers/virtio/virtio_ring.c:706) virtqueue_add_sgs (drivers/virtio/virtio_ring.c:2839) virtnet_send_command_reply (drivers/net/virtio_net.c:3565) virtnet_rx_mode_work (drivers/net/virtio_net.c:3833) process_one_work (kernel/workqueue.c:3322) worker_thread (kernel/workqueue.c:3405) Freed by task 119: kfree (mm/slub.c:6625) vp_del_vq.isra.0 (drivers/virtio/virtio_pci_common.c:259) vp_del_vqs (drivers/virtio/virtio_pci_common.c:285) virtnet_probe (drivers/net/virtio_net.c:7129) Use the workqueue disable mechanism to atomically reject new queue attempts and drain running work. Keep work disabled until probe succeeds or restore has reattached the netdev, then enable it and replay receive-mode state for a running interface. Disable it before resource teardown in freeze and remove. Fixes: b9f7425239a0 ("virtio-net: convert rx mode setting to use workqueue") Cc: stable@vger.kernel.org Reported-by: Assisted-by: LLM Signed-off-by: Weiming Shi Link: https://patch.msgid.link/20261017114237.2786302-2-bestswngs@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/virtio_net.c | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c index daab43ac92ce..4e8a792cec0e 100644 --- a/drivers/net/virtio_net.c +++ b/drivers/net/virtio_net.c @@ -441,9 +441,6 @@ struct virtnet_info { /* Work struct for setting rx mode */ struct work_struct rx_mode_work; - /* OK to queue work setting RX mode? */ - bool rx_mode_work_enabled; - /* Does the affinity hint is set for virtqueues? */ bool affinity_hint_set; @@ -710,16 +707,13 @@ static void virtnet_rq_free_buf(struct virtnet_info *vi, static void enable_rx_mode_work(struct virtnet_info *vi) { - rtnl_lock(); - vi->rx_mode_work_enabled = true; - rtnl_unlock(); + if (enable_work(&vi->rx_mode_work) && netif_running(vi->dev)) + schedule_work(&vi->rx_mode_work); } static void disable_rx_mode_work(struct virtnet_info *vi) { - rtnl_lock(); - vi->rx_mode_work_enabled = false; - rtnl_unlock(); + disable_work_sync(&vi->rx_mode_work); } static void virtqueue_napi_schedule(struct napi_struct *napi, @@ -3925,8 +3919,7 @@ static void virtnet_set_rx_mode(struct net_device *dev) { struct virtnet_info *vi = netdev_priv(dev); - if (vi->rx_mode_work_enabled) - schedule_work(&vi->rx_mode_work); + schedule_work(&vi->rx_mode_work); } static int virtnet_vlan_rx_add_vid(struct net_device *dev, @@ -5742,7 +5735,6 @@ static void virtnet_freeze_down(struct virtio_device *vdev) /* Make sure no work handler is accessing the device */ flush_work(&vi->config_work); disable_rx_mode_work(vi); - flush_work(&vi->rx_mode_work); if (netif_running(vi->dev)) { rtnl_lock(); @@ -5772,8 +5764,6 @@ static int virtnet_restore_up(struct virtio_device *vdev) virtio_device_ready(vdev); - enable_rx_mode_work(vi); - if (netif_running(vi->dev)) { rtnl_lock(); err = virtnet_open(vi->dev); @@ -5785,6 +5775,8 @@ static int virtnet_restore_up(struct virtio_device *vdev) netif_tx_lock_bh(vi->dev); netif_device_attach(vi->dev); netif_tx_unlock_bh(vi->dev); + + enable_rx_mode_work(vi); return 0; err_destroy_pools: @@ -6880,6 +6872,7 @@ static int virtnet_probe(struct virtio_device *vdev) INIT_WORK(&vi->config_work, virtnet_config_changed_work); INIT_WORK(&vi->rx_mode_work, virtnet_rx_mode_work); + disable_work(&vi->rx_mode_work); if (virtio_has_feature(vdev, VIRTIO_NET_F_MRG_RXBUF)) { vi->mergeable_rx_bufs = true; @@ -7038,8 +7031,6 @@ static int virtnet_probe(struct virtio_device *vdev) if (vi->has_rss || vi->has_rss_hash_report) virtnet_init_default_rss(vi); - enable_rx_mode_work(vi); - for (i = 0; i < ARRAY_SIZE(guest_offloads); i++) { unsigned int fbit; @@ -7143,6 +7134,8 @@ static int virtnet_probe(struct virtio_device *vdev) goto free_unregister_netdev; } + enable_rx_mode_work(vi); + pr_debug("virtnet: registered device %s with %d RX and TX vq's\n", dev->name, max_queue_pairs); @@ -7195,7 +7188,6 @@ static void virtnet_remove(struct virtio_device *vdev) /* Make sure no work handler is accessing the device. */ flush_work(&vi->config_work); disable_rx_mode_work(vi); - flush_work(&vi->rx_mode_work); virtnet_free_irq_moder(vi); From 7eed173447fba937a19e2f5f2dc79780793f72e7 Mon Sep 17 00:00:00 2001 From: Alexandre Cassen Date: Mon, 19 Oct 2026 15:31:56 +0200 Subject: [PATCH 80/94] net/mlx5: HWS, fix modify header pattern compare on big-endian mlx5hws_pat_compare_pattern() casts each __be64 action to __be32 to compare only its control dword. On big-endian this keeps the data dword instead, which HWS clears from the pattern of set and add actions. Two such actions writing the same value into different fields then share a pattern and the second rule modifies the wrong field. Read the first dword of each action from memory instead. Little-endian behavior is unchanged. Discovered by an AI code review agent [1] and confirmed by code inspection. Not reproduced for lack of big-endian hardware running HWS. Tested for regressions on ConnectX-7 with firmware 28.48.1000 on a little-endian host. Fixes: aefc15a0fa1c ("net/mlx5: HWS, added modify header pattern and args handling") Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261010120147.135825-1-acassen%40corp.free.fr [1] Reviewed-by: Simon Horman Signed-off-by: Alexandre Cassen Reviewed-by: Yevgeny Kliteynik Link: https://patch.msgid.link/20261019133159.1742411-2-acassen@corp.free.fr Signed-off-by: Paolo Abeni --- .../net/ethernet/mellanox/mlx5/core/steering/hws/pat_arg.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/pat_arg.c b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/pat_arg.c index 9af449c2b4d5..512299b59fd0 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/pat_arg.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/pat_arg.c @@ -116,8 +116,8 @@ static bool mlx5hws_pat_compare_pattern(int cur_num_of_actions, return false; } else { /* Compare just the control, not the values */ - if ((__force __be32)actions[i] != - (__force __be32)cur_actions[i]) + if (*(__be32 *)&actions[i] != + *(__be32 *)&cur_actions[i]) return false; } } From d425ec9bb9371d52d492ac4d09f0da6e26561684 Mon Sep 17 00:00:00 2001 From: Alexandre Cassen Date: Mon, 19 Oct 2026 15:31:57 +0200 Subject: [PATCH 81/94] net/mlx5: HWS, fix modify header pool match for copy actions mlx5_fs_hws_mh_pool_match() compares a single dword of each action since rules pass their values as arguments. Copy and add_field actions keep their destination in the second dword and HWS reads them from the pattern alone. Two modify headers whose copies differ only in destination then share a pool and the second rule writes to the wrong field. Use the HWS core comparison that checks these actions in full. Found while checking a Sashiko AI review report and confirmed against the HWS pattern code. Not reproduced. No in-kernel user puts such actions on the FDB but RDMA flow actions can. Tested for regressions on ConnectX-7 with firmware 28.48.1000 on a little-endian host. Fixes: b36315ca69cb ("net/mlx5: fs, add HWS modify header API function") Signed-off-by: Alexandre Cassen Reviewed-by: Yevgeny Kliteynik Link: https://patch.msgid.link/20261019133159.1742411-3-acassen@corp.free.fr Signed-off-by: Paolo Abeni --- .../mellanox/mlx5/core/steering/hws/fs_hws_pools.c | 11 ++++------- .../mellanox/mlx5/core/steering/hws/pat_arg.c | 8 ++++---- .../mellanox/mlx5/core/steering/hws/pat_arg.h | 5 +++++ 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/fs_hws_pools.c b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/fs_hws_pools.c index 86828e329187..05aa143fa088 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/fs_hws_pools.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/fs_hws_pools.c @@ -3,6 +3,7 @@ #include #include "fs_hws_pools.h" +#include "internal.h" #define MLX5_FS_HWS_DEFAULT_BULK_LEN 65536 #define MLX5_FS_HWS_POOL_MAX_THRESHOLD BIT(18) @@ -389,7 +390,7 @@ bool mlx5_fs_hws_mh_pool_match(struct mlx5_fs_pool *mh_pool, struct mlx5hws_action_mh_pattern *pattern) { struct mlx5hws_action_mh_pattern *pool_pattern; - int num_actions, i; + int num_actions; pool_pattern = mh_pool->pool_ctx; if (WARN_ON_ONCE(!pool_pattern)) @@ -398,12 +399,8 @@ bool mlx5_fs_hws_mh_pool_match(struct mlx5_fs_pool *mh_pool, if (pattern->sz != pool_pattern->sz) return false; num_actions = pattern->sz / MLX5_UN_SZ_BYTES(set_add_copy_action_in_auto); - for (i = 0; i < num_actions; i++) { - if ((__force __be32)pattern->data[i] != - (__force __be32)pool_pattern->data[i]) - return false; - } - return true; + return mlx5hws_pat_compare_pattern(num_actions, pool_pattern->data, + num_actions, pattern->data); } struct mlx5hws_action *mlx5_fc_get_hws_action(struct mlx5hws_context *ctx, diff --git a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/pat_arg.c b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/pat_arg.c index 512299b59fd0..a49115f04025 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/pat_arg.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/pat_arg.c @@ -96,10 +96,10 @@ void mlx5hws_pat_uninit_pattern_cache(struct mlx5hws_pattern_cache *cache) kfree(cache); } -static bool mlx5hws_pat_compare_pattern(int cur_num_of_actions, - __be64 cur_actions[], - int num_of_actions, - __be64 actions[]) +bool mlx5hws_pat_compare_pattern(int cur_num_of_actions, + __be64 cur_actions[], + int num_of_actions, + __be64 actions[]) { int i; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/pat_arg.h b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/pat_arg.h index 7fbd8dc7aa18..2be7787104e1 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/pat_arg.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/pat_arg.h @@ -49,6 +49,11 @@ int mlx5hws_pat_init_pattern_cache(struct mlx5hws_pattern_cache **cache); void mlx5hws_pat_uninit_pattern_cache(struct mlx5hws_pattern_cache *cache); +bool mlx5hws_pat_compare_pattern(int cur_num_of_actions, + __be64 cur_actions[], + int num_of_actions, + __be64 actions[]); + bool mlx5hws_pat_verify_actions(struct mlx5hws_context *ctx, __be64 pattern[], size_t sz); int mlx5hws_arg_create(struct mlx5hws_context *ctx, From 9b8ae9aaa1b8e2b6c1cbf5613d3a37923cf53bc5 Mon Sep 17 00:00:00 2001 From: Jijie Shao Date: Fri, 16 Oct 2026 19:04:21 +0800 Subject: [PATCH 82/94] net: hibmcge: set netdev_ops before hbg_init() to fix NULL pointer dereference The driver assigns netdev_ops after hbg_init(), but hbg_init() calls hbg_mdio_init() -> hbg_phy_connect() -> phy_connect_direct() -> phy_attach_direct() -> phy_link_topo_add_phy(), which invokes netdev_need_ops_lock(). With CONFIG_NET_SHAPER enabled, this function dereferences dev->netdev_ops->net_shaper_ops. Since netdev_ops is still NULL at this point, a NULL pointer dereference occurs. Move the netdev_ops assignment before hbg_init() so that netdev_need_ops_lock() can safely access netdev_ops during PHY attachment. The issue was hit and verified on an evaluation board. Fixes: ff4edac6e9bd ("net: hibmcge: Implement some .ndo functions") Signed-off-by: Jijie Shao Reviewed-by: Daniel Machon Link: https://patch.msgid.link/20261016110421.2288670-1-shaojijie@huawei.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/hisilicon/hibmcge/hbg_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/hisilicon/hibmcge/hbg_main.c b/drivers/net/ethernet/hisilicon/hibmcge/hbg_main.c index 66792e2b0fad..a518dc66d9ef 100644 --- a/drivers/net/ethernet/hisilicon/hibmcge/hbg_main.c +++ b/drivers/net/ethernet/hisilicon/hibmcge/hbg_main.c @@ -448,6 +448,7 @@ static int hbg_probe(struct pci_dev *pdev, const struct pci_device_id *ent) if (ret) return ret; + netdev->netdev_ops = &hbg_netdev_ops; ret = hbg_init(priv); if (ret) return ret; @@ -460,7 +461,6 @@ static int hbg_probe(struct pci_dev *pdev, const struct pci_device_id *ent) netdev->pcpu_stat_type = NETDEV_PCPU_STAT_TSTATS; netdev->max_mtu = priv->dev_specs.max_mtu; netdev->min_mtu = priv->dev_specs.min_mtu; - netdev->netdev_ops = &hbg_netdev_ops; netdev->watchdog_timeo = 5 * HZ; hbg_hw_set_mtu(priv, ETH_DATA_LEN); From b0e4ea3a0cd42eccad9cc4153afcad555480b9e6 Mon Sep 17 00:00:00 2001 From: Willem de Bruijn Date: Mon, 19 Oct 2026 23:15:50 -0400 Subject: [PATCH 83/94] virtio-net: fix frag overflow in big packets receive In big packets mode with guest GSO, add_recvbuf_big() posts a chain of MAX_SKB_FRAGS + 1 pages. page_to_skb() copies only ETH_HLEN of the first page into the linear area and adds the rest of that page as a frag, then one frag per remaining page. A device that fills the entire buffer therefore causes MAX_SKB_FRAGS + 1 frags, writing one skb_frag_t past the end of skb_shinfo(skb)->frags[]. Post one page less. The buffer still fits a maximal GSO packet. A well behaved device does not trigger this, as GSO packets are at most 64KB. Also check the frag count in page_to_skb() before adding any frag as defense in depth. Fixes: 9ab86bbcf8be ("virtio_net: Defer skb allocation in receive path Date: Wed, 13 Jan 2010 12:53:38 -0800") Signed-off-by: Willem de Bruijn Acked-by: Michael S. Tsirkin Link: https://patch.msgid.link/20261020032108.81879-1-willemdebruijn.kernel@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/virtio_net.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c index 4e8a792cec0e..584bc05c577c 100644 --- a/drivers/net/virtio_net.c +++ b/drivers/net/virtio_net.c @@ -900,6 +900,15 @@ static struct sk_buff *page_to_skb(struct virtnet_info *vi, } BUG_ON(offset >= PAGE_SIZE); + + /* The first frag starts at offset, each next frag is a full page. */ + if (unlikely(DIV_ROUND_UP(offset + len, PAGE_SIZE) > MAX_SKB_FRAGS)) { + pr_debug("%s: rx error: len %u needs more than %d frags\n", + vi->dev->name, len, MAX_SKB_FRAGS); + kfree_skb(skb); + return NULL; + } + while (len) { unsigned int frag_size = min((unsigned)PAGE_SIZE - offset, len); skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, page, offset, @@ -6709,7 +6718,8 @@ static void virtnet_set_big_packets(struct virtnet_info *vi, const int mtu) */ if (mtu > ETH_DATA_LEN || guest_gso) { vi->big_packets = true; - vi->big_packets_num_skbfrags = guest_gso ? MAX_SKB_FRAGS : DIV_ROUND_UP(mtu, PAGE_SIZE); + vi->big_packets_num_skbfrags = guest_gso ? MAX_SKB_FRAGS - 1 : + DIV_ROUND_UP(mtu, PAGE_SIZE); } } From a031ec9ff5a2e55867430e74e9b497fe844116ba Mon Sep 17 00:00:00 2001 From: Daniel Machon Date: Tue, 20 Oct 2026 10:58:35 +0200 Subject: [PATCH 84/94] net: lan969x: fix DSM calendar for empty taxis lan969x_dsm_calendar_calc() computes the DSM calendar schedule of one taxi. sparx5_config_dsm_calendar() calls it for each taxi, reusing the same sparx5_calendar_data. Before this patch, the schedule is only cleared after the n_devs == 0 early return. For a taxi with no active ports, the function writes SPX5_DSM_CAL_EMPTY only to schedule[0] and leaves the remaining slots at whatever they held before. For the first taxi that is zero from kzalloc(), so sparx5_dsm_cal_len() returns a bogus length and sparx5_dsm_calendar_check() fails, which fails the probe: Port 0: distance 20 above limit 0 A later empty taxi inherits the previous taxi's schedule instead, which passes the check, so only an empty taxi 0 triggers the failure. Clear the full schedule up front on every path, and on the empty path place a single idle marker (SPX5_DSM_CAL_MAX_DEVS_PER_TAXI) in slot 0, so the update step sees a calendar length of one and programs one idle slot into the hardware, matching what sparx5_dsm_calendar_calc() does. Reported by a customer whose DT enables none of the ports in a taxi. Reproduced on lan969x by disabling all taxi 0 ports in the DT. Fixes: 5d2ba3941016 ("net: lan969x: add function for calculating the DSM calendar") Signed-off-by: Daniel Machon Link: https://patch.msgid.link/20261020-lan969x-dsm-empty-taxi-v1-1-14790f1e7430@microchip.com Signed-off-by: Jakub Kicinski --- .../ethernet/microchip/sparx5/lan969x/lan969x_calendar.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_calendar.c b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_calendar.c index e857640df185..4442c0c1a957 100644 --- a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_calendar.c +++ b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_calendar.c @@ -82,6 +82,9 @@ int lan969x_dsm_calendar_calc(struct sparx5 *sparx5, u32 taxi, struct lan969x_dsm_cal_dev_speed *speed; int err; + for (u32 i = 0; i < SPX5_DSM_CAL_LEN; i++) + data->schedule[i] = SPX5_DSM_CAL_EMPTY; + /* Maximum bandwidth for this taxi */ taxi_bw = (128 * 1000000) / sparx5_clk_period(sparx5->coreclock); @@ -125,7 +128,7 @@ int lan969x_dsm_calendar_calc(struct sparx5 *sparx5, u32 taxi, } if (n_devs == 0) { - data->schedule[0] = SPX5_DSM_CAL_EMPTY; + data->schedule[0] = SPX5_DSM_CAL_MAX_DEVS_PER_TAXI; return 0; } @@ -167,9 +170,6 @@ int lan969x_dsm_calendar_calc(struct sparx5 *sparx5, u32 taxi, return -EINVAL; } - for (u32 i = 0; i < SPX5_DSM_CAL_LEN; i++) - data->schedule[i] = SPX5_DSM_CAL_EMPTY; - /* Place the remaining devices */ for (u32 i = 0; i < DSM_CAL_DEV_MAX; i++) { speed = &dev_speeds[i]; From 9770818b8a92fb5fb77725bd5055f165eee3f52e Mon Sep 17 00:00:00 2001 From: Daniel Machon Date: Tue, 20 Oct 2026 10:57:27 +0200 Subject: [PATCH 85/94] net: lan969x: don't disable hardirqs in fdma tx clear lan969x_fdma_tx_clear_buf() runs from the NAPI poll and frees the skbs of completed tx DCBs with napi_consume_skb(), under sparx5->tx_lock. Before this patch, the lock is taken with spin_lock_irqsave(), so the skbs are freed with hardirqs disabled. RX skbs are marked for page_pool recycling, so when such an skb is forwarded back out and its tx completion frees it here, the free path reaches __page_pool_put_page(), which warns via lockdep_assert_no_hardirq(). Take tx_lock with a plain spin_lock(), as the only other tx_lock user, sparx5_port_xmit_impl(), already does. tx_clear_buf() is only called from softirq context, so disabling hardirqs is not needed. Found with lockdep enabled (CONFIG_PROVE_LOCKING), which the assert depends on. Reproduced on lan969x by redirecting received frames out of another port with tc mirred. Fixes: d84ad2c0d80c ("net: lan969x: add FDMA implementation") Signed-off-by: Daniel Machon Link: https://patch.msgid.link/20261020-lan969x-fdma-tx-clear-irq-v1-1-b9f9d1eda317@microchip.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c index c7e0e58ff0f7..053d125eda65 100644 --- a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c +++ b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c @@ -54,10 +54,9 @@ static void lan969x_fdma_tx_clear_buf(struct sparx5 *sparx5, int weight) { struct fdma *fdma = &sparx5->tx.fdma; struct sparx5_tx_buf *db; - unsigned long flags; int i; - spin_lock_irqsave(&sparx5->tx_lock, flags); + spin_lock(&sparx5->tx_lock); for (i = 0; i < fdma->n_dcbs; ++i) { db = &sparx5->tx.dbs[i]; @@ -83,7 +82,7 @@ static void lan969x_fdma_tx_clear_buf(struct sparx5 *sparx5, int weight) db->used = false; } - spin_unlock_irqrestore(&sparx5->tx_lock, flags); + spin_unlock(&sparx5->tx_lock); } static void lan969x_fdma_free_pages(struct sparx5_rx *rx) From b5b8c81836e2bec7b444dfa2b14fd14e912e9d63 Mon Sep 17 00:00:00 2001 From: Qingfang Deng Date: Tue, 20 Oct 2026 17:21:11 +0800 Subject: [PATCH 86/94] pppoe: delete session hash entries by pointer match pppoe_connect() copies the requested PPPoE address to a socket before attempting to insert the socket into the session hash table. If the tuple already exists, __set_item() returns -EALREADY, but the copied address and ifindex remain set on the failed socket. Closing that socket then deletes the existing socket's hash entry through pppoe_release(), preventing it from receiving further session traffic. An unprivileged process in the same network namespace can trigger this through a duplicate connection attempt. To avoid the accidental delete, pass the pppox_sock to __delete_item(), and unlink only an entry whose pointer matches the requested socket. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Qingfang Deng Link: https://patch.msgid.link/20261020092118.47416-1-qingfang.deng@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/ppp/pppoe.c | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/drivers/net/ppp/pppoe.c b/drivers/net/ppp/pppoe.c index bf7414b46a26..ee0e318d6a2a 100644 --- a/drivers/net/ppp/pppoe.c +++ b/drivers/net/ppp/pppoe.c @@ -194,18 +194,16 @@ static int __set_item(struct pppoe_net *pn, struct pppox_sock *po) return 0; } -static void __delete_item(struct pppoe_net *pn, __be16 sid, - char *addr, int ifindex) +static void __delete_item(struct pppoe_net *pn, const struct pppox_sock *po) { - int hash = hash_item(sid, addr); + int hash = hash_item(po->pppoe_pa.sid, po->pppoe_pa.remote); struct pppox_sock *ret, __rcu **src; ret = rcu_dereference_protected(pn->hash_table[hash], lockdep_is_held(&pn->hash_lock)); src = &pn->hash_table[hash]; while (ret) { - if (cmp_addr(&ret->pppoe_pa, sid, addr) && - ret->pppoe_ifindex == ifindex) { + if (ret == po) { struct pppox_sock *next; next = rcu_dereference_protected(ret->next, @@ -236,11 +234,10 @@ static inline struct pppox_sock *get_item(struct pppoe_net *pn, __be16 sid, return po; } -static inline void delete_item(struct pppoe_net *pn, __be16 sid, - char *addr, int ifindex) +static void delete_item(struct pppoe_net *pn, const struct pppox_sock *po) { spin_lock(&pn->hash_lock); - __delete_item(pn, sid, addr, ifindex); + __delete_item(pn, po); spin_unlock(&pn->hash_lock); } @@ -570,8 +567,7 @@ static int pppoe_release(struct socket *sock) * protect "po" from concurrent updates * on pppoe_flush_dev */ - delete_item(pn, po->pppoe_pa.sid, po->pppoe_pa.remote, - po->pppoe_ifindex); + delete_item(pn, po); sock_orphan(sk); sock->sk = NULL; @@ -621,8 +617,7 @@ static int pppoe_connect(struct socket *sock, struct sockaddr_unsized *uservaddr if (stage_session(po->pppoe_pa.sid)) { pppox_unbind_sock(sk); pn = pppoe_pernet(sock_net(sk)); - delete_item(pn, po->pppoe_pa.sid, - po->pppoe_pa.remote, po->pppoe_ifindex); + delete_item(pn, po); if (po->pppoe_dev) { dev_put(po->pppoe_dev); po->pppoe_dev = NULL; @@ -672,8 +667,7 @@ static int pppoe_connect(struct socket *sock, struct sockaddr_unsized *uservaddr error = ppp_register_net_channel(dev_net(dev), &po->chan); if (error) { - delete_item(pn, po->pppoe_pa.sid, - po->pppoe_pa.remote, po->pppoe_ifindex); + delete_item(pn, po); goto err_put; } From f16b8c6a656e62bfa7bb6a3b876f9ccdaf53bd43 Mon Sep 17 00:00:00 2001 From: Tianyi Gao Date: Fri, 16 Oct 2026 22:42:16 +0100 Subject: [PATCH 87/94] veth: schedule the peer's NAPI after XDP_TX when only GRO is enabled veth_xdp_xmit() queues XDP_TX frames on the peer's ring if the peer has NAPI. A peer with GRO enabled and no XDP program also has NAPI. veth_xdp_flush() then schedules the peer's NAPI, but only if the peer's xdp_prog is set, so for such a peer the frames are never processed. Nothing else schedules that NAPI by default. Once the ring is full, every XDP_TX frame is dropped, and so are the skbs that veth_xmit() queues on the same ring. Commit 0e672f306a28 ("veth: check for NAPI instead of xdp_prog before xmit of XDP frame") changed the check in veth_xdp_xmit() but not the one in veth_xdp_flush(). Make veth_xdp_flush() check the napi pointer instead of xdp_prog, as veth_xdp_xmit() does. Fixes: 0e672f306a28 ("veth: check for NAPI instead of xdp_prog before xmit of XDP frame") Signed-off-by: Tianyi Gao Reviewed-by: Mohsin Bashir Acked-by: Lorenzo Bianconi Acked-by: Jesper Dangaard Brouer Link: https://patch.msgid.link/20261016214217.16942-2-tianyi@cloudflare.com Signed-off-by: Jakub Kicinski --- drivers/net/veth.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/veth.c b/drivers/net/veth.c index 643b97dc5245..9405ffac598a 100644 --- a/drivers/net/veth.c +++ b/drivers/net/veth.c @@ -612,7 +612,7 @@ static void veth_xdp_flush(struct veth_rq *rq, struct veth_xdp_tx_bq *bq) rcv_priv = netdev_priv(rcv); rcv_rq = &rcv_priv->rq[veth_select_rxq(rcv)]; /* xdp_ring is initialized on receive side? */ - if (unlikely(!rcu_access_pointer(rcv_rq->xdp_prog))) + if (unlikely(!rcu_access_pointer(rcv_rq->napi))) goto out; __veth_xdp_flush(rcv_rq); From a70575ae9e812908acf658ec785f81daeba1a23b Mon Sep 17 00:00:00 2001 From: Tianyi Gao Date: Fri, 16 Oct 2026 22:42:17 +0100 Subject: [PATCH 88/94] selftests: net: veth: test XDP_TX to a peer with GRO enabled Test that XDP_TX frames reach a peer that has GRO enabled and no XDP program. GRO enables NAPI on the peer, so the frames are queued on the peer's ring, and the peer's NAPI must be scheduled to process them. Attach a program that returns XDP_TX for every frame, send 5 echo requests and check that all of them were sent back and that the peer's NAPI received them, as counted in its xdp_packets statistic. IPv6 is disabled on both devices, so that only the echo requests are counted. Add the program to xdp_dummy.bpf.c in a section of its own, so that the tests that load "section xdp" still get XDP_PASS. Signed-off-by: Tianyi Gao Link: https://patch.msgid.link/20261016214217.16942-3-tianyi@cloudflare.com Signed-off-by: Jakub Kicinski --- .../testing/selftests/net/lib/xdp_dummy.bpf.c | 6 +++++ tools/testing/selftests/net/veth.sh | 25 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/tools/testing/selftests/net/lib/xdp_dummy.bpf.c b/tools/testing/selftests/net/lib/xdp_dummy.bpf.c index e73fab3edd9f..95abd67cc184 100644 --- a/tools/testing/selftests/net/lib/xdp_dummy.bpf.c +++ b/tools/testing/selftests/net/lib/xdp_dummy.bpf.c @@ -16,4 +16,10 @@ int xdp_dummy_prog_frags(struct xdp_md *ctx) return XDP_PASS; } +SEC("xdp_tx") +int xdp_dummy_tx(struct xdp_md *ctx) +{ + return XDP_TX; +} + char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/net/veth.sh b/tools/testing/selftests/net/veth.sh index 8b867e0675a7..0167f78604ef 100755 --- a/tools/testing/selftests/net/veth.sh +++ b/tools/testing/selftests/net/veth.sh @@ -334,6 +334,31 @@ ip netns exec $NS_DST ethtool -K veth$DST rx-udp-gro-forwarding on chk_gro " - aggregation with TSO off" 1 cleanup +create_ns +ip netns exec $NS_SRC sysctl -qw net.ipv6.conf.veth$SRC.disable_ipv6=1 +ip netns exec $NS_DST sysctl -qw net.ipv6.conf.veth$DST.disable_ipv6=1 +ip netns exec $NS_SRC ethtool -K veth$SRC gro on +ip -n $NS_DST link set dev veth$DST xdp object ${BPF_FILE} section xdp_tx +# veth$DST sends every frame back, so neither ARP nor ping gets an answer +mac=`ip netns exec $NS_DST cat /sys/class/net/veth$DST/address` +ip -n $NS_SRC neigh replace $BM_NET_V4$DST lladdr $mac dev veth$SRC +ip netns exec $NS_SRC ping -qc 5 -i 0.01 -W 1 $BM_NET_V4$DST >/dev/null +tx=`ip netns exec $NS_DST ethtool -S veth$DST | \ + awk '/rx_queue_[0-9]+_xdp_tx:/ {s += $2} END {print s + 0}'` +rx=`ip netns exec $NS_SRC ethtool -S veth$SRC | \ + awk '/rx_queue_[0-9]+_xdp_packets:/ {s += $2} END {print s + 0}'` +printf "%-60s" "xdp_tx to peer with gro on" +if [ $tx -ne 5 ]; then + echo " fail - $tx frames sent back, expected 5" + ret=1 +elif [ $rx -ne 5 ]; then + echo " fail - $rx frames received by the peer NAPI, expected 5" + ret=1 +else + echo " ok " +fi +cleanup + create_ns CUR_TX=1 From 4957ea03bf2696f5da12c09144f48d040696ecc2 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Tue, 20 Oct 2026 17:19:54 +0200 Subject: [PATCH 89/94] net: shrink the payload-only part of skb dataref to one bit skb_shinfo(skb)->dataref is split in two 16-bit halves: the lower one counts all references, the upper one the payload-only (nohdr) references. __skb_clone() increments dataref without bound, and skb_cloned() only checks the low 16 bits. With 65,537 references the low half reads 1, the head looks private, and pskb_expand_head() (or skb_unclone(), skb_cow(), ...) frees it while clones still point to it. An unprivileged user can reach this from a user and network namespace: about 32,770 AF_PACKET sockets on lo bring dataref to 0x10001, then any pskb_expand_head() on the receive path (here a pull of a large IPv6 Hop-by-Hop header) frees the shared head: BUG: KASAN: slab-use-after-free in _copy_to_iter+0x187/0x1330 Read of size 1590 at addr ff110001104a9002 by task dataref_wrap/87 skb_copy_datagram_iter+0x60/0x190 packet_recvmsg+0x1f5/0x12b0 __sys_recvfrom+0x18d/0x290 Freed by task 87: kfree+0xf3/0x3b0 pskb_expand_head+0x344/0xeb0 __pskb_pull_tail+0xf9/0x1bf0 ipv6_parse_hopopts+0x377/0x4f0 ip6_rcv_core+0xc67/0x1620 The upper half never holds more than 1. It is only set by __skb_header_release(), with atomic_set() on an skb owning its head (TCP/SCTP output, and GRO which never sees cloned skbs). __skb_clone() clears nohdr, and __copy_skb_header() does not copy it. Move SKB_DATAREF_SHIFT to 30, so that the reference count gets 30 bits, and dataref stays positive. Wrapping it would need 2^30 live clones of a single skb, which socket buffer limits and memory make unreachable. This issue was found by Anthropic using Claude, and validated by Ada Logics. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: Stephan Lipp Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20261020151954.22953-1-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- include/linux/skbuff.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h index d6894f463bf4..707082f68f21 100644 --- a/include/linux/skbuff.h +++ b/include/linux/skbuff.h @@ -633,9 +633,9 @@ struct skb_shared_info { * * Transport layers send out clones of payload skbs they hold for * retransmissions. To allow lower layers of the stack to prepend their headers - * we split &skb_shared_info.dataref into two halves. - * The lower 16 bits count the overall number of references. - * The higher 16 bits indicate how many of the references are payload-only. + * we split &skb_shared_info.dataref into two parts. + * The lower 30 bits count the overall number of references. + * Bit 30 indicates whether one of the references is payload-only. * skb_header_cloned() checks if skb is allowed to add / write the headers. * * The creator of the skb (e.g. TCP) marks its skb as &sk_buff.nohdr @@ -655,7 +655,7 @@ struct skb_shared_info { * Having multiple payload-only skbs with different lengths of hdr_len is not * possible. The payload-only skbs should never leave their owner. */ -#define SKB_DATAREF_SHIFT 16 +#define SKB_DATAREF_SHIFT 30 #define SKB_DATAREF_MASK ((1 << SKB_DATAREF_SHIFT) - 1) From e0a958ddfc89f2e1c43448cdaeb5b30e54e19448 Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Tue, 20 Oct 2026 18:36:59 +0300 Subject: [PATCH 90/94] net/mlx5e: Stop asserting for RTNL when closing channels mlx5e_close_channels() has an ASSERT_RTNL() from the time when RTNL was required for closing channels. But the driver has been switched to using the netdev lock a while back, and this fired from a flow which was forgotten during the conversion: mlx5e_tx_timeout_work() -> mlx5e_reporter_tx_timeout() -> mlx5e_health_report() -> mlx5e_tx_reporter_timeout_recover() -> mlx5e_safe_reopen_channels() -> mlx5e_safe_switch_params() -> mlx5e_close_channels() The RTNL is not acquired here, but the netdev lock is, and it is enough. Rather than switching to netdev_assert_locked(), simply drop the assert. Upper layers (networking core, ethtool core) provide locking and lower layers (NAPI helpers) assert that the netdev lock is held. Fixes: 8f7b00307bf1 ("net/mlx5e: Convert mlx5 netdevs to instance locking") Signed-off-by: Cosmin Ratiu Reviewed-by: Carolina Jubran Signed-off-by: Tariq Toukan Acked-by: Stanislav Fomichev Link: https://patch.msgid.link/20261020153659.3440902-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en_main.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c index 4c6060d54fbe..38ce7d0a9001 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c @@ -3075,7 +3075,6 @@ void mlx5e_close_channels(struct mlx5e_channels *chs) { int i; - ASSERT_RTNL(); if (chs->ptp) { mlx5e_ptp_close(chs->ptp); chs->ptp = NULL; From a55eb6a622141352131aa6156bbd6ce5da79d9b5 Mon Sep 17 00:00:00 2001 From: Travis Howard Date: Mon, 19 Oct 2026 20:03:10 +0000 Subject: [PATCH 91/94] tg3: Add Datto SIRIS 5 1U Server to restart AER quirk table The Datto SIRIS 5 1U Server is a Dell OEMR XL R640 (Dell's OEM-ready variant of the PowerEdge R640) sold under Datto's identity. Its DMI strings report sys_vendor "datto" and product_name "SIRIS 5 1U Server", so it does not match the existing "PowerEdge R640" entry in tg3_restart_aer_quirk_table. As on the PowerEdge R640, a system restart triggers a fatal PCIe AER on the BCM5720 during ACPI _PTS for S5, which the iDRAC logs as PCI1318 and which hangs the reboot. Power off is unaffected. Clearing the same PCI_EXP_DEVCTL error-reporting bits on both BCM5720 functions from userspace immediately before restart allows the system to reboot normally, confirming the quirk applies to this platform. Fixes: e0efe83ed325 ("tg3: Disable tg3 PCIe AER on system reboot") Signed-off-by: Travis Howard Link: https://patch.msgid.link/20261019-tg3-datto-aer-quirk-v1-1-f0eccfb2ab5d@lafayetteinstrument.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/tg3.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/ethernet/broadcom/tg3.c b/drivers/net/ethernet/broadcom/tg3.c index 8b6806a79edf..b6aa10f2bdf7 100644 --- a/drivers/net/ethernet/broadcom/tg3.c +++ b/drivers/net/ethernet/broadcom/tg3.c @@ -18229,6 +18229,12 @@ static const struct dmi_system_id tg3_restart_aer_quirk_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "PowerEdge R750"), }, }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "datto"), + DMI_MATCH(DMI_PRODUCT_NAME, "SIRIS 5 1U Server"), + }, + }, {} }; From ffdf678dd16610e3f006dac904bd37c09684673c Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Tue, 20 Oct 2026 10:48:10 +0100 Subject: [PATCH 92/94] tun: Require CAP_NET_ADMIN to attach to an ownerless device MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Twenty years ago I made creating a tuntap device require CAP_NET_ADMIN in commit ca6bb5d7ab22 ("[NET]: Require CAP_NET_ADMIN to create tuntap devices.") The idea was to allow the permissions on /dev/net/tun to be relaxed to 0666 and thus allow owners of tun devices assigned with TUNSETOWNER to actually open it and get at their devices. What we missed at the time was that persistent devices with *no* owner were already essentially world-consumable, and anyone who could open the device node could also attach to those. Even then, using persistent devices with no explicitly set owner — on the (false) presumption that CAP_NET_ADMIN would be required to open them — wasn't an egregious userspace bug. There are cases where it does make sense to separate the creation/configuration of the netdevice, from the process which shovels packets into it. Since then, TUNSETGROUP was added a year later in commit 8c644623fe7e ("[NET]: Allow group ownership of TUN/TAP devices."). Its commit message noted that if both uid and gid are set, both have to match (in itself a bizarre choice) — but still not that with *neither* set, no permission at all was required. Then in 2012 commit c8d68e6be1c3 ("tuntap: multiqueue support") allowed new queues to be attached to a device from the moment of its creation, with no way for userspace to close the race between creation and a subsequent TUNSET{OWNER,GROUP}. In 2024, commit 3ca459eaba1bf ("tun: fix group permission check") tried to fix things to be saner: only require the user *or* the group to match, in line with standard POSIX permissions, and require CAP_NET_ADMIN if neither the user nor group authorised the access (although it put the CAP_NET_ADMIN check first, and would wrongly have reported the capability as having been used even when it was not). That fix was reverted in commit a70c7b3cbc06 ("tun: revert fix group permission check") due to allegedly causing a regression in an selinux test suite which *expected* the attachment to succeed when authorised by none of user, group, nor CAP_NET_ADMIN: https://lore.kernel.org/netdev/CAFqZXNtkCBT4f+PwyVRmQGoT3p1eVa01fCG_aNtpt6dakXncUg@mail.gmail.com/ We should not break UAPI. But we don't have to consider an selinux test case which deliberately exercises a security flaw to be UAPI worth preserving either. To which end, I have fixed it in selinux-testsuite commit b2f894f0016b ("tests/tun_tap: set an owner on the device for the relabel tests"). So: fix the lack of CAP_NET_ADMIN check on devices which *haven't* been assigned an owner or group, while leaving the weird tun ownership model as it was (user *and* group have to match, if both are set). If someone still wants to fix the latter (which wasn't actually the reason the previous attempt was reverted), they can do it under separate cover. Permit attaching to an existing device only if an owner and/or group was set and the caller matches all that were set, or if the caller has CAP_NET_ADMIN. The only behavioural change is that a device with no owner or group now requires CAP_NET_ADMIN to attach — precisely the loophole we want to close. And put the CAP_NET_ADMIN check in the right place this time. Fixes: ca6bb5d7ab22 ("[NET]: Require CAP_NET_ADMIN to create tuntap devices.") Reported-by: Andrew Lutomirski Signed-off-by: David Woodhouse Cc: stable@vger.kernel.org Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/3c77f88acff5b3c2617b22d64faad15f6212619c.camel@infradead.org Signed-off-by: Jakub Kicinski --- drivers/net/tun.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/drivers/net/tun.c b/drivers/net/tun.c index 242899f7fd07..7cca1a6f72ff 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -513,14 +513,18 @@ static u16 tun_select_queue(struct net_device *dev, struct sk_buff *skb, return ret; } -static inline bool tun_not_capable(struct tun_struct *tun) +static inline bool tun_permitted(struct tun_struct *tun) { const struct cred *cred = current_cred(); struct net *net = dev_net(tun->dev); - return ((uid_valid(tun->owner) && !uid_eq(cred->euid, tun->owner)) || - (gid_valid(tun->group) && !in_egroup_p(tun->group))) && - !ns_capable(net->user_ns, CAP_NET_ADMIN); + /* If both uid and gid are set, *both* must match */ + if ((uid_valid(tun->owner) || gid_valid(tun->group)) && + (!uid_valid(tun->owner) || uid_eq(cred->euid, tun->owner)) && + (!gid_valid(tun->group) || in_egroup_p(tun->group))) + return true; + + return ns_capable(net->user_ns, CAP_NET_ADMIN); } static void tun_set_real_num_queues(struct tun_struct *tun) @@ -2877,7 +2881,7 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) !!(tun->flags & IFF_MULTI_QUEUE)) return -EINVAL; - if (tun_not_capable(tun)) + if (!tun_permitted(tun)) return -EPERM; err = security_tun_dev_open(tun->security); if (err < 0) From 473167593f42551ce6b00c05cee8cd20309d45ac Mon Sep 17 00:00:00 2001 From: Binbin Deng <18983559317@163.com> Date: Mon, 19 Oct 2026 18:43:52 +0800 Subject: [PATCH 93/94] tipc: fix use-after-free of broadcast reception link at node release When the last reference to a tipc_node is dropped, the node itself is deferred to the RCU callback tipc_node_free(), but the nested broadcast reception link, n->bc_entry.link, is kfree()d synchronously in tipc_node_kref_release(), before call_rcu(), and the pointer is never cleared. Readers that obtained the node from node_list and then dereference bc_entry.link deep into the link (tipc_link_name() in tipc_nl_node_reset_link_stats(), bcl->name/stats in tipc_nl_add_bc_link() on the link dump path) therefore touch freed memory although the enclosing node stays alive for a full grace period. The sibling per-bearer links get "kfree + NULL" under the node write lock in tipc_node_clear_links(); the broadcast entry was missed. The race is reachable through the node cleanup timer: once a peer has been unreachable for NODE_CLEANUP_AFTER (300 s), tipc_node_timeout() -> tipc_node_cleanup() unlinks the node under the write lock, and the final tipc_node_put() at the caller runs outside node_list_lock, so the kfree executes in timer softirq context, concurrently with genl dump/ reset walkers that hold only rcu_read_lock(). (A netlink "peer remove" cannot race those walkers itself: tipc_genl_family has no parallel_ops, so all doit/dumpit calls serialize on the global genl lock.) Reproduced in a QEMU/KVM guest running v7.3.0 with KASAN and PREEMPT(full): 10 TIPC peer namespaces on veth bearers, bearer removal staggered so the 300 s node cleanup timers expire while other tasks loop link dumps with the broadcast filter and reset-link-stats lookups. KASAN hit within two cleanup cycles: BUG: KASAN: slab-use-after-free in strlen+0x85/0xa0 Read of size 1 at addr ffff88800b54a004 by task poc_eth/371 strlen+0x85/0xa0 tipc_nl_add_bc_link+0x2f9/0xd40 __tipc_nl_add_node_links+0x10c/0x140 tipc_nl_node_dump_link+0x8d5/0xd50 Freed by task 0: kfree+0x121/0x380 tipc_node_timeout+0xa33/0xcf0 run_timer_softirq+0x144/0x250 sysvec_apic_timer_interrupt with the freed object being the kmalloc-1k tipc_link allocated by tipc_link_bc_create() during peer discovery. The same defect fires in strcmp() called from tipc_nl_node_reset_link_stats(). Move the kfree of n->bc_entry.link into the tipc_node_free() RCU callback so the broadcast link lives exactly as long as the enclosing node. All readers are covered: the netlink walkers are inside rcu_read_lock(), and the packet reception path reaches the node via tipc_node_find(), whose kref defers kref_release itself. Found during a static-analysis-assisted audit: candidate sites were surfaced by a Coccinelle rule, and the reader/writer paths were confirmed by LLM-assisted (GLM-5.3) code inspection. Fixes: d25a01257e42 ("tipc: fix crash during node removal") Assisted-by: GLM-5.3 coccinelle Signed-off-by: Binbin Deng <18983559317@163.com> Reviewed-by: Simon Horman Reviewed-by: Tung Nguyen Link: https://patch.msgid.link/20261019104352.488347-1-18983559317@163.com Signed-off-by: Paolo Abeni --- net/tipc/node.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/tipc/node.c b/net/tipc/node.c index d7cbfa786c13..53b0993c2c23 100644 --- a/net/tipc/node.c +++ b/net/tipc/node.c @@ -303,6 +303,7 @@ static void tipc_node_free(struct rcu_head *rp) #ifdef CONFIG_TIPC_CRYPTO tipc_crypto_stop(&n->crypto_rx); #endif + kfree(n->bc_entry.link); kfree(n); } @@ -310,7 +311,6 @@ static void tipc_node_kref_release(struct kref *kref) { struct tipc_node *n = container_of(kref, struct tipc_node, kref); - kfree(n->bc_entry.link); call_rcu(&n->rcu, tipc_node_free); } From f4377bf85887f57b59abb5479f7660a81ffe4449 Mon Sep 17 00:00:00 2001 From: Henry Martin Date: Mon, 19 Oct 2026 19:58:59 +0800 Subject: [PATCH 94/94] tipc: fix broadcast sender hang on last acker departure TIPC group broadcast senders are blocked while a replicast still has outstanding member acks: tipc_group_bc_cong() returns true as long as grp->bc_ackers is non-zero, and clears *grp->open, which is what tipc_poll() uses to report EPOLLOUT (via tsk->group_is_open). When the last outstanding acker departs before acking, tipc_group_delete_member() decrements grp->bc_ackers to zero but never restores *grp->open. As a result poll(EPOLLOUT) never fires again. A writer blocked in sendmsg() is only released if some other wakeup happens after the deletion: with TIPC_GROUP_MEMBER_EVTS the leave event delivery calls sk_data_ready() and wakes it, but without member events nothing does and it sleeps indefinitely, since the default send timeout is MAX_SCHEDULE_TIMEOUT. Commit 99cc2a62e07a ("tipc: reject invalid and unexpected GRP_ACK_MSG to prevent bc_ackers underflow") already calls this out in its changelog as a pre-existing issue. Restore the open state exactly on the 1->0 transition in the delete path, mirroring the GRP_ACK_MSG completion path, and invoke sk->sk_write_space() from tipc_sk_filter_rcv() when the group just became writable again. Also make the ack accounting precise. The delete path used to test less(m->bc_acked, grp->bc_snd_nxt - 1), which also matches members that were never counted for the current replicast (JOINING/LEAVING members keep a stale bc_acked), and the GRP_ACK_MSG handler accepted an ack from any member whose bc_acked differed from the expected value. Either could zero bc_ackers on behalf of a member that owes nothing and silently void an ack still owed by a real acker. Track whether a member owes an ack for the current replicast in a new per-member bc_pending flag, set when the member is counted and cleared when its ack arrives, and use it both to accept an ack and to decide on deletion. bc_acked has no remaining user and is removed. Suggested by Tung Quang Nguyen. This issue was discovered by Tencent CodeBuddy Security. Cc: stable@vger.kernel.org Fixes: 2f487712b893 ("tipc: guarantee that group broadcast doesn't bypass group unicast") Signed-off-by: Henry Martin Reviewed-by: Tung Nguyen Link: https://patch.msgid.link/20261019115859.3471295-1-bsdhenrymartin@gmail.com Signed-off-by: Paolo Abeni --- net/tipc/group.c | 19 ++++++++++--------- net/tipc/socket.c | 12 +++++++++++- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/net/tipc/group.c b/net/tipc/group.c index 74f6d3dac078..0c6a762000b0 100644 --- a/net/tipc/group.c +++ b/net/tipc/group.c @@ -74,7 +74,7 @@ struct tipc_member { u16 window; u16 bc_rcv_nxt; u16 bc_syncpt; - u16 bc_acked; + bool bc_pending; }; struct tipc_group { @@ -316,7 +316,6 @@ static struct tipc_member *tipc_group_create_member(struct tipc_group *grp, m->node = node; m->port = port; m->instance = instance; - m->bc_acked = grp->bc_snd_nxt - 1; ret = tipc_group_add_to_tree(grp, m); if (ret < 0) { kfree(m); @@ -340,9 +339,12 @@ static void tipc_group_delete_member(struct tipc_group *grp, rb_erase(&m->tree_node, &grp->members); grp->member_cnt--; - /* Check if we were waiting for replicast ack from this member */ - if (grp->bc_ackers && less(m->bc_acked, grp->bc_snd_nxt - 1)) - grp->bc_ackers--; + /* If this member owed an ack for the current replicast and it + * was the last one, restore the open state so blocked broadcast + * senders can proceed. + */ + if (grp->bc_ackers && m->bc_pending && !--grp->bc_ackers) + *grp->open = true; list_del_init(&m->list); list_del_init(&m->small_win); @@ -394,7 +396,6 @@ void tipc_group_update_member(struct tipc_member *m, int len) void tipc_group_update_bc_members(struct tipc_group *grp, int len, bool ack) { - u16 prev = grp->bc_snd_nxt - 1; struct tipc_member *m; struct rb_node *n; u16 ackers = 0; @@ -403,8 +404,8 @@ void tipc_group_update_bc_members(struct tipc_group *grp, int len, bool ack) m = container_of(n, struct tipc_member, tree_node); if (tipc_group_is_receiver(m)) { tipc_group_update_member(m, len); - m->bc_acked = prev; ackers++; + m->bc_pending = ack; } } @@ -800,9 +801,9 @@ void tipc_group_proto_rcv(struct tipc_group *grp, bool *usr_wakeup, if (!m || !grp->bc_ackers) return; acked = msg_grp_bc_acked(hdr); - if (acked != grp->bc_snd_nxt || m->bc_acked == acked) + if (acked != grp->bc_snd_nxt || !m->bc_pending) return; - m->bc_acked = acked; + m->bc_pending = false; if (--grp->bc_ackers) return; list_del_init(&m->small_win); diff --git a/net/tipc/socket.c b/net/tipc/socket.c index d5d70eb230b5..65cc0c4964b3 100644 --- a/net/tipc/socket.c +++ b/net/tipc/socket.c @@ -2356,8 +2356,18 @@ static void tipc_sk_filter_rcv(struct sock *sk, struct sk_buff *skb, if (unlikely(!msg_isdata(hdr))) tipc_sk_proto_rcv(sk, &inputq, xmitq); - if (unlikely(grp)) + if (unlikely(grp)) { + bool was_open = tsk->group_is_open; + tipc_group_filter_msg(grp, &inputq, xmitq); + /* A departing member may have held the last outstanding + * replicast ack, which can restore the open state inside + * tipc_group_filter_msg(); wake up senders blocked in + * poll()/write() if the group just became writable. + */ + if (!was_open && tsk->group_is_open) + sk->sk_write_space(sk); + } if (unlikely(!grp) && mtyp == TIPC_MCAST_MSG) tipc_mcast_filter_msg(net, &tsk->mc_method.deferredq, &inputq);