From 9802123ceca818aa69284c615114ca55b9cc94a1 Mon Sep 17 00:00:00 2001 From: Xingyuan Mo Date: Mon, 19 Oct 2026 15:37:57 +0800 Subject: [PATCH 001/137] sctp: reject forged cookie peer_addr with unknown address family When cookie authentication is disabled, COOKIE-ECHO peer_addr is attacker-controlled. An invalid sa_family made sctp_get_af_specific() return NULL and crash in sctp_transport_init() on af_specific->sockaddr_len. Validate it with af->addr_valid() in sctp_unpack_cookie(), which also rejects a mismatched family, e.g. an AF_INET6 peer_addr on an IPv4 socket that would later crash in sctp_v6_get_dst() on inet6_sk(sk)->opt. Validate a copy of peer_addr, since addr_valid() rewrites a v4-mapped v6 address in place and the cookie still lives in the received skb, which is not guaranteed to be unshared. Store the validated copy in the new association and pass it to sctp_process_init() instead of the raw cookie, so a v4-mapped v6 address is normalised to AF_INET before the association and its primary transport are built from it. For that, sctp_v6_addr_valid() has to return 0 for a non-v4-mapped address when it is called with a socket that is not PF_INET6, so that IPv6 addresses are never used on IPv4 sockets. The check is done after the v4-mapped handling, so v4-mapped addresses passed to an IPv4 socket through the socket API keep being accepted. As addr_valid() now also checks sk_family and ipv6_only_sock, simplify the address handling in sctp_process_param() with it, as it additionally filters out non-unicast addresses. As a side effect, a v4-mapped address in an IPv6 address parameter is now normalised to AF_INET before the transport is created, matching what the socket API paths already do. Also add the missing !af check in sctp_process_init(), as sctp_get_af_specific(AF_INET6) returns NULL on CONFIG_IPV6=n builds. BUG: KASAN: null-ptr-deref in sctp_transport_new+0xa7/0x350 Read of size 4 at addr 00000000000000b4 by task poc/682 Call Trace: sctp_transport_new+0xa7/0x350 sctp_assoc_add_peer+0x153/0x850 sctp_process_init+0xf9/0x1180 sctp_sf_do_5_1D_ce+0x464/0xbc0 sctp_do_sm+0x114/0x2990 sctp_endpoint_bh_rcv+0x280/0x430 sctp_inq_push+0xdd/0x100 sctp_rcv+0x17f5/0x1ae0 sctp4_rcv+0x2b/0x40 ip_protocol_deliver_rcu+0x25b/0x270 ip_local_deliver+0xd1/0xe0 Kernel panic - not syncing: Fatal exception in interrupt Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Xingyuan Mo Acked-by: Xin Long Link: https://patch.msgid.link/20261019073747.178626-1-hdthky0@gmail.com Signed-off-by: Jakub Kicinski --- net/sctp/ipv6.c | 6 +++++- net/sctp/sm_make_chunk.c | 24 +++++++++++++----------- net/sctp/sm_statefuns.c | 2 +- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/net/sctp/ipv6.c b/net/sctp/ipv6.c index ef26878f1282..97390569c4bd 100644 --- a/net/sctp/ipv6.c +++ b/net/sctp/ipv6.c @@ -725,7 +725,8 @@ static int sctp_v6_available(union sctp_addr *addr, struct sctp_sock *sp) * SCTP. * * Output: - * Return 0 - If the address is a non-unicast or an illegal address. + * Return 0 - If the address is a non-unicast or an illegal address, + * or a non-v4-mapped address on a non-PF_INET6 socket. * Return 1 - If the address is a unicast. */ static int sctp_v6_addr_valid(union sctp_addr *addr, @@ -749,6 +750,9 @@ static int sctp_v6_addr_valid(union sctp_addr *addr, if (!(ret & IPV6_ADDR_UNICAST)) return 0; + if (sp && sctp_opt2sk(sp)->sk_family != PF_INET6) + return 0; + return 1; } diff --git a/net/sctp/sm_make_chunk.c b/net/sctp/sm_make_chunk.c index ddc31a5dbad3..d64c3ae33298 100644 --- a/net/sctp/sm_make_chunk.c +++ b/net/sctp/sm_make_chunk.c @@ -1733,7 +1733,9 @@ struct sctp_association *sctp_unpack_cookie( struct sctp_cookie *bear_cookie; struct sctp_chunkhdr *ch; unsigned int len, chlen; + union sctp_addr paddr; enum sctp_scope scope; + struct sctp_af *af; ktime_t kt; /* Header size is static data prior to the actual cookie, including @@ -1842,6 +1844,11 @@ struct sctp_association *sctp_unpack_cookie( goto fail; } + paddr = bear_cookie->peer_addr; + af = sctp_get_af_specific(paddr.sa.sa_family); + if (!af || !af->addr_valid(&paddr, sctp_sk(ep->base.sk), NULL)) + goto malformed; + /* Make a new base association. */ scope = sctp_scope(sctp_source(chunk)); retval = sctp_association_new(ep, ep->base.sk, scope, gfp); @@ -1858,6 +1865,7 @@ struct sctp_association *sctp_unpack_cookie( /* Populate the association from the cookie. */ memcpy(&retval->c, bear_cookie, sizeof(*bear_cookie)); + retval->c.peer_addr = paddr; if (sctp_assoc_set_bind_addr_from_cookie(retval, bear_cookie, GFP_ATOMIC) < 0) { @@ -2382,8 +2390,8 @@ int sctp_process_init(struct sctp_association *asoc, struct sctp_chunk *chunk, (param.p->type == SCTP_PARAM_IPV4_ADDRESS || param.p->type == SCTP_PARAM_IPV6_ADDRESS)) { af = sctp_get_af_specific(param_type2af(param.p->type)); - if (!af->from_addr_param(&addr, param.addr, - chunk->sctp_hdr->source, 0)) + if (!af || !af->from_addr_param(&addr, param.addr, + chunk->sctp_hdr->source, 0)) continue; if (sctp_cmp_addr_exact(sctp_source(chunk), &addr)) src_match = 1; @@ -2555,17 +2563,11 @@ static int sctp_process_param(struct sctp_association *asoc, */ switch (param.p->type) { case SCTP_PARAM_IPV6_ADDRESS: - if (PF_INET6 != asoc->base.sk->sk_family) - break; - goto do_addr_param; - case SCTP_PARAM_IPV4_ADDRESS: - /* v4 addresses are not allowed on v6-only socket */ - if (ipv6_only_sock(asoc->base.sk)) - break; -do_addr_param: af = sctp_get_af_specific(param_type2af(param.p->type)); - if (!af->from_addr_param(&addr, param.addr, htons(asoc->peer.port), 0)) + if (!af || !af->from_addr_param(&addr, param.addr, + htons(asoc->peer.port), 0) || + !af->addr_valid(&addr, sctp_sk(asoc->base.sk), NULL)) break; scope = sctp_scope(peer_addr); if (sctp_in_scope(net, &addr, scope)) diff --git a/net/sctp/sm_statefuns.c b/net/sctp/sm_statefuns.c index 8dc65498763c..b2f609f177fb 100644 --- a/net/sctp/sm_statefuns.c +++ b/net/sctp/sm_statefuns.c @@ -814,7 +814,7 @@ enum sctp_disposition sctp_sf_do_5_1D_ce(struct net *net, * effects--it is safe to run them here. */ if (!sctp_process_init(new_asoc, chunk, - &chunk->subh.cookie_hdr->c.peer_addr, + &new_asoc->c.peer_addr, peer_init, GFP_ATOMIC)) goto nomem_init; From 89b9db72918bc209029fad8cccc42191b498d511 Mon Sep 17 00:00:00 2001 From: Kaifeng Wang Date: Mon, 19 Oct 2026 17:45:22 +0000 Subject: [PATCH 002/137] tcp: reject devmem tx with fastopen and repair tcp_sendmsg_locked() enforces that devmem TX can only proceed if the zero-copy path is active and a valid dmabuf binding exists. However, subsequent branches in tcp_sendmsg_locked() can still intercept the message before it reaches the devmem zero-copy loop: 1. TCP Fast Open (MSG_FASTOPEN or DEFER_CONNECT): If TCP_FASTOPEN_CONNECT is set, the socket may have a valid dst with NETIF_F_SG (so zc == MSG_ZEROCOPY and binding is present), but tcp_sendmsg_fastopen() -> tcp_send_syn_data() will use copy_page_from_iter() to byte-copy from the iterator. Since iov_base represents dma-buf offsets rather than user virtual addresses, this misinterprets offsets as user pointers and copies the caller's memory at those addresses into the SYN packet. 2. TCP repair mode: If tp->repair is enabled with TCP_RECV_QUEUE, tcp_send_rcvq() similarly calls skb_copy_datagram_from_iter(), byte-copying from the iterator. Neither path supports or makes sense for devmem transmission. Reject devmem sends inside the Fast Open and TCP repair branches when a devmem binding is present. Checking inside the tp->repair branch also covers the case where repair mode is enabled while sk_stream_wait_connect() drops the socket lock. Additionally, drop the !sockc_err gate from the devmem validation check and return sockc_err if set. Otherwise, if a valid SCM_DEVMEM_DMABUF is followed by a failing cmsg, sockc.dmabuf_id remains set while binding is not acquired, allowing the Fast Open and TCP_RECV_QUEUE repair paths to proceed and return before sockc_err is checked. This pre-existing issue was identified by Sashiko AI review on commit 125755776bc6 ("tcp: reject non zerocopy devmem tx") and has not been hit in production. Fixes: 125755776bc6 ("tcp: reject non zerocopy devmem tx") Fixes: bd61848900bf ("net: devmem: Implement TX path") Reviewed-by: Mina Almasry Signed-off-by: Kaifeng Wang Acked-by: Stanislav Fomichev Reviewed-by: Eric Dumazet Reviewed-by: Pavel Begunkov Link: https://patch.msgid.link/20261019174522.55644-1-kaifengw@google.com Signed-off-by: Jakub Kicinski --- net/ipv4/tcp.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index 99d7237a623d..76df69aa0115 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -1172,14 +1172,18 @@ int tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size) zc = MSG_SPLICE_PAGES; } - if (!sockc_err && sockc.dmabuf_id && (zc != MSG_ZEROCOPY || !binding)) { - err = -EINVAL; + if (sockc.dmabuf_id && (zc != MSG_ZEROCOPY || !binding)) { + err = sockc_err ?: -EINVAL; goto out_err; } if (unlikely(flags & MSG_FASTOPEN || inet_test_bit(DEFER_CONNECT, sk)) && !tp->repair) { + if (binding) { + err = -EINVAL; + goto out_err; + } err = tcp_sendmsg_fastopen(sk, msg, &copied_syn, size, uarg); if (err == -EINPROGRESS && copied_syn > 0) goto out; @@ -1203,6 +1207,10 @@ int tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size) } if (unlikely(tp->repair)) { + if (binding) { + err = -EINVAL; + goto out_err; + } if (tp->repair_queue == TCP_RECV_QUEUE) { copied = tcp_send_rcvq(sk, msg, size); goto out_nopush; From 94a6736bb5abe6b2ef38490d0cab19c1c6a7c71f Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Tue, 20 Oct 2026 14:39:36 +0800 Subject: [PATCH 003/137] ipmr: only copy pktinfo to cache reports ipmr_cache_report() prepares IP_PKTINFO on the original skb. The ipv4_pktinfo_prepare() helper updates skb->cb and clears the pktinfo fields when packet info is disabled. Since the skb can continue through the data path, preserve and restore its original control block around the helper. The original implementation copies the entire control block to the report skb. For synthetic RTM_GETROUTE packets, NETLINK_CB data is interpreted as IPCB by the raw IPv4 receive path. With IP_RECVOPTS or IP_RETOPTS enabled, corrupted option offsets cause out-of-bounds reads or a stack buffer overflow. WHOLEPKT and WRVIFWHOLE reports also inherit stale IP option offsets when their header is prepended. Clear the report skb control block. Only prepare and copy pktinfo when the input skb has an IPv4 header. Synthetic route-query skbs use NETLINK_CB, not IPCB, so leave their report control block zeroed. Restore pkt->cb after preparing pktinfo for regular IPv4 packets. Fixes: bb7403655b3c ("ipmr: support IP_PKTINFO on cache report IGMP msg") Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Reported-by: Vega Link: https://lore.kernel.org/netdev/20260903113055.GA90787@shredder/ Suggested-by: Ido Schimmel Suggested-by: Eric Dumazet Signed-off-by: Zhiling Zou Reviewed-by: Ido Schimmel Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/0545f0c83f5ee2f6696210f87268e811ef655715.1792476357.git.zhilinz@nebusec.ai Signed-off-by: Jakub Kicinski --- net/ipv4/ipmr.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c index e4c51ca47301..acacaae05cc4 100644 --- a/net/ipv4/ipmr.c +++ b/net/ipv4/ipmr.c @@ -1063,6 +1063,7 @@ static void ipmr_cache_resolve(struct net *net, struct mr_table *mrt, static int ipmr_cache_report(const struct mr_table *mrt, struct sk_buff *pkt, vifi_t vifi, int assert) { + unsigned char pkt_cb[sizeof(pkt->cb)]; const int ihl = ip_hdrlen(pkt); struct sock *mroute_sk; struct igmphdr *igmp; @@ -1118,8 +1119,6 @@ static int ipmr_cache_report(const struct mr_table *mrt, msg = (struct igmpmsg *)skb_network_header(skb); msg->im_vif = vifi; msg->im_vif_hi = vifi >> 8; - ipv4_pktinfo_prepare(mroute_sk, pkt, false); - memcpy(skb->cb, pkt->cb, sizeof(skb->cb)); /* Add our header. * Note that code, csum and group fields are cleared. */ @@ -1130,6 +1129,15 @@ static int ipmr_cache_report(const struct mr_table *mrt, skb->transport_header = skb->network_header; } + memset(skb->cb, 0, sizeof(skb->cb)); + /* Route queries from ipmr_get_route() carry NETLINK_CB, not IPCB. */ + if (ip_hdr(pkt)->version) { + memcpy(pkt_cb, pkt->cb, sizeof(pkt_cb)); + ipv4_pktinfo_prepare(mroute_sk, pkt, false); + *PKTINFO_SKB_CB(skb) = *PKTINFO_SKB_CB(pkt); + memcpy(pkt->cb, pkt_cb, sizeof(pkt_cb)); + } + igmpmsg_netlink_event(mrt, skb); /* Deliver to mrouted */ From 79dc34beb1fdb3da79753e51e15c68591a2561fe Mon Sep 17 00:00:00 2001 From: Zizhen Liu Date: Thu, 22 Oct 2026 18:42:17 +0800 Subject: [PATCH 004/137] net: skbuff: propagate shared-frag marker in skb_zerocopy() skb_zerocopy() moves frag descriptors from @from into @to and takes a reference on each frag, but it does not copy skb_shinfo(from)->flags into skb_shinfo(to)->flags. A destination skb holding the same page-cache backed frags as the source therefore reports skb_has_shared_frag() as false. That breaks the invariant relied on by later in-place writers. ESP input checks skb_has_shared_frag() before deciding whether an uncloned nonlinear skb can skip skb_cow_data(); with the marker lost, an skb that skb_zerocopy() built for nfnetlink_queue or for an openvswitch upcall is decrypted in place, letting an unprivileged user write into the page cache of a root-owned read-only file. Propagate SKBFL_SHARED_FRAG when frag descriptors are copied, as done for the other frag-transfer helpers. Fixes: af2806f8f90a ("net: Export skb_zerocopy() to zerocopy from one skb to another") Fixes: f4c50a4034e6 ("xfrm: esp: avoid in-place decrypt on shared skb frags") Cc: stable@kernel.org Signed-off-by: Zizhen Liu Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20261022104217.1852191-1-pointerliu@126.com Signed-off-by: Jakub Kicinski --- net/core/skbuff.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/core/skbuff.c b/net/core/skbuff.c index eb9fcc4b1fd1..6a7c02b7653a 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -3966,6 +3966,8 @@ skb_zerocopy(struct sk_buff *to, struct sk_buff *from, int len, int hlen) } skb_shinfo(to)->nr_frags = j; + skb_shinfo(to)->flags |= skb_shinfo(from)->flags & SKBFL_SHARED_FRAG; + if (i > 0 && from->unreadable) to->unreadable = 1; From e3aaeebbef56ceaa27001d1d5a0c1d73d648e6f4 Mon Sep 17 00:00:00 2001 From: James Clark Date: Tue, 20 Oct 2026 07:35:25 +0700 Subject: [PATCH 005/137] net: ethernet: ti: am65-cpts: fix support for PTP_EXTTS_REQUEST2 ioctl Commit 7c571ac57d9d ("net: ptp: introduce .supported_extts_flags to ptp_clock_info") modified the PTP core to validate the supported flags of external timestamp requests, rather than relying on each driver to check them. am65_cpts_extts_enable() ignores the flags, and am65-cpts was not given any .supported_extts_flags. Since PTP_EXTTS_REQUEST2 always implies PTP_STRICT_FLAGS, the core now rejects every PTP_EXTTS_REQUEST2 for this driver with -EOPNOTSUPP, where before that commit the same requests succeeded. Only the PTP_EXTTS_REQUEST ioctl still works. Tests on a BeagleY-AI (J722S) show that the CPTS hardware timestamp push inputs timestamp just the rising edge. Encode this in .supported_extts_flags. A PTP_EXTTS_REQUEST2 for the rising edge then succeeds, and one for the falling edge or for both edges fails with -EOPNOTSUPP. Fixes: 7c571ac57d9d ("net: ptp: introduce .supported_extts_flags to ptp_clock_info") Signed-off-by: James Clark Acked-by: Jacob Keller Reviewed-by: Vadim Fedorenko Reviewed-by: Kory Maincent Link: https://patch.msgid.link/20261020003525.1237742-1-jjc@jclark.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/ti/am65-cpts.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/ti/am65-cpts.c b/drivers/net/ethernet/ti/am65-cpts.c index efbf2ef976a3..df5659ead72f 100644 --- a/drivers/net/ethernet/ti/am65-cpts.c +++ b/drivers/net/ethernet/ti/am65-cpts.c @@ -779,6 +779,7 @@ static long am65_cpts_ts_work(struct ptp_clock_info *ptp); static struct ptp_clock_info am65_ptp_info = { .owner = THIS_MODULE, .name = "CTPS timer", + .supported_extts_flags = PTP_RISING_EDGE | PTP_STRICT_FLAGS, .adjfine = am65_cpts_ptp_adjfine, .adjtime = am65_cpts_ptp_adjtime, .gettimex64 = am65_cpts_ptp_gettimex, From 286154ae6998a32cca698abd713e1d4728e81924 Mon Sep 17 00:00:00 2001 From: Dimitri Daskalakis Date: Tue, 20 Oct 2026 10:27:06 -0700 Subject: [PATCH 006/137] eth: fbnic: Correct packet header prefetch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The existing call to net_prefetch() fetched a stale address. The data pointer is not initialized until after the call to xdp_prepare_buff(). Thus, making the prefetch ineffective. Open code the address calcuation to give the prefetch as much time as possible. This was found by manual code inspection. No issues have been observed from this. Fixes: 9064ab485f04 ("eth: fbnic: Prefetch packet headers on Rx") Signed-off-by: Dimitri Daskalakis Reviewed-by: Björn Töpel Reviewed-by: Daniel Machon Link: https://patch.msgid.link/20261020172706.1090951-1-dimitri.daskalakis1@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/meta/fbnic/fbnic_txrx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c index c62d885a678a..c572a0062e77 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c @@ -1025,7 +1025,7 @@ static void fbnic_pkt_prepare(struct fbnic_napi_vector *nv, u64 rcd, /* Build frame around buffer */ hdr_start = page_address(page) + hdr_pg_start; - net_prefetch(pkt->buff.data); + net_prefetch(hdr_start + headroom); xdp_prepare_buff(&pkt->buff, hdr_start, headroom, len - FBNIC_RX_PAD, true); From b530456be77160b13efe8dececb6c826538bd4a2 Mon Sep 17 00:00:00 2001 From: Piotr Kubik Date: Mon, 19 Oct 2026 18:24:52 +0200 Subject: [PATCH 007/137] net: pse-pd: si3474: keep detection enabled on the other PIs DETECT_CLASS_ENABLE holds the detection and classification bits of all four channels of a quad. si3474_pi_enable() wrote the register with the bits of its own PI only, so it cleared them for the other PI of the quad. In AUTO mode that PI keeps its admin state and a PD that already has power keeps it, but the port runs no new detection: a PD plugged in later, or one waiting for detection when the other PI is enabled, gets no power. Read the register and set the PI bits, as the PORT_MODE update above already does. Discovered by an AI code review agent. Confirmed on an SDG-9000 board (MediaTek MT7988A, two Si3474 in AUTO pin mode) with Linux 6.18: with both PIs of one quad disabled, enabling PI A and then PI B left DETECT_CLASS_ENABLE with the bits of PI B only, and a PD on PI A never got power. No kernel change was needed to reproduce it. Tested on the same board with the same sequence in both orders: DETECT_CLASS_ENABLE keeps the bits of both PIs, and the PD gets power. No kernel selftest covers PSE hardware. Fixes: a2317231df4b ("net: pse-pd: Add Si3474 PSE controller driver") Signed-off-by: Piotr Kubik Reviewed-by: Kory Maincent Link: https://patch.msgid.link/20261019162453.1244120-2-piotr@kubik.pl Signed-off-by: Jakub Kicinski --- drivers/net/pse-pd/si3474.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/net/pse-pd/si3474.c b/drivers/net/pse-pd/si3474.c index efb0af041599..a66a982e5ec4 100644 --- a/drivers/net/pse-pd/si3474.c +++ b/drivers/net/pse-pd/si3474.c @@ -312,8 +312,13 @@ static int si3474_pi_enable(struct pse_controller_dev *pcdev, int id) /* DETECT_CLASS_ENABLE must be set when using AUTO mode, * otherwise PI does not power up - datasheet section 2.10.2 */ - val = CHAN_BIT(chan0) | CHAN_UPPER_BIT(chan0) | - CHAN_BIT(chan1) | CHAN_UPPER_BIT(chan1); + ret = i2c_smbus_read_byte_data(client, DETECT_CLASS_ENABLE_REG); + if (ret < 0) + return ret; + + val = (u8)ret; + val |= CHAN_BIT(chan0) | CHAN_UPPER_BIT(chan0) | + CHAN_BIT(chan1) | CHAN_UPPER_BIT(chan1); ret = i2c_smbus_write_byte_data(client, DETECT_CLASS_ENABLE_REG, val); if (ret) From 110eb808324fd9713b04d874c97283d965e43d2a Mon Sep 17 00:00:00 2001 From: Piotr Kubik Date: Mon, 19 Oct 2026 18:24:53 +0200 Subject: [PATCH 008/137] net: pse-pd: si3474: validate the channels of a PI si3474_get_of_channels() accepted channel 8, one past the last channel. The driver then accessed channel 4 instead: quad 1, channel index 0. It also accepted a 4-pair PI whose two channels are not a channel pair of one quad. The chip forms a 4-pair port only from channels 0+1 or 2+3 of a quad (2P/4P mode bit in POWER_ALLOCATION, PORT_REMAP default), and the driver sends every register access of a PI to the quad of its first channel, so such a PI enabled and measured the wrong channels. Reject both at probe. Discovered by an AI code review agent; no shipped device tree triggers it. Validated and tested on an SDG-9000 board (MediaTek MT7988A, two Si3474) with Linux 6.18 and a modified device tree: one chip with a channel at reg 8, the other with a 4-pair PI on channels 1 and 2. The driver now rejects both at probe, and the unmodified device tree still probes all eight PIs. No kernel selftest covers PSE hardware. Fixes: a2317231df4b ("net: pse-pd: Add Si3474 PSE controller driver") Signed-off-by: Piotr Kubik Reviewed-by: Kory Maincent Link: https://patch.msgid.link/20261019162453.1244120-3-piotr@kubik.pl Signed-off-by: Jakub Kicinski --- drivers/net/pse-pd/si3474.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/drivers/net/pse-pd/si3474.c b/drivers/net/pse-pd/si3474.c index a66a982e5ec4..35152d462e82 100644 --- a/drivers/net/pse-pd/si3474.c +++ b/drivers/net/pse-pd/si3474.c @@ -250,9 +250,9 @@ static int si3474_get_of_channels(struct si3474_priv *priv) "Failed to read channel reg property\n"); return ret; } - if (chan_id > SI3474_MAX_CHANS) { + if (chan_id >= SI3474_MAX_CHANS) { dev_err(&priv->client[0]->dev, - "Incorrect channel number: %d\n", chan_id); + "Incorrect channel number: %u\n", chan_id); return -EINVAL; } @@ -267,6 +267,16 @@ static int si3474_get_of_channels(struct si3474_priv *priv) pi->np); return -EINVAL; } + + /* The 2P/4P mode bit pairs channels 0+1 and 2+3 of a quad */ + if (pairset_found && + (priv->pi[pi_no].chan[0] ^ 1) != priv->pi[pi_no].chan[1]) { + dev_err(&priv->client[0]->dev, + "PI %pOF: channels %u and %u are not a channel pair\n", + pi->np, priv->pi[pi_no].chan[0], + priv->pi[pi_no].chan[1]); + return -EINVAL; + } } return 0; From 45e0a63f9c86a558afc70d79b6df38f8ec7f502a Mon Sep 17 00:00:00 2001 From: Aamir Ahmed Date: Tue, 20 Oct 2026 23:33:46 +0100 Subject: [PATCH 009/137] gve: fix out-of-bounds read in gve_get_flow_rule_entry() gve_get_flow_rule_entry() checks the requested location against the first and last cached rules, but rules_cache_num can be 0 while rules_cache_synced is true: GVE_FLOW_RULE_QUERY_RULES returns the rules from the requested location onwards, so a request past the last installed rule - or any request when no rules are installed - returns zero rules, which the driver accepts and marks as synced. The next query evaluates rules_cache[*cache_num - 1] with *cache_num == 0. The zero-initialised cache does not trip the earlier comparison, *cache_num - 1 wraps to 0xFFFFFFFF in u32, and the read faults about 352 GiB past the cache. ETHTOOL_GRXCLSRULE needs no capability, so an unprivileged user can trigger it: BUG: unable to handle page fault for address: ff240996c4c64fa8 #PF: supervisor read access in kernel mode RIP: gve_get_flow_rule_entry+0xd4/0x110 [gve] Treat an empty cache as a cache miss and re-query. Fixes: 6f3bc487565d ("gve: Add flow steering ethtool support") Cc: stable@vger.kernel.org Signed-off-by: Aamir Ahmed Reviewed-by: Eugene Lee Reviewed-by: Harshitha Ramamurthy Link: https://patch.msgid.link/AS8P251MB0001135AF3233180DC42DCD1C8672@AS8P251MB0001.EURP251.PROD.OUTLOOK.COM Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/google/gve/gve_flow_rule.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/google/gve/gve_flow_rule.c b/drivers/net/ethernet/google/gve/gve_flow_rule.c index fae552f4ad6f..d7f9aa898a80 100644 --- a/drivers/net/ethernet/google/gve/gve_flow_rule.c +++ b/drivers/net/ethernet/google/gve/gve_flow_rule.c @@ -206,6 +206,7 @@ int gve_get_flow_rule_entry(struct gve_priv *priv, struct ethtool_rxnfc *cmd) return -EOPNOTSUPP; if (!priv->flow_rules_cache.rules_cache_synced || + !*cache_num || fsp->location < be32_to_cpu(rules_cache[0].location) || fsp->location > be32_to_cpu(rules_cache[*cache_num - 1].location)) { err = gve_adminq_query_flow_rules(priv, GVE_FLOW_RULE_QUERY_RULES, fsp->location); From 9642ce6cb51ebd596d0a02074ba9b2a387e974b0 Mon Sep 17 00:00:00 2001 From: Daniel Machon Date: Wed, 21 Oct 2026 12:48:26 +0200 Subject: [PATCH 010/137] net: sparx5/lan969x: do not read past skb->len when injecting a frame sparx5_inject() writes the frame to the injection FIFO one 32-bit word at a time, and pads frames shorter than 60 bytes with zero words. Before this patch, the last word is loaded whole even when skb->len is not a multiple of four, so 1 to 3 bytes past the end of the frame are read. For a frame shorter than 60 bytes those bytes are also transmitted, as VLD_BYTES marks the whole last word valid and the padding starts at the next word. Write the trailing bytes from a zeroed word. The number of words written and VLD_BYTES are unchanged. Found by an AI code review agent, and reproduced on lan969x and sparx5 with register injection: bytes 57 to 59 of a 57 byte frame were not zero. With the fix they are zero, and traffic still passes on both chips. Fixes: f3cad2611a77 ("net: sparx5: add hostmode with phylink support") Signed-off-by: Daniel Machon Reviewed-by: Simon Horman Link: https://patch.msgid.link/20261021-sparx5-inject-tail-bytes-v1-1-7dcc035c8ddf@microchip.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/microchip/sparx5/sparx5_packet.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_packet.c b/drivers/net/ethernet/microchip/sparx5/sparx5_packet.c index f713656f1fae..14c9ae673e35 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_packet.c +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_packet.c @@ -169,8 +169,8 @@ static int sparx5_inject(struct sparx5 *sparx5, struct sk_buff *skb, struct net_device *ndev) { + u32 val, w, count, last; int grp = INJ_QUEUE; - u32 val, w, count; u8 *buf; val = spx5_rd(sparx5, QS_INJ_STATUS); @@ -189,14 +189,23 @@ static int sparx5_inject(struct sparx5 *sparx5, for (w = 0; w < IFH_LEN; w++) spx5_wr(ifh[w], sparx5, QS_INJ_WR(grp)); - /* Write words, round up */ - count = DIV_ROUND_UP(skb->len, 4); + /* Write the whole words of the frame */ + count = skb->len / 4; buf = skb->data; for (w = 0; w < count; w++, buf += 4) { val = get_unaligned((const u32 *)buf); spx5_wr(val, sparx5, QS_INJ_WR(grp)); } + /* Write the last 1 to 3 bytes, zero padded to a whole word */ + last = skb->len % 4; + if (last) { + val = 0; + memcpy(&val, buf, last); + spx5_wr(val, sparx5, QS_INJ_WR(grp)); + w++; + } + /* Add padding */ while (w < (60 / 4)) { spx5_wr(0, sparx5, QS_INJ_WR(grp)); From 1df26063ab9ba64f5fb11049fa4be686b7fc309e Mon Sep 17 00:00:00 2001 From: Daniel Machon Date: Wed, 21 Oct 2026 16:04:59 +0200 Subject: [PATCH 011/137] net: sparx5: fix tx DCB index drift in fdma xmit sparx5_fdma_xmit() advances dcb_index before checking that the FDMA is done with the DCB, and does not undo the advance when the check fails. Before commit f4aa7e361ae2 ("net: sparx5: use FDMA library for adding DCB's in the tx path") the next DCB was always the one after last_dcb. Since then dcb_index drifts one DCB further away from last_dcb on every failed transmit. While the ring is full, this drops frames even though completed DCBs sit behind dcb_index. After a run of failures, dcb_index can also land on last_dcb itself. Re-adding last_dcb loses the frame, because the FDMA has already passed the tail of the chain, and that DCB is never completed again. Fix this by making dcb_index the next DCB to use, and advancing it only after the DCB has been added, as the rx path does. dcb_index and last_dcb now only move together, so dcb_index always points at the DCB after last_dcb. The issue was found by an AI code review agent. It was reproduced on a sparx5 PCB135 board with a modified kernel that forces 63 consecutive transmit failures. Afterwards, about 1 in 64 frames is lost for good. With the fix, the same test loses no frames. Fixes: f4aa7e361ae2 ("net: sparx5: use FDMA library for adding DCB's in the tx path") Signed-off-by: Daniel Machon Reviewed-by: Simon Horman Link: https://patch.msgid.link/20261021-sparx5-fdma-last-dcb-v2-1-7b8fabdb9239@microchip.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/microchip/sparx5/sparx5_fdma.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_fdma.c b/drivers/net/ethernet/microchip/sparx5/sparx5_fdma.c index dbe86f937b21..15ec9b053824 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_fdma.c +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_fdma.c @@ -217,7 +217,6 @@ int sparx5_fdma_xmit(struct sparx5 *sparx5, u32 *ifh, struct sk_buff *skb, struct fdma *fdma = &tx->fdma; void *virt_addr; - fdma_dcb_advance(fdma); if (!fdma_db_is_done(fdma_db_get(fdma, fdma->dcb_index, 0))) return -EINVAL; @@ -235,6 +234,8 @@ int sparx5_fdma_xmit(struct sparx5 *sparx5, u32 *ifh, struct sk_buff *skb, FDMA_DCB_STATUS_BLOCKO(0) | FDMA_DCB_STATUS_BLOCKL(skb->len + IFH_LEN * 4 + 4)); + fdma_dcb_advance(fdma); + sparx5_fdma_reload(sparx5, fdma); return NETDEV_TX_OK; From 86537a8fb24750fc25a96a27ff29400054d052b5 Mon Sep 17 00:00:00 2001 From: Hangbin Liu Date: Thu, 22 Oct 2026 11:09:35 +0800 Subject: [PATCH 012/137] selftests: gro: remove unused variable total_data I hit a build warning when building the net selftests. gro.c: In function 'check_capacity_pkts': gro.c:1249:13: warning: variable 'total_data' set but not used [-Wunused-but-set-variable=] 1249 | int total_data = 0; | ^~~~~~~~~~ Remove the total_data variable, as it is no longer used. Fixes: d97348474708 ("selftests: drv-net: gro: always wait for FIN in the capacity test") Signed-off-by: Hangbin Liu Reviewed-by: Antoine Tenart Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20261022-self_unused_var-v1-1-8ab10a6b7f44@kylinos.cn Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/lib/gro.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/testing/selftests/net/lib/gro.c b/tools/testing/selftests/net/lib/gro.c index fb6a72275636..5aaf7a897b0b 100644 --- a/tools/testing/selftests/net/lib/gro.c +++ b/tools/testing/selftests/net/lib/gro.c @@ -1339,7 +1339,6 @@ static void check_capacity_pkts(int fd) int coalesced[num_flows]; struct tcphdr *tcph; int ip_ext_len = 0; - int total_data = 0; int pkt_size = -1; int data_len = 0; int flow_id; @@ -1402,7 +1401,6 @@ static void check_capacity_pkts(int fd) } num_pkt++; - total_data += data_len; } /* Check flow ordering. We expect to see all non-coalesced first segs From ed642349d87dd7067a44c30319f0cffd22780376 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Tue, 20 Oct 2026 20:12:39 +0800 Subject: [PATCH 013/137] tls: fix the open record check in the max payload size setsockopt do_tls_setsockopt_tx_payload_len() refuses to resize records while one is open, but the check is broken in two ways. It reaches for the open record through tls_sw_ctx_tx(), an unchecked cast of ctx->priv_ctx_tx. Under device offload that pointer is a tls_offload_context_tx, so the check reads a field of the wrong struct: it returns EBUSY on whatever happens to be there, and never sees the record that really is open. Dispatch on tx_conf. The socket lock alone is also not enough to tell no sender is in flight. The device tx path drops it in sk_stream_wait_memory() after tls_push_record() cleared open_record, so setsockopt can slip in mid-sendmsg and shrink the limit. tls_push_data() latches the limit once per call, and a record left open by MSG_MORE can then be bigger than the new limit, making the u32 subtraction in the copy clamp wrap around and build an oversized record. Take tx_lock like the sendmsg paths do so setsockopt waits for in-flight senders. Re-reading the limit inside the tls_push_data() loop could close the wrap too, but a parked sender would still finish with the old limit, so serializing on tx_lock is simpler. Take tx_lock interruptible, as tls_sw_sendmsg() does. Both sendmsg paths keep it across sk_stream_wait_memory(), which sleeps until the peer reads, so a plain mutex_lock() could park setsockopt in D state for as long as the sender waits. The lock_sock() after it is fine uninterruptible: a sender sleeping for memory has released the socket lock, so that wait only covers short critical sections, like every other setsockopt. Fixes: 82cb5be6ad64 ("net/tls: support setting the maximum payload size") Signed-off-by: Jiayuan Chen Link: https://patch.msgid.link/20261020121240.29082-1-jiayuan.chen@linux.dev Signed-off-by: Jakub Kicinski --- net/tls/tls_main.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/net/tls/tls_main.c b/net/tls/tls_main.c index 8bdb78718a8d..6b889f568dc1 100644 --- a/net/tls/tls_main.c +++ b/net/tls/tls_main.c @@ -831,15 +831,26 @@ static int do_tls_setsockopt_no_pad(struct sock *sk, sockptr_t optval, return rc; } +static bool tls_tx_record_is_open(struct tls_context *ctx) +{ + switch (ctx->tx_conf) { + case TLS_SW: + return !!tls_sw_ctx_tx(ctx)->open_rec; + case TLS_HW: + return !!tls_offload_ctx_tx(ctx)->open_record; + default: + return false; + } +} + static int do_tls_setsockopt_tx_payload_len(struct sock *sk, sockptr_t optval, unsigned int optlen) { struct tls_context *ctx = tls_get_ctx(sk); - struct tls_sw_context_tx *sw_ctx = tls_sw_ctx_tx(ctx); u16 value; bool tls_13 = ctx->prot_info.version == TLS_1_3_VERSION; - if (sw_ctx && sw_ctx->open_rec) + if (tls_tx_record_is_open(ctx)) return -EBUSY; if (sockptr_is_null(optval) || optlen != sizeof(value)) @@ -860,6 +871,7 @@ static int do_tls_setsockopt_tx_payload_len(struct sock *sk, sockptr_t optval, static int do_tls_setsockopt(struct sock *sk, int optname, sockptr_t optval, unsigned int optlen) { + struct tls_context *ctx; int rc = 0; switch (optname) { @@ -879,9 +891,15 @@ static int do_tls_setsockopt(struct sock *sk, int optname, sockptr_t optval, rc = do_tls_setsockopt_no_pad(sk, optval, optlen); break; case TLS_TX_MAX_PAYLOAD_LEN: + ctx = tls_get_ctx(sk); + /* Senders sleep with tx_lock held, so take it interruptible */ + rc = mutex_lock_interruptible(&ctx->tx_lock); + if (rc) + break; lock_sock(sk); rc = do_tls_setsockopt_tx_payload_len(sk, optval, optlen); release_sock(sk); + mutex_unlock(&ctx->tx_lock); break; default: rc = -ENOPROTOOPT; From c3a1ddd5e116540912441606a2cd4b944be1622a Mon Sep 17 00:00:00 2001 From: Hanson Wang Date: Fri, 16 Oct 2026 20:48:58 +0800 Subject: [PATCH 014/137] net: usb: aqc111: fix set_mac_address return value aqc111_set_mac_addr() returns the result of aqc111_write_cmd() on success. That function wraps usb_control_msg(), which returns the number of bytes transferred (6 for ETH_ALEN) rather than zero. Callers such as netif_set_mac_address() and bonding treat any non-zero return value as failure when setting the MAC address. Return 0 on success and propagate negative error codes on failure. Signed-off-by: Hanson Wang Reviewed-by: Simon Horman Link: https://patch.msgid.link/20261016124858.1939743-1-hanson.wang@ugreen.com Signed-off-by: Jakub Kicinski --- drivers/net/usb/aqc111.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/net/usb/aqc111.c b/drivers/net/usb/aqc111.c index dd53f413c38f..12a99df77f0d 100644 --- a/drivers/net/usb/aqc111.c +++ b/drivers/net/usb/aqc111.c @@ -471,8 +471,12 @@ static int aqc111_set_mac_addr(struct net_device *net, void *p) return ret; /* Set the MAC address */ - return aqc111_write_cmd(dev, AQ_ACCESS_MAC, SFR_NODE_ID, ETH_ALEN, - ETH_ALEN, net->dev_addr); + ret = aqc111_write_cmd(dev, AQ_ACCESS_MAC, SFR_NODE_ID, + ETH_ALEN, ETH_ALEN, net->dev_addr); + if (ret < 0) + return ret; + + return 0; } static int aqc111_vlan_rx_kill_vid(struct net_device *net, From 546daf344420b5dce27eded3ad15be12628dd185 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 21 Oct 2026 18:18:58 +0200 Subject: [PATCH 015/137] net: fix frag_list leak in skb_segment_list() error path skb_segment_list() detaches the frag_list from the head skb and moves its members one by one to the skb->next chain. On error, err_linearize frees that chain with kfree_skb_list(skb->next), relying on the members not yet processed to still be linked after the current one. This does not hold when the current member was shared: it is replaced by skb_clone(), and __skb_clone() clears ->next. If __skb_linearize() then fails on that clone, the remaining members are no longer reachable from skb->next nor from skb_shinfo(skb)->frag_list, and they leak. The skb_shared() error branch already relinks nskb->next to list_skb before jumping to err_linearize. Do it unconditionally right after nskb is added to the chain, so every error exit sees the remaining members. On the last member list_skb is NULL, which still terminates the chain. Fixes: 53475c5dd856 ("net: fix use-after-free when UDP GRO with shared fraglist") Signed-off-by: Eric Dumazet Cc: Simon Polack Reviewed-by: Willem de Bruijn Reviewed-by: Wang Zhan Reviewed-by: Jiayuan Chen Link: https://patch.msgid.link/20261021161858.761205-1-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- net/core/skbuff.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/net/core/skbuff.c b/net/core/skbuff.c index 6a7c02b7653a..48f676411724 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -4782,10 +4782,13 @@ struct sk_buff *skb_segment_list(struct sk_buff *skb, else tail->next = nskb; - if (unlikely(err)) { - nskb->next = list_skb; + /* nskb may be a clone with a NULL ->next: keep the remaining + * members linked so that err_linearize frees them. + */ + nskb->next = list_skb; + + if (unlikely(err)) goto err_linearize; - } tail = nskb; From 46bd2bf65b93573f3b73ce8c61f862ac901c076d Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Wed, 21 Oct 2026 09:28:05 +0300 Subject: [PATCH 016/137] net/mlx5e: SHAMPO: Do not merge PSP packets PSP packets are UDP-encapsulated, and enabling HW GRO (SHAMPO in mlx5) makes it possible to merge those packets in a silly way: IP|UDP|PSP1|payload1|PSP2|payload2|... That happens because the HW GRO machinery doesn't know about PSP and considers it part of the UDP payload. Reject merging when the incoming CQE has PSP marker or the pending skb has the PSP extension. Fixes: 29d7f433fcec ("net/mlx5e: Add Rx data path offload") Signed-off-by: Cosmin Ratiu Reviewed-by: Dragos Tatulea Signed-off-by: Tariq Toukan Reviewed-by: Daniel Zahka Link: https://patch.msgid.link/20261021062805.3466208-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../ethernet/mellanox/mlx5/core/en_accel/en_accel.h | 6 ++++++ .../ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.h | 13 +++++++++++++ drivers/net/ethernet/mellanox/mlx5/core/en_rx.c | 6 ++++-- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/en_accel.h b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/en_accel.h index 3f212e46fc2f..5a340f1435e6 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/en_accel.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/en_accel.h @@ -218,6 +218,12 @@ static inline void mlx5e_accel_tx_finish(struct mlx5e_txqsq *sq, #endif } +static inline bool +mlx5e_accel_shampo_can_merge(struct mlx5e_rq *rq, struct mlx5_cqe64 *cqe) +{ + return mlx5e_psp_shampo_can_merge(rq->hw_gro_data->skb, cqe); +} + static inline int mlx5e_accel_init_rx(struct mlx5e_priv *priv) { return mlx5e_ktls_init_rx(priv); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.h b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.h index 2b080c39cc37..9fd1ee789de0 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.h @@ -85,6 +85,13 @@ static inline bool mlx5e_psp_is_rx_flow(struct mlx5_cqe64 *cqe) return MLX5_PSP_METADATA_MARKER(be32_to_cpu(cqe->ft_metadata)); } +static inline bool +mlx5e_psp_shampo_can_merge(struct sk_buff *skb, struct mlx5_cqe64 *cqe) +{ + /* PSP packets cannot be merged. */ + return !skb_ext_exist(skb, SKB_EXT_PSP) && !mlx5e_psp_is_rx_flow(cqe); +} + bool mlx5e_psp_offload_handle_rx_skb(struct net_device *netdev, struct sk_buff *skb, struct mlx5_cqe64 *cqe); #else @@ -110,6 +117,12 @@ static inline bool mlx5e_psp_is_rx_flow(struct mlx5_cqe64 *cqe) return false; } +static inline bool +mlx5e_psp_shampo_can_merge(struct sk_buff *skb, struct mlx5_cqe64 *cqe) +{ + return true; +} + static inline bool mlx5e_psp_offload_handle_rx_skb(struct net_device *netdev, struct sk_buff *skb, struct mlx5_cqe64 *cqe) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c b/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c index f8a6efd56c51..22fea4739a2c 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c @@ -49,6 +49,7 @@ #include "en_rep.h" #include "en/rep/tc.h" #include "ipoib/ipoib.h" +#include "en_accel/en_accel.h" #include "en_accel/ipsec.h" #include "en_accel/macsec.h" #include "en_accel/psp_rxtx.h" @@ -2268,8 +2269,9 @@ static void mlx5e_handle_rx_cqe_mpwrq_shampo(struct mlx5e_rq *rq, struct mlx5_cq } if (*skb && - !(match && mlx5e_hw_gro_skb_has_enough_space(*skb, data_bcnt, - page_size))) { + !(match && + mlx5e_hw_gro_skb_has_enough_space(*skb, data_bcnt, page_size) && + mlx5e_accel_shampo_can_merge(rq, cqe))) { match = false; mlx5e_shampo_flush_skb(rq, cqe, match); } From 520631554a689d2cd52452578f3140f045730ee0 Mon Sep 17 00:00:00 2001 From: Gal Pressman Date: Wed, 21 Oct 2026 09:42:31 +0300 Subject: [PATCH 017/137] net/mlx5e: IPsec, Fix out-of-bounds comparison of neighbour keys mlx5e_ipsec_netevent_event() keeps the L2 headers of offloaded tunnel mode SAs up to date: on every neighbour change it looks for SAs whose endpoint matches the neighbour's address. The compare width, 4 bytes for IPv4 or 16 for IPv6, is chosen from the SA's address family, with no check that the neighbour is of the same family. An ARP entry only stores a 4-byte key, so with an IPv6 tunnel mode SA offloaded, any IPv4 neighbour update in the system is compared as if it were a 16-byte IPv6 address, reading past the end of the object: BUG: KASAN: slab-out-of-bounds in mlx5e_ipsec_netevent_event+0x736/0x8d0 [mlx5_core] Read of size 4 at addr ff110001260a4ad8 by task swapper/8/0 Call Trace: mlx5e_ipsec_netevent_event+0x736/0x8d0 [mlx5_core] notifier_call_chain+0x97/0x2b0 atomic_notifier_call_chain+0x5d/0x130 neigh_timer_handler+0x35c/0x1140 call_timer_fn+0x164/0x490 __run_timers+0x5b9/0x960 run_timer_softirq+0xf0/0x1d0 handle_softirqs+0x189/0x8a0 __irq_exit_rcu+0x10d/0x250 ... Allocated by task 1015: __kmalloc_noprof+0x2e6/0x8b0 ___neigh_create+0xcc9/0x24c0 ip_finish_output2+0xfc7/0x1e80 ip_output+0x172/0x3b0 ip_send_skb+0xa5/0x1b0 udp_send_skb+0x852/0x10f0 udp_sendmsg+0x1333/0x2150 ... The opposite mix (IPv4 SA with IPv6 neighbour) stays in bounds, but only the first 32 bits of the IPv6 address are compared, so an unrelated neighbour can match. Skip neighbours whose address family doesn't match the SA. Fixes: 4c24272b4e2b ("net/mlx5e: Listen to ARP events to update IPsec L2 headers in tunnel mode") Signed-off-by: Gal Pressman Reviewed-by: Leon Romanovsky Reviewed-by: Yael Chemla Signed-off-by: Tariq Toukan Reviewed-by: Fernando Fernandez Mancera Link: https://patch.msgid.link/20261021064231.3466989-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c index 16edd3263aed..7e991a09b3ff 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c @@ -958,6 +958,9 @@ static int mlx5e_ipsec_netevent_event(struct notifier_block *nb, xa_for_each_marked(&ipsec->sadb, idx, sa_entry, MLX5E_IPSEC_TUNNEL_SA) { attrs = &sa_entry->attrs; + if (attrs->addrs.family != n->tbl->family) + continue; + if (attrs->addrs.family == AF_INET) { if (!neigh_key_eq32(n, &attrs->addrs.saddr.a4) && !neigh_key_eq32(n, &attrs->addrs.daddr.a4)) From b7dfff5e3eb3984d42026a6d653ec2a3d5cec729 Mon Sep 17 00:00:00 2001 From: Allison Henderson Date: Mon, 19 Oct 2026 17:28:15 -0700 Subject: [PATCH 018/137] net/rds: include the scope id in the sendmsg connection cache check rds_sendmsg() reuses the connection cached in rs->rs_conn when its peer address and ToS match the request. The interface index is part of a connection's identity as well: rds_conn_create_outgoing() passes the request's scope_id down as dev_if, and rds_conn_lookup() compares c_dev_if, so sends to the same link-local address through two interfaces are two different connections. The cache-hit test never looked at it. A socket bound to a non-link-local address has rs_bound_scope_id 0, and the scope check at the top of rds_sendmsg() accepts any non-zero destination scope for such a socket. So after a send to fe80::x%ifA, a send to fe80::x%ifB hits the cached ifA connection and the datagram leaves through ifA, to whichever peer answers to that address there. Compare c_dev_if with the request's scope_id in the cache test, so that such a send takes the lookup path and finds, or creates, the ifB connection - the same key the lookup uses. A send without a destination takes its scope from the socket, and the connection it created was keyed on that same scope, so the comparison holds for it too; the next patch gives that send the connected peer's own scope. Found by inspection while reworking this cache for the connection lifetime series on net-next; not observed in the field. Fixes: 1e2b44e78eea ("rds: Enable RDS IPv6 support") Signed-off-by: Allison Henderson Link: https://patch.msgid.link/20261020002816.3087414-2-achender@kernel.org Signed-off-by: Jakub Kicinski --- net/rds/send.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/rds/send.c b/net/rds/send.c index 1afa981e5c06..38b9cc9e0d20 100644 --- a/net/rds/send.c +++ b/net/rds/send.c @@ -1342,7 +1342,8 @@ int rds_sendmsg(struct socket *sock, struct msghdr *msg, size_t payload_len) /* rds_conn_create has a spinlock that runs with IRQ off. * Caching the conn in the socket helps a lot. */ if (rs->rs_conn && ipv6_addr_equal(&rs->rs_conn->c_faddr, &daddr) && - rs->rs_tos == rs->rs_conn->c_tos) { + rs->rs_tos == rs->rs_conn->c_tos && + rs->rs_conn->c_dev_if == scope_id) { conn = rs->rs_conn; } else { conn = rds_conn_create_outgoing(sock_net(sock->sk), From 3fe64a5f670dbd6c7c84c2345b0b9726b3b79b31 Mon Sep 17 00:00:00 2001 From: Allison Henderson Date: Mon, 19 Oct 2026 17:28:16 -0700 Subject: [PATCH 019/137] net/rds: keep the connected peer's scope id apart from the bound one rds_connect() has nowhere to keep the scope of a link-local peer, so it stores it in rs_bound_scope_id, the scope of the socket's own bound address, for rds_bind() to check a later link-local bind against. That field is then also what a send without a destination uses as the request's scope, what getpeername() and recvmsg() report as the peer's scope, and what a later bind() overwrites: rds_add_bound() stores the bound address's scope unconditionally, which for a non-link-local address is 0. So after connect(fe80::x%ifA) followed by bind(global), a send() with no destination asks for fe80::x with scope 0. rds_conn_lookup() keys on the interface, so that finds or creates a connection with c_dev_if 0, which the TCP transport then tries to connect through sin6_scope_id 0 and tcp_v6_connect() rejects for a link-local peer: the connected socket's data is queued on a connection that can never come up. In the other order, bind(global) then connect(fe80::x%ifB), the connect leaves a global-bound socket with rs_bound_scope_id ifB, so sends to other link-local peers are refused as off-link and sends to global peers inherit a meaningless interface. Give the connected peer its own rs_conn_scope_id. rds_connect() records it there and leaves the bound scope alone, rds_bind()'s connected-socket check compares against it, and the destination-less send takes its scope from it - falling back to the bound scope for a non-link-local peer, so that send() and sendto() to the connected peer compute the same scope, and so the same connection. getpeername() reports the connected peer with its own scope. recvmsg() used to report every sender with the socket's bound scope; a connected socket receives from any peer, so neither the bound scope nor the connected peer's scope is right for an arbitrary sender. It now reports a link-local sender with the interface recorded on its connection, c_dev_if: the TCP accept path takes it from the incoming socket, and the IB passive side resolves it from the local address, the closest the CM gets to the arrival interface. Any other sender is reported with scope 0, the inet6 convention. Either way, a reply to the address an application was given reaches the same connection. Found by inspection while reworking the sendmsg connection cache; not observed in the field. Fixes: 1e2b44e78eea ("rds: Enable RDS IPv6 support") Signed-off-by: Allison Henderson Link: https://patch.msgid.link/20261020002816.3087414-3-achender@kernel.org Signed-off-by: Jakub Kicinski --- net/rds/af_rds.c | 15 +++++++++------ net/rds/bind.c | 4 ++-- net/rds/rds.h | 2 ++ net/rds/recv.c | 11 ++++++++++- net/rds/send.c | 6 +++++- 5 files changed, 28 insertions(+), 10 deletions(-) diff --git a/net/rds/af_rds.c b/net/rds/af_rds.c index d5defe9172e3..e84d00bbe9a1 100644 --- a/net/rds/af_rds.c +++ b/net/rds/af_rds.c @@ -136,8 +136,7 @@ static int rds_getname(struct socket *sock, struct sockaddr *uaddr, sin6->sin6_port = rs->rs_conn_port; sin6->sin6_addr = rs->rs_conn_addr; sin6->sin6_flowinfo = 0; - /* scope_id is the same as in the bound address. */ - sin6->sin6_scope_id = rs->rs_bound_scope_id; + sin6->sin6_scope_id = rs->rs_conn_scope_id; uaddr_len = sizeof(*sin6); } } else { @@ -572,6 +571,7 @@ static int rds_connect(struct socket *sock, struct sockaddr_unsized *uaddr, } ipv6_addr_set_v4mapped(sin->sin_addr.s_addr, &rs->rs_conn_addr); rs->rs_conn_port = sin->sin_port; + rs->rs_conn_scope_id = 0; break; #if IS_ENABLED(CONFIG_IPV6) @@ -616,11 +616,14 @@ static int rds_connect(struct socket *sock, struct sockaddr_unsized *uaddr, ret = -EINVAL; break; } - /* Remember the connected address scope ID. It will - * be checked against the binding local address when - * the socket is bound. + /* Remember the connected address scope ID. It is + * checked against the binding local address when + * the socket is bound, and gives a send without a + * destination its scope. */ - rs->rs_bound_scope_id = sin6->sin6_scope_id; + rs->rs_conn_scope_id = sin6->sin6_scope_id; + } else { + rs->rs_conn_scope_id = 0; } rs->rs_conn_addr = sin6->sin6_addr; rs->rs_conn_port = sin6->sin6_port; diff --git a/net/rds/bind.c b/net/rds/bind.c index f800d920d969..3ac59cd512a2 100644 --- a/net/rds/bind.c +++ b/net/rds/bind.c @@ -233,8 +233,8 @@ int rds_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int addr_len) * non-link local address (scope_id is 0). */ if (!ipv6_addr_any(&rs->rs_conn_addr) && scope_id && - rs->rs_bound_scope_id && - scope_id != rs->rs_bound_scope_id) { + rs->rs_conn_scope_id && + scope_id != rs->rs_conn_scope_id) { ret = -EINVAL; goto out; } diff --git a/net/rds/rds.h b/net/rds/rds.h index 2db49573dacd..9b1ffc49c0a6 100644 --- a/net/rds/rds.h +++ b/net/rds/rds.h @@ -646,6 +646,8 @@ struct rds_sock { struct in6_addr rs_conn_addr; #define rs_conn_addr_v4 rs_conn_addr.s6_addr32[3] __be16 rs_conn_port; + /* scope of rs_conn_addr when it is link-local, 0 otherwise */ + __u32 rs_conn_scope_id; struct rds_transport *rs_transport; /* diff --git a/net/rds/recv.c b/net/rds/recv.c index 93ee4546ae36..00ab60f2d996 100644 --- a/net/rds/recv.c +++ b/net/rds/recv.c @@ -791,7 +791,16 @@ int rds_recvmsg(struct socket *sock, struct msghdr *msg, size_t size, sin6->sin6_port = inc->i_hdr.h_sport; sin6->sin6_addr = inc->i_saddr; sin6->sin6_flowinfo = 0; - sin6->sin6_scope_id = rs->rs_bound_scope_id; + /* A link-local sender is reported with the + * interface recorded on its connection (the + * accepting socket's device for TCP, the one + * resolved for the local address on IB); any + * other sender has no scope. + */ + sin6->sin6_scope_id = + (ipv6_addr_type(&inc->i_saddr) & + IPV6_ADDR_LINKLOCAL) ? + inc->i_conn->c_dev_if : 0; msg->msg_namelen = sizeof(*sin6); } } diff --git a/net/rds/send.c b/net/rds/send.c index 38b9cc9e0d20..f38967f65af1 100644 --- a/net/rds/send.c +++ b/net/rds/send.c @@ -1256,7 +1256,11 @@ int rds_sendmsg(struct socket *sock, struct msghdr *msg, size_t payload_len) lock_sock(sk); daddr = rs->rs_conn_addr; dport = rs->rs_conn_port; - scope_id = rs->rs_bound_scope_id; + /* The connected peer's scope for a link-local peer, else + * the bound scope, as an explicit send to that peer would + * compute below. + */ + scope_id = rs->rs_conn_scope_id ?: rs->rs_bound_scope_id; release_sock(sk); } From 5536d5553f274c8baa77c44854c9091657213970 Mon Sep 17 00:00:00 2001 From: Greg Thelen Date: Wed, 21 Oct 2026 08:26:58 -0700 Subject: [PATCH 020/137] selftests/net: tcp_mmap: remove unused -k option and keepflag Commit aacb0c2e524c ("selftests: net: tcp_mmap must use TCP_ZEROCOPY_RECEIVE") removed the only read of keepflag in child_thread(), leaving the file-scope keepflag variable set by the -k option in main() but never read. This triggers a Clang 23 warning: tcp_mmap.c:91:12: error: variable 'keepflag' set but not used [-Werror,-Wunused-but-set-global] Remove the unused keepflag variable and the dead -k getopt option. Fixes: aacb0c2e524c ("selftests: net: tcp_mmap must use TCP_ZEROCOPY_RECEIVE") Signed-off-by: Greg Thelen Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20261021152658.2618709-1-gthelen@google.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/tcp_mmap.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tools/testing/selftests/net/tcp_mmap.c b/tools/testing/selftests/net/tcp_mmap.c index 487ae659a1f1..86241bed7f08 100644 --- a/tools/testing/selftests/net/tcp_mmap.c +++ b/tools/testing/selftests/net/tcp_mmap.c @@ -85,7 +85,6 @@ static int rcvbuf; /* Default: autotuning. Can be set with -r option static int sndbuf; /* Default: autotuning. Can be set with -w option */ static int zflg; /* zero copy option. (MSG_ZEROCOPY for sender, mmap() for receiver */ static int xflg; /* hash received data (simple xor) (-h option) */ -static int keepflag; /* -k option: receiver shall keep all received file in memory (no munmap() calls) */ static int integrity; /* -i option: sender and receiver compute sha256 over the data.*/ static size_t chunk_size = 512*1024; @@ -456,7 +455,7 @@ int main(int argc, char *argv[]) int sflg = 0; int mss = 0; - while ((c = getopt(argc, argv, "46p:svr:w:H:zxkP:M:C:a:i")) != -1) { + while ((c = getopt(argc, argv, "46p:svr:w:H:zxP:M:C:a:i")) != -1) { switch (c) { case '4': cfg_family = PF_INET; @@ -490,9 +489,6 @@ int main(int argc, char *argv[]) case 'x': xflg = 1; break; - case 'k': - keepflag = 1; - break; case 'P': max_pacing_rate = atoi(optarg) ; break; From c955325db639e9503941227e14e1f1ccc99442a1 Mon Sep 17 00:00:00 2001 From: yanxia Date: Tue, 20 Oct 2026 10:01:47 +0800 Subject: [PATCH 021/137] veth: fix stale peer max_mtu on XDP program replacement veth_xdp_set() calculates the peer MTU limit from the incoming program's XDP fragment capability, but only stores it when no program was attached. Replacing a program with one having a different fragment capability therefore leaves the peer's max_mtu stale. Replacing a regular program with an xdp.frags program keeps rejecting otherwise valid jumbo MTUs. The opposite replacement leaves a larger limit in place and permits MTUs which a fresh attachment of the regular program would reject. Update peer->max_mtu after every successful attachment or replacement. Keep the GSO feature update conditional on the first attachment. Found during review of the XDP replacement path and confirmed with native XDP on an isolated veth pair. Before the fix, direct replacement rejects MTU 9000 after switching to xdp.frags, and accepts it after switching back to a regular program. Detaching and attaching instead produces the expected results in both directions. UML tests on net reproduce the problem before this change and match the detach/attach controls afterwards, with both devices up or down and with either endpoint receiving XDP. Failed replacements preserve the previous state; same-type replacements and detach also behave as expected. Small-frame traffic passes in the devices-up cases. Fixes: 7cda76d858a4 ("veth: Allow jumbo frames in xdp mode") Cc: stable@vger.kernel.org Signed-off-by: yanxia Link: https://patch.msgid.link/20261020020147.790274-1-yanxia@cmss.chinamobile.com Signed-off-by: Jakub Kicinski --- drivers/net/veth.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/net/veth.c b/drivers/net/veth.c index 6205742f88d9..3eca92d3dc60 100644 --- a/drivers/net/veth.c +++ b/drivers/net/veth.c @@ -1627,10 +1627,9 @@ static int veth_xdp_set(struct net_device *dev, struct bpf_prog *prog, } } - if (!old_prog) { + if (!old_prog) peer->hw_features &= ~NETIF_F_GSO_SOFTWARE; - peer->max_mtu = max_mtu; - } + peer->max_mtu = max_mtu; xdp_features_set_redirect_target(peer, true); } From c43c1c24f3cdc0d2d3f59e5686573ac3129637aa Mon Sep 17 00:00:00 2001 From: Nandor Kracser Date: Wed, 21 Oct 2026 09:32:33 +0200 Subject: [PATCH 022/137] tls: avoid field-spanning write when copying crypto info do_tls_setsockopt_conf() copies the cipher-specific part of the crypto info to crypto_info + 1, where crypto_info points at the 4-byte info member of union tls_crypto_context. When optval is a kernel sockptr, copy_from_sockptr_offset() is a memcpy(), and FORTIFY_SOURCE bounds the destination by that member: [ 6.948348] memcpy: detected field-spanning write (size 52) of single field "dst" at include/linux/sockptr.h:49 (size 0) [ 6.948480] WARNING: include/linux/sockptr.h:49 at tls_setsockopt+0x9b2/0xa30, CPU#1: init/1 [ 7.001355] Call Trace: [ 7.003146] do_sock_setsockopt+0xb0/0x190 [ 7.003975] __sys_setsockopt+0x76/0xc0 [ 7.005368] do_syscall_64+0xda/0x4b0 tls_setsockopt() gets a kernel sockptr whenever a cgroup setsockopt BPF program is attached, even one that leaves the option alone, because __cgroup_bpf_run_filter_setsockopt() passes optval on in a kernel buffer. With a no-op program on the root cgroup, a plain TLS_TX setsockopt() with AES-GCM-256 triggers the warning above on an x86_64 build with CONFIG_FORTIFY_SOURCE. Copy into the union at offset sizeof(struct tls_crypto_info) instead. crypto_ctx already points at the right union for both the first set and the TLS 1.3 rekey (tmp) case, and the address is unchanged. Fixes: a7b75c5a8c41 ("net: pass a sockptr_t into ->setsockopt") Signed-off-by: Nandor Kracser Link: https://patch.msgid.link/20261021073233.16720-1-nandor@riptides.io Signed-off-by: Jakub Kicinski --- net/tls/tls_main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/tls/tls_main.c b/net/tls/tls_main.c index 6b889f568dc1..b8d5a843f761 100644 --- a/net/tls/tls_main.c +++ b/net/tls/tls_main.c @@ -699,8 +699,8 @@ static int do_tls_setsockopt_conf(struct sock *sk, sockptr_t optval, goto err_crypto_info; } - rc = copy_from_sockptr_offset(crypto_info + 1, optval, - sizeof(*crypto_info), + rc = copy_from_sockptr_offset((u8 *)crypto_ctx + sizeof(*crypto_info), + optval, sizeof(*crypto_info), optlen - sizeof(*crypto_info)); if (rc) { rc = -EFAULT; From 7076f684d5132e6a6145e1da2fa74771ecd052e5 Mon Sep 17 00:00:00 2001 From: Dudu Lu Date: Wed, 21 Oct 2026 23:54:01 +0800 Subject: [PATCH 023/137] net/sched: etf: initialise the sock_exterr_skb before filling it report_sock_error() clones the transmitted skb, overlays a struct sock_exterr_skb on its control block and fills in the seven struct sock_extended_err fields, but never clears addr_offset and port. skb_clone() copies cb[] verbatim, so the errqueue entry carries the TX skb's stale control block to the receive side, and the ipv4 error-queue reader trusts those bytes: net/ipv4/ip_sockglue.c:556 sin->sin_addr.s_addr = *(__be32 *)(skb_network_header(skb) + serr->addr_offset); sin->sin_port = serr->port; The gate that is supposed to guard that read only asks whether an address was requested at all: net/ipv4/ip_sockglue.c:484 return serr->ee.ee_origin == SO_EE_ORIGIN_ICMP || serr->ee.ee_origin == SO_EE_ORIGIN_LOCAL || serr->port; SO_EE_ORIGIN_TXTIME is neither ICMP nor LOCAL, so for sch_etf the gate is decided by serr->port alone -- by the stale byte pair. When it is non-zero the reader dereferences skb_network_header() + addr_offset with an arbitrary offset. On the TCP path cb[40..44) is not zero: the transmit path stored delivered_mstamp there (tcp_rate_skb_sent()), which is exactly where addr_offset/port sit in the arm64 struct layout. Reached with an unmodified 7.3-rc4 (arm64, KASAN, in-tree modules): a TCP socket enables TX-timestamp error reporting (SO_TXTIME with SOF_TXTIME_REPORT_ERRORS), a sch_etf qdisc drops the packet, and a recvmsg() reaches ip_recv_error(): BUG: KASAN: slab-out-of-bounds in ip_recv_error+0x970/0xa0c Read of size 4 at addr ffff0000158ffdd5 by task A-227-A227-1_S2/113 ip_recv_error+0x970/0xa0c inet_recv_error+0x94/0xdc tcp_recvmsg+0x378/0x408 ... The buggy address is located 149 bytes to the right of allocated 704-byte region Zero the whole overlay rather than the two fields individually: the errqueue entry is handed to the receive side as a self-contained record, which is what every other producer in the tree already does (sch_etf was the only error-queue producer that cleared neither field). This does not change what the in-tree selftest observes -- so_txtime.c reads only ee_origin/ee_errno/ee_code/ee_data/ee_info, never msg_name or sin_port. Fixes: 4b15c7075352 ("net/sched: Make etf report drops on error_queue") Signed-off-by: Dudu Lu Reviewed-by: Simon Horman Link: https://patch.msgid.link/20261021155401.65997-1-phx0fer@gmail.com Signed-off-by: Jakub Kicinski --- net/sched/sch_etf.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/sched/sch_etf.c b/net/sched/sch_etf.c index ada87a81da6a..aaafcf640bc7 100644 --- a/net/sched/sch_etf.c +++ b/net/sched/sch_etf.c @@ -147,6 +147,7 @@ static void report_sock_error(struct sk_buff *skb, u32 err, u8 code) return; serr = SKB_EXT_ERR(clone); + memset(serr, 0, sizeof(*serr)); serr->ee.ee_errno = err; serr->ee.ee_origin = SO_EE_ORIGIN_TXTIME; serr->ee.ee_type = 0; From 6430fb7f1cc4e329e386521b7b208df443de92f2 Mon Sep 17 00:00:00 2001 From: Henry Martin Date: Mon, 26 Oct 2026 11:09:28 +0800 Subject: [PATCH 024/137] netlink: fix out-of-bounds bitmap access clearing stale mc groups netlink_realloc_groups() sizes nlk->groups to the number of groups that exist at bind/ADD_MEMBERSHIP time. When more multicast groups are registered later (e.g. a new genl family), existing sockets keep their smaller bitmap. When clearing a multicast group of a departing family, __netlink_clear_multicast_users() runs netlink_update_socket_mc() on each socket on mc_list, which does test_bit()/__assign_bit() on group - 1 with no regard to nlk->ngroups. Skip sockets whose bitmap predates the group: such a socket can never have joined it. The overflow windows differ per architecture, both needing privileged module (un)loading. On 32-bit kernels the ~54 dynamically allocated genl groups already exceed the initial 32-bit bitmap, so the table can grow there directly. On 64-bit kernels the same in-tree set fits the initial 64-bit bitmap only while allocation stays contiguous: genl_allocate_reserve_groups() takes the first free contiguous range and unregister does not compact, so an unload/reload sequence can fragment the ~59 free IDs until no fitting range remains, forcing the table past 64 bits and reaching the same stale-bitmap overflow on the next family teardown. This issue was discovered by Tencent CodeBuddy Security. Fixes: 84659eb529b3 ("[NETLIKN]: Allow removing multicast groups.") Signed-off-by: Henry Martin Signed-off-by: Dave Miller --- net/netlink/af_netlink.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/netlink/af_netlink.c b/net/netlink/af_netlink.c index a904dc3bcad6..fec2879c497d 100644 --- a/net/netlink/af_netlink.c +++ b/net/netlink/af_netlink.c @@ -2157,8 +2157,12 @@ void __netlink_clear_multicast_users(struct sock *ksk, unsigned int group) struct netlink_table *tbl = &nl_table[ksk->sk_protocol]; struct hlist_node *tmp; - sk_for_each_bound_safe(sk, tmp, &tbl->mc_list) + sk_for_each_bound_safe(sk, tmp, &tbl->mc_list) { + /* bitmap predates the group, can't be a member */ + if (group - 1 >= nlk_sk(sk)->ngroups) + continue; netlink_update_socket_mc(nlk_sk(sk), group, 0); + } } struct nlmsghdr * From 306f788134a21a3d60c1eb872d0ec0ce7c7e09bc Mon Sep 17 00:00:00 2001 From: Dudu Lu Date: Wed, 21 Oct 2026 23:54:33 +0800 Subject: [PATCH 025/137] net/sched: gred: keep the qdisc lock while updating the table gred_change_table_def() publishes table->DPs/def/red_flags under sch_tree_lock(), drops the lock, and then keeps writing state the datapath reads without it: - gred_enable_rio_mode()/gred_disable_rio_mode()/gred_disable_wred_mode() set/clear table->flags; - the red_flags_changed loop writes table->tab[i]->red_flags; - the shadowed-VQ loop frees table->tab[i] and clears the slot. gred_enqueue() and gred_dequeue() read those same fields (test_bit on table->flags, q->red_flags, t->tab[]/t->DPs) under the same root lock on another CPU, so the post-unlock writes race with the enqueue path: BUG: KCSAN: data-race in gred_change_table_def [sch_gred] / gred_enqueue [sch_gred] read (marked) to 0xffff000007ad3b00 of 8 bytes by task 110 on cpu 0: gred_enqueue+0x88/0xc60 [sch_gred] dev_qdisc_enqueue+0x4c/0x19c __dev_queue_xmit+0x10a4/0x1ea4 The comment justifying the early unlock only covers the DP-vs-DPs check, which the shadowed-VQ free loop relies on; it does not cover the scalar mode bits and per-VQ red_flags written afterwards. Move the unlock below those updates so everything the datapath reads is published under the lock that protects it. Fixes: 6639607ed9de ("[PKT_SCHED]: GRED: Use a central table definition change procedure") Signed-off-by: Dudu Lu Reviewed-by: Simon Horman Reviewed-by: Daniel Machon Link: https://patch.msgid.link/20261021155434.66572-1-phx0fer@gmail.com Signed-off-by: Jakub Kicinski --- net/sched/sch_gred.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/net/sched/sch_gred.c b/net/sched/sch_gred.c index f04f425c6c44..f3d9a7252949 100644 --- a/net/sched/sch_gred.c +++ b/net/sched/sch_gred.c @@ -442,13 +442,6 @@ static int gred_change_table_def(struct Qdisc *sch, struct nlattr *dps, red_flags_changed = table->red_flags != sopt->flags; table->red_flags = sopt->flags; - /* - * Every entry point to GRED is synchronized with the above code - * and the DP is checked against DPs, i.e. shadowed VQs can no - * longer be found so we can unlock right here. - */ - sch_tree_unlock(sch); - if (sopt->grio) { gred_enable_rio_mode(table); gred_disable_wred_mode(table); @@ -474,6 +467,15 @@ static int gred_change_table_def(struct Qdisc *sch, struct nlattr *dps, } } + /* + * Keep the lock until every field the datapath reads (the + * RIO/WRED mode bits, the per-VQ red_flags and the tab[] slots) + * has been updated: gred_enqueue()/gred_dequeue() read them under + * the same lock, so publishing them after the unlock races with + * the enqueue path. + */ + sch_tree_unlock(sch); + gred_offload(sch, TC_GRED_REPLACE); return 0; } From bfa09dccf98cf78dd9282259b2140bd1472f5ae9 Mon Sep 17 00:00:00 2001 From: Wei Jie Law Date: Wed, 21 Oct 2026 10:03:09 +0800 Subject: [PATCH 026/137] net: usb: pegasus: do not advance rx_skb->data on dropped frames For the 0x8513 chip, read_bulk_callback() skips the 2-byte receive header by advancing pegasus->rx_skb->data in place. This is done before the pkt_len > PEGASUS_MTU check, and when that check drops the frame the good path resubmits the same skb with the advanced rx_skb->data as the transfer buffer, still PEGASUS_MTU bytes long. Every frame with an oversized length field therefore moves the receive buffer two more bytes towards the end of the skb data area. A device that keeps sending such frames makes the host controller write received data over skb_shared_info and past the end of the allocation, and the corrupted skb_shared_info is used when the skb is eventually freed. With an emulated device sending frames filled with 0x5a on a KASAN kernel: BUG: KASAN: slab-out-of-bounds in dummy_timer+0xade/0x3f00 [dummy_hcd] Write of size 1536 at addr ffff888105821202 by task swapper/1/0 ... Oops: general protection fault, probably for non-canonical address 0xeb4b474b4b4b4b4c: 0000 [#1] PREEMPT SMP KASAN NOPTI KASAN: maybe wild-memory-access in range [0x5a5a5a5a5a5a5a60-0x5a5a5a5a5a5a5a67] RIP: 0010:skb_release_data+0x36f/0x800 Only skip the header with skb_reserve() once the frame is going to be passed up, so the receive buffer always starts at the beginning of the skb data area. Unlike the old pointer adjustment, skb_reserve() also moves skb->tail, so the two header bytes now count against the buffer size: lower the length limit for the 0x8513 accordingly, otherwise a pkt_len of PEGASUS_MTU - 1 or PEGASUS_MTU would trip skb_over_panic(). Found by code review with LLM assistance and reproduced with an emulated USB device (raw-gadget + dummy_hcd). Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Wei Jie Law Reviewed-by: Simon Horman Link: https://patch.msgid.link/20261021020310.34045-2-law.wei.jie@gt.tech.gov.sg Signed-off-by: Jakub Kicinski --- drivers/net/usb/pegasus.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/usb/pegasus.c b/drivers/net/usb/pegasus.c index aba1a640fc26..7c7d6b69998f 100644 --- a/drivers/net/usb/pegasus.c +++ b/drivers/net/usb/pegasus.c @@ -461,6 +461,7 @@ static void read_bulk_callback(struct urb *urb) u8 *buf = urb->transfer_buffer; int rx_status, count = urb->actual_length; int status = urb->status; + int rx_offset = 0; __u16 pkt_len; if (!pegasus) @@ -510,7 +511,7 @@ static void read_bulk_callback(struct urb *urb) if (pegasus->chip == 0x8513) { pkt_len = le32_to_cpu(*(__le32 *)urb->transfer_buffer); pkt_len &= 0x0fff; - pegasus->rx_skb->data += 2; + rx_offset = 2; } else { pkt_len = buf[count - 3] << 8; pkt_len += buf[count - 4]; @@ -522,13 +523,14 @@ static void read_bulk_callback(struct urb *urb) * If the packet is unreasonably long, quietly drop it rather than * kernel panicing by calling skb_put. */ - if (pkt_len > PEGASUS_MTU) + if (pkt_len > PEGASUS_MTU - rx_offset) goto goon; /* * at this point we are sure pegasus->rx_skb != NULL * so we go ahead and pass up the packet. */ + skb_reserve(pegasus->rx_skb, rx_offset); skb_put(pegasus->rx_skb, pkt_len); pegasus->rx_skb->protocol = eth_type_trans(pegasus->rx_skb, net); netif_rx(pegasus->rx_skb); From efee699f6de06611234ef9b0abd52db1340e5452 Mon Sep 17 00:00:00 2001 From: Wei Jie Law Date: Wed, 21 Oct 2026 10:03:10 +0800 Subject: [PATCH 027/137] net: usb: pegasus: check rx pkt_len against the received length read_bulk_callback() takes the frame length from the device, from the receive header on the 0x8513 and from the trailing status bytes on the other chips, and only checks it against PEGASUS_MTU before passing that many bytes up the stack. In two cases the stack then uses bytes that the device did not send: - A device that sends a short transfer but claims a long frame gets the stale or uninitialized remainder of the receive skb delivered as packet data. - A device that claims a frame shorter than an Ethernet header gets past the check as well. eth_type_trans() takes pkt_type from h_dest and the protocol from h_proto without checking skb->len. If the transfer is also short, those come from bytes left in the skb by an earlier transfer, or from uninitialized memory. Drop frames shorter than ETH_HLEN, and frames whose claimed length, plus the 0x8513 header, does not fit in the bytes actually received, and count them as length errors. urb->actual_length never exceeds the PEGASUS_MTU sized transfer buffer, so this also covers the existing PEGASUS_MTU check. Valid Ethernet frames are at least 60 bytes long without the FCS, so they are not affected. Found by code review with LLM assistance. With an emulated USB device, a 101-byte transfer claiming a 1500-byte frame was passed up as a 1500-byte packet before this change and is dropped after it, and frames claiming 0, 4 and 13 bytes were passed up and are now dropped and counted in rx_length_errors. The extra bytes happened to be zero in the first test, so disclosure of actual kernel data was not demonstrated. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Wei Jie Law Reviewed-by: Simon Horman Link: https://patch.msgid.link/20261021020310.34045-3-law.wei.jie@gt.tech.gov.sg Signed-off-by: Jakub Kicinski --- drivers/net/usb/pegasus.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/net/usb/pegasus.c b/drivers/net/usb/pegasus.c index 7c7d6b69998f..68e4b6658dd2 100644 --- a/drivers/net/usb/pegasus.c +++ b/drivers/net/usb/pegasus.c @@ -520,11 +520,17 @@ static void read_bulk_callback(struct urb *urb) } /* - * If the packet is unreasonably long, quietly drop it rather than - * kernel panicing by calling skb_put. + * Drop frames shorter than an Ethernet header, because + * eth_type_trans() reads the header whatever the length, and frames + * longer than what was actually received. Passing either up would + * expose stale buffer contents, and skb_put() could panic on the + * latter. count is at most PEGASUS_MTU. */ - if (pkt_len > PEGASUS_MTU - rx_offset) + if (pkt_len < ETH_HLEN || pkt_len + rx_offset > count) { + net->stats.rx_errors++; + net->stats.rx_length_errors++; goto goon; + } /* * at this point we are sure pegasus->rx_skb != NULL From dacb77e32e2bba78f90a8637fd27e13e2c9775dd Mon Sep 17 00:00:00 2001 From: Prashant Sengar Date: Wed, 21 Oct 2026 01:45:37 +0530 Subject: [PATCH 028/137] net: usb: lan78xx: avoid NULL PHY dereference in WoL setup LAN7801 supports fixed-link configurations without an attached PHY. However, lan78xx_set_wol() unconditionally passes netdev->phydev to phy_ethtool_set_wol(), causing a NULL pointer dereference when a userspace WoL request reaches this path. Report no WoL capabilities when no PHY is attached, and return -EOPNOTSUPP from lan78xx_set_wol() before modifying the wakeup configuration. Keep the existing phylib WoL handling for devices with an attached PHY. The issue was reproduced using USB Raw Gadget and dummy_hcd in QEMU. An ETHTOOL_SWOL ioctl triggered a kernel Oops with the original driver. With this change, the PHY-less configuration no longer advertises unsupported WoL operations, avoiding the crash. Fixes: e110bc825897 ("net: usb: lan78xx: Convert to PHYLINK for improved PHY and MAC management") Signed-off-by: Prashant Sengar Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20261020201537.2465655-1-prashantsengar25031@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/usb/lan78xx.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/net/usb/lan78xx.c b/drivers/net/usb/lan78xx.c index 3b92ec5916ad..181e8c087655 100644 --- a/drivers/net/usb/lan78xx.c +++ b/drivers/net/usb/lan78xx.c @@ -1767,6 +1767,12 @@ static void lan78xx_get_wol(struct net_device *netdev, u32 buf; struct lan78xx_priv *pdata = (struct lan78xx_priv *)(dev->data[0]); + if (!netdev->phydev) { + wol->supported = 0; + wol->wolopts = 0; + return; + } + if (usb_autopm_get_interface(dev->intf) < 0) return; @@ -1798,6 +1804,9 @@ static int lan78xx_set_wol(struct net_device *netdev, if (wol->wolopts & ~WAKE_ALL) return -EINVAL; + if (!netdev->phydev) + return -EOPNOTSUPP; + ret = usb_autopm_get_interface(dev->intf); if (ret < 0) return ret; From 9463e8d8c6922a5aa87f7e4fe6deb4f16925d498 Mon Sep 17 00:00:00 2001 From: Alexandre Cassen Date: Wed, 21 Oct 2026 21:32:18 +0200 Subject: [PATCH 029/137] net/sched: act_api: inherit filter offload flags only on new actions tcf_action_init() gives the skip_sw and skip_hw flags of a filter to the actions it binds. An action that another filter created gets them too, they then apply to every filter that uses this action. When a skip_sw filter reuses the action of a software filter, the software filter silently stops running it. This happens even when the filter add fails on a later action. A skip_hw filter likewise prevents a later filter without flags from reusing the action. Give the flags only to the actions that the filter creates. The mismatch check still rejects a filter that reuses an action with a flag that the filter does not have. Discovered by manual code inspection after the Sashiko AI review [1] of a tunnel_key patch questioned how actions get the filter flags. Reproduced on netdevsim without kernel changes. The egress filter below runs in software only, since netdevsim offloads only ingress filters. Once the skip_sw filter reuses its drop action, the last command shows skip_sw on this action and the egress filter stops dropping packets: ethtool -K eth0 hw-tc-offload on tc qdisc add dev eth0 clsact tc filter add dev eth0 egress protocol ip flower dst_ip 192.0.2.1 \ action gact drop index 1 tc filter add dev eth0 ingress protocol ip flower skip_sw \ dst_ip 192.0.2.2 action gact drop index 1 tc actions get action gact index 1 Tested with the reproducer and the tc-testing action, matchall and flower tests. Fixes: 34d35b4edbbe ("net/sched: act_api: deny mismatched skip_sw/skip_hw flags for actions created by classifiers") Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261009051930.3619697-1-acassen%40corp.free.fr [1] Signed-off-by: Alexandre Cassen Reviewed-by: Simon Horman Link: https://patch.msgid.link/20261021193222.1318333-2-acassen@corp.free.fr Signed-off-by: Jakub Kicinski --- net/sched/act_api.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/sched/act_api.c b/net/sched/act_api.c index 841e4584f0fe..e142d33dc5ef 100644 --- a/net/sched/act_api.c +++ b/net/sched/act_api.c @@ -1560,6 +1560,9 @@ int tcf_action_init(struct net *net, struct tcf_proto *tp, struct nlattr *nla, err = -EINVAL; goto err; } + /* Other filters share an existing action */ + if (init_res[i - 1] != ACT_P_CREATED) + continue; if (skip_sw) act->tcfa_flags |= TCA_ACT_FLAGS_SKIP_SW; if (skip_hw) From 4b3a32b56e5bada8e05ddaba1340ade9d0f22679 Mon Sep 17 00:00:00 2001 From: Alexandre Cassen Date: Wed, 21 Oct 2026 21:32:19 +0200 Subject: [PATCH 030/137] selftests: tc-testing: test offload flags of reused matchall actions Check that a skip_hw filter leaves the flags of an action it reuses unchanged, even when the filter add fails on a later action. Also check that a filter without skip_hw still cannot reuse a skip_hw action. The dummy device has no tc offload, so skip_hw stands in for skip_sw. Signed-off-by: Alexandre Cassen Reviewed-by: Simon Horman Link: https://patch.msgid.link/20261021193222.1318333-3-acassen@corp.free.fr Signed-off-by: Jakub Kicinski --- .../tc-testing/tc-tests/filters/matchall.json | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/tools/testing/selftests/tc-testing/tc-tests/filters/matchall.json b/tools/testing/selftests/tc-testing/tc-tests/filters/matchall.json index f8d28c415bc3..6b3eea401fc8 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/filters/matchall.json +++ b/tools/testing/selftests/tc-testing/tc-tests/filters/matchall.json @@ -481,6 +481,77 @@ "$TC actions del action police index 199" ] }, + { + "id": "230c", + "name": "Keep the flags of a gact action reused by a matchall filter with skip_hw", + "category": [ + "filter", + "matchall" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DUMMY ingress", + "$TC filter add dev $DUMMY parent ffff: handle 0x1 prio 1 protocol ipv4 matchall action gact pass index 199" + ], + "cmdUnderTest": "$TC filter add dev $DUMMY parent ffff: handle 0x2 prio 2 protocol ipv4 matchall skip_hw action gact pass index 199", + "expExitCode": "0", + "verifyCmd": "$TC actions get action gact index 199", + "matchPattern": "skip_hw", + "matchCount": "0", + "teardown": [ + "$TC qdisc del dev $DUMMY ingress" + ] + }, + { + "id": "1f7a", + "name": "Keep the flags of a gact action reused by a rejected matchall filter with skip_hw", + "category": [ + "filter", + "matchall" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DUMMY ingress", + "$TC actions add action gact pass index 200", + "$TC filter add dev $DUMMY parent ffff: handle 0x1 prio 1 protocol ipv4 matchall action gact pass index 199" + ], + "cmdUnderTest": "$TC filter add dev $DUMMY parent ffff: handle 0x2 prio 2 protocol ipv4 matchall skip_hw action gact pass index 199 action gact pass index 200", + "expExitCode": "2", + "verifyCmd": "$TC actions get action gact index 199", + "matchPattern": "skip_hw", + "matchCount": "0", + "teardown": [ + "$TC qdisc del dev $DUMMY ingress", + "$TC actions del action gact index 200" + ] + }, + { + "id": "9117", + "name": "Reject the reuse of a skip_hw gact action by a matchall filter without skip_hw", + "category": [ + "filter", + "matchall" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DUMMY ingress", + "$TC filter add dev $DUMMY parent ffff: handle 0x1 prio 1 protocol ipv4 matchall skip_hw action gact pass index 199" + ], + "cmdUnderTest": "$TC filter add dev $DUMMY parent ffff: handle 0x2 prio 2 protocol ipv4 matchall action gact pass index 199", + "expExitCode": "2", + "verifyCmd": "$TC filter get dev $DUMMY parent ffff: handle 2 prio 2 protocol ipv4 matchall", + "matchPattern": "^filter parent ffff: protocol ip pref 2 matchall.*handle 0x2.*", + "matchCount": "0", + "teardown": [ + "$TC qdisc del dev $DUMMY ingress" + ] + }, { "id": "2638", "name": "Add matchall and try to get it", From c5234f89d7cb311880ef218cb7a969794a106678 Mon Sep 17 00:00:00 2001 From: Dudu Lu Date: Wed, 21 Oct 2026 23:54:17 +0800 Subject: [PATCH 031/137] net/sched: taprio: unwind the netdevice notifier if register_qdisc() fails taprio_module_init() registers taprio_device_notifier and then returns register_qdisc()'s error directly. When register_qdisc() fails the notifier stays registered, but the loader's failure path frees the module image without ever calling the __exit function, so netdev_chain is left pointing at taprio_device_notifier.notifier_call -- a function pointer that lives in text that has just been freed. register_qdisc() has two failure returns: -EEXIST (duplicate .id) and -EINVAL (malformed cl_ops). taprio_class_ops satisfies every checked implication, so -EINVAL is unreachable and -EEXIST is the only reachable failure; it needs a pre-existing "taprio" entry in qdisc_base. In-tree that is reachable only through bpf_qdisc_reg() (CONFIG_NET_SCH_BPF); a module calling the exported register_qdisc() reaches it as well. Once the notifier is left dangling, the next netdevice event that walks netdev_chain calls through the stale pointer: BUG: KASAN: vmalloc-out-of-bounds in notifier_call_chain+0x650/0xb5c Read of size 8 at addr ffff80001fc5ab08 by task ip/110 notifier_call_chain+0x650/0xb5c raw_notifier_call_chain+0x34/0x64 call_netdevice_notifiers_info+0xa8/0x198 __dev_close_many+0xe0/0x800 ... >ffff80001fc5ab00: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 The shadow row is all f8 (KASAN_VMALLOC_INVALID): the read lands in vmalloc memory that was poisoned when the module image was freed, and the indirect call is unguarded because the only check, func_ptr_is_kernel_text(), is compiled out unless CONFIG_DEBUG_NOTIFIERS. Mirror the sibling sch_cbs.c and act_mirred.c, which unwind the notifier on this failure path. Fixes: 7b9eba7ba0c1 ("net/sched: taprio: fix picos_per_byte miscalculation") Signed-off-by: Dudu Lu Reviewed-by: Simon Horman Link: https://patch.msgid.link/20261021155417.66254-1-phx0fer@gmail.com Signed-off-by: Jakub Kicinski --- net/sched/sch_taprio.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/sched/sch_taprio.c b/net/sched/sch_taprio.c index 8e7fb6d0fc00..86740f4e82f5 100644 --- a/net/sched/sch_taprio.c +++ b/net/sched/sch_taprio.c @@ -2586,7 +2586,11 @@ static int __init taprio_module_init(void) if (err) return err; - return register_qdisc(&taprio_qdisc_ops); + err = register_qdisc(&taprio_qdisc_ops); + if (err) + unregister_netdevice_notifier(&taprio_device_notifier); + + return err; } static void __exit taprio_module_exit(void) From 72ee27b97986f4d331fe1c26e4333f12738d982f Mon Sep 17 00:00:00 2001 From: alex todadze Date: Tue, 20 Oct 2026 22:42:24 +0000 Subject: [PATCH 032/137] net: stmmac: fix rx checksum offload with extended descriptors With extended descriptors, bit 0 of RDES0 is the Extended Status Available flag. enh_desc_get_rx_status() still reads it as the payload checksum error bit of the checksum offload table, so every IP frame that has extended status is reported as csum_none and its checksum is verified again in software. When extended status is available, take the result from RDES4 instead. A TCP, UDP or ICMP payload with no IP header error, no payload error and no checksum bypass is a good frame. Everything else keeps the status from RDES0. Since RDES4 now decides the checksum status, read it after the own bit with dma_rmb(). While at it, fix the IP payload type mask, which covered bits 6:2 instead of 2:0 and had no users. Tested on a Siflower SF19A2890 (GMAC 3.70) with the board as the iperf3 endpoint. TCP receive goes from 326 to 378 Mbit/s with one stream and from 259 to 329 Mbit/s with four, and CPU use while receiving 200 Mbit/s drops from 26.6% to 21.5%. In stmmac_rx(), frames with a bad TCP, UDP or ICMP checksum, over IPv4 and IPv6 and including fragmented UDP, are still passed up as CHECKSUM_NONE and dropped by the stack. Fixes: c24602ef8664 ("stmmac: support extend descriptors") Signed-off-by: alex todadze Link: https://patch.msgid.link/20261020-stmmac-rx-csum-v2-1-330c8d0951ea@proton.me Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/descs.h | 6 ++++- .../net/ethernet/stmicro/stmmac/enh_desc.c | 25 +++++++++++++++++-- drivers/net/ethernet/stmicro/stmmac/hwif.h | 6 ++--- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 19 ++++++++++---- 4 files changed, 45 insertions(+), 11 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/descs.h b/drivers/net/ethernet/stmicro/stmmac/descs.h index 846007cc245f..0af819a98fda 100644 --- a/drivers/net/ethernet/stmicro/stmmac/descs.h +++ b/drivers/net/ethernet/stmicro/stmmac/descs.h @@ -116,7 +116,7 @@ #define ETDES1_BUFFER2_SIZE_MASK GENMASK(28, 16) /* Extended Receive descriptor definitions */ -#define ERDES4_IP_PAYLOAD_TYPE_MASK GENMASK(6, 2) +#define ERDES4_IP_PAYLOAD_TYPE_MASK GENMASK(2, 0) #define ERDES4_IP_HDR_ERR BIT(3) #define ERDES4_IP_PAYLOAD_ERR BIT(4) #define ERDES4_IP_CSUM_BYPASSED BIT(5) @@ -147,6 +147,10 @@ #define RDES_PTP_SIGNALING 0xa #define RDES_PTP_PKT_RESERVED_TYPE 0xf +#define RDES_EXT_PAYLOAD_UDP 0x1 +#define RDES_EXT_PAYLOAD_TCP 0x2 +#define RDES_EXT_PAYLOAD_ICMP 0x3 + /* Basic descriptor structure for normal and alternate descriptors */ struct dma_desc { __le32 des0; diff --git a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c index 051253601225..023b54b72478 100644 --- a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c +++ b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c @@ -109,8 +109,25 @@ static int enh_desc_coe_rdes0(int ipc_err, int type, int payload_err) return ret; } -static void enh_desc_get_ext_status(struct stmmac_extra_stats *x, - struct dma_extended_desc *p) +static int enh_desc_ext_rx_csum(u32 rdes0, u32 rdes4, int status) +{ + u32 type = FIELD_GET(ERDES4_IP_PAYLOAD_TYPE_MASK, rdes4); + + if (status != csum_none || !(rdes0 & RDES0_FRAME_TYPE)) + return status; + if (!(rdes4 & (ERDES4_IPV4_PKT_RCVD | ERDES4_IPV6_PKT_RCVD)) || + (rdes4 & (ERDES4_IP_HDR_ERR | ERDES4_IP_PAYLOAD_ERR | + ERDES4_IP_CSUM_BYPASSED))) + return status; + if (type != RDES_EXT_PAYLOAD_UDP && type != RDES_EXT_PAYLOAD_TCP && + type != RDES_EXT_PAYLOAD_ICMP) + return status; + + return good_frame; +} + +static int enh_desc_get_ext_status(struct stmmac_extra_stats *x, + struct dma_extended_desc *p, int status) { u32 rdes0 = le32_to_cpu(p->basic.des0); u32 rdes4 = le32_to_cpu(p->des4); @@ -170,7 +187,11 @@ static void enh_desc_get_ext_status(struct stmmac_extra_stats *x, x->l4_filter_match++; if (rdes4 & ERDES4_L3_L4_FILT_NO_MATCH_MASK) x->l3_l4_filter_no_match++; + + status = enh_desc_ext_rx_csum(rdes0, rdes4, status); } + + return status; } static int enh_desc_get_rx_status(struct stmmac_extra_stats *x, diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index 50b590d67d87..b2e557788803 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -70,8 +70,8 @@ struct stmmac_desc_ops { /* Return the reception status looking at the RDES1 */ int (*rx_status)(struct stmmac_extra_stats *x, struct dma_desc *p); - void (*rx_extended_status)(struct stmmac_extra_stats *x, - struct dma_extended_desc *p); + int (*rx_extended_status)(struct stmmac_extra_stats *x, + struct dma_extended_desc *p, int status); /* Set tx timestamp enable bit */ void (*enable_tx_timestamp) (struct dma_desc *p); /* get tx timestamp status */ @@ -128,7 +128,7 @@ struct stmmac_desc_ops { #define stmmac_rx_status(__priv, __args...) \ stmmac_do_callback(__priv, desc, rx_status, __args) #define stmmac_rx_extended_status(__priv, __args...) \ - stmmac_do_void_callback(__priv, desc, rx_extended_status, __args) + stmmac_do_callback(__priv, desc, rx_extended_status, __args) #define stmmac_enable_tx_timestamp(__priv, __args...) \ stmmac_do_void_callback(__priv, desc, enable_tx_timestamp, __args) #define stmmac_get_tx_timestamp_status(__priv, __args...) \ diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index b20e5038eead..9ded31150fbe 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -5700,9 +5700,13 @@ static int stmmac_rx_zc(struct stmmac_priv *priv, int limit, u32 queue) if (!buf->xdp) break; - if (priv->extend_desc) - stmmac_rx_extended_status(priv, &priv->xstats, - rx_q->dma_erx + entry); + if (priv->extend_desc) { + /* Read the extended status after the own bit */ + dma_rmb(); + status = stmmac_rx_extended_status(priv, &priv->xstats, + &rx_q->dma_erx[entry], + status); + } if (unlikely(status == discard_frame)) { xsk_buff_free(buf->xdp); buf->xdp = NULL; @@ -5883,8 +5887,13 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue) prefetch(np); - if (priv->extend_desc) - stmmac_rx_extended_status(priv, &priv->xstats, rx_q->dma_erx + entry); + if (priv->extend_desc) { + /* Read the extended status after the own bit */ + dma_rmb(); + status = stmmac_rx_extended_status(priv, &priv->xstats, + &rx_q->dma_erx[entry], + status); + } if (unlikely(status == discard_frame)) { page_pool_put_page(rx_q->page_pool, buf->page, 0, true); buf->page = NULL; From c6648c9f3b51c5a8a3df470c1542b2aa378d88f9 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 22 Oct 2026 17:54:36 +0200 Subject: [PATCH 033/137] net: harden qdisc_pkt_len_segs_init() against malicious GSO packets An SKB_GSO_DODGY packet can carry untrusted gso_size and headers. For instance, gso_size=1 and hdr_len=65532 for a 524275-byte frame makes qdisc_pkt_len_segs_init() compute an accounted length of about 4,294,574,091 bytes (after gso_segs silently wrapped in a u16). With DRR quantum 256, the dequeue path then needs ~16.8 million deficit refills before it can dequeue the skb, keeping the CPU in the qdisc path long enough to trigger the soft-lockup watchdog. Commit 8f735d64382d ("net/sched: bound qdisc_pkt_len to prevent qdisc soft lockup") added QDISC_PKT_LEN_MAX to bound size table amplification. Use the same bound for DODGY packets: - Make gso_segs a u32 and drop packets whose segment count does not fit GSO_MAX_SEGS, instead of silently truncating it. - Drop packets whose estimated qdisc_pkt_len() would exceed QDISC_PKT_LEN_MAX. Fixes: 15e5a0307164 ("net_sched: better precise estimation on packet length for untrusted packets") Reported-by: shaos Closes: https://lore.kernel.org/netdev/20261022144755.65025-1-shawnq1@163.com/ Signed-off-by: Eric Dumazet Cc: Jamal Hadi Salim Cc: "Michael S. Tsirkin" Acked-by: Michael S. Tsirkin Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20261022155436.117507-1-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- net/core/dev.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/net/core/dev.c b/net/core/dev.c index 1a09e7869118..52fa7a88c5d0 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -4283,7 +4283,7 @@ static enum skb_drop_reason qdisc_pkt_len_segs_init(struct sk_buff *skb) { struct skb_shared_info *shinfo = skb_shinfo(skb); unsigned int hdr_len, tlen; - u16 gso_segs; + u32 gso_segs; qdisc_skb_cb(skb)->pkt_len = skb->len; if (!shinfo->gso_size) { @@ -4334,6 +4334,15 @@ static enum skb_drop_reason qdisc_pkt_len_segs_init(struct sk_buff *skb) if (payload <= 0) return SKB_DROP_REASON_SKB_BAD_GSO; gso_segs = DIV_ROUND_UP(payload, shinfo->gso_size); + /* shinfo->gso_segs is a 16bit field. + * Also make sure untrusted gso_size/hdr_len can not inflate + * qdisc_pkt_len() beyond QDISC_PKT_LEN_MAX, or deficit + * schedulers could spin in their refill loops. + */ + if (unlikely(gso_segs > GSO_MAX_SEGS || + skb->len + (u64)(gso_segs - 1) * hdr_len > + QDISC_PKT_LEN_MAX)) + return SKB_DROP_REASON_SKB_BAD_GSO; shinfo->gso_segs = gso_segs; qdisc_skb_cb(skb)->pkt_segs = gso_segs; } From a81513bf5bdf29cf00c0a11f56be515c1ec260f4 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Fri, 23 Oct 2026 18:45:27 +0800 Subject: [PATCH 034/137] bonding: fix out-of-bounds read of arp_ip_target arp_ip_target is a module parameter array with BOND_MAX_ARP_TARGETS entries. In the loop parsing it in bond_check_params(), arp_ip_count is the number of accepted targets, while i is the index into arp_ip_target. Invalid and duplicate entries are skipped without bumping arp_ip_count, so bounding the loop with arp_ip_count lets i run past the end of arp_ip_target, e.g. 16 targets with one duplicate: BUG: KASAN: global-out-of-bounds in bond_check_params.constprop.0+0x1148/0x1440 Read of size 8 at addr ffffffff96e9ee00 by task swapper/0/1 Call Trace: dump_stack_lvl+0x91/0xf0 print_report+0xd1/0x630 kasan_report+0xf3/0x130 __asan_report_load8_noabort+0x14/0x30 bond_check_params.constprop.0+0x1148/0x1440 bonding_init+0x13/0xe0 do_one_initcall+0xe7/0x5b0 kernel_init_freeable+0xa91/0x1120 kernel_init+0x24/0x220 ret_from_fork+0x5b3/0x820 ret_from_fork_asm+0x1a/0x30 The buggy address belongs to the variable: arp_ip_target+0x80/0xa0 Bound the loop with i instead. arp_ip_count never exceeds i, so it stays within BOND_MAX_ARP_TARGETS too. Fixes: 5a5c5fd48e3b ("bonding: arp_ip_count and arp_targets can be wrong") Cc: stable@vger.kernel.org Signed-off-by: Jiayuan Chen Reviewed-by: Fernando Fernandez Mancera Reviewed-by: Nikolay Aleksandrov Acked-by: Jay Vosburgh Link: https://patch.msgid.link/20261023104528.232427-1-jiayuan.chen@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/bonding/bond_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index 09aa45d71fab..7019e5d8fdc8 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -6273,7 +6273,7 @@ static int __init bond_check_params(struct bond_params *params) } for (arp_ip_count = 0, i = 0; - (arp_ip_count < BOND_MAX_ARP_TARGETS) && arp_ip_target[i]; i++) { + (i < BOND_MAX_ARP_TARGETS) && arp_ip_target[i]; i++) { __be32 ip; /* not a complete check, but good enough to catch mistakes */ From 389952e8776a4663044df2ae3e8987a9e81964cf Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Sun, 25 Oct 2026 12:13:37 +0100 Subject: [PATCH 035/137] ipmr: clear skb->cb[] in reg_vif_xmit() reg_vif_xmit() hands the skb to ipmr_cache_report() as an IGMPMSG_WHOLEPKT report. When the mroute socket has IP_PKTINFO enabled, ipmr_cache_report() calls ipv4_pktinfo_prepare() on the original skb, which reads skb->cb[] as IPCB. By the time the pimreg device transmits the packet, skb->cb[] is not IPCB anymore: qdisc_pkt_len_init() overwrites IPCB(skb)->iif with qdisc_skb_cb(skb)->pkt_len, and any qdisc installed on the device or a tc BPF program writing skb->cb[] fills the area holding the IP options and IPCB(skb)->flags. For locally generated packets routed out pimreg (they carry an rtable dst), the mroute socket can therefore receive a bogus ipi_ifindex (the packet length), and the l3mdev slave test in ipv4_pktinfo_prepare() is driven by stale qdisc data. Clear IPCB before building the report, as done for IPv6 in commit 238164201431 ("ip6mr: clear skb->cb[] in reg_vif_xmit()"). The skb is freed right after, so nothing else depends on its control block. Fixes: bb7403655b3c ("ipmr: support IP_PKTINFO on cache report IGMP msg") Signed-off-by: Eric Dumazet Reviewed-by: Ido Schimmel Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20261025111337.142787-1-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- net/ipv4/ipmr.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c index acacaae05cc4..8b84142fd775 100644 --- a/net/ipv4/ipmr.c +++ b/net/ipv4/ipmr.c @@ -550,6 +550,11 @@ static netdev_tx_t reg_vif_xmit(struct sk_buff *skb, struct net_device *dev) DEV_STATS_ADD(dev, tx_bytes, skb->len); DEV_STATS_INC(dev, tx_packets); + /* skb->cb[] holds qdisc/tc state at this point, not IPCB. + * ipmr_cache_report() reads it as IPCB to build IP_PKTINFO. + */ + memset(IPCB(skb), 0, sizeof(*IPCB(skb))); + /* Pairs with WRITE_ONCE() in vif_add() and vif_delete() */ ipmr_cache_report(mrt, skb, READ_ONCE(mrt->mroute_reg_vif_num), IGMPMSG_WHOLEPKT); From 134351edc28e083c61012b6c69e8e54dd7fa5bcc Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Sun, 25 Oct 2026 09:17:28 +0200 Subject: [PATCH 036/137] ipmr: fully initialize the synthetic IP header in ipmr_get_route() For an unresolved multicast route query, ipmr_get_route() builds a synthetic IP header in the headroom obtained from skb_realloc_headroom(), which does not zero the memory it allocates. Only a few fields are initialized, leaving others as stale heap contents. ipmr_cache_report() copies this header into the IGMPMSG_NOCACHE report that is queued to the mroute socket, so a recvmsg() on that socket leaks the uninitialized bytes to user space: the 'tos' field [1] and the 'id' and 'frag_off' fields [2]. Fix by zeroing the IP header before filling in the few fields that are used, so that the entire header is initialized, as in ip6mr_get_route(). [1] BUG: KMSAN: kernel-infoleak in _copy_to_iter raw_recvmsg Uninit was stored to memory at: ipmr_cache_report ipmr_cache_unresolved ipmr_get_route Uninit was created at: pskb_expand_head skb_realloc_headroom ipmr_get_route Byte 1 of 28 is uninitialized [2] BUG: KMSAN: kernel-infoleak in _copy_to_iter raw_recvmsg Uninit was stored to memory at: ipmr_cache_report ipmr_cache_unresolved ipmr_get_route Uninit was created at: pskb_expand_head skb_realloc_headroom ipmr_get_route Bytes 4-7 of 28 are uninitialized Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Reported-by: Sashiko Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/0545f0c83f5ee2f6696210f87268e811ef655715.1792476357.git.zhilinz%40nebusec.ai Signed-off-by: Ido Schimmel Reviewed-by: Hangbin Liu Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20261025071729.34967-2-idosch@nvidia.com Signed-off-by: Jakub Kicinski --- net/ipv4/ipmr.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c index 8b84142fd775..4a22d9f23425 100644 --- a/net/ipv4/ipmr.c +++ b/net/ipv4/ipmr.c @@ -2511,6 +2511,7 @@ int ipmr_get_route(struct net *net, struct sk_buff *skb, skb_push(skb2, sizeof(struct iphdr)); skb_reset_network_header(skb2); iph = ip_hdr(skb2); + memset(iph, 0, sizeof(*iph)); iph->ihl = sizeof(struct iphdr) >> 2; iph->saddr = saddr; iph->daddr = daddr; From aa1799e93f5ebb973252aec649d84d12d9c7fd1c Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Sun, 25 Oct 2026 09:17:29 +0200 Subject: [PATCH 037/137] ipmr, ip6mr: validate packets injected into the register vif on xmit reg_vif_xmit() passes the packet to ipmr_cache_report() which copies its IP header into the IGMPMSG_WHOLEPKT report that is queued to the mroute socket. Nothing guarantees that the packet contains a full IP header, so when a short packet is injected on the register device (e.g., using AF_PACKET), the copy reads past the end of the packet and a recvmsg() on the mroute socket leaks the uninitialized bytes to user space [1]. IPv6 has the same problem [2]. Its reg_vif_xmit() does call pskb_inet_may_pull(), but the helper keys off skb->protocol and does not pull anything when the protocol is neither IPv4 nor IPv6. Fix by dropping packets whose protocol does not match the address family of the register device or that do not contain a full network header. [1] BUG: KMSAN: kernel-infoleak in _copy_to_iter raw_recvmsg Uninit was stored to memory at: ipmr_cache_report reg_vif_xmit packet_sendmsg Uninit was created at: __alloc_skb sock_alloc_send_pskb packet_sendmsg Bytes 1-3 of 21 are uninitialized [2] BUG: KMSAN: kernel-infoleak in _copy_to_iter rawv6_recvmsg Uninit was stored to memory at: ip6mr_cache_report reg_vif_xmit packet_sendmsg Uninit was created at: __alloc_skb sock_alloc_send_pskb packet_sendmsg Bytes 8-23 of 41 are uninitialized Fixes: 14fb64e1f449 ("[IPV6] MROUTE: Support PIM-SM (SSM).") Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Reported-by: Sashiko Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/0545f0c83f5ee2f6696210f87268e811ef655715.1792476357.git.zhilinz%40nebusec.ai Signed-off-by: Ido Schimmel Reviewed-by: Hangbin Liu Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20261025071729.34967-3-idosch@nvidia.com Signed-off-by: Jakub Kicinski --- net/ipv4/ipmr.c | 6 ++++++ net/ipv6/ip6mr.c | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c index 4a22d9f23425..915b210c4512 100644 --- a/net/ipv4/ipmr.c +++ b/net/ipv4/ipmr.c @@ -539,6 +539,12 @@ static netdev_tx_t reg_vif_xmit(struct sk_buff *skb, struct net_device *dev) }; int err; + if (skb->protocol != htons(ETH_P_IP) || !pskb_inet_may_pull(skb)) { + DEV_STATS_INC(dev, tx_errors); + kfree_skb(skb); + return NETDEV_TX_OK; + } + rcu_read_lock(); err = ipmr_fib_lookup(net, &fl4, &mrt); if (err < 0) { diff --git a/net/ipv6/ip6mr.c b/net/ipv6/ip6mr.c index 3a551be30f15..a628acf50b0d 100644 --- a/net/ipv6/ip6mr.c +++ b/net/ipv6/ip6mr.c @@ -642,7 +642,7 @@ static netdev_tx_t reg_vif_xmit(struct sk_buff *skb, .flowi6_mark = skb->mark, }; - if (!pskb_inet_may_pull(skb)) + if (skb->protocol != htons(ETH_P_IPV6) || !pskb_inet_may_pull(skb)) goto tx_err; rcu_read_lock(); From 09280680f2ea060514be2a39ea84209fb6d721d4 Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Thu, 22 Oct 2026 03:01:16 +0800 Subject: [PATCH 038/137] ipmr: drop packets with unknown ingress VIF A packet can race with VIF publication and make ipmr_find_vif() return -1 even though the VIF device pointer is already visible. The parent VIF device check can then pass with an invalid ingress VIF. For (*,G) entries, this can also let the packet be forwarded back out its ingress VIF. Drop packets whose ingress VIF lookup fails in both IPv4 and IPv6 forwarding, before checking the MFC parent device. This prevents forwarding with an unknown ingress VIF and avoids the (*,G) hairpin. Without the early check, the negative index can reach the IPv4 offload lookup only for an skb marked for L3 forwarding offload, currently done by mlxsw. The race is rare; the supplied PoC reports only the UBSAN negative-index diagnostic. Fixes: 4eadb88244d1 ("ipmr: do not acquire mrt_lock while calling ip_mr_forward()") Fixes: 9b1c21d898fd ("ip6mr: do not acquire mrt_lock while calling ip6_mr_forward()") Cc: stable@vger.kernel.org Reported-by: Vega Link: https://lore.kernel.org/netdev/b966eb102e98f299e45be59ada033a8766e4c6df.1791899747.git.zhilinz@nebusec.ai/ Suggested-by: Ido Schimmel Suggested-by: Naman Gulati Signed-off-by: Zhiling Zou Reviewed-by: Naman Gulati Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/5271fcb94a4d9e697ed64a348e92ea9e74faab1c.1792608753.git.zhilinz@nebusec.ai Signed-off-by: Jakub Kicinski --- net/ipv4/ipmr.c | 3 ++- net/ipv6/ip6mr.c | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c index 915b210c4512..e8f7d3b573d0 100644 --- a/net/ipv4/ipmr.c +++ b/net/ipv4/ipmr.c @@ -2089,7 +2089,8 @@ static void ip_mr_forward(struct net *net, struct mr_table *mrt, } /* Wrong interface: drop packet and (maybe) send PIM assert. */ - if (rcu_access_pointer(mrt->vif_table[vif].dev) != dev) { + if (true_vifi < 0 || + rcu_access_pointer(mrt->vif_table[vif].dev) != dev) { if (rt_is_output_route(skb_rtable(skb))) { /* It is our own packet, looped back. * Very complicated situation... diff --git a/net/ipv6/ip6mr.c b/net/ipv6/ip6mr.c index a628acf50b0d..af968a2c2a78 100644 --- a/net/ipv6/ip6mr.c +++ b/net/ipv6/ip6mr.c @@ -2225,7 +2225,8 @@ static void ip6_mr_forward(struct net *net, struct mr_table *mrt, /* * Wrong interface: drop packet and (maybe) send PIM assert. */ - if (rcu_access_pointer(mrt->vif_table[vif].dev) != dev) { + if (true_vifi < 0 || + rcu_access_pointer(mrt->vif_table[vif].dev) != dev) { atomic_long_inc(&c->_c.mfc_un.res.wrong_if); if (true_vifi >= 0 && READ_ONCE(mrt->mroute_do_assert) && From 5308a2049b764aa55513c90c68933abf596c51d2 Mon Sep 17 00:00:00 2001 From: Tung Nguyen Date: Fri, 23 Oct 2026 12:28:18 +0700 Subject: [PATCH 039/137] tipc: fix refcount panic in tipc_rcv() when receiving shared skb TIPC assumes that the received skb is not shared with other users. However, syzbot reports a bug triggered by receiving a shared skb: ... RIP: 0010:pskb_expand_head+0x135a/0x13a0 net/core/skbuff.c:2308 __pskb_pull_tail+0x111/0x1870 net/core/skbuff.c:2890 skb_cow_data+0x2ed/0xc50 net/core/skbuff.c:5398 tipc_aead_decrypt net/tipc/crypto.c:892 [inline] tipc_crypto_rcv+0x346/0x23d0 net/tipc/crypto.c:1843 tipc_rcv+0x4b7/0x1040 net/tipc/node.c:2119 tipc_l2_rcv_msg+0x2d9/0x470 net/tipc/bearer.c:669 deliver_ptype_list_skb net/core/dev.c:2533 [inline] ... The root cause is that local delivery hooks in the kernel (e.g., deliver_skb()) may increment the skb's reference count (`users`) before TIPC processes it on ingress. Fix this by calling skb_share_check() to ensure exclusive ownership before passing the skb to tipc_rcv(). As TIPC needs to modify the skb's data, the cloned skb needs to be uncloned when needed. This can be addressed in separate patch. Reported-by: syzbot+0017742620a9d06f06ef@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=0017742620a9d06f06ef Closes: https://lore.kernel.org/all/20261018145928.58317-1-ngocthang2710.1999@gmail.com/T/ Fixes: b97bf3fd8f6a ("[TIPC] Initial merge") Co-developed-by: Nguyen Ngoc Thang Signed-off-by: Nguyen Ngoc Thang Signed-off-by: Tung Nguyen Link: https://patch.msgid.link/20261023052921.54220-1-tung.quang.nguyen@est.tech Signed-off-by: Jakub Kicinski --- net/tipc/bearer.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/tipc/bearer.c b/net/tipc/bearer.c index 65fa18928fe3..774f68d1a2b3 100644 --- a/net/tipc/bearer.c +++ b/net/tipc/bearer.c @@ -661,6 +661,10 @@ static int tipc_l2_rcv_msg(struct sk_buff *skb, struct net_device *dev, { struct tipc_bearer *b; + skb = skb_share_check(skb, GFP_ATOMIC); + if (!skb) + return NET_RX_DROP; + rcu_read_lock(); b = rcu_dereference(dev->tipc_ptr) ?: rcu_dereference(orig_dev->tipc_ptr); From 850a7225bc61cdfaf0c4b02ff341ae7cb54902f5 Mon Sep 17 00:00:00 2001 From: Yingjie Wang <1075151112@qq.com> Date: Fri, 23 Oct 2026 22:40:36 +0800 Subject: [PATCH 040/137] tcp: limit tcp_tso_rtt_log to a valid shift count tcp_tso_autosize() right-shifts the u32 returned by tcp_min_rtt() by sysctl_tcp_tso_rtt_log. The sysctl uses proc_dou8vec_minmax() without an upper bound, so it accepts values through 255. Values above 31 make the shift undefined. A process with CAP_NET_ADMIN in the target network namespace can set the per-netns sysctl. With CONFIG_UBSAN_SHIFT=y, setting it to 32 and sending TCP data over loopback triggers: UBSAN: shift-out-of-bounds in net/ipv4/tcp_output.c:2264:30 shift exponent 32 is too large for 32-bit type 'unsigned int' Call Trace: __ubsan_handle_shift_out_of_bounds tcp_tso_segs tcp_write_xmit __tcp_push_pending_frames tcp_push tcp_sendmsg_locked Limit the sysctl to 31, the largest valid shift count for a u32, and document the accepted range. Writes of larger values now fail with -EINVAL. On a UBSAN/KASAN build of the net tree, values 0 and 31 remained accepted, values 32 and 255 returned -EINVAL, and TCP traffic with the valid boundary values produced no sanitizer diagnostics. Fixes: 65466904b015 ("tcp: adjust TSO packet sizes based on min_rtt") Cc: stable@vger.kernel.org Signed-off-by: Yingjie Wang <1075151112@qq.com> Reviewed-by: Jiayuan Chen Reviewed-by: Neal Cardwell Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/tencent_31D2FFCCE76C50A695A1270151F825E90906@qq.com Signed-off-by: Jakub Kicinski --- Documentation/networking/ip-sysctl.rst | 2 ++ net/ipv4/sysctl_net_ipv4.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Documentation/networking/ip-sysctl.rst b/Documentation/networking/ip-sysctl.rst index 4b19c8d3efda..d3523c9064a2 100644 --- a/Documentation/networking/ip-sysctl.rst +++ b/Documentation/networking/ip-sysctl.rst @@ -1128,6 +1128,8 @@ tcp_tso_rtt_log - INTEGER If you want to use the old autosizing, set this sysctl to 0. + Possible values are [0, 31], inclusive. + Default: 9 (2^9 = 512 usec) tcp_pacing_ss_ratio - INTEGER diff --git a/net/ipv4/sysctl_net_ipv4.c b/net/ipv4/sysctl_net_ipv4.c index e3760daa3470..bf193e7ec516 100644 --- a/net/ipv4/sysctl_net_ipv4.c +++ b/net/ipv4/sysctl_net_ipv4.c @@ -26,6 +26,7 @@ static int ip_local_port_range_max[] = { 65535, 65535 }; static int tcp_adv_win_scale_min = -31; static int tcp_adv_win_scale_max = 31; static int tcp_app_win_max = 31; +static int tcp_tso_rtt_log_max = 31; static int tcp_min_snd_mss_min = TCP_MIN_SND_MSS; static int tcp_min_snd_mss_max = 65535; static int tcp_rto_max_max = TCP_RTO_MAX_SEC * MSEC_PER_SEC; @@ -1406,6 +1407,7 @@ static const struct ctl_table ipv4_net_table[] = { .maxlen = sizeof(u8), .mode = 0644, .proc_handler = proc_dou8vec_minmax, + .extra2 = &tcp_tso_rtt_log_max, }, { .procname = "tcp_min_rtt_wlen", From 406bf2dc8d6d3e93fdae0b66d2ae99b6b62e479c Mon Sep 17 00:00:00 2001 From: Qingfang Deng Date: Fri, 23 Oct 2026 16:00:00 +0800 Subject: [PATCH 041/137] pppoe: drop discovery frames with no MAC header Commit 8540827ebac6 ("pppoe: fix reception of frames with no mac header") drops PPPoE session frames with no MAC header to avoid reading uninitialized memory. Apply the same to PPPoE discovery frames, as both functions read eth_hdr(skb)->h_source. Found by LLM static analysis, and reproduced using the same TUN injection as described in the mentioned commit. Fixes: 8540827ebac6 ("pppoe: fix reception of frames with no mac header") Signed-off-by: Qingfang Deng Reviewed-by: Simon Horman Link: https://patch.msgid.link/20261023080002.221332-1-qingfang.deng@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/ppp/pppoe.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ppp/pppoe.c b/drivers/net/ppp/pppoe.c index ee0e318d6a2a..bdaa8cf8404b 100644 --- a/drivers/net/ppp/pppoe.c +++ b/drivers/net/ppp/pppoe.c @@ -460,6 +460,9 @@ static int pppoe_disc_rcv(struct sk_buff *skb, struct net_device *dev, if (skb->pkt_type != PACKET_HOST) goto abort; + if (skb_mac_header_len(skb) < ETH_HLEN) + goto abort; + if (!pskb_may_pull(skb, sizeof(struct pppoe_hdr))) goto abort; From a204e9c001dfafe83cac01b16508b1cf5c953355 Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Fri, 23 Oct 2026 10:44:09 +0200 Subject: [PATCH 042/137] gtp: hold RCU in gtp_genl_send_echo_req() Such function is invoked in process context, performs an RCU device lookup and dereference the found device with no additional protection. ============================= WARNING: suspicious RCU usage 7.3.0-virtme #7 Not tainted ----------------------------- net/core/dev.c:1047 RCU-list traversed in non-reader section!! other info that might help us debug this: rcu_scheduler_active = 2, debug_locks = 1 locks held by python3/233: 2, last CPU#5: #0: ffffffffbab93268 (cb_lock){++++}-{4:4}, at: genl_rcv+0x19/0x40 #1: ffffffffbab93320 (genl_mutex){+.+.}-{4:4}, at: genl_rcv_msg+0x12c/0x160 stack backtrace: CPU: 5 UID: 0 PID: 233 Comm: python3 Not tainted 7.3.0#7 PREEMPT(full) Hardware name: Bochs, BIOS Bochs 01/01/2011 Call Trace: dump_stack_lvl+0x6f/0xb0 lockdep_rcu_suspicious.cold+0x37/0x8e dev_get_by_index_rcu+0x135/0x180 gtp_find_dev+0xe7/0x360 [gtp] gtp_genl_send_echo_req+0x298/0xb30 [gtp] genl_family_rcv_msg_doit+0x20f/0x310 genl_family_rcv_msg+0x3a8/0x640 genl_rcv_msg+0xbb/0x160 netlink_rcv_skb+0x154/0x3a0 genl_rcv+0x28/0x40 netlink_unicast+0x54b/0x860 netlink_sendmsg+0x7fb/0xf60 __sys_sendto+0x3d6/0x450 __x64_sys_sendto+0xe4/0x1f0 do_syscall_64+0xff/0x530 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7ffa43b0256e Code: 4d 89 d8 e8 d4 bd 00 00 4c 8b 5d f8 41 8b 93 08 03 00 00 59 5e 48 83 f8 fc 74 11 c9 c3 0f 1f 80 00 00 00 00 48 8b 45 10 0f 05 c3 83 e2 39 83 fa 08 75 e7 e8 03 ff ff ff 0f 1f 00 f3 0f 1e fa RSP: 002b:00007ffd3b8828d0 EFLAGS: 00000202 ORIG_RAX: 000000000000002c RAX: ffffffffffffffda RBX: 00007ffd3b8829e0 RCX: 00007ffa43b0256e RDX: 0000000000000034 RSI: 00007ffa3519d0d0 RDI: 0000000000000006 RBP: 00007ffd3b8828e0 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000202 R12: 00007ffa351cc0c0 R13: 00007ffd3b8829e0 R14: 00007ffa354ca130 R15: 00007ffa4433d498 Add the missing RCU guards and keep it for the whole function body, while the RCU-protected device is used. Found by code inspection, validated explicitly testing the relevant code path. Fixes: d33bd757d362 ("gtp: Implement GTP echo request") Signed-off-by: Paolo Abeni Link: https://patch.msgid.link/ed27d133fe73afd377d3fa4e553c34e7987b6dd9.1792744478.git.pabeni@redhat.com Signed-off-by: Jakub Kicinski --- drivers/net/gtp.c | 44 ++++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/drivers/net/gtp.c b/drivers/net/gtp.c index 15b5d845fc3b..86c8c88b702c 100644 --- a/drivers/net/gtp.c +++ b/drivers/net/gtp.c @@ -2355,9 +2355,9 @@ static int gtp_genl_send_echo_req(struct sk_buff *skb, struct genl_info *info) struct gtp_dev *gtp; struct flowi4 fl4; struct rtable *rt; + int len, ret = 0; struct sock *sk; __be16 port; - int len; if (!info->attrs[GTPA_VERSION] || !info->attrs[GTPA_LINK] || @@ -2369,14 +2369,21 @@ static int gtp_genl_send_echo_req(struct sk_buff *skb, struct genl_info *info) dst_ip = nla_get_be32(info->attrs[GTPA_PEER_ADDRESS]); src_ip = nla_get_be32(info->attrs[GTPA_MS_ADDRESS]); + rcu_read_lock(); gtp = gtp_find_dev(sock_net(skb->sk), info->attrs); - if (!gtp) - return -ENODEV; + if (!gtp) { + ret = -ENODEV; + goto unlock; + } - if (!gtp->sk_created) - return -EOPNOTSUPP; - if (!(gtp->dev->flags & IFF_UP)) - return -ENETDOWN; + if (!gtp->sk_created) { + ret = -EOPNOTSUPP; + goto unlock; + } + if (!(gtp->dev->flags & IFF_UP)) { + ret = -ENETDOWN; + goto unlock; + } if (version == GTP_V0) { struct gtp0_header *gtp0_h; @@ -2385,8 +2392,10 @@ static int gtp_genl_send_echo_req(struct sk_buff *skb, struct genl_info *info) sizeof(struct iphdr) + sizeof(struct udphdr); skb_to_send = netdev_alloc_skb_ip_align(gtp->dev, len); - if (!skb_to_send) - return -ENOMEM; + if (!skb_to_send) { + ret = -ENOMEM; + goto unlock; + } sk = gtp->sk0; port = htons(GTP0_PORT); @@ -2402,8 +2411,10 @@ static int gtp_genl_send_echo_req(struct sk_buff *skb, struct genl_info *info) sizeof(struct iphdr) + sizeof(struct udphdr); skb_to_send = netdev_alloc_skb_ip_align(gtp->dev, len); - if (!skb_to_send) - return -ENOMEM; + if (!skb_to_send) { + ret = -ENOMEM; + goto unlock; + } sk = gtp->sk1u; port = htons(GTP1U_PORT); @@ -2413,7 +2424,8 @@ static int gtp_genl_send_echo_req(struct sk_buff *skb, struct genl_info *info) memset(gtp1u_h, 0, sizeof(struct gtp1_header_long)); gtp1u_build_echo_msg(gtp1u_h, GTP_ECHO_REQ); } else { - return -ENODEV; + ret = -ENODEV; + goto unlock; } rt = ip4_route_output_gtp(&fl4, sk, dst_ip, src_ip); @@ -2421,7 +2433,8 @@ static int gtp_genl_send_echo_req(struct sk_buff *skb, struct genl_info *info) netdev_dbg(gtp->dev, "no route for echo request to %pI4\n", &dst_ip); kfree_skb(skb_to_send); - return -ENODEV; + ret = -ENODEV; + goto unlock; } local_bh_disable(); @@ -2435,7 +2448,10 @@ static int gtp_genl_send_echo_req(struct sk_buff *skb, struct genl_info *info) dev_net(gtp->dev)), false, 0); local_bh_enable(); - return 0; + +unlock: + rcu_read_unlock(); + return ret; } static const struct nla_policy gtp_genl_policy[GTPA_MAX + 1] = { From 13b364c765e69db1f8446399d8db0b331c23b50c Mon Sep 17 00:00:00 2001 From: Binbin Deng <18983559317@163.com> Date: Tue, 20 Oct 2026 10:26:30 +0800 Subject: [PATCH 043/137] net: atlantic: Unregister before clearing RXNFC rules aq_pci_remove() clears the driver's RXNFC rule list before unregistering the netdevice. Until unregister_netdev() returns, ethtool operations can still call aq_get_rxnfc_rule(), aq_get_rxnfc_all_rules(), aq_add_rxnfc_rule(), or aq_del_rxnfc_rule(). Those paths traverse the same unlocked hlist that aq_clear_rxnfc_all_rules() is mutating and freeing. The race is reachable by issuing RXNFC ethtool commands concurrently with PCI device removal or driver unbind. A reader can dereference a node that has just been freed, and two writers can corrupt the list or free the same rule more than once, causing KASAN reports, list corruption, or a kernel panic. Reverse the removal order: unregister the netdevice first, then clear the RXNFC rules. After unregister_netdev() returns, ethtool can no longer enter the driver, while the PCI device is still available for the rule-clearing routine to issue hardware commands. 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. The issue was not observed at runtime; it is identified by code inspection only. Fixes: 8d0bcb012f57 ("net: aquantia: add infrastructure for ntuple rules") Signed-off-by: Binbin Deng <18983559317@163.com> Link: https://patch.msgid.link/20261020022630.515209-1-18983559317@163.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/aquantia/atlantic/aq_pci_func.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/aquantia/atlantic/aq_pci_func.c b/drivers/net/ethernet/aquantia/atlantic/aq_pci_func.c index 04b3ef840b36..7a4afd25d240 100644 --- a/drivers/net/ethernet/aquantia/atlantic/aq_pci_func.c +++ b/drivers/net/ethernet/aquantia/atlantic/aq_pci_func.c @@ -342,9 +342,9 @@ static void aq_pci_remove(struct pci_dev *pdev) struct aq_nic_s *self = pci_get_drvdata(pdev); if (self->ndev) { - aq_clear_rxnfc_all_rules(self); if (self->ndev->reg_state == NETREG_REGISTERED) unregister_netdev(self->ndev); + aq_clear_rxnfc_all_rules(self); #if IS_ENABLED(CONFIG_MACSEC) aq_macsec_free(self); From b88b826ec54a06f48a42580c609ba81293ebec8e Mon Sep 17 00:00:00 2001 From: Paul Moses Date: Wed, 21 Oct 2026 22:47:33 +0000 Subject: [PATCH 044/137] xsk: restore UMEM flags after failed pool assignment xp_assign_dev() sets XDP_UMEM_SG_FLAG before checking whether the device supports multibuffer zero-copy. If assignment fails, xsk_bind() destroys the pool but retains the registered UMEM. A subsequent bind without XDP_USE_SG inherits the stale flag. The flag also changes RX buffer sizing after DMA mapping. On igc, a failed SG bind followed by a non-SG retry with 4096-byte chunks and 1792 bytes of headroom passes admission with 2048 bytes, but programs a 1024-byte RX buffer. On I225-V hardware, an ordinary 1514-byte frame is truncated to 1024 bytes or 1008 with RX timestamps enabled. Restoring the original flags eliminates the truncation in both cases. Save the UMEM flags before assignment and restore them on failure. Discovered during development of AF_XDP changes for igc. Fixes: d609f3d228a8 ("xsk: add multi-buffer support for sockets sharing umem") Cc: stable@vger.kernel.org Signed-off-by: Paul Moses Acked-by: Maciej Fijalkowski Link: https://patch.msgid.link/20261021224705.2078753-1-p@1g4.org Signed-off-by: Jakub Kicinski --- net/xdp/xsk_buff_pool.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/xdp/xsk_buff_pool.c b/net/xdp/xsk_buff_pool.c index 0ebd4d46f7ab..7750d2ec4ee2 100644 --- a/net/xdp/xsk_buff_pool.c +++ b/net/xdp/xsk_buff_pool.c @@ -166,6 +166,7 @@ int xp_assign_dev(struct xsk_buff_pool *pool, { u32 needed = netdev->mtu + ETH_PAD_LEN; u32 segs = netdev->xdp_zc_max_segs; + u8 umem_flags = pool->umem->flags; bool mbuf = flags & XDP_USE_SG; bool force_zc, force_copy; struct netdev_bpf bpf; @@ -268,6 +269,7 @@ int xp_assign_dev(struct xsk_buff_pool *pool, if (!force_zc) err = 0; /* fallback to copy mode */ if (err) { + pool->umem->flags = umem_flags; xsk_clear_pool_at_qid(netdev, queue_id); hlist_del(&pool->dev_node); pool->netdev = NULL; From 9e2ba4a17735de011acf3ba3f6af977d29da5ff4 Mon Sep 17 00:00:00 2001 From: Jin Li Date: Thu, 22 Oct 2026 18:30:22 +0800 Subject: [PATCH 045/137] selftests: net: forwarding: bridge_vlan_mcast: Allow default startup query interval to vary by HZ The mcast_startup_query_interval default value is calculated as 125 * HZ / 4. With CONFIG_HZ=250, this results in 3124 instead of 3125 due to integer truncation. The test currently hardcodes 3125, which fails on kernels built with CONFIG_HZ=250. Allow a +/- 1 tolerance for the default value check to account for this rounding, while still catching genuine regressions in the default value. Signed-off-by: Jin Li Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20261022103023.834927-1-jinli@lanxincomputing.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/forwarding/bridge_vlan_mcast.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/net/forwarding/bridge_vlan_mcast.sh b/tools/testing/selftests/net/forwarding/bridge_vlan_mcast.sh index c4cd2078a8db..c07c2af5df94 100755 --- a/tools/testing/selftests/net/forwarding/bridge_vlan_mcast.sh +++ b/tools/testing/selftests/net/forwarding/bridge_vlan_mcast.sh @@ -383,7 +383,8 @@ vlmc_startup_query_test() echo -n $goutput | jq -e ".[].vlans[] | select(.vlan == 10 and \ - .mcast_startup_query_interval == 3125) " &>/dev/null + .mcast_startup_query_interval >= 3124 and \ + .mcast_startup_query_interval <= 3126) " &>/dev/null check_err $? "Wrong default mcast_startup_query_interval global vlan option value" log_test "Vlan mcast_startup_query_interval global option default value" From 108ca34cf4b453f6375a70079e32b11866b02601 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Sat, 24 Oct 2026 15:23:06 +0200 Subject: [PATCH 046/137] net: usb: cdc_ncm: don't enable BHs in tx_fixup under netpoll netpoll holds the txq lock and calls ndo_start_xmit() with IRQs off but BHs on. The spin_unlock_bh() in cdc_ncm_tx_fixup() then runs pending softirqs. If one of them transmits, it spins on the txq lock that netpoll holds. With netconsole on a cdc_ncm device this ends in a soft or hard lockup. Every caller of cdc_ncm_tx_fixup() holds the txq lock with BHs or IRQs off, so the softirq user of ctx->mtx cannot interrupt it. Use plain spin_lock(). Fixes: c84ff1d6dff3 ("cdc_ncm: reduce driver latency in the data path") Reported-by: Mikkel Eskildsen Closes: https://lore.kernel.org/netdev/PRAP189MB185019A5A8D411482BB6E0CDA9982@PRAP189MB1850.EURP189.PROD.OUTLOOK.COM/ Signed-off-by: Nicolai Buchwitz Link: https://patch.msgid.link/20261024-cdc-ncm-netpoll-v3-1-6284ca03f967@tipi-net.de Signed-off-by: Jakub Kicinski --- drivers/net/usb/cdc_ncm.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/net/usb/cdc_ncm.c b/drivers/net/usb/cdc_ncm.c index 35db38cb3e4a..3bd7cb66f3fc 100644 --- a/drivers/net/usb/cdc_ncm.c +++ b/drivers/net/usb/cdc_ncm.c @@ -1535,14 +1535,17 @@ cdc_ncm_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags) if (ctx == NULL) goto error; - spin_lock_bh(&ctx->mtx); + /* BHs or IRQs are always off here. netpoll only disables IRQs, and + * spin_unlock_bh() would then run softirqs with the txq lock held. + */ + spin_lock(&ctx->mtx); if (ctx->is_ndp16) skb_out = cdc_ncm_fill_tx_frame(dev, skb, cpu_to_le32(USB_CDC_NCM_NDP16_NOCRC_SIGN)); else skb_out = cdc_ncm_fill_tx_frame(dev, skb, cpu_to_le32(USB_CDC_NCM_NDP32_NOCRC_SIGN)); - spin_unlock_bh(&ctx->mtx); + spin_unlock(&ctx->mtx); return skb_out; error: From bfd7ced012c7857b5ab5edf7e416c2789b652702 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Sat, 24 Oct 2026 15:23:07 +0200 Subject: [PATCH 047/137] net: usb: cdc_mbim: don't enable BHs in tx_fixup under netpoll cdc_mbim_tx_fixup() has the same spin_unlock_bh() as cdc_ncm_tx_fixup(). Under netpoll it runs pending softirqs while the txq lock is held, and a transmit from one of them spins on that lock. Use plain spin_lock() as in cdc_ncm. Fixes: 9bf211a30b3f ("net: cdc_mbim: adding MBIM driver") Signed-off-by: Nicolai Buchwitz Link: https://patch.msgid.link/20261024-cdc-ncm-netpoll-v3-2-6284ca03f967@tipi-net.de Signed-off-by: Jakub Kicinski --- drivers/net/usb/cdc_mbim.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/usb/cdc_mbim.c b/drivers/net/usb/cdc_mbim.c index a7010a0664c7..8f5734ac35b8 100644 --- a/drivers/net/usb/cdc_mbim.c +++ b/drivers/net/usb/cdc_mbim.c @@ -289,9 +289,10 @@ static struct sk_buff *cdc_mbim_tx_fixup(struct usbnet *dev, struct sk_buff *skb } } - spin_lock_bh(&ctx->mtx); + /* BHs or IRQs are always off here, see cdc_ncm_tx_fixup() */ + spin_lock(&ctx->mtx); skb_out = cdc_ncm_fill_tx_frame(dev, skb, sign); - spin_unlock_bh(&ctx->mtx); + spin_unlock(&ctx->mtx); return skb_out; error: From 6c799b4998c87bb44574bb090b8728bb3b3dbc47 Mon Sep 17 00:00:00 2001 From: Cheng Yongkang Date: Thu, 22 Oct 2026 08:26:48 -0700 Subject: [PATCH 048/137] net: tun: hold napi_mutex when deleting NAPI in tun_detach_all() With IFF_NAPI_FRAGS, tun_get_user() takes tfile->napi_mutex, gets the skb from napi_get_frags() and copies the user data into it before it enters the RCU read-side section around napi_gro_frags(). tun_detach_all() waits with synchronize_net() and then calls tun_napi_del() without napi_mutex. netif_napi_del() frees napi->skb through napi_free_frags(). A writer that is still copying has not yet entered its RCU section, so synchronize_net() does not wait for it, and deleting the device frees the skb while the writer is using it: BUG: KASAN: slab-use-after-free in skb_copy_datagram_from_iter+0x175/0x720 Read of size 8 at addr ffff888077def990 by task br029-poc/9406 Call Trace: skb_copy_datagram_from_iter+0x175/0x720 tun_get_user+0xb42/0x3d00 tun_chr_write_iter+0x113/0x200 ... Freed by task 9405: kmem_cache_free+0x184/0x670 __netif_napi_del_locked+0x2bb/0x3c0 tun_net_uninit+0x566/0xa70 unregister_netdevice_many_notify+0x1a26/0x2090 rtnl_dellink+0x514/0x710 __tun_detach() also calls tun_napi_del() without the mutex, but only from tun_chr_close(), when no write on that file can be in flight. Take napi_mutex around tun_napi_del() in tun_detach_all() for both attached and disabled queues. Fixes: 90e33d459407 ("tun: enable napi_gro_frags() for TUN/TAP driver") Signed-off-by: Cheng Yongkang Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/179268538303.70821.16548460883330758972@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/tun.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/tun.c b/drivers/net/tun.c index 439565a264ee..770b8fe085c3 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -688,14 +688,18 @@ static void tun_detach_all(struct net_device *dev) synchronize_net(); for (i = 0; i < n; i++) { tfile = rtnl_dereference(tun->tfiles[i]); + mutex_lock(&tfile->napi_mutex); tun_napi_del(tfile); + mutex_unlock(&tfile->napi_mutex); /* Drop read queue */ tun_queue_purge(tfile); xdp_rxq_info_unreg(&tfile->xdp_rxq); sock_put(&tfile->sk); } list_for_each_entry_safe(tfile, tmp, &tun->disabled, next) { + mutex_lock(&tfile->napi_mutex); tun_napi_del(tfile); + mutex_unlock(&tfile->napi_mutex); tun_enable_queue(tfile); tun_queue_purge(tfile); xdp_rxq_info_unreg(&tfile->xdp_rxq); From a58b221496dd576191095d12996f39cee58cf6a8 Mon Sep 17 00:00:00 2001 From: Tao Gong Date: Thu, 22 Oct 2026 20:56:48 +0000 Subject: [PATCH 049/137] net: dsa: mt7530: trap frames to conduit in MFC on EN7581 and AN7583 On EN7581, STP BPDUs received on switch user ports never reach the CPU. Like MT7531 and MT7988, EN7581 and AN7583 only set the CPU port bitmap in MT7531_CFC and are skipped by mt753x_conduit_state_change(). On EN7581 this is not enough: trapped BPDUs reach the CPU only when MT7530_CPU_EN and MT7530_CPU_PORT are also set in MT753X_MFC. Add EN7581 and AN7583 to mt753x_conduit_state_change(). Tested on an AN7581 board (Gemtek W1700K, airoha,en7581-switch), where only lan3 and lan4 are switch user ports. With RSTP BPDUs arriving on lan4: - without this patch, none reached the CPU; - setting CPU_EN and CPU_PORT by hand delivered them, and clearing them stopped them; - with this patch, they are received from boot. Only BPDUs were tested. AN7583 is untested and is included because the driver sets it up like EN7581. Fixes: 2b0229f67932 ("net: dsa: mt7530: Add EN7581 support") Fixes: d76556db10bf ("net: dsa: mt7530: Add AN7583 support") Acked-by: Lorenzo Bianconi Signed-off-by: Tao Gong Link: https://patch.msgid.link/20261022205649.2351696-1-gongtao0607@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/dsa/mt7530.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c index 7781a63b4e6f..d4714bc102ee 100644 --- a/drivers/net/dsa/mt7530.c +++ b/drivers/net/dsa/mt7530.c @@ -3300,10 +3300,13 @@ mt753x_conduit_state_change(struct dsa_switch *ds, /* Set the CPU port to trap frames to for MT7530. Trapped frames will be * forwarded to the numerically smallest CPU port whose conduit - * interface is up. + * interface is up. On EN7581, trapped BPDUs do not reach the CPU port + * without this, even with the CPU port bitmap set. AN7583 is set up + * the same way as EN7581. */ if (priv->id != ID_MT7530 && priv->id != ID_MT7621 && - priv->id != ID_EN7528) + priv->id != ID_EN7528 && + priv->id != ID_EN7581 && priv->id != ID_AN7583) return; mask = BIT(cpu_dp->index); From fbea154a86641d2543a871aa19f7c77a366de542 Mon Sep 17 00:00:00 2001 From: Allison Henderson Date: Wed, 21 Oct 2026 12:58:36 -0700 Subject: [PATCH 050/137] net/rds: do not fail init when CONFIG_SYSCTL=n With CONFIG_SYSCTL=n, register_net_sysctl() and register_net_sysctl_sz() are inline stubs that return NULL. rds_sysctl_init(), rds_ib_sysctl_init() and rds_tcp_init_net() all treat that NULL as -ENOMEM, so RDS refuses to initialize at all even though every tunable has a sane compiled-in default. When RDS is built in, that is worse than a missing feature: the failed rds_init() destroys rds_wq, but the rds_rdma initcall still runs, and when rds_ib_sysctl_init() fails the same way, the error path in rds_ib_init() flushes the already freed workqueue: BUG: KASAN: slab-use-after-free in touch_wq_lockdep_map ... __flush_workqueue rds_ib_init rds_rdma_init ... Allocated by task 1: alloc_workqueue_noprof rds_threads_init rds_init ... Freed by task 0: kfree rcu_do_batch Only treat the NULL return as a failure when sysctl support is actually compiled in. The exit paths need no change: unregister_net_sysctl_table() is an empty stub with CONFIG_SYSCTL=n and already tolerates a NULL header otherwise. The underlying problem of built-in transports initializing after rds_init() has failed is independent of sysctl and is addressed separately. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-lkp/202610190310.9737b2e3-lkp@intel.com Fixes: 3e5048495c85 ("RDS: sysctls") Fixes: e6babe4cc4ce ("RDS/IB: Stats and sysctls") Fixes: c6a58ffed536 ("RDS: TCP: Add sysctl tunables for sndbuf/rcvbuf on rds-tcp socket") Suggested-by: Joel Granados Signed-off-by: Allison Henderson Reviewed-by: Joel Granados Acked-by: Oleg Nesterov Link: https://patch.msgid.link/20261021195837.142802-2-achender@kernel.org Signed-off-by: Paolo Abeni --- net/rds/ib_sysctl.c | 2 +- net/rds/sysctl.c | 2 +- net/rds/tcp.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/net/rds/ib_sysctl.c b/net/rds/ib_sysctl.c index 2af678e71e3c..bc97a3eecad6 100644 --- a/net/rds/ib_sysctl.c +++ b/net/rds/ib_sysctl.c @@ -114,7 +114,7 @@ void rds_ib_sysctl_exit(void) int rds_ib_sysctl_init(void) { rds_ib_sysctl_hdr = register_net_sysctl(&init_net, "net/rds/ib", rds_ib_sysctl_table); - if (!rds_ib_sysctl_hdr) + if (!rds_ib_sysctl_hdr && IS_ENABLED(CONFIG_SYSCTL)) return -ENOMEM; return 0; } diff --git a/net/rds/sysctl.c b/net/rds/sysctl.c index 025f518a4349..968d6279bbd3 100644 --- a/net/rds/sysctl.c +++ b/net/rds/sysctl.c @@ -103,7 +103,7 @@ int rds_sysctl_init(void) rds_sysctl_reg_table = register_net_sysctl(&init_net, "net/rds", rds_sysctl_rds_table); - if (!rds_sysctl_reg_table) + if (!rds_sysctl_reg_table && IS_ENABLED(CONFIG_SYSCTL)) return -ENOMEM; return 0; } diff --git a/net/rds/tcp.c b/net/rds/tcp.c index 774a71f88d37..34815b4f658b 100644 --- a/net/rds/tcp.c +++ b/net/rds/tcp.c @@ -637,7 +637,7 @@ static __net_init int rds_tcp_init_net(struct net *net) tbl[RDS_TCP_RCVBUF].data = &rtn->rcvbuf_size; rtn->rds_tcp_sysctl = register_net_sysctl_sz(net, "net/rds/tcp", tbl, ARRAY_SIZE(rds_tcp_sysctl_table)); - if (!rtn->rds_tcp_sysctl) { + if (!rtn->rds_tcp_sysctl && IS_ENABLED(CONFIG_SYSCTL)) { pr_warn("could not register sysctl\n"); err = -ENOMEM; goto fail; From ffbbc315b8b883a1c0e131fb3186b9428d2812aa Mon Sep 17 00:00:00 2001 From: Allison Henderson Date: Wed, 21 Oct 2026 12:58:37 -0700 Subject: [PATCH 051/137] net/rds: do not initialize transports after rds_init() failed rds, rds_rdma and rds_tcp each have their own module_init(). When they are built in, those run in link order, so rds_init() goes first, but do_initcalls() does not stop when it fails. The transports then initialize against a core that has already been torn down: rds_wq has been destroyed, yet rds_ib_dev_put(), rds_ib_unregister_client() and rds_tcp_accept_work() still queue onto or flush it, and rds_trans_register() hands the transports to a core whose socket family may never have been registered. This was seen with CONFIG_SYSCTL=n, where rds_sysctl_init() failed and rds_ib_init()'s error path then flushed the freed rds_wq [1], but any rds_init() failure after rds_threads_init() has the same effect. Record whether rds_init() succeeded and have rds_rdma_init() and rds_tcp_init() bail out with -ENODEV otherwise. Modular builds are unaffected: the transports need symbols exported by rds.ko, so they cannot be loaded unless rds_init() succeeded. [1] https://lore.kernel.org/oe-lkp/202610190310.9737b2e3-lkp@intel.com Fixes: 55b7ed0b582f ("RDS: Common RDMA transport code") Fixes: 70041088e3b9 ("RDS: Add TCP transport to RDS") Suggested-by: Oleg Nesterov Signed-off-by: Allison Henderson Link: https://patch.msgid.link/20261021195837.142802-3-achender@kernel.org Signed-off-by: Paolo Abeni --- net/rds/af_rds.c | 14 ++++++++++++++ net/rds/rdma_transport.c | 3 +++ net/rds/rds.h | 1 + net/rds/tcp.c | 3 +++ 4 files changed, 21 insertions(+) diff --git a/net/rds/af_rds.c b/net/rds/af_rds.c index e84d00bbe9a1..fb6b8ddb2d11 100644 --- a/net/rds/af_rds.c +++ b/net/rds/af_rds.c @@ -925,8 +925,11 @@ static void rds6_sock_info(struct socket *sock, unsigned int len, } #endif +static bool rds_initialized; + static void rds_exit(void) { + rds_initialized = false; sock_unregister(rds_family_ops.family); proto_unregister(&rds_proto); rds_conn_exit(); @@ -947,6 +950,16 @@ module_exit(rds_exit); u32 rds_gen_num; +/* When RDS is built in, a failed rds_init() does not stop the rds_rdma + * and rds_tcp initcalls from running against a core that has already + * been torn down (rds_wq in particular). Let them bail out early. + */ +bool rds_is_initialized(void) +{ + return rds_initialized; +} +EXPORT_SYMBOL_GPL(rds_is_initialized); + static int __init rds_init(void) { int ret; @@ -983,6 +996,7 @@ static int __init rds_init(void) rds_info_register_func(RDS6_INFO_SOCKETS, rds6_sock_info); rds_info_register_func(RDS6_INFO_RECV_MESSAGES, rds6_sock_inc_info); #endif + rds_initialized = true; goto out; diff --git a/net/rds/rdma_transport.c b/net/rds/rdma_transport.c index 12559a8ca912..75532734c054 100644 --- a/net/rds/rdma_transport.c +++ b/net/rds/rdma_transport.c @@ -336,6 +336,9 @@ static int __init rds_rdma_init(void) { int ret; + if (!rds_is_initialized()) + return -ENODEV; + ret = rds_ib_init(); if (ret) goto out; diff --git a/net/rds/rds.h b/net/rds/rds.h index 9b1ffc49c0a6..e3937c1ff3a1 100644 --- a/net/rds/rds.h +++ b/net/rds/rds.h @@ -1042,6 +1042,7 @@ extern unsigned int rds_sysctl_trace_level; int rds_threads_init(void); void rds_threads_exit(void); extern struct workqueue_struct *rds_wq; +bool rds_is_initialized(void); void rds_queue_reconnect(struct rds_conn_path *cp); void rds_connect_worker(struct work_struct *); void rds_shutdown_worker(struct work_struct *); diff --git a/net/rds/tcp.c b/net/rds/tcp.c index 34815b4f658b..c1ad37505417 100644 --- a/net/rds/tcp.c +++ b/net/rds/tcp.c @@ -815,6 +815,9 @@ static int __init rds_tcp_init(void) { int ret; + if (!rds_is_initialized()) + return -ENODEV; + rds_tcp_conn_slab = KMEM_CACHE(rds_tcp_connection, 0); if (!rds_tcp_conn_slab) { ret = -ENOMEM; From e3a629477d52a9f49d50d72903be06a4d1f4aa58 Mon Sep 17 00:00:00 2001 From: Hannes Laimer Date: Fri, 23 Oct 2026 13:14:27 +0200 Subject: [PATCH 052/137] vxlan: keep reserved_bits across changelink vxlan_nl2conf() derives reserved_bits from the extension attributes set in the netlink message if IFLA_VXLAN_RESERVED_BITS is absent. A changelink having it set is rejected, so this means that every changelink will reset all extension bits and reserve them again. vxlan_nl2flag() rejects setting a flag on an attribute that is create-only on changelink. So also setting an extension attribute will fail before it can un-reserve its fields. The extension's header bits being reserved again then makes vxlan_rcv() drop frames that have them set in their header as invalid. The device still reports the extension as enabled, because only the reserved_bits are rebuilt and the flags are just copied. A simple reproducer is ip link add vxgbp type vxlan id 1 dstport 4789 local 10.0.0.1 gbp ip link set dev vxgbp type vxlan ageing 300 After setting the unrelated ageing, incoming packets with the GBP bit set in the header start getting dropped. Keeping the reserved_bits on changelink is safe because anything a changelink accepts leaves the header bits enabled extensions use unchanged. Ran into this when adding support for the GBP flag in ifupdown2, see link below. The device kept dropping packets after some time, it turned out it always started after a changelink. Tested this by changing ageing by hand on a vxlan device that was created by ifupdown2 with the gbp flag, and checking the reserved bits using ip -d link show vxgbp | grep -o 'reserved_bits 0x[0-9a-f]*' Without the fix I got 0xf7ffffff000000ff after the changelink, and with the fix it stayed 0x77000000000000ff. The following patch contains a selftest that fails without the fix and passes with it. Fixes: e4f8647767cf ("vxlan: Track reserved bits explicitly as part of the configuration") Link: https://lore.proxmox.com/pve-devel/20260828134708.692354-1-h.laimer@proxmox.com/ Signed-off-by: Hannes Laimer Reviewed-by: Nikolay Aleksandrov Reviewed-by: Petr Machata Link: https://patch.msgid.link/20261023-b4-vxlan-changelink-gbp-v1-1-f0d13951c09b@proxmox.com Tested-by: Gabriel Goller Reviewed-by: Gabriel Goller Reviewed-by: Hangbin Liu Signed-off-by: Paolo Abeni --- drivers/net/vxlan/vxlan_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index 278d41f58d89..223539099778 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -4488,7 +4488,7 @@ static int vxlan_nl2conf(struct nlattr *tb[], struct nlattr *data[], } conf->reserved_bits = reserved_bits; - } else { + } else if (!changelink) { /* For backwards compatibility, only allow reserved fields to be * used by VXLAN extensions if explicitly requested. */ From e7cb2f638f7e4f5f95ca7956566de70e5e915ba3 Mon Sep 17 00:00:00 2001 From: Hannes Laimer Date: Fri, 23 Oct 2026 13:14:28 +0200 Subject: [PATCH 053/137] selftests: forwarding: vxlan_reserved: add gbp changelink test On a device created with gbp, run the reserved bit probes before and after an unrelated changelink. Check that what the device accepts has not changed. Signed-off-by: Hannes Laimer Reviewed-by: Nikolay Aleksandrov Reviewed-by: Petr Machata Link: https://patch.msgid.link/20261023-b4-vxlan-changelink-gbp-v1-2-f0d13951c09b@proxmox.com Tested-by: Gabriel Goller Reviewed-by: Gabriel Goller Reviewed-by: Hangbin Liu Signed-off-by: Paolo Abeni --- .../selftests/net/forwarding/vxlan_reserved.sh | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tools/testing/selftests/net/forwarding/vxlan_reserved.sh b/tools/testing/selftests/net/forwarding/vxlan_reserved.sh index 709845123727..0ce264cc57f8 100755 --- a/tools/testing/selftests/net/forwarding/vxlan_reserved.sh +++ b/tools/testing/selftests/net/forwarding/vxlan_reserved.sh @@ -40,6 +40,7 @@ reserved_31_test reserved_56_test reserved_63_test + gbp_changelink_test "} NUM_NETIFS=4 @@ -338,6 +339,20 @@ reserved_63_test() reserved_test 63 } +gbp_changelink_test_do() +{ + vxlan_device_add gbp + __default_test_do 25 "gbp" + ip link set dev vx1 type vxlan ageing 300 + __default_test_do 25 "gbp after changelink" +} + +gbp_changelink_test() +{ + in_defer_scope \ + gbp_changelink_test_do +} + trap cleanup EXIT setup_prepare From 363c8c79220d0db4d65dcd570529ddda15067cb5 Mon Sep 17 00:00:00 2001 From: Kaho Jiang Date: Wed, 21 Oct 2026 19:57:33 -0400 Subject: [PATCH 054/137] net: ipv4: add zero check for snd_cwnd_clamp When RTAX_CWND is locked, user is able to pass 0 into it. This could lead to writing 0 to tp->snd_cwnd, leading to a division by zero error. Oops: divide error: 0000 [#1] SMP KASAN NOPTI Call Trace: tcp_reno_cong_avoid (net/ipv4/tcp_cong.c:510 (discriminator 1)) tcp_ack (net/ipv4/tcp_input.c:3517 net/ipv4/tcp_input.c:3873 net/ipv4/tcp_input.c:4440) tcp_rcv_established (net/ipv4/tcp_input.c:6685) tcp_v4_do_rcv (net/ipv4/tcp_ipv4.c:1854) tcp_v4_rcv (net/ipv4/tcp_ipv4.c:2248) ip_protocol_deliver_rcu (net/ipv4/ip_input.c:207 (discriminator 1)) ip_local_deliver_finish (net/ipv4/ip_input.c:241 (discriminator 1)) ip_local_deliver (./include/linux/netfilter.h:325 ./include/linux/netfilter.h:319 net/ipv4/ip_input.c:262) ip_rcv (./include/net/dst.h:481 (discriminator 6) net/ipv4/ip_input.c:492 (discriminator 6) ./include/linux/netfilter.h:325 (discriminator 6) ./include/li) __netif_receive_skb_one_core (net/core/dev.c:6297 (discriminator 4)) __netif_receive_skb (net/core/dev.c:6410) process_backlog (net/core/dev.c:6761) __napi_poll.constprop.0 (net/core/dev.c:7830) net_rx_action (net/core/dev.c:7893 net/core/dev.c:8050) handle_softirqs (kernel/softirq.c:645) __irq_exit_rcu (kernel/softirq.c:679 kernel/softirq.c:519 kernel/softirq.c:758) irq_exit_rcu (kernel/softirq.c:775) sysvec_apic_timer_interrupt (arch/x86/kernel/apic/apic.c:1062 (discriminator 35) arch/x86/kernel/apic/apic.c:1062 (discriminator 35)) RIP: 0010:tcp_cong_avoid_ai (net/ipv4/tcp_cong.c:482) Fix by checking if user pass in zero as parameter and if lock is present. Fixes: 814d488c6126 ("tcp: fix the timid additive increase on stretch ACKs") Cc: stable@vger.kernel.org Suggested-by: Eric Dumazet Reported-by: co+71b6509cb791e531@bugs.sh Signed-off-by: Kaho Jiang Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20261021235732.1313305-2-kj2864@nyu.edu Signed-off-by: Paolo Abeni --- net/ipv4/metrics.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/ipv4/metrics.c b/net/ipv4/metrics.c index ad40762a8b38..4052a90249c6 100644 --- a/net/ipv4/metrics.c +++ b/net/ipv4/metrics.c @@ -60,6 +60,11 @@ static int ip_metrics_convert(struct nlattr *fc_mx, if (ecn_ca) metrics[RTAX_FEATURES - 1] |= DST_FEATURE_ECN_CA; + if ((metrics[RTAX_LOCK - 1] & (1 << RTAX_CWND)) && !metrics[RTAX_CWND - 1]) { + NL_SET_ERR_MSG(extack, "Locked cwnd metric must be non zero"); + return -EINVAL; + } + return 0; } From e7b01bc0202a57f1b7dcdf19b2ffab6b781d47f8 Mon Sep 17 00:00:00 2001 From: Qingfang Deng Date: Mon, 26 Oct 2026 14:12:58 +0800 Subject: [PATCH 055/137] pppoe: wait for RCU readers before reusing a socket When pppoe_connect() disconnects or reuses a socket, it removes the old session from the hash table and resets the socket's fields. An RCU reader that reached the entry before it was unlinked can still be using its address, ifindex or next pointer. If sockets A and B share a hash bucket with A preceding B, a concurrent __get_item() looking for B can be traversing A when it is removed. Clearing A->next then makes the lookup miss B and drop the packet. Reinserting A can similarly redirect the reader into another hash chain. Call synchronize_net() after unlinking the old session and before clearing its fields. This allows existing receive-path and /proc/net/pppoe readers to finish before the socket is cleared or reused. This was found by Sashiko's static analysis, and reproduced on a VM stress test with KCSAN. BUG: KCSAN: data-race in hash_item+0x1c/0x60 race at unknown origin, with read to 0xffff888000a1c566 of 2 bytes by task 147 on cpu 2: hash_item+0x1c/0x60 pppoe_seq_next+0x112/0x1b0 seq_read_iter+0x595/0x700 seq_read+0xb0/0xe0 proc_reg_read+0xd0/0xf0 vfs_read+0xbd/0x220 ksys_read+0xa9/0x100 __x64_sys_read+0x47/0x60 x64_sys_call+0x98/0x1fd0 do_syscall_64+0x5b/0x260 entry_SYSCALL_64_after_hwframe+0x4b/0x53 value changed: 0x0002 -> 0x0000 Fixes: 72cdc67e7fa7 ("pppoe: remove rwlock usage") Reported-by: Sashiko Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261020092118.47416-1-qingfang.deng@linux.dev Signed-off-by: Qingfang Deng Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20261026061300.256009-1-qingfang.deng@linux.dev Signed-off-by: Paolo Abeni --- drivers/net/ppp/pppoe.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/ppp/pppoe.c b/drivers/net/ppp/pppoe.c index bdaa8cf8404b..9edf8151bd7d 100644 --- a/drivers/net/ppp/pppoe.c +++ b/drivers/net/ppp/pppoe.c @@ -621,6 +621,10 @@ static int pppoe_connect(struct socket *sock, struct sockaddr_unsized *uservaddr pppox_unbind_sock(sk); pn = pppoe_pernet(sock_net(sk)); delete_item(pn, po); + + /* wait for concurrent readers before clearing the fields */ + synchronize_net(); + if (po->pppoe_dev) { dev_put(po->pppoe_dev); po->pppoe_dev = NULL; From 6d5a2cb0b7d6caf170d37ae7ac74738d592d2a05 Mon Sep 17 00:00:00 2001 From: Dudu Lu Date: Wed, 21 Oct 2026 23:55:38 +0800 Subject: [PATCH 056/137] net: xps: bound the cleanup loop by the old map's num_tc __netif_set_xps_queue() walks the OLD dev_maps to free its entries. The per-row trip count uses the NEW num_tc while the row stride uses the OLD dev_maps->num_tc, so when dev->num_tc was raised without rebuilding the XPS map the last row indexes attr_map[] past the allocation: BUG: KASAN: slab-out-of-bounds in __netif_set_xps_queue+0x1458/0x1ee8 Read of size 8 at addr ffff000014f69458 by task insmod/112 The loop frees entries in dev_maps, so both the trip count and the stride must come from dev_maps->num_tc. The map is sized XPS_CPU_DEV_MAPS_SIZE(old num_tc)/XPS_RXQ_DEV_MAPS_SIZE(old num_tc, ...), so iterating the new (larger) num_tc reads past the end. Fixes: 255c04a87f43 ("net: embed num_tc in the xps maps") Signed-off-by: Dudu Lu Reviewed-by: Antoine Tenart Link: https://patch.msgid.link/20261021155538.67867-1-phx0fer@gmail.com Signed-off-by: Paolo Abeni --- net/core/dev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/core/dev.c b/net/core/dev.c index 52fa7a88c5d0..9021e6d36ab2 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -3041,7 +3041,7 @@ int __netif_set_xps_queue(struct net_device *dev, const unsigned long *mask, goto out_no_old_maps; for (j = 0; j < dev_maps->nr_ids; j++) { - for (i = num_tc, tci = j * dev_maps->num_tc; i--; tci++) { + for (i = dev_maps->num_tc, tci = j * dev_maps->num_tc; i--; tci++) { map = xmap_dereference(dev_maps->attr_map[tci]); if (!map) continue; From f75062536e2d45ee182828f0e47e0124be2fdc8d Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 26 Oct 2026 09:20:35 +0100 Subject: [PATCH 057/137] ixgbe: reserve TX timestamp after skb preparation ixgbe_xmit_frame_ring() takes an extra skb reference with skb_get() for the pending TX timestamp before preparing the skb for transmit. Several of the following steps may need to reallocate the skb head: - ixgbe_tso() calls skb_cow_head(), which reaches pskb_expand_head() when the header is cloned (e.g. a packet tap holds a clone). - ixgbe_tx_csum() falls back to skb_checksum_help() for unsupported checksum offsets, which can call pskb_expand_head() through skb_ensure_writable(). - With DCB and a software VLAN tag, skb_cow_head() is called to rewrite the VLAN priority. pskb_expand_head() used to BUG() on shared skbs. Since commit c7d8bc3dd511 ("net: skbuff: don't BUG() on bad arguments to pskb_expand_head()") it returns -EINVAL instead, so these packets are dropped (TSO, DCB) or sent with an unfinished checksum (skb_checksum_help() errors are ignored by ixgbe_tx_csum()). Reserve the TX timestamp right before ixgbe_tx_map(), after all skb preparation, as i40e and ice already do. The timestamp request remains covered on the FCoE path, and the cleanup on ixgbe_tx_map() failure is unchanged. Fixes: 891dc0821d20 ("ixgbe: Add ptp work item to poll for the Tx timestamp") Cc: stable@vger.kernel.org Signed-off-by: Eric Dumazet Reviewed-by: Maciej Fijalkowski Link: https://patch.msgid.link/20261026082035.335770-1-edumazet@kernel.org Signed-off-by: Paolo Abeni --- drivers/net/ethernet/intel/ixgbe/ixgbe_main.c | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c index f91856498eb2..b9496f0bee13 100644 --- a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c +++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c @@ -9444,23 +9444,6 @@ netdev_tx_t ixgbe_xmit_frame_ring(struct sk_buff *skb, } protocol = vlan_get_protocol(skb); - if (unlikely(skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) && - adapter->ptp_clock) { - if (adapter->tstamp_config.tx_type == HWTSTAMP_TX_ON && - !test_and_set_bit_lock(__IXGBE_PTP_TX_IN_PROGRESS, - &adapter->state)) { - skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; - tx_flags |= IXGBE_TX_FLAGS_TSTAMP; - - /* schedule check for Tx timestamp */ - adapter->ptp_tx_skb = skb_get(skb); - adapter->ptp_tx_start = jiffies; - schedule_work(&adapter->ptp_tx_work); - } else { - adapter->tx_hwtstamp_skipped++; - } - } - #ifdef CONFIG_PCI_IOV /* * Use the l2switch_enable flag - would be false if the DMA @@ -9525,6 +9508,29 @@ netdev_tx_t ixgbe_xmit_frame_ring(struct sk_buff *skb, #ifdef IXGBE_FCOE xmit_fcoe: #endif /* IXGBE_FCOE */ + /* Reserve the Tx timestamp only after all skb preparation: + * skb_get() makes the skb shared, and pskb_expand_head() + * (reached from skb_cow_head() or skb_checksum_help()) + * refuses shared skbs. + */ + if (unlikely(skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) && + adapter->ptp_clock) { + if (adapter->tstamp_config.tx_type == HWTSTAMP_TX_ON && + !test_and_set_bit_lock(__IXGBE_PTP_TX_IN_PROGRESS, + &adapter->state)) { + skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; + tx_flags |= IXGBE_TX_FLAGS_TSTAMP; + first->tx_flags |= IXGBE_TX_FLAGS_TSTAMP; + + /* schedule check for Tx timestamp */ + adapter->ptp_tx_skb = skb_get(skb); + adapter->ptp_tx_start = jiffies; + schedule_work(&adapter->ptp_tx_work); + } else { + adapter->tx_hwtstamp_skipped++; + } + } + if (ixgbe_tx_map(tx_ring, first, hdr_len)) goto cleanup_tx_timestamp; From b4a7180558eb3cd8affa1e240b22171425bdfcb4 Mon Sep 17 00:00:00 2001 From: Daniel Machon Date: Mon, 26 Oct 2026 14:46:15 +0100 Subject: [PATCH 058/137] net: lan966x: do not inject uninitialised bytes past skb->len lan966x_port_ifh_xmit() writes the frame to the injection FIFO one 32-bit word at a time, and pads frames shorter than 60 bytes with zero words. Before this patch, the last word is loaded whole even when skb->len is not a multiple of four, so 1 to 3 bytes past the end of the frame are read. For a frame shorter than 60 bytes those bytes are also transmitted, as VLD_BYTES marks the whole last word valid and the padding starts at the next word. Write the trailing bytes from a zeroed word. The number of words written and VLD_BYTES are unchanged. Found by an AI code review agent. Reproduced on a lan9668 board with register injection, by sending a 57 byte frame and capturing it on the link partner: byte 57 of the padded frame was not zero. With the fix all padding bytes are zero. Fixes: d28d6d2e37d1 ("net: lan966x: add port module support") Signed-off-by: Daniel Machon Link: https://patch.msgid.link/20261026-lan966x-inject-tail-bytes-v1-1-51d599a48313@microchip.com Signed-off-by: Paolo Abeni --- .../ethernet/microchip/lan966x/lan966x_main.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_main.c b/drivers/net/ethernet/microchip/lan966x/lan966x_main.c index e4c9c5812cca..c276fec2f9d6 100644 --- a/drivers/net/ethernet/microchip/lan966x/lan966x_main.c +++ b/drivers/net/ethernet/microchip/lan966x/lan966x_main.c @@ -263,9 +263,8 @@ static int lan966x_port_ifh_xmit(struct sk_buff *skb, lan_wr((__force u32)ifh[i], lan966x, QS_INJ_WR(grp)); } - /* Write frame */ - count = DIV_ROUND_UP(skb->len, 4); - last = skb->len % 4; + /* Write the whole words of the frame */ + count = skb->len / 4; for (i = 0; i < count; ++i) { /* Wait until the fifo is ready */ err = lan966x_port_inj_ready(lan966x, grp); @@ -275,6 +274,20 @@ static int lan966x_port_ifh_xmit(struct sk_buff *skb, lan_wr(((u32 *)skb->data)[i], lan966x, QS_INJ_WR(grp)); } + /* Write the last 1 to 3 bytes, zero padded to a whole word */ + last = skb->len % 4; + if (last) { + /* Wait until the fifo is ready */ + err = lan966x_port_inj_ready(lan966x, grp); + if (err) + goto err; + + val = 0; + memcpy(&val, skb->data + count * 4, last); + lan_wr(val, lan966x, QS_INJ_WR(grp)); + ++i; + } + /* Add padding */ while (i < (LAN966X_BUFFER_MIN_SZ / 4)) { /* Wait until the fifo is ready */ From 2cffcf1aeabb187e4f08fe4f5363128a3c45d786 Mon Sep 17 00:00:00 2001 From: Daniel Machon Date: Mon, 26 Oct 2026 14:46:28 +0100 Subject: [PATCH 059/137] net: lan966x: do not warn on MAC entries that are not port destined lan966x_mac_irq_handler() scans the rows of the MAC table that the analyzer reports as changed, reads back all four columns of each row and treats ANA_MACACCESS.DEST_IDX as a port number. Before this patch, a destination index above num_phys_ports hits a WARN_ON(). The driver installs such entries itself: lan966x_mac_cpu_learn() uses PGID_CPU, and L2 multicast entries use a general-purpose PGID. A row holding one of them is read back whenever another entry in the same row is learned or aged out, so the scan warns in normal operation, repeatedly once the MAC table is busy. Skipping such a column is already the right behaviour. Drop the WARN_ON() and keep the bounds check as a plain filter. Found by an AI code review agent. Reproduced on a lan9668 board by bridging two ports with a 10 s ageing time and sending three rounds of 2048 new source MACs: the base kernel warned 42 times from lan966x_mac_irq_process(), the fixed kernel did not warn. Fixes: 5ccd66e01cbe ("net: lan966x: add support for interrupts from analyzer") Signed-off-by: Daniel Machon Link: https://patch.msgid.link/20261026-lan966x-mac-warn-v1-1-9c885f9cbda9@microchip.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/microchip/lan966x/lan966x_mac.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_mac.c b/drivers/net/ethernet/microchip/lan966x/lan966x_mac.c index 7a2294a0d099..986c60ac2343 100644 --- a/drivers/net/ethernet/microchip/lan966x/lan966x_mac.c +++ b/drivers/net/ethernet/microchip/lan966x/lan966x_mac.c @@ -447,7 +447,7 @@ static void lan966x_mac_irq_process(struct lan966x *lan966x, u32 row, lan966x_mac_process_raw_entry(&raw_entries[column], mac, &vid, &dest_idx); - if (WARN_ON(dest_idx >= lan966x->num_phys_ports)) + if (dest_idx >= lan966x->num_phys_ports) continue; /* If the entry in SW is found, then there is nothing @@ -501,7 +501,7 @@ static void lan966x_mac_irq_process(struct lan966x *lan966x, u32 row, lan966x_mac_process_raw_entry(&raw_entries[column], mac, &vid, &dest_idx); - if (WARN_ON(dest_idx >= lan966x->num_phys_ports)) + if (dest_idx >= lan966x->num_phys_ports) continue; spin_lock(&lan966x->mac_lock); From ce3192e0dafa829e76edefecc16a32589b53c875 Mon Sep 17 00:00:00 2001 From: Daniel Machon Date: Mon, 26 Oct 2026 14:46:39 +0100 Subject: [PATCH 060/137] net: lan966x: fix the DB dataptr of frames from ndo_xdp_xmit lan966x_fdma_xmit_xdpf() transmits both XDP_TX frames and xdp_frames from ndo_xdp_xmit, and builds the DB with lan966x_fdma_xdp_tx_dataptr_cb(), which adds XDP_PACKET_HEADROOM to the stored DMA address. Before this patch, the headroom is also added for xdp_frames. For XDP_TX the stored address is the start of the page pool page, but for an xdp_frame it is the mapping of the IFH placed in front of the frame data, so frames from ndo_xdp_xmit are read from XDP_PACKET_HEADROOM bytes after their IFH. Use the plain tx dataptr callback for xdp_frames. Found by an AI code review agent. Reproduced on a lan9668 board with an XDP program redirecting frames from one port to another: none of the 20 frames arrived intact on the link partner. With the fix all 20 did. Fixes: 29cc3a66a81d ("net: lan966x: use FDMA library for adding DCB's in the tx path") Signed-off-by: Daniel Machon Link: https://patch.msgid.link/20261026-lan966x-xdp-xmit-dataptr-v1-1-d0dd068324de@microchip.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/microchip/lan966x/lan966x_fdma.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_fdma.c b/drivers/net/ethernet/microchip/lan966x/lan966x_fdma.c index a7940eca5df3..8ea86b800af7 100644 --- a/drivers/net/ethernet/microchip/lan966x/lan966x_fdma.c +++ b/drivers/net/ethernet/microchip/lan966x/lan966x_fdma.c @@ -686,7 +686,8 @@ int lan966x_fdma_xmit_xdpf(struct lan966x_port *port, void *ptr, u32 len) FDMA_DCB_STATUS_BLOCKO(0) | FDMA_DCB_STATUS_BLOCKL(next_dcb_buf->len), &fdma_nextptr_cb, - &lan966x_fdma_xdp_tx_dataptr_cb); + len ? &lan966x_fdma_xdp_tx_dataptr_cb : + &lan966x_fdma_tx_dataptr_cb); /* Start the transmission */ lan966x_fdma_tx_start(tx); From 074caa30a62726c0ee959e8b8ccb64cda0b26ae5 Mon Sep 17 00:00:00 2001 From: Daniel Machon Date: Mon, 26 Oct 2026 14:46:54 +0100 Subject: [PATCH 061/137] net: lan966x: release the in-flight slot of a stale TX skb lan966x_ptp_txtstamp_request() counts every two step TX skb waiting for its timestamp in lan966x->ptp_skbs, and refuses new ones once LAN966X_MAX_PTP_ID are in flight. lan966x_ptp_txtstamp_old_release() frees the skbs that have waited longer than LAN966X_PTP_TIMEOUT. Before this patch, old_release() does not decrement ptp_skbs, so every skb whose timestamp never arrives, for example because the switch discarded the frame, keeps its slot forever. Once LAN966X_MAX_PTP_ID slots are lost, two step frames fail to transmit on every port of the switch. Return the number of released skbs from old_release() and subtract it from ptp_skbs in txtstamp_request(), under the ptp_ts_id_lock it already takes. This fixes the accounting only. A port's stale skbs are still aged out only when that port sends a two step frame, so a port that stops sending them keeps its stale skbs, and their slots, until it sends one again. That behaviour is unchanged by this patch. Found by an AI code review agent. Reproduced on a lan9668 board. Fixes: 77eecf25bd9d ("net: lan966x: Update extraction/injection for timestamping") Signed-off-by: Daniel Machon Link: https://patch.msgid.link/20261026-lan966x-ptp-stale-tx-slot-v1-1-9f7c895eb1ff@microchip.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/microchip/lan966x/lan966x_ptp.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_ptp.c b/drivers/net/ethernet/microchip/lan966x/lan966x_ptp.c index 8c40db90ee8f..9be2cfe6feeb 100644 --- a/drivers/net/ethernet/microchip/lan966x/lan966x_ptp.c +++ b/drivers/net/ethernet/microchip/lan966x/lan966x_ptp.c @@ -376,9 +376,10 @@ static void lan966x_ptp_classify(struct lan966x_port *port, struct sk_buff *skb, *rew_op = IFH_REW_OP_TWO_STEP_PTP; } -static void lan966x_ptp_txtstamp_old_release(struct lan966x_port *port) +static unsigned int lan966x_ptp_txtstamp_old_release(struct lan966x_port *port) { struct sk_buff *skb, *skb_tmp; + unsigned int purged = 0; unsigned long flags; spin_lock_irqsave(&port->tx_skbs.lock, flags); @@ -389,14 +390,18 @@ static void lan966x_ptp_txtstamp_old_release(struct lan966x_port *port) __skb_unlink(skb, &port->tx_skbs); dev_kfree_skb_any(skb); + purged++; } spin_unlock_irqrestore(&port->tx_skbs.lock, flags); + + return purged; } int lan966x_ptp_txtstamp_request(struct lan966x_port *port, struct sk_buff *skb) { struct lan966x *lan966x = port->lan966x; + unsigned int purged; unsigned long flags; u8 pdu_type; u8 rew_op; @@ -408,9 +413,10 @@ int lan966x_ptp_txtstamp_request(struct lan966x_port *port, if (rew_op != IFH_REW_OP_TWO_STEP_PTP) return 0; - lan966x_ptp_txtstamp_old_release(port); + purged = lan966x_ptp_txtstamp_old_release(port); spin_lock_irqsave(&lan966x->ptp_ts_id_lock, flags); + lan966x->ptp_skbs -= purged; if (lan966x->ptp_skbs == LAN966X_MAX_PTP_ID) { spin_unlock_irqrestore(&lan966x->ptp_ts_id_lock, flags); return -EBUSY; From 573a6d4c7a21e34d7a2174b8d3c7ca67a5e850b6 Mon Sep 17 00:00:00 2001 From: Daniel Machon Date: Mon, 26 Oct 2026 14:47:05 +0100 Subject: [PATCH 062/137] net: microchip: fdma: fix DCB corruption under sustained load __fdma_dcb_add() prepares a DCB and links it into a channel's chain by writing its address to the NEXT_PTR of the current last DCB. The FDMA may be processing the chain while this happens. Before this patch, the link is written before the new DCB's own nextptr and info fields, so the FDMA can follow it to a DCB that hasn't been fully prepared yet. Under sustained load this stalls the FDMA. Prepare the new DCB fully before linking it, and update the previous DCB's NEXT_PTR using the two-step sequence from the datasheet (poison the LSB and flush, then clear it) so the FDMA never observes a partially-updated pointer. Reported by a customer running iperf3 --bidir against a lan969x board. Reproduced on lan969x with iperf3, throughput collapsing to zero: [ 5] 1.00-2.00 sec 73.9 MBytes 620 Mbits/sec 0 305 KBytes [ 5] 2.00-3.00 sec 73.9 MBytes 620 Mbits/sec 0 305 KBytes [ 5] 3.00-4.00 sec 13.9 MBytes 116 Mbits/sec 2 1.41 KBytes [ 5] 4.00-5.00 sec 0.00 Bytes 0.00 bits/sec 1 1.41 KBytes [ 5] 5.00-6.00 sec 0.00 Bytes 0.00 bits/sec 0 1.41 KBytes [ 5] 6.00-7.00 sec 0.00 Bytes 0.00 bits/sec 1 1.41 KBytes The race is timing-dependent: across three reproductions the stall appeared anywhere from ~400s to ~3000s into the run. Fixes: 30e48a75df9c ("net: microchip: add FDMA library") Signed-off-by: Daniel Machon Link: https://patch.msgid.link/20261026-microchip-fdma-dcb-corruption-v1-1-36dab6caca07@microchip.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/microchip/fdma/fdma_api.c | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/microchip/fdma/fdma_api.c b/drivers/net/ethernet/microchip/fdma/fdma_api.c index f7a42348e932..fed41da550c5 100644 --- a/drivers/net/ethernet/microchip/fdma/fdma_api.c +++ b/drivers/net/ethernet/microchip/fdma/fdma_api.c @@ -43,9 +43,19 @@ int __fdma_dcb_add(struct fdma *fdma, int dcb_idx, u64 info, u64 status, u64 *dataptr)) { struct fdma_dcb *dcb = fdma_dcb_get(fdma, dcb_idx); + struct fdma_dcb *prev = fdma->last_dcb; u64 nextptr; int i, err; + /* Per the FDMA datasheet, adding a DCB to an active channel takes + * three steps: + * 1) Write NEXT_PTR of the current last DCB with the LSB forced to + * FDMA_DCB_INVALID_DATA, and flush that write. + * 2) Clear the LSB to complete the link. + * 3) Reload the channel (FDMA_CH_RELOAD); this is done by the caller + * once the chain has been extended. + */ + for (i = 0; i < fdma->n_dbs; i++) { err = __fdma_db_add(fdma, dcb_idx, i, status, db_cb); if (unlikely(err)) @@ -56,13 +66,25 @@ int __fdma_dcb_add(struct fdma *fdma, int dcb_idx, u64 info, u64 status, if (unlikely(err)) return err; - fdma->last_dcb->nextptr = cpu_to_le64(nextptr); - - fdma->last_dcb = dcb; - dcb->nextptr = cpu_to_le64(FDMA_DCB_INVALID_DATA); dcb->info = cpu_to_le64(info); + /* On the first DCB of fdma_dcbs_init() prev is the DCB itself; + * don't link it to itself. + */ + if (prev != dcb) { + /* Write NEXT_PTR of the current last DCB with the LSB forced + * to FDMA_DCB_INVALID_DATA, and flush that write. + */ + prev->nextptr = cpu_to_le64(nextptr | FDMA_DCB_INVALID_DATA); + dma_wmb(); + + /* Clear the LSB to complete the link. */ + prev->nextptr = cpu_to_le64(nextptr); + } + + fdma->last_dcb = dcb; + return 0; } EXPORT_SYMBOL_GPL(__fdma_dcb_add); From d3ff07f5acc573e5f72bf1c581bae5d05191ba61 Mon Sep 17 00:00:00 2001 From: Kamil Sawicki Date: Fri, 23 Oct 2026 20:48:35 -0400 Subject: [PATCH 063/137] net: limit priority VLAN untag depth The priority VLAN receive path can repeatedly remove VID 0 tags and retry packet processing. Unlike the read-only VLAN protocol parser, this path has no limit on the number of nested tags it consumes. Each skb_vlan_untag() advances skb->data and resets header offsets. The offsets are stored in 16-bit fields. With enough nested tags, the network or transport offset can wrap. A later protocol handler can then derive a large pull length from the wrapped offsets and access memory outside the skb allocation. Apply the existing VLAN_MAX_DEPTH limit to the exceptional VID 0 retry. Count the tag already stored in skb VLAN metadata as the first level, then stop before untagging a ninth level. Keep the accounting inside this uncommon branch so the regular VLAN receive path is unaffected. A single VID 0 priority tag before a real VLAN tag is valid. No normal deployment was found that intentionally emits multiple consecutive VID 0 tags; permitting eight levels is therefore conservative. The issue was found while researching the Linux network receive path for Google kvmCTF, with OpenAI Codex (gpt-5.6-sol) assisting the analysis. The affected path, reproducer and fix were manually reviewed and tested. Fixes: 36b2f61a42c2 ("net: handle 802.1P vlan 0 packets properly") Cc: stable@vger.kernel.org Reviewed-by: Willem de Bruijn Signed-off-by: Kamil Sawicki Reviewed-by: Kuniyuki Iwashima Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20261024004835.17862-1-user148783812897@gmail.com Signed-off-by: Jakub Kicinski --- net/core/dev.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/core/dev.c b/net/core/dev.c index 9021e6d36ab2..b3622e532f26 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -6315,6 +6315,8 @@ static int __netif_receive_skb_core(struct sk_buff **pskb, bool pfmemalloc, } if (unlikely(skb_vlan_tag_present(skb)) && !netdev_uses_dsa(skb->dev)) { + unsigned int priority_tag_depth = 1; + check_vlan_id: if (skb_vlan_tag_get_id(skb)) { /* Vlan id is non 0 and vlan_do_receive() above couldn't @@ -6326,6 +6328,12 @@ static int __netif_receive_skb_core(struct sk_buff **pskb, bool pfmemalloc, * 802.1Q or 802.1AD and vlan_do_receive() above could * not find vlan dev for vlan id 0. */ + if (unlikely(priority_tag_depth >= VLAN_MAX_DEPTH)) { + drop_reason = SKB_DROP_REASON_RECURSION_LIMIT; + goto drop; + } + + priority_tag_depth++; __vlan_hwaccel_clear_tag(skb); skb = skb_vlan_untag(skb); if (unlikely(!skb)) From 24927bdf0f62f63470a065c21d80fff7e4e9f207 Mon Sep 17 00:00:00 2001 From: Dudu Lu Date: Wed, 21 Oct 2026 23:55:22 +0800 Subject: [PATCH 064/137] net-sysfs: reject a one-entry RPS flow table store_rps_dev_flow_table_cnt() accepts rps_flow_cnt == 1. For count == 1 the round-up loop leaves mask == 0, which is stored as log == 0 in the tag (tag_ptr |= ilog2(mask) + 1). get_rps_cpu() then calls rfs_slot() -> hash_32(hash, rps_tag_to_log(tag_ptr)) with log == 0: include/linux/hash.h:68 return __hash_32(val) >> (32 - bits); /* bits == 0 -> shift by 32 */ The shift by 32 is undefined and yields the full 32-bit hash, which is used to index the one-entry rps_dev_flow[] table unchecked: UBSAN: shift-out-of-bounds in ./include/linux/hash.h:68:24 shift exponent 32 is too large for 32-bit type 'unsigned int' __ubsan_handle_shift_out_of_bounds+0x11c/0x208 get_rps_cpu+0x12fc/0x1344 netif_rx_internal+0x1e0/0x4d4 rps_dev_flow[] is then read (rflow->cpu) and written (last_qtail) out of bounds on the packet path. Require at least two entries. Fixes: f3483c8e1da6 ("net: rfs: hash function change") Signed-off-by: Dudu Lu Link: https://patch.msgid.link/20261021155522.67521-1-phx0fer@gmail.com Signed-off-by: Jakub Kicinski --- net/core/net-sysfs.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/net/core/net-sysfs.c b/net/core/net-sysfs.c index b9eeb9972f2b..5c0402389c6d 100644 --- a/net/core/net-sysfs.c +++ b/net/core/net-sysfs.c @@ -1110,6 +1110,13 @@ static ssize_t store_rps_dev_flow_table_cnt(struct netdev_rx_queue *queue, while ((mask | (mask >> 1)) != mask) mask |= (mask >> 1); + /* Require at least two entries. A one-entry table rounds up + * to mask == 0, which encodes log == 0 in the tag. + * get_rps_cpu() then computes hash_32(hash, 0), a shift by 32. + */ + if (!mask) + return -EINVAL; + /* Do not accept too large tables. */ if (mask > (INT_MAX / sizeof(*table) - 1)) return -EINVAL; From ef8f0a1d888e0280452792f6d4edad237c934a51 Mon Sep 17 00:00:00 2001 From: Fernando Fernandez Mancera Date: Mon, 26 Oct 2026 18:44:03 +0100 Subject: [PATCH 065/137] devlink: region: fix snapshot id leak on kernel-allocated ids When DEVLINK_CMD_REGION_NEW is issued without a snapshot id, the kernel allocates one with __devlink_region_snapshot_id_get(). That helper counts the caller as a user of the id, and the caller must release this hold once it is done. devlink_nl_region_new_doit() never drops it. The refcount of the id goes 1 (allocation) -> 2 (snapshot created) and stays at 2. When the snapshot is deleted the count only drops to 1, so the id is never erased from the snapshot_ids xarray. The id then stays in use for the lifetime of the devlink instance: requesting a snapshot with that explicit id fails with -EEXIST, and there is no snapshot left to delete to clean it up. Reproducer steps: # devlink region new pci/0000:13:00.0/device-caps pci/0000:13:00.0/device-caps: snapshot 0 # devlink region del pci/0000:13:00.0/device-caps snapshot 0 # devlink region new pci/0000:13:00.0/device-caps snapshot 0 kernel answers: File exists Drop the caller's reference right after the snapshot has been created, as the snapshot then holds its own. Fixes: 043b3e22768d ("devlink: let kernel allocate region snapshot id") Reported-by: Guikun Yang Closes: https://lore.kernel.org/netdev/tencent_BE912BA710539F285F9FCF28E854ABEFCD07@qq.com/ Signed-off-by: Fernando Fernandez Mancera Link: https://patch.msgid.link/20261026174403.512-1-fmancera@suse.de Signed-off-by: Jakub Kicinski --- net/devlink/region.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/devlink/region.c b/net/devlink/region.c index 537779bbff07..c22016f23a4a 100644 --- a/net/devlink/region.c +++ b/net/devlink/region.c @@ -719,6 +719,11 @@ int devlink_nl_region_new_doit(struct sk_buff *skb, struct genl_info *info) if (!snapshot_id_attr) { struct sk_buff *msg; + /* Drop the allocation reference, the snapshot + * now holds its own. + */ + __devlink_snapshot_id_decrement(devlink, snapshot_id); + snapshot = devlink_region_snapshot_get_by_id(region, snapshot_id); if (WARN_ON(!snapshot)) { From b6743653d134c9ef8436e2db1be1ad4c1e0d0091 Mon Sep 17 00:00:00 2001 From: Xia Yan Date: Thu, 22 Oct 2026 17:08:09 +0800 Subject: [PATCH 066/137] macvlan: fix passthru promiscuity accounting while down macvlan_changelink() adjusts the lower device's promiscuity count when NOPROMISC changes, even if the passthru macvlan is down. The reference is acquired in macvlan_open() and released in macvlan_stop(), so a down interface has no reference to change. Clearing NOPROMISC while down leaves an extra reference after the next open/stop cycle. Setting it while down can remove a reference held by another user of the lower device. Guard the count adjustment with netif_running(dev). Keep storing the requested flags while down so the next open uses the new setting. Comparing changelink with open/stop exposed the unmatched count changes; the same issue was also described in earlier macvlan reviews. A netlink test on UML reproduced both DOWN cases before the fix and verified the expected counts afterwards. Repeated flag updates, two open/stop cycles, deletion and bidirectional small-frame delivery were also checked. Full x86_64 builds completed with WERROR disabled for allmodconfig, and WERROR and KASAN disabled for allyesconfig, including the resulting dependency changes. Neither comparison showed new diagnostics. Fixes: df8ef8f3aaa6 ("macvlan: add FDB bridge ops and macvlan flags") Link: https://lore.kernel.org/netdev/179170088566.434549.4699134797461783473@kernel.org/ Link: https://lore.kernel.org/netdev/20261016003009.2692872-1-kuba@kernel.org/ Cc: stable@vger.kernel.org Signed-off-by: Xia Yan Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20261022090809.12019-1-yanxia@cmss.chinamobile.com Signed-off-by: Jakub Kicinski --- drivers/net/macvlan.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/macvlan.c b/drivers/net/macvlan.c index afad90b9222a..a48c9e662fe9 100644 --- a/drivers/net/macvlan.c +++ b/drivers/net/macvlan.c @@ -1664,7 +1664,8 @@ static int macvlan_changelink(struct net_device *dev, if (data && data[IFLA_MACVLAN_FLAGS]) { __u16 flags = nla_get_u16(data[IFLA_MACVLAN_FLAGS]); bool promisc = (flags ^ vlan->flags) & MACVLAN_FLAG_NOPROMISC; - if (macvlan_passthru(vlan->port) && promisc) { + if (macvlan_passthru(vlan->port) && promisc && + netif_running(dev)) { int err; if (flags & MACVLAN_FLAG_NOPROMISC) From 947ccedf683ed6cc53c1f88ac9eb8747e4567ed6 Mon Sep 17 00:00:00 2001 From: Zhewei Yuan Date: Mon, 26 Oct 2026 09:52:07 +0000 Subject: [PATCH 067/137] tcp: Fix fastreuse cache initialization on first bind inet_csk_update_fastreuse() initializes the reuse caches only when tb->bhash2 is empty, but both inet_csk_get_port() and __inet_inherit_port() link tb2 before calling it, so the first-bind branch is never taken. As a result, SO_REUSEADDR binds never get fastreuse=1 and keep scanning existing owners, and SO_REUSEPORT starts in STRICT instead of ANY. Initialize the caches when tb2 is the only entry in tb->bhash2 and has no owners. Timewait sockets are also linked on tb2->owners, so this is equivalent to the old hlist_empty(&tb->owners) test. Found by manual code inspection and confirmed with GDB. Binding 16000 SO_REUSEADDR sockets to one address/port in a KVM guest takes 4.18 s before and 19.4 ms after this patch. Existing bind/reuse selftests pass. Analysis and testing were assisted by an AI tool (Codex). Fixes: 8002d44fe84d ("tcp: Check hlist_empty(&tb->bhash2) instead of hlist_empty(&tb->owners).") Reviewed-by: Eric Dumazet Signed-off-by: Zhewei Yuan Reviewed-by: Kuniyuki Iwashima Reviewed-by: Jason Xing Link: https://patch.msgid.link/20261026095208.58504-1-yzwlhm@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv4/inet_connection_sock.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c index 8151c638a24c..93b306d91b6d 100644 --- a/net/ipv4/inet_connection_sock.c +++ b/net/ipv4/inet_connection_sock.c @@ -446,7 +446,8 @@ void inet_csk_update_fastreuse(const struct sock *sk, { bool reuse = sk->sk_reuse && sk->sk_state != TCP_LISTEN; - if (hlist_empty(&tb->bhash2)) { + if (hlist_is_singular_node(&tb2->bhash_node, &tb->bhash2) && + hlist_empty(&tb2->owners)) { tb->fastreuse = reuse; if (sk->sk_reuseport) { tb->fastreuseport = FASTREUSEPORT_ANY; From d54b21d824302af52d06574870817fbe14774dab Mon Sep 17 00:00:00 2001 From: David Yang Date: Mon, 26 Oct 2026 02:20:18 +0800 Subject: [PATCH 068/137] net: hisilicon: hisi_femac: Stop the receiver before releasing the RX ring hisi_femac_net_close() frees the receive ring while the RX address queue still holds every address that was pushed to IQ_ADDR. A port that keeps receiving then DMAs arrived frames into buffers that other slab caches have already taken back; on an EC6108V9C (hi3798mv100) the queue still held all 52 armed addresses right after the interface was closed, and had dropped to 43 after 15 s of broadcast traffic. Reset the port before the buffers are released, so that the receiver no longer holds any of their addresses. Fixes: 542ae60af24f ("net: hisilicon: Add Fast Ethernet MAC driver") Cc: stable@vger.kernel.org Signed-off-by: David Yang Link: https://patch.msgid.link/20261025182022.2959333-1-mmyangfl@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/hisilicon/hisi_femac.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/hisilicon/hisi_femac.c b/drivers/net/ethernet/hisilicon/hisi_femac.c index 016605e86f0b..a44ea0a2b1ee 100644 --- a/drivers/net/ethernet/hisilicon/hisi_femac.c +++ b/drivers/net/ethernet/hisilicon/hisi_femac.c @@ -491,6 +491,7 @@ static int hisi_femac_net_close(struct net_device *dev) netif_stop_queue(dev); napi_disable(&priv->napi); + hisi_femac_port_reset(priv); hisi_femac_free_skb_rings(priv); return 0; From b2e17432e7ee75997657f99862eb83be0c78a6c1 Mon Sep 17 00:00:00 2001 From: Daniel Machon Date: Mon, 26 Oct 2026 14:45:58 +0100 Subject: [PATCH 069/137] net: lan966x: don't disable hardirqs in fdma tx clear path lan966x_fdma_tx_clear_buf() runs from the NAPI poll and frees the buffers of completed tx DCBs with napi_consume_skb(), page_pool_recycle_direct() and xdp_return_frame_bulk(), under lan966x->tx_lock. Before this patch, the lock is taken with spin_lock_irqsave(), so the buffers 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 other tx_lock users, lan966x_port_xmit() and lan966x_fdma_xmit_xdpf(), already do. tx_clear_buf() runs from the NAPI poll, so disabling hardirqs there is not needed. Found with lockdep enabled (CONFIG_PROVE_LOCKING), which the assert depends on. Reproduced on a lan9668 board by redirecting received frames out of another port with tc mirred. Fixes: 11871aba1974 ("net: lan96x: Use page_pool API") Signed-off-by: Daniel Machon Link: https://patch.msgid.link/20261026-lan966x-fdma-tx-clear-hardirq-v1-1-5f70f026a2e4@microchip.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/microchip/lan966x/lan966x_fdma.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_fdma.c b/drivers/net/ethernet/microchip/lan966x/lan966x_fdma.c index 8ea86b800af7..21668bbcc906 100644 --- a/drivers/net/ethernet/microchip/lan966x/lan966x_fdma.c +++ b/drivers/net/ethernet/microchip/lan966x/lan966x_fdma.c @@ -336,14 +336,13 @@ static void lan966x_fdma_tx_clear_buf(struct lan966x *lan966x, int weight) struct lan966x_tx_dcb_buf *dcb_buf; struct fdma *fdma = &tx->fdma; struct xdp_frame_bulk bq; - unsigned long flags; bool clear = false; struct fdma_db *db; int i; xdp_frame_bulk_init(&bq); - spin_lock_irqsave(&lan966x->tx_lock, flags); + spin_lock(&lan966x->tx_lock); for (i = 0; i < fdma->n_dcbs; ++i) { dcb_buf = &tx->dcbs_buf[i]; @@ -388,7 +387,7 @@ static void lan966x_fdma_tx_clear_buf(struct lan966x *lan966x, int weight) if (clear) lan966x_fdma_wakeup_netdev(lan966x); - spin_unlock_irqrestore(&lan966x->tx_lock, flags); + spin_unlock(&lan966x->tx_lock); } static int lan966x_fdma_rx_check_frame(struct lan966x_rx *rx, u64 *src_port) From 9663edfa71e8807890203578dab6db60c6b548c4 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Mon, 26 Oct 2026 09:47:32 -0700 Subject: [PATCH 070/137] net: dsa: mv88e6xxx: fix 6351 family statistics snapshot The 88E6171, 88E6175, 88E6350 and 88E6351 take the port number in the low bits of the statistics operation register. The 6320 snapshot helper instead encodes the port as (port + 1) << 5. On an EA4500 with an 88E6171, this causes ethtool -S to return physical port 0's MIB counters for every user port. Traffic on other ports does not update their reported hardware counters. Before the snapshot operation was moved into the chip operations, only the 6320 and 6352 families used the shifted encoding. The refactoring incorrectly assigned that encoding to all four 6351 family chips. Use the unshifted snapshot helper for the entire 6351 family. This matches the Marvell DSDT statsCapture() implementation, which uses the raw port number for chips outside DEV_RMON_PORT_BITS, including these four chips. The original 88E6171 change was tested on an EA4500: pings through different switch ports updated only the corresponding port's hardware counters. The other three chips have not been hardware tested. Fixes: a605a0fe7115 ("net: dsa: mv88e6xxx: Abstract stats_snapshot into ops structure") Link: https://lore.kernel.org/all/404d5f2d-24a5-4edc-897b-b1b3c54a20e1@lunn.ch/ Signed-off-by: Florian Fainelli Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20261026164733.737299-2-f.fainelli@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/mv88e6xxx/chip.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c index 2a975ecd79c7..c6537d0faac5 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.c +++ b/drivers/net/dsa/mv88e6xxx/chip.c @@ -4611,7 +4611,7 @@ static const struct mv88e6xxx_ops mv88e6171_ops = { .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, .port_get_cmode = mv88e6352_port_get_cmode, .port_setup_message_port = mv88e6xxx_setup_message_port, - .stats_snapshot = mv88e6320_g1_stats_snapshot, + .stats_snapshot = mv88e6xxx_g1_stats_snapshot, .stats_set_histogram = mv88e6095_g1_stats_set_histogram, .stats_get_sset_count = mv88e6095_stats_get_sset_count, .stats_get_strings = mv88e6095_stats_get_strings, @@ -4715,7 +4715,7 @@ static const struct mv88e6xxx_ops mv88e6175_ops = { .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, .port_get_cmode = mv88e6352_port_get_cmode, .port_setup_message_port = mv88e6xxx_setup_message_port, - .stats_snapshot = mv88e6320_g1_stats_snapshot, + .stats_snapshot = mv88e6xxx_g1_stats_snapshot, .stats_set_histogram = mv88e6095_g1_stats_set_histogram, .stats_get_sset_count = mv88e6095_stats_get_sset_count, .stats_get_strings = mv88e6095_stats_get_strings, @@ -5381,7 +5381,7 @@ static const struct mv88e6xxx_ops mv88e6350_ops = { .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, .port_get_cmode = mv88e6352_port_get_cmode, .port_setup_message_port = mv88e6xxx_setup_message_port, - .stats_snapshot = mv88e6320_g1_stats_snapshot, + .stats_snapshot = mv88e6xxx_g1_stats_snapshot, .stats_set_histogram = mv88e6095_g1_stats_set_histogram, .stats_get_sset_count = mv88e6095_stats_get_sset_count, .stats_get_strings = mv88e6095_stats_get_strings, @@ -5427,7 +5427,7 @@ static const struct mv88e6xxx_ops mv88e6351_ops = { .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, .port_get_cmode = mv88e6352_port_get_cmode, .port_setup_message_port = mv88e6xxx_setup_message_port, - .stats_snapshot = mv88e6320_g1_stats_snapshot, + .stats_snapshot = mv88e6xxx_g1_stats_snapshot, .stats_set_histogram = mv88e6095_g1_stats_set_histogram, .stats_get_sset_count = mv88e6095_stats_get_sset_count, .stats_get_strings = mv88e6095_stats_get_strings, From df66431f39426b685f6693f0731cd5881c51679c Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Mon, 26 Oct 2026 09:47:33 -0700 Subject: [PATCH 071/137] net: dsa: mv88e6xxx: fix 88E6171 statistics type The 88E6171 uses the 6095 statistics operations, which advertise and name BANK0 and PORT counters. Its stats_type bitmap instead selects BANK0 and BANK1. Since statistics collection started using that bitmap, it has written a different set of counters from the one advertised to ethtool. For this chip, get_sset_count() accounts for 30 BANK0 counters, three PORT counters and five ATU/VTU counters. Collection instead writes 30 BANK0 counters, 26 BANK1 counters and five ATU/VTU counters: 61 entries rather than the advertised 38. This mislabels the tail of the returned statistics and writes past the advertised statistics buffer. Select BANK0 and PORT counters to match the chip's statistics operations and restore the filter used before the bitmap was introduced. Tested on an EA4500, where the two-bridge forwarding and multicast snooping tests now pass their hardware counter checks. Fixes: 5595e3613ea7 ("dsa: mv88e6xxx: Move available stats into info structure") Reviewed-by: Andrew Lunn Signed-off-by: Florian Fainelli Link: https://patch.msgid.link/20261026164733.737299-3-f.fainelli@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/mv88e6xxx/chip.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c index c6537d0faac5..46aef6dfe77e 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.c +++ b/drivers/net/dsa/mv88e6xxx/chip.c @@ -6023,7 +6023,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { .age_time_coeff = 15000, .g1_irqs = 9, .g2_irqs = 10, - .stats_type = STATS_TYPE_BANK0 | STATS_TYPE_BANK1, + .stats_type = STATS_TYPE_BANK0 | STATS_TYPE_PORT, .atu_move_port_mask = 0xf, .pvt = true, .multi_chip = true, From 67ae1b80cf73f592b35e4b5e5001a24d6d6c8637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Mete=20Kaya?= Date: Sat, 24 Oct 2026 11:42:08 +0300 Subject: [PATCH 072/137] usbnet: fix EVENT_RX_PAUSED race in usbnet_skb_return() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On SMP, usbnet_skb_return() can observe EVENT_RX_PAUSED as set on CPU0 while usbnet_resume_rx() clears the flag and drains rxq_pause on CPU1. The skb can then be queued after the drain has completed and remain stranded until the next MTU change or device stop. Synchronize the EVENT_RX_PAUSED state transition and paused-queue dequeue operations with rxq_pause.lock. In usbnet_resume_rx() splice the paused queue into a local queue under the lock, then drain the local queue lock-free. In usbnet_skb_return() use a double-check pattern under rxq_pause.lock on the slow path, keeping the normal RX fast path lock-free. Use spin_lock_irqsave() to match the lockdep class shared with other sk_buff_head locks taken from hardirq context in defer_bh(). Discovered by Sashiko, not triggered, not tested on real hardware. Fixes: 43daa96b166c ("usbnet: Stop RX Q on MTU change") Signed-off-by: Ömer Mete Kaya Link: https://patch.msgid.link/20261024084538.256596-1-omermetekaya0@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/usb/usbnet.c | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c index 84f97f448b2d..97a30dd22c54 100644 --- a/drivers/net/usb/usbnet.c +++ b/drivers/net/usb/usbnet.c @@ -329,9 +329,14 @@ void usbnet_skb_return(struct usbnet *dev, struct sk_buff *skb) unsigned long flags; int status; - if (test_bit(EVENT_RX_PAUSED, &dev->flags)) { - skb_queue_tail(&dev->rxq_pause, skb); - return; + if (unlikely(test_bit(EVENT_RX_PAUSED, &dev->flags))) { + spin_lock_irqsave(&dev->rxq_pause.lock, flags); + if (test_bit(EVENT_RX_PAUSED, &dev->flags)) { + __skb_queue_tail(&dev->rxq_pause, skb); + spin_unlock_irqrestore(&dev->rxq_pause.lock, flags); + return; + } + spin_unlock_irqrestore(&dev->rxq_pause.lock, flags); } /* only update if unset to allow minidriver rx_fixup override */ @@ -698,13 +703,20 @@ EXPORT_SYMBOL_GPL(usbnet_pause_rx); void usbnet_resume_rx(struct usbnet *dev) { + struct sk_buff_head q; struct sk_buff *skb; + unsigned long flags; int num = 0; - local_bh_disable(); - clear_bit(EVENT_RX_PAUSED, &dev->flags); + __skb_queue_head_init(&q); - while ((skb = skb_dequeue(&dev->rxq_pause)) != NULL) { + local_bh_disable(); + spin_lock_irqsave(&dev->rxq_pause.lock, flags); + clear_bit(EVENT_RX_PAUSED, &dev->flags); + skb_queue_splice_init(&dev->rxq_pause, &q); + spin_unlock_irqrestore(&dev->rxq_pause.lock, flags); + + while ((skb = __skb_dequeue(&q))) { usbnet_skb_return(dev, skb); num++; } From 5d7f3375cd574fc83f0cd30839b21455c0e79ae2 Mon Sep 17 00:00:00 2001 From: David Yang Date: Mon, 26 Oct 2026 16:32:48 +0800 Subject: [PATCH 073/137] net: dsa: motorcomm: Fix function parameter types yt921x_fdb_add_index_in12() mistakenly takes the two register images as u16, while all registers are 32-bit wide, silently losing the upper half. Widen the parameters to u32. Discoved by LLM but impacting existing registers. Fixes: 186623f4aa72 ("net: dsa: yt921x: Add support for Motorcomm YT921x") Signed-off-by: David Yang Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20261026083252.12684-1-mmyangfl@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/motorcomm/chip.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c index d4fc20c52a58..33e5fc127244 100644 --- a/drivers/net/dsa/motorcomm/chip.c +++ b/drivers/net/dsa/motorcomm/chip.c @@ -679,8 +679,8 @@ yt921x_fdb_flush_port(struct yt921x_priv *priv, int port, bool flush_static) } static int -yt921x_fdb_add_index_in12(struct yt921x_priv *priv, u16 index, u16 ctrl1, - u16 ctrl2) +yt921x_fdb_add_index_in12(struct yt921x_priv *priv, u16 index, u32 ctrl1, + u32 ctrl2) { u32 ctrl; u32 val; From 557d80138618b9d17407733d4ec25b33d3971d60 Mon Sep 17 00:00:00 2001 From: Peter Morrow Date: Tue, 20 Oct 2026 17:42:39 +0100 Subject: [PATCH 074/137] net/mlx4: always create the eth and ib auxiliary devices Since commit 7d22b1cb9d84 ("mlx4: Connect the infiniband part to the auxiliary bus"), mlx4_core creates the "ib" auxiliary device only for an IB port or a device that advertises IBoE. Before it, each mlx4 interface was offered every device and decided in its own add callback. mlx4_ib_add() accepted Ethernet ports without IBoE and registered them for raw packet QPs only. ConnectX-3 VFs on Azure have an Ethernet port and no IBoE, so since v6.6 they get no RDMA device. DPDK's mlx4 PMD, which needs only raw packet QPs, then cannot open them: net_mlx4: cannot access device, is mlx4_ib loaded? Restore the old behaviour by creating both auxiliary devices for every device and letting mlx4_en and mlx4_ib decide in probe, as their add callbacks did. mlx4_ib_probe() returns -ENODEV when there are no usable ports, and mlx4_en_probe() binds a device without Ethernet ports but creates no netdevs, as mlx4_en_add() did. Tested on 6.12.111 on an Azure VM with two ConnectX-3 VFs: each gets an eth and an ib auxiliary device, and DPDK attaches both VFs and passes traffic through them. Fixes: 7d22b1cb9d84 ("mlx4: Connect the infiniband part to the auxiliary bus") Closes: https://lore.kernel.org/r/20261014133951.3219055-1-pdmorrow@gmail.com Suggested-by: Leon Romanovsky Assisted-by: LLM Signed-off-by: Peter Morrow Reviewed-by: Simon Horman Reviewed-by: Daniel Machon Acked-by: Tariq Toukan Signed-off-by: Dave Miller --- drivers/net/ethernet/mellanox/mlx4/intf.c | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx4/intf.c b/drivers/net/ethernet/mellanox/mlx4/intf.c index 82e467c45d23..31860b4abe43 100644 --- a/drivers/net/ethernet/mellanox/mlx4/intf.c +++ b/drivers/net/ethernet/mellanox/mlx4/intf.c @@ -43,23 +43,12 @@ static DEFINE_IDA(mlx4_adev_ida); static bool is_eth_supported(struct mlx4_dev *dev) { - for (int port = 1; port <= dev->caps.num_ports; port++) - if (dev->caps.port_type[port] == MLX4_PORT_TYPE_ETH) - return true; - - return false; + return true; } static bool is_ib_supported(struct mlx4_dev *dev) { - for (int port = 1; port <= dev->caps.num_ports; port++) - if (dev->caps.port_type[port] == MLX4_PORT_TYPE_IB) - return true; - - if (dev->caps.flags & MLX4_DEV_CAP_FLAG_IBOE) - return true; - - return false; + return true; } static const struct mlx4_adev_device { From 1c8a70075235cd733095532a97f2405037c52a7b Mon Sep 17 00:00:00 2001 From: Rishikesh Jethwani Date: Tue, 20 Oct 2026 15:44:59 -0600 Subject: [PATCH 075/137] tls: keep TX and RX hardware offload on the same device After TX is offloaded on device A, a route change, or a change in the usable-slave set of an 802.3ad or balance-xor (layer3+4) bond, can make RX setup program device B. tls_device_attach() sees the existing TLS destructor and keeps ctx->netdev pointing at A. The RX driver state on B is then untracked: tls_dev_resync() and tls_dev_del() are invoked on A for a context it never created, and neither socket close nor NETDEV_DOWN on B calls tls_dev_del() for B, so the driver context leaks. The reverse setup order has the same problem. Check the shared device binding under device_offload_lock before each tls_dev_add(). Permit the first attachment and a second direction on the same device. Reject a different device, including when the old binding was cleared by NETDEV_DOWN: the TLS destructor remains installed and tls_device_attach() cannot establish a new binding. Setup then falls back to software for the requested direction. The RX check precedes allocation and software setup. TX uses its existing resource unwind path without calling the driver. Document the shared binding and clarify that fallback is per direction. This issue was discovered during code review and subsequently reproduced on hardware using mlx5 and TLS 1.2 AES-GCM-128. Changing the route between TX and RX setup installed the second direction on a different device. Driver traces showed successful adds on both devices, but both deletes at socket close targeted the first device, leaving an orphaned context on the second. Both setup orders reproduced the leak, even though all current core TLS context counters returned to zero. No crash was observed. With the fix, the same tests correctly fall back to software for the second direction and leave no orphaned hardware context. Fixes: 4799ac81e52a ("tls: Add rx inline crypto offload") Signed-off-by: Rishikesh Jethwani Signed-off-by: Dave Miller --- Documentation/networking/tls-offload.rst | 16 ++++++++++------ net/tls/tls_device.c | 24 ++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/Documentation/networking/tls-offload.rst b/Documentation/networking/tls-offload.rst index e5802bcd4d22..cf0e599c06a1 100644 --- a/Documentation/networking/tls-offload.rst +++ b/Documentation/networking/tls-offload.rst @@ -71,12 +71,16 @@ During driver initialization device sets the ``NETIF_F_HW_TLS_RX`` and pointer in the :c:member:`tlsdev_ops` member of the :c:type:`struct net_device `. -When TLS cryptographic connection state is installed on a ``ktls`` socket -(note that it is done twice, once for RX and once for TX direction, -and the two are completely independent), the kernel checks if the underlying -network device is offload-capable and attempts the offload. In case offload -fails the connection is handled entirely in software using the same mechanism -as if the offload was never tried. +TLS cryptographic connection state is installed separately for RX and TX +on a ``ktls`` socket. For each direction, the kernel checks if the underlying +network device is offload-capable and attempts the offload. If offload fails, +that direction is handled in software as if offload had never been tried. + +Both hardware directions share one device binding. Once one direction is +offloaded, the other direction must use the same device. If a route change +selects a different device, or ``NETDEV_DOWN`` has cleared the binding, +hardware offload of the other direction is declined and it uses software +instead. Offload request is performed via the :c:member:`tls_dev_add` callback of :c:type:`struct tlsdev_ops `: diff --git a/net/tls/tls_device.c b/net/tls/tls_device.c index 1a5f328d80a5..4cdfbb57ecff 100644 --- a/net/tls/tls_device.c +++ b/net/tls/tls_device.c @@ -1031,6 +1031,20 @@ int tls_device_decrypted(struct sock *sk, struct tls_context *tls_ctx) return tls_device_reencrypt(sk, tls_ctx); } +/* Both directions share ctx->netdev. A second direction must use that + * device; after NETDEV_DOWN clears it, no new direction can be offloaded. + */ +static bool tls_device_can_attach(struct tls_context *ctx, struct sock *sk, + struct net_device *netdev) +{ + struct net_device *ctx_netdev; + + ctx_netdev = rcu_dereference_protected(ctx->netdev, + lockdep_is_held(&device_offload_lock)); + return sk->sk_destruct != tls_device_sk_destruct || + ctx_netdev == netdev; +} + static void tls_device_attach(struct tls_context *ctx, struct sock *sk, struct net_device *netdev) { @@ -1166,6 +1180,11 @@ int tls_set_device_offload(struct sock *sk) goto release_lock; } + if (!tls_device_can_attach(ctx, sk, netdev)) { + rc = -EOPNOTSUPP; + goto release_lock; + } + ctx->priv_ctx_tx = offload_ctx; rc = netdev->tlsdev_ops->tls_dev_add(netdev, sk, TLS_OFFLOAD_CTX_DIR_TX, &ctx->crypto_send.info, @@ -1236,6 +1255,11 @@ int tls_set_device_offload_rx(struct sock *sk, struct tls_context *ctx) goto release_lock; } + if (!tls_device_can_attach(ctx, sk, netdev)) { + rc = -EOPNOTSUPP; + goto release_lock; + } + context = kzalloc_obj(*context); if (!context) { rc = -ENOMEM; From 22dd6bb2bafdf8b9740773a1e05cdb4df7a09ca2 Mon Sep 17 00:00:00 2001 From: Kiran Kumar K Date: Fri, 23 Oct 2026 07:19:05 +0000 Subject: [PATCH 076/137] octeontx2-af: fix inner LC ltype match for RSS flowkey Inner LC ltypes differ from outer LC ltypes, but RSS flowkey programming was using outer LC IP/IP6 ltype masks for inner IP keys. For inner IPv4/IPv6, match only the base IP ltype and do not include extension-header matches. Keep existing outer LC masks unchanged. This fixes inner-tunnel RSS key calculation and prevents mismatches in inner IP flow classification. Signed-off-by: kmahammadraf Signed-off-by: Kiran Kumar K Signed-off-by: Dave Miller --- drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c | 8 ++++++-- 1 file changed, 6 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 f4e232a73de8..29559db68929 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c @@ -4111,9 +4111,12 @@ static int set_flowkey_fields(struct nix_rx_flowkey_alg *alg, u32 flow_cfg) case NIX_FLOW_KEY_TYPE_INNR_IPV4: field->lid = NPC_LID_LC; field->ltype_match = NPC_LT_LC_IP; + field->ltype_mask = NPC_LT_LC_IP_MATCH_MSK; + if (key_type == NIX_FLOW_KEY_TYPE_INNR_IPV4) { field->lid = NPC_LID_LG; field->ltype_match = NPC_LT_LG_TU_IP; + field->ltype_mask = 0xF; } field->hdr_offset = 12; /* SIP offset */ field->bytesm1 = 7; /* SIP + DIP, 8 bytes */ @@ -4132,16 +4135,18 @@ static int set_flowkey_fields(struct nix_rx_flowkey_alg *alg, u32 flow_cfg) field->bytesm1 = 3; /* DIP, 4 bytes */ } } - field->ltype_mask = NPC_LT_LC_IP_MATCH_MSK; keyoff_marker = false; break; case NIX_FLOW_KEY_TYPE_IPV6: case NIX_FLOW_KEY_TYPE_INNR_IPV6: field->lid = NPC_LID_LC; field->ltype_match = NPC_LT_LC_IP6; + field->ltype_mask = NPC_LT_LC_IP6_MATCH_MSK; + if (key_type == NIX_FLOW_KEY_TYPE_INNR_IPV6) { field->lid = NPC_LID_LG; field->ltype_match = NPC_LT_LG_TU_IP6; + field->ltype_mask = 0xF; } field->hdr_offset = 8; /* SIP offset */ field->bytesm1 = 31; /* SIP + DIP, 32 bytes */ @@ -4161,7 +4166,6 @@ static int set_flowkey_fields(struct nix_rx_flowkey_alg *alg, u32 flow_cfg) field->bytesm1 = 15; /* DIP,16 bytes */ } } - field->ltype_mask = NPC_LT_LC_IP6_MATCH_MSK; break; case NIX_FLOW_KEY_TYPE_TCP: case NIX_FLOW_KEY_TYPE_UDP: From 6fb4e32f2ce3234ae0c416872e911706a3484430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Fri, 23 Oct 2026 18:51:19 +0000 Subject: [PATCH 077/137] inet: frags: prevent acceptance of partially authenticated reassembly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A reassembled skb retains the secpath from its first fragment, which makes it possible for the final policy check to accept bytes that were not covered by the transform recorded on the skb. This can affect IPsec tunnel traffic, including AH and ESP, and allow a packet to be accepted even though only part of its payload was authenticated. Reject a fragment when its secpath differs from the fragments already in the queue, and keep overlap statistics limited to actual overlaps. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Jérémy Jean Reviewed-by: Eric Dumazet Signed-off-by: Dave Miller --- include/net/inet_frag.h | 1 + include/net/xfrm.h | 17 +++++++++++++++++ net/ipv4/inet_fragment.c | 5 +++++ net/ipv4/ip_fragment.c | 3 ++- net/ipv6/reassembly.c | 3 ++- 5 files changed, 27 insertions(+), 2 deletions(-) diff --git a/include/net/inet_frag.h b/include/net/inet_frag.h index 365925c9d262..b02d2472b003 100644 --- a/include/net/inet_frag.h +++ b/include/net/inet_frag.h @@ -171,6 +171,7 @@ extern const u8 ip_frag_ecn_table[16]; #define IPFRAG_OK 0 #define IPFRAG_DUP 1 #define IPFRAG_OVERLAP 2 +#define IPFRAG_MIXED_SECPATH 3 int inet_frag_queue_insert(struct inet_frag_queue *q, struct sk_buff *skb, int offset, int end); void *inet_frag_reasm_prepare(struct inet_frag_queue *q, struct sk_buff *skb, diff --git a/include/net/xfrm.h b/include/net/xfrm.h index a6d69aaa6cd2..298ca9bb0083 100644 --- a/include/net/xfrm.h +++ b/include/net/xfrm.h @@ -1201,6 +1201,23 @@ secpath_reset(struct sk_buff *skb) #endif } +static inline bool +secpath_equal(const struct sk_buff *skb1, const struct sk_buff *skb2) +{ + const struct sec_path *sp1 = skb_sec_path(skb1); + const struct sec_path *sp2 = skb_sec_path(skb2); + int i; + + if (!sp1 || !sp2) + return sp1 == sp2; + if (sp1->len != sp2->len) + return false; + for (i = 0; i < sp1->len; i++) + if (sp1->xvec[i] != sp2->xvec[i]) + return false; + return true; +} + static inline int xfrm_addr_any(const xfrm_address_t *addr, unsigned short family) { diff --git a/net/ipv4/inet_fragment.c b/net/ipv4/inet_fragment.c index b286ee429da8..c18da91c1f03 100644 --- a/net/ipv4/inet_fragment.c +++ b/net/ipv4/inet_fragment.c @@ -23,6 +23,7 @@ #include #include #include +#include /* Use skb->cb to track consecutive/adjacent fragments coming at * the end of the queue. Nodes in the rb-tree queue will @@ -441,6 +442,10 @@ int inet_frag_queue_insert(struct inet_frag_queue *q, struct sk_buff *skb, { struct sk_buff *last = q->fragments_tail; + /* The reassembled skb keeps the first fragment's secpath. */ + if (last && !secpath_equal(last, skb)) + return IPFRAG_MIXED_SECPATH; + /* An IP fragment is never a GSO packet, but an untrusted source * (virtio_net_hdr) may have attached GSO metadata to it. Do not let * that reach the reassembled skb, whose head keeps the first diff --git a/net/ipv4/ip_fragment.c b/net/ipv4/ip_fragment.c index c790d2f49487..36105dd19282 100644 --- a/net/ipv4/ip_fragment.c +++ b/net/ipv4/ip_fragment.c @@ -378,8 +378,9 @@ static int ip_frag_queue(struct ipq *qp, struct sk_buff *skb, int *refs) err = -EINVAL; goto err; } + if (err == IPFRAG_OVERLAP) + __IP_INC_STATS(net, IPSTATS_MIB_REASM_OVERLAPS); err = -EINVAL; - __IP_INC_STATS(net, IPSTATS_MIB_REASM_OVERLAPS); discard_qp: inet_frag_kill(&qp->q, refs); __IP_INC_STATS(net, IPSTATS_MIB_REASMFAILS); diff --git a/net/ipv6/reassembly.c b/net/ipv6/reassembly.c index a7964125c764..8418ed9423f4 100644 --- a/net/ipv6/reassembly.c +++ b/net/ipv6/reassembly.c @@ -241,8 +241,9 @@ static int ip6_frag_queue(struct net *net, err = -EINVAL; goto err; } + if (err == IPFRAG_OVERLAP) + __IP6_INC_STATS(net, idev, IPSTATS_MIB_REASM_OVERLAPS); err = -EINVAL; - __IP6_INC_STATS(net, idev, IPSTATS_MIB_REASM_OVERLAPS); discard_fq: inet_frag_kill(&fq->q, refs); __IP6_INC_STATS(net, idev, IPSTATS_MIB_REASMFAILS); From d6f81fd6abe209f9803d8f74ce9ec69b8126bad3 Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Fri, 23 Oct 2026 16:02:56 -0400 Subject: [PATCH 078/137] net: sched: prevent scheduling deactivated qdiscs TAPRIO can schedule a deactivated qdisc after some_qdisc_is_busy() has reported it idle. Commit 47e8dbb6e763 ("net/sched: do not reset queues in graft operations") made graft deactivation skip queue reset, leaving TAPRIO's timer armed. A late callback schedules NET_TX softirq work, whose dequeue path can race the unlocked child-queue reset in taprio_reset(). The same skb can be queued for deferred freeing by RTNL and dequeued for transmission. In the reproducer, the TAP reader frees it before __rtnl_unlock() reads its next pointer. KASAN reports a slab-use-after-free in __rtnl_unlock+0xa3/0xd0, with a read of size 8 from the freed skb. Reject deactivated qdiscs in __netif_schedule(). Callers' RCU protection lets the existing grace period join earlier scheduling calls before the busy check. Restart nonempty transmit queues on reactivation so suppressed scheduling does not strand packets. Fixes: 47e8dbb6e763 ("net/sched: do not reset queues in graft operations") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/netdev/20260805102514.740834-1-david.lee@trailofbits.com/ 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: Dave Miller --- net/core/dev.c | 3 +++ net/sched/sch_generic.c | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/net/core/dev.c b/net/core/dev.c index b3622e532f26..87a79d6004c8 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -3478,6 +3478,9 @@ static void __netif_reschedule(struct Qdisc *q) void __netif_schedule(struct Qdisc *q) { + if (unlikely(test_bit(__QDISC_STATE_DEACTIVATED, &q->state))) + return; + /* If q->defer_list is not empty, at least one thread is * in __dev_xmit_skb() before llist_del_all(&q->defer_list). * This thread will attempt to run the queue. diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c index 6f6a6f0d5eb0..bff425a14526 100644 --- a/net/sched/sch_generic.c +++ b/net/sched/sch_generic.c @@ -1280,6 +1280,10 @@ static void transition_one_qdisc(struct net_device *dev, if (need_watchdog_p) { WRITE_ONCE(dev_queue->trans_start, jiffies); *need_watchdog_p = 1; + + /* Resume backlog left by a suppressed schedule while deactivated. */ + if (!qdisc_is_empty(new_qdisc)) + netif_schedule_queue(dev_queue); } } From 1e860f42f41c6fbf3304f6a52be38245b9394982 Mon Sep 17 00:00:00 2001 From: Tristan Madani Date: Fri, 23 Oct 2026 19:53:09 +0000 Subject: [PATCH 079/137] ppp: fix stale pointer access after pskb_may_pull in ppp_ccp_peek ppp_ccp_peek() caches a pointer to skb->data in dp after the first pskb_may_pull(), then uses dp after a second pskb_may_pull(skb, len + 2) in the CCP_CONFACK handler. If the second pull triggers pskb_expand_head() due to a cloned skb or frag linearization, skb->head is freed and reallocated, leaving dp stale. The stale dp is subsequently used in CCP_OPT_LENGTH() and passed to decomp_init()/comp_init() which read from the freed memory. Re-derive dp from skb->data after the second pskb_may_pull. Fixes: 1da177e4c3f41 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Tristan Madani Signed-off-by: Dave Miller --- drivers/net/ppp/ppp_generic.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ppp/ppp_generic.c b/drivers/net/ppp/ppp_generic.c index 1a610a18893b..3998ce4332f4 100644 --- a/drivers/net/ppp/ppp_generic.c +++ b/drivers/net/ppp/ppp_generic.c @@ -3157,6 +3157,7 @@ ppp_ccp_peek(struct ppp *ppp, struct sk_buff *skb, int inbound) len = CCP_LENGTH(dp); if (!pskb_may_pull(skb, len + 2)) return; /* too short */ + dp = skb->data + 2; dp += CCP_HDRLEN; len -= CCP_HDRLEN; if (len < CCP_OPT_MINLEN || len < CCP_OPT_LENGTH(dp)) From 083aaafd453c3fb14411519d6ada9a8917573fdb Mon Sep 17 00:00:00 2001 From: Sicong Huang Date: Sat, 24 Oct 2026 12:49:03 +0800 Subject: [PATCH 080/137] nfp: flower: fix use-after-free in neighbour update work The netevent notifier handles neighbour updates and redirects by queuing nfp_tun_neigh_update() on system_highpri_wq. Each work item holds a reference to the neighbour, but only stores a pointer to the flower app. During device removal, nfp_tunnel_config_stop() unregisters the notifier. This waits for notifier callbacks to return, but does not wait for the work they queued. The teardown then frees app->priv and app without waiting for these work items. Flushing cmsg_work in nfp_flower_clean() does not cover the neighbour update work. A pending work item can therefore run after the app has been freed. Its call to nfp_flower_get_port_id_from_netdev() can then access freed memory while checking whether the neighbour's device can be offloaded. Even notifications for unrelated devices can reach this check. The same problem affects shutdown and probe error paths that tear down the app after the netevent notifier has been registered. One possible interleaving is shown below. The neighbour belongs to another network device that remains registered. CPU0 (remove) CPU1 (notifier / kworker) nfp_tun_neigh_event_handler() queue_work(system_highpri_wq, ...) return /* Work has not run yet. */ nfp_tunnel_config_stop() unregister_netevent_notifier() /* Notifier callbacks have finished, but work may be pending. */ nfp_flower_clean() vfree(app->priv) app->priv = NULL nfp_app_free() kfree(app) nfp_tun_neigh_update() app = update_work->app nfp_flower_get_port_id_from_netdev() app->priv /* use-after-free */ The work items are allocated per event and free themselves, so they cannot be cancelled individually, and flushing system_highpri_wq is not allowed. Use a dedicated workqueue for neighbour updates instead. In nfp_tunnel_config_stop(), unregister the notifier and destroy the workqueue before cleaning up the tunnel state. This prevents new work from being queued and waits for pending and running work to finish while the app and its resources are still valid. Found by code inspection. Fixes: 0ad722bd9ee3 ("nfp: flower: fix for take a mutex lock in soft irq context and rcu lock") Signed-off-by: Sicong Huang Reviewed-by: Daniel Machon Signed-off-by: Dave Miller --- .../net/ethernet/netronome/nfp/flower/main.h | 2 ++ .../netronome/nfp/flower/tunnel_conf.c | 24 ++++++++++++++----- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/netronome/nfp/flower/main.h b/drivers/net/ethernet/netronome/nfp/flower/main.h index 2b7c947ff4f2..bd4399758ed4 100644 --- a/drivers/net/ethernet/netronome/nfp/flower/main.h +++ b/drivers/net/ethernet/netronome/nfp/flower/main.h @@ -94,6 +94,7 @@ struct nfp_fl_stats_id { * @ipv6_off_lock: Lock for the IPv6 address list * @mac_off_ids: IDA to manage id assignment for offloaded MACs * @neigh_nb: Notifier to monitor neighbour state + * @neigh_wq: Workqueue for neighbour updates */ struct nfp_fl_tunnel_offloads { struct rhashtable offloaded_macs; @@ -103,6 +104,7 @@ struct nfp_fl_tunnel_offloads { struct mutex ipv6_off_lock; struct ida mac_off_ids; struct notifier_block neigh_nb; + struct workqueue_struct *neigh_wq; }; /** diff --git a/drivers/net/ethernet/netronome/nfp/flower/tunnel_conf.c b/drivers/net/ethernet/netronome/nfp/flower/tunnel_conf.c index 3b47e9fe64e1..46b6cda7f3d3 100644 --- a/drivers/net/ethernet/netronome/nfp/flower/tunnel_conf.c +++ b/drivers/net/ethernet/netronome/nfp/flower/tunnel_conf.c @@ -736,7 +736,7 @@ nfp_tun_neigh_event_handler(struct notifier_block *nb, unsigned long event, if (!update_work) return NOTIFY_DONE; - queue_work(system_highpri_wq, &update_work->work); + queue_work(app_priv->tun.neigh_wq, &update_work->work); return NOTIFY_DONE; } @@ -1509,6 +1509,13 @@ int nfp_tunnel_config_start(struct nfp_app *app) if (err) return err; + priv->tun.neigh_wq = alloc_workqueue("nfp-flower-neigh", + WQ_HIGHPRI | WQ_PERCPU, 0); + if (!priv->tun.neigh_wq) { + err = -ENOMEM; + goto err_free_macs; + } + ida_init(&priv->tun.mac_off_ids); /* Initialise priv data for IPv4/v6 offloading. */ @@ -1521,13 +1528,17 @@ int nfp_tunnel_config_start(struct nfp_app *app) priv->tun.neigh_nb.notifier_call = nfp_tun_neigh_event_handler; err = register_netevent_notifier(&priv->tun.neigh_nb); - if (err) { - rhashtable_free_and_destroy(&priv->tun.offloaded_macs, - nfp_check_rhashtable_empty, NULL); - return err; - } + if (err) + goto err_destroy_neigh_wq; return 0; + +err_destroy_neigh_wq: + destroy_workqueue(priv->tun.neigh_wq); +err_free_macs: + rhashtable_free_and_destroy(&priv->tun.offloaded_macs, + nfp_check_rhashtable_empty, NULL); + return err; } void nfp_tunnel_config_stop(struct nfp_app *app) @@ -1537,6 +1548,7 @@ void nfp_tunnel_config_stop(struct nfp_app *app) struct list_head *ptr, *storage; unregister_netevent_notifier(&priv->tun.neigh_nb); + destroy_workqueue(priv->tun.neigh_wq); ida_destroy(&priv->tun.mac_off_ids); From 94cea342e8915812b45f93c56e134041c7fdb4d7 Mon Sep 17 00:00:00 2001 From: Fang Xieyan Date: Mon, 26 Oct 2026 17:07:17 +0800 Subject: [PATCH 081/137] net: sched: em_ipt: validate hook value from netlink em_ipt_change() accepts the user-provided TCA_EM_IPT_HOOK value without validating its range. The value is later used in check_match() as "1 << im->hook". A hook value of 32 or greater causes undefined behavior and may undermine the xt match hook-mask validation. The value is also passed to nf_hook_state_init() as a hook index. Reject hook values outside the supported IPv4/IPv6 Netfilter hook range after checking the required attributes, before loading the xt match or allocating the match state. Reviewed-by: Weibin Liu Signed-off-by: Fang Xieyan Signed-off-by: Dave Miller --- net/sched/em_ipt.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/sched/em_ipt.c b/net/sched/em_ipt.c index 3650117da47f..26f1c4269a48 100644 --- a/net/sched/em_ipt.c +++ b/net/sched/em_ipt.c @@ -141,6 +141,9 @@ static int em_ipt_change(struct net *net, void *data, int data_len, !tb[TCA_EM_IPT_MATCH_DATA] || !tb[TCA_EM_IPT_NFPROTO]) return -EINVAL; + if (nla_get_u32(tb[TCA_EM_IPT_HOOK]) >= NF_INET_NUMHOOKS) + return -EINVAL; + nfproto = nla_get_u8(tb[TCA_EM_IPT_NFPROTO]); switch (nfproto) { case NFPROTO_IPV4: From b4aac29a21d96974ae6e6d8e7b1b01e1f95c4348 Mon Sep 17 00:00:00 2001 From: Yongjian Wang Date: Mon, 26 Oct 2026 15:23:08 +0800 Subject: [PATCH 082/137] Revert "selftests: net: packetdrill: add tests for advertised MSS with PMTU exceptions" This reverts commit e5c8e301b497 ("selftests: net: packetdrill: add tests for advertised MSS with PMTU exceptions"). These tests assert the behaviour introduced by commit 2640e6419594 ("net: advertise TCP MSS from the configured MTU, not the learned PMTU"), which is reverted in the next patch because it causes a regression. Remove them first so the series stays bisectable. Signed-off-by: Yongjian Wang Reviewed-by: Eric Dumazet Reviewed-by: Jiayuan Chen Link: https://patch.msgid.link/20261026-advmss-revert-v1-1-272fa4b7ecb6@gmail.com Signed-off-by: Jakub Kicinski --- .../net/packetdrill/tcp_advmss_pmtu_ipv4.pkt | 67 ------------------- .../net/packetdrill/tcp_advmss_pmtu_ipv6.pkt | 67 ------------------- 2 files changed, 134 deletions(-) delete mode 100644 tools/testing/selftests/net/packetdrill/tcp_advmss_pmtu_ipv4.pkt delete mode 100644 tools/testing/selftests/net/packetdrill/tcp_advmss_pmtu_ipv6.pkt diff --git a/tools/testing/selftests/net/packetdrill/tcp_advmss_pmtu_ipv4.pkt b/tools/testing/selftests/net/packetdrill/tcp_advmss_pmtu_ipv4.pkt deleted file mode 100644 index f2ef931b77a1..000000000000 --- a/tools/testing/selftests/net/packetdrill/tcp_advmss_pmtu_ipv4.pkt +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -// -// Test that IPv4 advertised MSS in SYN-ACK is derived from the configured -// interface MTU (1500 -> MSS 1460), not the ICMP-learned Path MTU. - ---ip_version=ipv4 - -`./defaults.sh -ethtool -K tun0 tso off -` - -// -// Connection 1: Learn PMTU exception (MTU 1200 -> MSS 1160) -// - 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 - +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 - +0 bind(3, ..., ...) = 0 - +0 listen(3, 1) = 0 - - +0 < S 0:0(0) win 65535 - +0 > S. 0:0(0) ack 1 - +.1 < . 1:1(0) ack 1 win 257 - +0 accept(3, ..., ...) = 4 - -// Send a full 1460-byte segment - +0 write(4, ..., 1460) = 1460 - +0 > P. 1:1461(1460) ack 1 - -// ICMP Fragmentation Needed arrives indicating next-hop MTU 1200 - +0 < icmp unreachable frag_needed mtu 1200 [1:1461(1460)] - -// Local host retransmits using the learned MTU 1200 (MSS = 1200 - 40 = 1160) - +0 > . 1:1161(1160) ack 1 - +0 > P. 1161:1461(300) ack 1 - +0 < R 1:1(0) ack 1461 win 0 - -// Close connection 1 and listener - +0 close(4) = 0 - +0 close(3) = 0 - -// -// Connection 2: New connection from the same peer -// - +0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 - +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 - +0 bind(3, ..., ...) = 0 - +0 listen(3, 1) = 0 - - +0 < S 0:0(0) win 65535 - -// Verify: SYN-ACK MUST advertise configured MSS 1460, NOT the learned PMTU MSS 1160 - +0 > S. 0:0(0) ack 1 - +0 < . 1:1(0) ack 1 win 257 - +0 accept(3, ..., ...) = 4 - -// Verify: Outgoing transmit MSS is still constrained by the learned PMTU 1200 - +0 write(4, ..., 1460) = 1460 - +0 > . 1:1161(1160) ack 1 - +0 > P. 1161:1461(300) ack 1 - +0 < . 1:1(0) ack 1461 win 257 - -// Clean up - +0 close(4) = 0 - +0 > F. 1461:1461(0) ack 1 - +0 < F. 1:1(0) ack 1462 win 257 - +0 > . 1462:1462(0) ack 2 - +0 close(3) = 0 diff --git a/tools/testing/selftests/net/packetdrill/tcp_advmss_pmtu_ipv6.pkt b/tools/testing/selftests/net/packetdrill/tcp_advmss_pmtu_ipv6.pkt deleted file mode 100644 index c7638b11a815..000000000000 --- a/tools/testing/selftests/net/packetdrill/tcp_advmss_pmtu_ipv6.pkt +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -// -// Test that IPv6 advertised MSS in SYN-ACK is derived from the configured -// interface MTU (1520 -> MSS 1460), not the ICMPv6-learned Path MTU. - ---ip_version=ipv6 - -`./defaults.sh -ethtool -K tun0 tso off -` - -// -// Connection 1: Learn PMTU exception (MTU 1280 -> MSS 1220) -// - 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 - +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 - +0 bind(3, ..., ...) = 0 - +0 listen(3, 1) = 0 - - +0 < S 0:0(0) win 65535 - +0 > S. 0:0(0) ack 1 - +.1 < . 1:1(0) ack 1 win 257 - +0 accept(3, ..., ...) = 4 - -// Send a full 1460-byte segment - +0 write(4, ..., 1460) = 1460 - +0 > P. 1:1461(1460) ack 1 - -// ICMPv6 Packet Too Big arrives indicating next-hop MTU 1280 - +0 < icmp packet_too_big mtu 1280 [1:1461(1460)] - -// Local host retransmits using the learned MTU 1280 (MSS = 1280 - 40 - 20 = 1220) - +0 > . 1:1221(1220) ack 1 - +0 > P. 1221:1461(240) ack 1 - +0 < R 1:1(0) ack 1461 win 0 - -// Close connection 1 and listener - +0 close(4) = 0 - +0 close(3) = 0 - -// -// Connection 2: New connection from the same peer -// - +0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 - +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 - +0 bind(3, ..., ...) = 0 - +0 listen(3, 1) = 0 - - +0 < S 0:0(0) win 65535 - -// Verify: SYN-ACK MUST advertise configured MSS 1460, NOT the learned PMTU MSS 1220 - +0 > S. 0:0(0) ack 1 - +0 < . 1:1(0) ack 1 win 257 - +0 accept(3, ..., ...) = 4 - -// Verify: Outgoing transmit MSS is still constrained by the learned PMTU 1280 - +0 write(4, ..., 1460) = 1460 - +0 > . 1:1221(1220) ack 1 - +0 > P. 1221:1461(240) ack 1 - +0 < . 1:1(0) ack 1461 win 257 - -// Clean up - +0 close(4) = 0 - +0 > F. 1461:1461(0) ack 1 - +0 < F. 1:1(0) ack 1462 win 257 - +0 > . 1462:1462(0) ack 2 - +0 close(3) = 0 From 564313447e545eea13038f74ada9272ab129b3cb Mon Sep 17 00:00:00 2001 From: Yongjian Wang Date: Mon, 26 Oct 2026 15:23:09 +0800 Subject: [PATCH 083/137] Revert "net: advertise TCP MSS from the configured MTU, not the learned PMTU" This reverts commit 2640e6419594 ("net: advertise TCP MSS from the configured MTU, not the learned PMTU"). That commit stopped the learned PMTU from lowering the MSS we advertise in SYN and SYN-ACK. For symmetric paths it argued that "the peer runs its own PMTU discovery and usually already knows the real path MTU". That only holds if ICMP "fragmentation needed" can reach the peer. When the peer sits behind a firewall that drops inbound ICMP, its PMTU discovery is a black hole (RFC 2923, section 2.1), and the smaller MSS we advertised was the only thing keeping its full-size segments off the narrow hop. Inbound ICMP filtering is very common in enterprise and carrier networks, and it is pervasive in mainland China. We hit this in production after the commit was backported to 6.12.111: TCP connections that reach a server through a relay with a smaller-MTU hop complete the handshake, then stall as soon as the client sends full-size segments. Clients on networks that let ICMP through, and connections that bypass the relay, are unaffected, which made it hard to trace back to the kernel. Reproducer, network namespaces on a single host: cli --(1500)-- r1 --(1456)-- r2 --(1500)-- srv r1 drops ICMP frag-needed towards cli. srv has already learned PMTU 1456 towards cli, then cli uploads 1 MB to srv: kernel SYN-ACK MSS 1 MB upload 6.12.107 1416 OK 6.12.111 1460 stalls, times out 7.3-rc7 1460 stalls, times out 7.3-rc7 + this revert 1416 OK The asymmetric case the commit was written for does not need a new default: the advertised MSS can already be set per route with the advmss metric, which takes precedence over default_advmss(). With this revert applied, on an asymmetric path where srv learned PMTU 1456 towards cli while cli->srv is 1500 end to end: srv route to cli SYN-ACK MSS largest cli->srv segment default 1416 1404 advmss 1460 1460 1448 So DSR-style setups can opt in with "ip route ... advmss ", while hosts whose peers cannot complete PMTU discovery keep working by default. Fixes: 2640e6419594 ("net: advertise TCP MSS from the configured MTU, not the learned PMTU") Closes: https://lore.kernel.org/regressions/4F0FAEB6-A13B-47F3-BFF1-C706686C6F64@gmail.com/ Cc: stable@vger.kernel.org Signed-off-by: Yongjian Wang Reviewed-by: Eric Dumazet Reviewed-by: Jiayuan Chen Link: https://patch.msgid.link/20261026-advmss-revert-v1-2-272fa4b7ecb6@gmail.com Signed-off-by: Jakub Kicinski --- include/net/ip.h | 25 ------------------------- include/net/ip6_route.h | 37 ------------------------------------- net/ipv4/route.c | 4 ++-- net/ipv6/route.c | 2 +- 4 files changed, 3 insertions(+), 65 deletions(-) diff --git a/include/net/ip.h b/include/net/ip.h index 618b9806c10b..0acc4ba5e5b3 100644 --- a/include/net/ip.h +++ b/include/net/ip.h @@ -513,31 +513,6 @@ static inline unsigned int ip_dst_mtu_maybe_forward(const struct dst_entry *dst, return res; } -/* Configured/administrative MTU of a route, for advertising the TCP MSS. - * - * Unlike ip_dst_mtu_maybe_forward(), this deliberately ignores the - * ICMP-learned path MTU (rt->rt_pmtu). The advertised MSS bounds what the - * peer may send to us and must reflect our receive capability (the device or - * route-configured MTU), not a path MTU learned on the reverse (send) - * direction, which may not apply to the peer->us path and outlives the fnhe - * for the whole connection. See RFC 2923 section 2.3 and the comment above - * tcp_advertise_mss(). - */ -static inline unsigned int ip_dst_mtu_configured(const struct dst_entry *dst) -{ - unsigned int mtu, res; - - rcu_read_lock(); - mtu = dst_metric_raw(dst, RTAX_MTU); - if (!mtu) - mtu = READ_ONCE(dst_dev_rcu(dst)->mtu); - mtu = min_t(unsigned int, mtu, IP_MAX_MTU); - res = mtu - lwtunnel_headroom(dst->lwtstate, mtu); - rcu_read_unlock(); - - return res; -} - static inline unsigned int ip_skb_dst_mtu(struct sock *sk, const struct sk_buff *skb) { diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h index 5f47cef0be46..95f936be32c6 100644 --- a/include/net/ip6_route.h +++ b/include/net/ip6_route.h @@ -396,43 +396,6 @@ static inline unsigned int ip6_dst_mtu_maybe_forward(const struct dst_entry *dst return mtu - lwtunnel_headroom(dst->lwtstate, mtu); } -/* Configured/administrative MTU of a route, for advertising the TCP MSS. - * - * Unlike ip6_dst_mtu_maybe_forward(), this ignores any ICMPv6-learned path - * MTU (which is kept on the RTF_CACHE exception route) and returns the MTU of - * the underlying route (fib6_pmtu) or the egress device. The advertised MSS - * bounds what the peer may send to us and must reflect our receive - * capability, not a path MTU learned on the reverse (send) direction. See - * RFC 2923 section 2.3 and the comment above tcp_advertise_mss(). - */ -static inline unsigned int ip6_dst_mtu_configured(const struct dst_entry *dst) -{ - const struct rt6_info *rt = dst_rt6_info(dst); - const struct fib6_info *from; - struct inet6_dev *idev; - unsigned int mtu = 0; - - rcu_read_lock(); - /* IPv6 keeps the learned PMTU and the configured MTU in the same - * RTAX_MTU slot: the learned value sits on this (possibly RTF_CACHE) - * dst, the configured one on the underlying route. Reach the latter - * via ->from (fib6_pmtu), populated by ip6_route_info_create(). - */ - from = rcu_dereference(rt->from); - if (from) - mtu = from->fib6_pmtu; - if (!mtu) { - mtu = IPV6_MIN_MTU; - idev = __in6_dev_get(dst_dev_rcu(dst)); - if (idev) - mtu = max_t(unsigned int, mtu, READ_ONCE(idev->cnf.mtu6)); - } - rcu_read_unlock(); - - mtu = min_t(unsigned int, mtu, IP6_MAX_MTU); - return mtu - lwtunnel_headroom(dst->lwtstate, mtu); -} - u32 ip6_mtu_from_fib6(const struct fib6_result *res, const struct in6_addr *daddr, const struct in6_addr *saddr); diff --git a/net/ipv4/route.c b/net/ipv4/route.c index 595a3b7f1783..93b9f4189525 100644 --- a/net/ipv4/route.c +++ b/net/ipv4/route.c @@ -1365,8 +1365,8 @@ static unsigned int ipv4_default_advmss(const struct dst_entry *dst) rcu_read_lock(); net = dst_dev_net_rcu(dst); - advmss = max_t(unsigned int, ip_dst_mtu_configured(dst) - header_size, - net->ipv4.ip_rt_min_advmss); + advmss = max_t(unsigned int, ipv4_mtu(dst) - header_size, + net->ipv4.ip_rt_min_advmss); rcu_read_unlock(); return min(advmss, IPV4_MAX_PMTU - header_size); diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 94e5ef1c4f83..5760a357e6e7 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -3239,7 +3239,7 @@ void ip6_sk_redirect(struct sk_buff *skb, struct sock *sk) static unsigned int ip6_default_advmss(const struct dst_entry *dst) { - unsigned int mtu = ip6_dst_mtu_configured(dst); + unsigned int mtu = dst6_mtu(dst); struct net *net; mtu -= sizeof(struct ipv6hdr) + sizeof(struct tcphdr); From 1c1f51c3ee9ccf86462e36548291ed44fecd5355 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Thu, 22 Oct 2026 10:54:06 +0200 Subject: [PATCH 084/137] net: bcmgenet: recover from a failed resume A resume that fails in bcmgenet_init_dma() returns with the interface still IFF_UP and the datapath gone. The next ifdown runs the teardown a second time and napi_disable() waits for a NAPI_STATE_SCHED bit that nothing will clear: task:ip state:D usleep_range_state+0xec/0x158 napi_disable_locked+0x254/0x318 napi_disable+0x3c/0x68 bcmgenet_netif_stop+0x1c8/0x318 bcmgenet_close+0x24/0xd0 __dev_close_many+0x18c/0x310 RTNL is held across the wait, so everything else blocks behind it. The same resume gates the clock and detaches the device. Close releases the clock a second time and ifup fails with -ENODEV. The hardware filters stay armed, so the next WoL suspend wakes on any frame. That suspend sets CMD_RX_EN for magic packet detection, so the WoL resume reuses rings that are gone. A failed resume should leave a state the ordinary paths can handle. Clear rx_cbs and tx_cbs as they are freed so they answer whether a datapath exists, and leave the clock alone so close and suspend release it once. The WoL resume rebuilds instead, so stop the PHY first, phy_start() only takes PHY_READY or PHY_HALTED. Flagged in a sashiko review and reproduced on a CM4 by forcing the resume allocation to fail. Fixes: b6e978e50444 ("net: bcmgenet: add suspend/resume callbacks") Fixes: 254f3239dd07 ("net: bcmgenet: revise suspend/resume") # for the WoL parts Signed-off-by: Nicolai Buchwitz Reviewed-by: Simon Horman Link: https://patch.msgid.link/20261022-nb-genet-fini-dma-net-v3-1-6e9fc89c0156@tipi-net.de Signed-off-by: Jakub Kicinski --- .../net/ethernet/broadcom/genet/bcmgenet.c | 92 +++++++++++++------ 1 file changed, 62 insertions(+), 30 deletions(-) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index a6b7797558b0..6e425d950af2 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -3112,7 +3112,9 @@ static void bcmgenet_fini_dma(struct bcmgenet_priv *priv) bcmgenet_free_rx_buffers(priv); bcmgenet_destroy_rx_page_pools(priv); kfree(priv->rx_cbs); + priv->rx_cbs = NULL; kfree(priv->tx_cbs); + priv->tx_cbs = NULL; } /* init_edma: Initialize DMA control register */ @@ -3170,6 +3172,7 @@ static int bcmgenet_init_dma(struct bcmgenet_priv *priv, bool flush_rx) priv->tx_cbs = kzalloc_objs(struct enet_cb, priv->num_tx_bds); if (!priv->tx_cbs) { kfree(priv->rx_cbs); + priv->rx_cbs = NULL; return -ENOMEM; } @@ -3189,7 +3192,9 @@ static int bcmgenet_init_dma(struct bcmgenet_priv *priv, bool flush_rx) bcmgenet_free_rx_buffers(priv); bcmgenet_destroy_rx_page_pools(priv); kfree(priv->rx_cbs); + priv->rx_cbs = NULL; kfree(priv->tx_cbs); + priv->tx_cbs = NULL; return ret; } @@ -3498,7 +3503,9 @@ static int bcmgenet_close(struct net_device *dev) netif_dbg(priv, ifdown, dev, "bcmgenet_close\n"); - bcmgenet_netif_stop(dev, false); + /* A failed resume has torn the datapath down already */ + if (priv->tx_cbs) + bcmgenet_netif_stop(dev, false); /* Really kill the PHY state machine and disconnect from it */ phy_disconnect(dev->phydev); @@ -3506,6 +3513,9 @@ static int bcmgenet_close(struct net_device *dev) free_irq(priv->irq0, priv); free_irq(priv->irq1, priv); + /* A failed resume left the device detached, __dev_open() refuses that */ + netif_device_attach(dev); + if (priv->internal_phy) ret = bcmgenet_power_down(priv, GENET_POWER_PASSIVE); @@ -4263,7 +4273,11 @@ static int bcmgenet_resume(struct device *d) if (device_may_wakeup(d) && priv->wolopts) { reg = bcmgenet_umac_readl(priv, UMAC_CMD); - if (reg & CMD_RX_EN) { + + /* The WoL power down sets CMD_RX_EN itself, so the bit + * alone does not mean the rings survived + */ + if (priv->tx_cbs && (reg & CMD_RX_EN)) { /* Successfully exited WoL, just resume data flows */ list_for_each_entry(rule, &priv->rxnfc_list, list) if (rule->state == BCMGENET_RXNFC_STATE_ENABLED) @@ -4293,6 +4307,9 @@ static int bcmgenet_resume(struct device *d) bcmgenet_rdma_disable(priv); bcmgenet_intr_disable(priv); bcmgenet_fini_dma(priv); + if (phy_is_started(dev->phydev) || + dev->phydev->state == PHY_ERROR) + phy_stop(dev->phydev); enable_irq(priv->irq1); } @@ -4315,8 +4332,9 @@ static int bcmgenet_resume(struct device *d) /* Reinitialize TDMA and RDMA and SW housekeeping */ ret = bcmgenet_init_dma(priv, false); if (ret) { + /* Leave the block clocked, close and suspend tear it down */ netdev_err(dev, "failed to initialize DMA\n"); - goto out_clk_disable; + return ret; } if (!device_may_wakeup(d)) @@ -4327,24 +4345,57 @@ static int bcmgenet_resume(struct device *d) netif_device_attach(dev); return 0; +} -out_clk_disable: - if (priv->internal_phy) - bcmgenet_power_down(priv, GENET_POWER_PASSIVE); - clk_disable_unprepare(priv->clk); - return ret; +/* Arm the configured wake filters and drop the rest, then clear old + * matches so that only a new one wakes the system + */ +static void bcmgenet_wol_arm_filters(struct bcmgenet_priv *priv) +{ + struct bcmgenet_rxnfc_rule *rule; + u32 reg, hfb_enable = 0; + + if (priv->wolopts & WAKE_FILTER) + list_for_each_entry(rule, &priv->rxnfc_list, list) + if (rule->fs.ring_cookie == RX_CLS_FLOW_WAKE && + rule->state == BCMGENET_RXNFC_STATE_ENABLED) + hfb_enable |= 1 << rule->fs.location; + + reg = bcmgenet_hfb_reg_readl(priv, HFB_CTRL); + if (GENET_IS_V1(priv) || GENET_IS_V2(priv)) { + reg &= ~RBUF_HFB_FILTER_EN_MASK; + reg |= hfb_enable << (RBUF_HFB_FILTER_EN_SHIFT + 1); + } else { + bcmgenet_hfb_reg_writel(priv, hfb_enable << 1, + HFB_FLT_ENABLE_V3PLUS + 4); + } + if (!hfb_enable) + reg &= ~RBUF_HFB_EN; + bcmgenet_hfb_reg_writel(priv, reg, HFB_CTRL); + + bcmgenet_intrl2_0_writel(priv, 0xFFFFFFFF, INTRL2_CPU_MASK_SET); + bcmgenet_intrl2_0_writel(priv, 0xFFFFFFFF, INTRL2_CPU_CLEAR); } static int bcmgenet_suspend(struct device *d) { struct net_device *dev = dev_get_drvdata(d); struct bcmgenet_priv *priv = netdev_priv(dev); - struct bcmgenet_rxnfc_rule *rule; - u32 reg, hfb_enable = 0; if (!netif_running(dev)) return 0; + /* A failed resume has torn the datapath down already. Resume still + * enables irq1 on the WoL path, so it has to be disabled here. + */ + if (!priv->tx_cbs) { + if (device_may_wakeup(d) && priv->wolopts) { + bcmgenet_wol_arm_filters(priv); + disable_irq(priv->irq1); + } + return 0; + } + netif_device_detach(dev); if (device_may_wakeup(d) && priv->wolopts) { @@ -4353,26 +4404,7 @@ static int bcmgenet_suspend(struct device *d) netif_tx_disable(dev); /* Suspend non-wake Rx data flows */ - if (priv->wolopts & WAKE_FILTER) - list_for_each_entry(rule, &priv->rxnfc_list, list) - if (rule->fs.ring_cookie == RX_CLS_FLOW_WAKE && - rule->state == BCMGENET_RXNFC_STATE_ENABLED) - hfb_enable |= 1 << rule->fs.location; - reg = bcmgenet_hfb_reg_readl(priv, HFB_CTRL); - if (GENET_IS_V1(priv) || GENET_IS_V2(priv)) { - reg &= ~RBUF_HFB_FILTER_EN_MASK; - reg |= hfb_enable << (RBUF_HFB_FILTER_EN_SHIFT + 1); - } else { - bcmgenet_hfb_reg_writel(priv, hfb_enable << 1, - HFB_FLT_ENABLE_V3PLUS + 4); - } - if (!hfb_enable) - reg &= ~RBUF_HFB_EN; - bcmgenet_hfb_reg_writel(priv, reg, HFB_CTRL); - - /* Clear any old filter matches so only new matches wake */ - bcmgenet_intrl2_0_writel(priv, 0xFFFFFFFF, INTRL2_CPU_MASK_SET); - bcmgenet_intrl2_0_writel(priv, 0xFFFFFFFF, INTRL2_CPU_CLEAR); + bcmgenet_wol_arm_filters(priv); if (-ETIMEDOUT == bcmgenet_tdma_disable(priv)) netdev_warn(priv->dev, From ff8a7c0c06762a2a2333fbd6148d637bbd3d5090 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Thu, 22 Oct 2026 10:54:07 +0200 Subject: [PATCH 085/137] net: bcmgenet: restore the Tx coalescing setting when the rings are rebuilt bcmgenet_set_coalesce() writes tx_max_coalesced_frames straight into DMA_MBUF_DONE_THRESH and keeps no copy of it, while bcmgenet_init_tx_ring() programs a fixed 1. An ifdown and ifup or a resume then puts the Tx coalescing back to one frame per interrupt. Rx keeps its value per ring and reapplies it on init. Do the same for Tx. Fixes: 2f9130709d2c ("net: bcmgenet: Implement TX coalescing control knobs") Signed-off-by: Nicolai Buchwitz Reviewed-by: Simon Horman Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20261022-nb-genet-fini-dma-net-v3-2-6e9fc89c0156@tipi-net.de Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/genet/bcmgenet.c | 10 ++++++++-- drivers/net/ethernet/broadcom/genet/bcmgenet.h | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index 6e425d950af2..4d2be2a21ed1 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -931,10 +931,13 @@ static int bcmgenet_set_coalesce(struct net_device *dev, /* Program all TX queues with the same values, as there is no * ethtool knob to do coalescing on a per-queue basis */ - for (i = 0; i <= priv->hw_params->tx_queues; i++) + for (i = 0; i <= priv->hw_params->tx_queues; i++) { + priv->tx_rings[i].tx_max_coalesced_frames = + ec->tx_max_coalesced_frames; bcmgenet_tdma_ring_writel(priv, i, ec->tx_max_coalesced_frames, DMA_MBUF_DONE_THRESH); + } for (i = 0; i <= priv->hw_params->rx_queues; i++) bcmgenet_set_ring_rx_coalesce(&priv->rx_rings[i], ec); @@ -2753,7 +2756,8 @@ static void bcmgenet_init_tx_ring(struct bcmgenet_priv *priv, bcmgenet_tdma_ring_writel(priv, index, 0, TDMA_PROD_INDEX); bcmgenet_tdma_ring_writel(priv, index, 0, TDMA_CONS_INDEX); - bcmgenet_tdma_ring_writel(priv, index, 1, DMA_MBUF_DONE_THRESH); + bcmgenet_tdma_ring_writel(priv, index, ring->tx_max_coalesced_frames, + DMA_MBUF_DONE_THRESH); /* Disable rate control for now */ bcmgenet_tdma_ring_writel(priv, index, flow_period_val, TDMA_FLOW_PERIOD); @@ -4171,6 +4175,8 @@ static int bcmgenet_probe(struct platform_device *pdev) priv->rx_rings[i].index = i; priv->rx_rings[i].rx_max_coalesced_frames = 1; } + for (i = 0; i <= priv->hw_params->tx_queues; i++) + priv->tx_rings[i].tx_max_coalesced_frames = 1; /* Initialize u64 stats seq counter for 32bit machines */ for (i = 0; i <= GENET_MAX_MQ_CNT; i++) { diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.h b/drivers/net/ethernet/broadcom/genet/bcmgenet.h index 86f2aed20dbe..e48ce39d3e7c 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h @@ -553,6 +553,7 @@ struct bcmgenet_tx_ring { unsigned int prod_index; /* Tx ring producer index SW copy */ unsigned int cb_ptr; /* Tx ring initial CB ptr */ unsigned int end_ptr; /* Tx ring end CB ptr */ + unsigned int tx_max_coalesced_frames; struct bcmgenet_priv *priv; }; From ca87a2dffbb550fc934647d5e8d1c4ae85590132 Mon Sep 17 00:00:00 2001 From: Joas Antonio dos Santos Date: Sat, 24 Oct 2026 22:47:24 -0300 Subject: [PATCH 086/137] net: dcb: initialize PFC value before calling getpfccfg dcbnl_getpfccfg() and dcbnl_cee_fill() pass the address of an uninitialized stack u8 to the driver's getpfccfg() op and then put it into the netlink reply unconditionally. The op returns void, so the core cannot tell whether the driver wrote the value. i40e_dcbnl_get_pfc_cfg() and ice_dcbnl_get_pfc_cfg() return without writing *setting unless the DCBX agent is host managed and in CEE mode. Both drivers normally run with firmware managed DCBX (DCB_CAP_DCBX_LLD_MANAGED), and i40e with FW LLDP disabled runs host managed in IEEE mode, so on a DCB capable i40e/ice port the PFC values in DCB_CMD_PFC_GCFG and DCB_CMD_CEE_GET replies, and in the CEE part of RTNLGRP_DCB notifications, come from uninitialized stack. The same u8 is reused for all eight priorities, so this is one byte per reply. dcb_doit() only requires CAP_NET_ADMIN for RTM_SETDCB, so the GET commands are available to unprivileged users. Set the value to 0 before each call, like the PG config path already presets its outputs to DCB_ATTR_VALUE_UNDEFINED. 0 is also what the reply contains today on kernels built with CONFIG_INIT_STACK_ALL_ZERO, so nothing changes for those. Found by code inspection, compile-tested only (W=1). I don't have i40e/ice hardware to check the reply. Fixes: b94b013eb626 ("ice: Implement DCBNL support") Fixes: 5effa78e7c94 ("i40e: Add netlink callbacks support for software based DCB") Signed-off-by: Joas Antonio dos Santos Link: https://patch.msgid.link/179289284460.50470.572724041164324949@gmail.com Signed-off-by: Jakub Kicinski --- net/dcb/dcbnl.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/dcb/dcbnl.c b/net/dcb/dcbnl.c index ab71f5312876..ad0aebe0ad3d 100644 --- a/net/dcb/dcbnl.c +++ b/net/dcb/dcbnl.c @@ -281,6 +281,7 @@ static int dcbnl_getpfccfg(struct net_device *netdev, struct nlmsghdr *nlh, if (!getall && !data[i]) continue; + value = 0; netdev->dcbnl_ops->getpfccfg(netdev, i - DCB_PFC_UP_ATTR_0, &value); ret = nla_put_u8(skb, i, value); @@ -1395,6 +1396,7 @@ static int dcbnl_cee_fill(struct sk_buff *skb, struct net_device *netdev) goto nla_put_failure; for (i = DCB_PFC_UP_ATTR_0; i <= DCB_PFC_UP_ATTR_7; i++) { + value = 0; ops->getpfccfg(netdev, i - DCB_PFC_UP_ATTR_0, &value); if (nla_put_u8(skb, i, value)) goto nla_put_failure; From 7cceaba6ffbc34be198ab0ee2a669ca657d6a681 Mon Sep 17 00:00:00 2001 From: Daniel Palmer Date: Sun, 25 Oct 2026 11:45:14 +0900 Subject: [PATCH 087/137] net: i825xx: 82596: use MMIO accessors for the MVME16x PORT/CA registers On the MVME16x the two port registers need to be written in order to latch the correct 32bit value. The way the code is currently written means that modern GCC can decide to optimize the writes and break that latching. Convert the MVME16x specific register accesses to use the proper io accessors to restore the latching and make the driver functional again. The Patch was tested by me on real Hardware (Motorola MVME177). On the Board is a Intel i82596CA 10Mb Ethernet Chip Stepping C. Reported-by: Denis Ahrens Signed-off-by: Daniel Palmer Tested-by: Denis Ahrens Link: https://patch.msgid.link/20261025024514.802862-1-daniel@0x0f.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/i825xx/82596.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/drivers/net/ethernet/i825xx/82596.c b/drivers/net/ethernet/i825xx/82596.c index 773d7aa29ef5..bb8798b0d3ae 100644 --- a/drivers/net/ethernet/i825xx/82596.c +++ b/drivers/net/ethernet/i825xx/82596.c @@ -196,11 +196,10 @@ enum commands { #define TX_TIMEOUT (HZ/20) -struct i596_reg { - unsigned short porthi; - unsigned short portlo; - unsigned long ca; -}; +/* MVME16x: the 82596 PORT and CA registers */ +#define MVME_I596_PORT_HI 0x00 +#define MVME_I596_PORT_LO 0x02 +#define MVME_I596_CA 0x04 #define EOF 0x8000 #define SIZE_MASK 0x3fff @@ -375,7 +374,9 @@ static inline void CA(struct net_device *dev) { #ifdef ENABLE_MVME16x_NET if (MACH_IS_MVME16x) { - ((struct i596_reg *) dev->base_addr)->ca = 1; + void __iomem *regs = (void __iomem *)dev->base_addr; + + __raw_writel(1, regs + MVME_I596_CA); } #endif #ifdef ENABLE_BVME6000_NET @@ -392,9 +393,15 @@ static inline void MPU_PORT(struct net_device *dev, int c, volatile void *x) { #ifdef ENABLE_MVME16x_NET if (MACH_IS_MVME16x) { - struct i596_reg *p = (struct i596_reg *) (dev->base_addr); - p->porthi = ((c) | (u32) (x)) & 0xffff; - p->portlo = ((c) | (u32) (x)) >> 16; + void __iomem *regs = (void __iomem *)dev->base_addr; + u32 v = (u32)c | (u32)x; + + /* + * The 32bit value is latched on the write of the lo register, + * so the 16bit writes must happen in this order. + */ + __raw_writew(v & 0xffff, regs + MVME_I596_PORT_HI); + __raw_writew(v >> 16, regs + MVME_I596_PORT_LO); } #endif #ifdef ENABLE_BVME6000_NET From 14368b9aa1017fee35d2589be7b17e0485c0f93e Mon Sep 17 00:00:00 2001 From: Antonio Quartulli Date: Mon, 26 Oct 2026 16:31:12 +0100 Subject: [PATCH 088/137] net/sched: fix packet reordering on requeue dev_requeue_skb() appends requeued skbs to the tail of q->gso_skb, while dequeue_skb() takes them back one at a time from the head. If the driver returns NETDEV_TX_BUSY again for the first of them, it moves behind the skbs still parked there and is sent after them. This showed up as heavy TCP reordering while benchmarking over veth, which returns NETDEV_TX_BUSY whenever its ring is full since commit dc82a33297fc ("veth: apply qdisc backpressure on full ptr_ring to reduce TX drops"). Requeue the skbs at the head of gso_skb, keeping their order. With 4 TCP streams over a veth pair with a pfifo qdisc, this removes about 1.3M out-of-order segments and 15-37k retransmits per 15 seconds, and raises throughput from about 40 to 76 Gbit/s. Fixes: a53851e2c321 ("net: sched: explicit locking in gso_cpu fallback") Fixes: 9540d977618c ("net: sched: fix skb leak in dev_requeue_skb()") Cc: stable@vger.kernel.org Signed-off-by: Antonio Quartulli Reviewed-by: Eric Dumazet Acked-by: Jesper Dangaard Brouer Link: https://patch.msgid.link/20261026153113.3275494-1-antonio@mandelbit.com Signed-off-by: Jakub Kicinski --- net/sched/sch_generic.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c index bff425a14526..3e8ee4a691c3 100644 --- a/net/sched/sch_generic.c +++ b/net/sched/sch_generic.c @@ -168,8 +168,11 @@ static inline void qdisc_enqueue_skb_bad_txq(struct Qdisc *q, static inline void dev_requeue_skb(struct sk_buff *skb, struct Qdisc *q) { + struct sk_buff_head requeue; spinlock_t *lock = NULL; + __skb_queue_head_init(&requeue); + if (q->flags & TCQ_F_NOLOCK) { lock = qdisc_lock(q); spin_lock(lock); @@ -178,7 +181,7 @@ static inline void dev_requeue_skb(struct sk_buff *skb, struct Qdisc *q) while (skb) { struct sk_buff *next = skb->next; - __skb_queue_tail(&q->gso_skb, skb); + __skb_queue_tail(&requeue, skb); /* it's still part of the queue */ if (qdisc_is_percpu_stats(q)) { @@ -194,6 +197,11 @@ static inline void dev_requeue_skb(struct sk_buff *skb, struct Qdisc *q) skb = next; } + /* the skbs may come from the head of gso_skb, so put them back there: + * appending them would let the ones still parked overtake them + */ + skb_queue_splice(&requeue, &q->gso_skb); + if (lock) { spin_unlock(lock); set_bit(__QDISC_STATE_MISSED, &q->state); From 78ded647d43e335891f981f68c5992a2e763cfcc Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 26 Oct 2026 18:52:14 -0700 Subject: [PATCH 089/137] tls: Honor O_NONBLOCK in tls_sw_splice_read() tls_sw_splice_read() takes its blocking behavior from SPLICE_F_NONBLOCK alone. When splicing from a socket to a pipe, do_splice() sets that flag from the pipe's O_NONBLOCK, not the socket's. On a nonblocking socket, a splice(2) call without SPLICE_F_NONBLOCK therefore sleeps in tls_rx_rec_wait() until a record arrives. tcp_splice_read() instead reads sock->file->f_flags and returns -EAGAIN. Polling first does not avoid the sleep once tls_sw_splice_read() consumes zero-length records, as it does after a later patch in this series. tls_sw_sock_is_readable() reports the socket readable while any record sits on rx_list, including a zero-length record that delivers no bytes to the pipe. For TLS 1.3 the strparser announces a record before decryption, when its plaintext length is still unknown, so the readiness test cannot screen such a record out. A splice that consumes the record and waits for the next stalls every connection an event loop multiplexes. An LLM audit of the TLS read paths found this defect. Treat the socket's O_NONBLOCK as nonblocking too. A caller that sets O_NONBLOCK on the socket and splices without SPLICE_F_NONBLOCK now gets -EAGAIN where the splice blocked before. sendfile(2) from a TLS socket changes the same way, because do_sendfile() leaves the input file's O_NONBLOCK out of the splice flags. Both now behave as they do on a plain TCP socket. Fixes: c46234ebb4d1 ("tls: RX path for ktls") Signed-off-by: Chuck Lever Link: https://patch.msgid.link/20261027015215.605919-2-kuba@kernel.org Signed-off-by: Jakub Kicinski --- net/tls/tls_sw.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c index 312e51270f29..771e02bff519 100644 --- a/net/tls/tls_sw.c +++ b/net/tls/tls_sw.c @@ -2007,10 +2007,14 @@ ssize_t tls_sw_splice_read(struct socket *sock, loff_t *ppos, struct tls_msg *tlm; struct sk_buff *skb; ssize_t copied = 0; + bool nonblock; int chunk; int err; - err = tls_rx_reader_lock(sk, ctx, flags & SPLICE_F_NONBLOCK); + nonblock = flags & SPLICE_F_NONBLOCK || + sock->file->f_flags & O_NONBLOCK; + + err = tls_rx_reader_lock(sk, ctx, nonblock); if (err < 0) return err; @@ -2024,8 +2028,7 @@ ssize_t tls_sw_splice_read(struct socket *sock, loff_t *ppos, } else { struct tls_decrypt_arg darg; - err = tls_rx_rec_wait(sk, flags & SPLICE_F_NONBLOCK, - true, false); + err = tls_rx_rec_wait(sk, nonblock, true, false); if (err <= 0) goto splice_read_end; From 7c5d62eff7008612fd2ad681e743d1f4adf2459e Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 26 Oct 2026 18:52:15 -0700 Subject: [PATCH 090/137] selftests: tls: Cover splice on a nonblocking socket Every existing splice call in this file either passes SPLICE_F_NONBLOCK or runs on a blocking socket, so nothing exercises the socket's own O_NONBLOCK on the splice path. A change that stops consulting sock->file->f_flags puts splice(2) and sendfile(2) back to sleeping in tls_rx_rec_wait(), and the suite still reports pass. Splice from a nonblocking socket without SPLICE_F_NONBLOCK, first with no record queued, which must return EAGAIN, then with a record to deliver. A kernel that ignores the socket flag sleeps until the harness timeout rather than returning. Signed-off-by: Chuck Lever Link: https://patch.msgid.link/20261027015215.605919-3-kuba@kernel.org Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/tls.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tools/testing/selftests/net/tls.c b/tools/testing/selftests/net/tls.c index 9d3cd4fff062..c4203401c4bf 100644 --- a/tools/testing/selftests/net/tls.c +++ b/tools/testing/selftests/net/tls.c @@ -889,6 +889,37 @@ TEST_F(tls, splice_to_pipe) EXPECT_EQ(memcmp(mem_send, mem_recv, send_len), 0); } +/* The socket's O_NONBLOCK governs the record wait, as on a plain TCP + * socket, even without SPLICE_F_NONBLOCK. + */ +TEST_F(tls, splice_nonblock_socket) +{ + struct pollfd pfd = { .fd = self->cfd, .events = POLLIN }; + int send_len = TLS_PAYLOAD_MAX_LEN; + char mem_send[TLS_PAYLOAD_MAX_LEN]; + char mem_recv[TLS_PAYLOAD_MAX_LEN]; + int flags, p[2]; + + flags = fcntl(self->cfd, F_GETFL, 0); + ASSERT_GE(flags, 0); + ASSERT_EQ(fcntl(self->cfd, F_SETFL, flags | O_NONBLOCK), 0); + + memrnd(mem_send, sizeof(mem_send)); + + ASSERT_GE(pipe(p), 0); + EXPECT_EQ(splice(self->cfd, NULL, p[1], NULL, send_len, 0), -1); + EXPECT_EQ(errno, EAGAIN); + + /* The record may still be in flight, wait for it. A failed splice() + * would leave the read() below blocking until the harness timeout. + */ + EXPECT_EQ(send(self->fd, mem_send, send_len, 0), send_len); + ASSERT_EQ(poll(&pfd, 1, 5000), 1); + ASSERT_EQ(splice(self->cfd, NULL, p[1], NULL, send_len, 0), send_len); + EXPECT_EQ(read(p[0], mem_recv, send_len), send_len); + EXPECT_EQ(memcmp(mem_send, mem_recv, send_len), 0); +} + TEST_F(tls, splice_cmsg_to_pipe) { char *test_str = "test_read"; From 4c6fbb1fbafa85029fa789b5b01f39ebcb459d1a Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Tue, 27 Oct 2026 06:52:45 +0000 Subject: [PATCH 091/137] ipv6: Protect multiple routes insertion by table lock. syzbot reported the warning below. [0] While call_fib6_multipath_entry_notifiers() is called locklessly, fib6_siblings could be changed concurrently, leading to the warning (and later null-ptr-deref). Let's protect multiple routes insertion with tb6_lock. Along with that, __ip6_ins_rt() and ip6_route_del() are changed to fib6_add() and fib6_del(). Note that fib6_del() finally calls rt6_multipath_rebalance(), which requires rcu_read_lock(). [0]: i != fen6_info->nsiblings WARNING: drivers/net/netdevsim/fib.c:831 at nsim_fib6_event_init drivers/net/netdevsim/fib.c:831 [inline], CPU#0: syz.0.157/6279 WARNING: drivers/net/netdevsim/fib.c:831 at nsim_fib6_prepare_event drivers/net/netdevsim/fib.c:947 [inline], CPU#0: syz.0.157/6279 WARNING: drivers/net/netdevsim/fib.c:831 at nsim_fib_event_schedule_work drivers/net/netdevsim/fib.c:1003 [inline], CPU#0: syz.0.157/6279 WARNING: drivers/net/netdevsim/fib.c:831 at nsim_fib_event_nb+0x1029/0x11d0 drivers/net/netdevsim/fib.c:1043, CPU#0: syz.0.157/6279 Modules linked in: CPU: 0 UID: 0 PID: 6279 Comm: syz.0.157 Not tainted syzkaller #0 PREEMPT(full) Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 07/24/2026 RIP: 0010:nsim_fib6_event_init drivers/net/netdevsim/fib.c:831 [inline] RIP: 0010:nsim_fib6_prepare_event drivers/net/netdevsim/fib.c:947 [inline] RIP: 0010:nsim_fib_event_schedule_work drivers/net/netdevsim/fib.c:1003 [inline] RIP: 0010:nsim_fib_event_nb+0x1029/0x11d0 drivers/net/netdevsim/fib.c:1043 Code: eb 0d 4d 89 fd e8 b7 c5 6c fa be 01 00 00 00 48 89 df e8 5a 38 85 fd 4d 89 ef 4c 8b 6c 24 20 e9 4a f3 ff ff e8 98 c5 6c fa 90 <0f> 0b 90 e9 db fa ff ff 89 e9 80 e1 07 80 c1 03 38 c1 0f 8c df f0 RSP: 0018:ffffc900047bef10 EFLAGS: 00010293 RAX: ffffffff875b59a8 RBX: 0000000000000002 RCX: ffff888079770000 RDX: 0000000000000000 RSI: 0000000000000002 RDI: 0000000000000001 RBP: ffffc900047bf0a0 R08: ffff88805c0bb02f R09: 1ffff1100b817605 R10: dffffc0000000000 R11: ffffed100b817606 R12: dffffc0000000000 R13: 0000000000000000 R14: 0000000000000001 R15: ffff88802c792000 FS: 00007f334ba076c0(0000) GS:ffff888123ad0000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007f334ba06ff8 CR3: 000000007a5b2000 CR4: 00000000003526f0 Call Trace: notifier_call_chain+0x1a5/0x3d0 kernel/notifier.c:85 atomic_notifier_call_chain+0xda/0x180 kernel/notifier.c:223 call_fib_notifiers+0x31/0x60 net/core/fib_notifier.c:36 call_fib6_multipath_entry_notifiers+0xf5/0x160 net/ipv6/ip6_fib.c:428 ip6_route_multipath_add net/ipv6/route.c:5542 [inline] inet6_rtm_newroute+0x14f5/0x1a30 net/ipv6/route.c:5664 rtnetlink_rcv_msg+0x802/0xc00 net/core/rtnetlink.c:7163 netlink_rcv_skb+0x226/0x4a0 net/netlink/af_netlink.c:2572 netlink_unicast_kernel net/netlink/af_netlink.c:1335 [inline] netlink_unicast+0x7bd/0x940 net/netlink/af_netlink.c:1361 netlink_sendmsg+0x813/0xb40 net/netlink/af_netlink.c:1916 sock_sendmsg_nosec+0x14e/0x190 net/socket.c:800 __sock_sendmsg net/socket.c:818 [inline] ____sys_sendmsg+0x54e/0x850 net/socket.c:2715 ___sys_sendmsg+0x2a5/0x360 net/socket.c:2769 __sys_sendmmsg+0x273/0x4d0 net/socket.c:2858 __do_sys_sendmmsg net/socket.c:2885 [inline] __se_sys_sendmmsg net/socket.c:2882 [inline] __x64_sys_sendmmsg+0xa0/0xc0 net/socket.c:2882 do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline] do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7f334ab9e1d9 Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48 RSP: 002b:00007f334ba07028 EFLAGS: 00000246 ORIG_RAX: 0000000000000133 RAX: ffffffffffffffda RBX: 00007f334ae26090 RCX: 00007f334ab9e1d9 RDX: 04000000000001f2 RSI: 0000200000000000 RDI: 0000000000000004 RBP: 00007f334ac35888 R08: 0000000000000000 R09: 0000000000000000 R10: 00000000000000fc R11: 0000000000000246 R12: 0000000000000000 R13: 00007f334ae26128 R14: 00007f334ae26090 R15: 00007ffd96688aa8 Fixes: 169fd62799e8 ("ipv6: Get rid of RTNL for SIOCADDRT and RTM_NEWROUTE.") Reported-by: syzbot+751b587577d0ebca10f2@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/6adbaed6.67317f00.38c4bc.001b.GAE@google.com/T/ Signed-off-by: Kuniyuki Iwashima Reviewed-by: Eric Dumazet Reviewed-by: David Ahern Link: https://patch.msgid.link/20261027065247.2871134-1-kuniyu@google.com Signed-off-by: Jakub Kicinski --- net/ipv6/ip6_fib.c | 4 ---- net/ipv6/route.c | 17 +++++++++++++---- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c index 0dc6ebee6e56..f7c69c9df411 100644 --- a/net/ipv6/ip6_fib.c +++ b/net/ipv6/ip6_fib.c @@ -1228,9 +1228,7 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct fib6_info *rt, fib6_nsiblings++; } BUG_ON(fib6_nsiblings != rt->fib6_nsiblings); - rcu_read_lock(); rt6_multipath_rebalance(temp_sibling); - rcu_read_unlock(); } /* @@ -1274,9 +1272,7 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct fib6_info *rt, sibling->fib6_nsiblings - 1); WRITE_ONCE(rt->fib6_nsiblings, 0); list_del_rcu(&rt->fib6_siblings); - rcu_read_lock(); rt6_multipath_rebalance(next_sibling); - rcu_read_unlock(); return err; } } diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 5760a357e6e7..616b86f89f63 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -4875,8 +4875,10 @@ void rt6_multipath_rebalance(struct fib6_info *rt) if (WARN_ON_ONCE(!first)) return; + rcu_read_lock(); total = rt6_multipath_total_weight(first); rt6_multipath_upper_bound_set(first, total); + rcu_read_unlock(); } static int fib6_ifup(struct fib6_info *rt, void *p_arg) @@ -5416,6 +5418,7 @@ static int ip6_route_multipath_add(struct fib6_config *cfg, struct fib6_info *rt_notif = NULL, *rt_last = NULL; struct nl_info *info = &cfg->fc_nlinfo; struct rt6_nh *nh, *nh_safe; + struct fib6_table *table; struct fib6_config r_cfg; struct rtnexthop *rtnh; LIST_HEAD(rt6_nh_list); @@ -5502,10 +5505,12 @@ static int ip6_route_multipath_add(struct fib6_config *cfg, */ info->skip_notify_kernel = 1; + table = rt->fib6_table; + spin_lock_bh(&table->tb6_lock); + err_nh = NULL; list_for_each_entry(nh, &rt6_nh_list, list) { - err = __ip6_ins_rt(nh->fib6_info, info, extack); - + err = fib6_add(&table->tb6_root, nh->fib6_info, info, extack); if (err) { if (replace && nhn) NL_SET_ERR_MSG_MOD(extack, @@ -5560,11 +5565,13 @@ static int ip6_route_multipath_add(struct fib6_config *cfg, /* success ... tell user about new route */ ip6_route_mpath_notify(rt_notif, rt_last, info, nlflags); + + spin_unlock_bh(&table->tb6_lock); goto cleanup; add_errout: /* send notification for routes that were added so that - * the delete notifications sent by ip6_route_del are + * the delete notifications sent by fib6_del() are * coherent */ if (rt_notif) @@ -5574,9 +5581,11 @@ static int ip6_route_multipath_add(struct fib6_config *cfg, list_for_each_entry(nh, &rt6_nh_list, list) { if (err_nh == nh) break; - ip6_route_del(&nh->r_cfg, extack); + fib6_del(nh->fib6_info, &nh->r_cfg.fc_nlinfo, + RT_DEL_REASON_UNSPEC); } + spin_unlock_bh(&table->tb6_lock); cleanup: list_for_each_entry_safe(nh, nh_safe, &rt6_nh_list, list) { fib6_info_release(nh->fib6_info); From 8146be04908d3efbe3c6e130f551edbfd7a9dab8 Mon Sep 17 00:00:00 2001 From: James Lee Date: Mon, 26 Oct 2026 11:32:32 +0800 Subject: [PATCH 092/137] net: mctp: usblib: Fix broken assertion The expression in a static assertion in the kunit tests was not considered constant by gcc, causing an error. Replace the static assert with a runtime kunit equivalent. Fixes: 1e12f734afc1 ("net: mctp: usblib: Add initial kunit tx tests") Reported-by: Geert Uytterhoeven Closes: https://lore.kernel.org/netdev/CAMuHMdXTGGdGWZ9sKz5_sQF1237gV844cEhkXr6v_TUHPOb78Q@mail.gmail.com/ Signed-off-by: James Lee Link: https://patch.msgid.link/20261026-dev-mctp-static-assert-fix-v1-1-ceef9913e891@codeconstruct.com.au Signed-off-by: Jakub Kicinski --- drivers/net/mctp/mctp-usblib-test.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/mctp/mctp-usblib-test.c b/drivers/net/mctp/mctp-usblib-test.c index b7e8743c561f..3a48b10164b0 100644 --- a/drivers/net/mctp/mctp-usblib-test.c +++ b/drivers/net/mctp/mctp-usblib-test.c @@ -816,8 +816,8 @@ static void mctp_usblib_test_tx_multi_push(struct kunit *test) }; int rc; - static_assert(!sends[ARRAY_SIZE(sends) - 1].more, - "The last push must claim there will be no more"); + KUNIT_ASSERT_TRUE_MSG(test, !sends[ARRAY_SIZE(sends) - 1].more, + "The last push must claim there will be no more"); max_length = 0; for (i = 0; i < ARRAY_SIZE(sends); i++) { From 49d8bbc21244f0c2d2ebf5ee03fb7352ff019ecc Mon Sep 17 00:00:00 2001 From: Coia Prant Date: Fri, 23 Oct 2026 15:52:12 +0800 Subject: [PATCH 093/137] net: pcs: xpcs-plat: fix runtime PM initialization The driver calls pm_runtime_set_active() before runtime PM is enabled and before the CSR clock is prepared and enabled. If the device is later suspended (e.g. during unbind), the runtime suspend callback tries to disable a clock that was never enabled, producing: clk_csr already disabled clk_csr already unprepared Set the initial runtime PM status to suspended instead, and register a devm action that force-suspends the device on teardown. Without that action the CSR clock would stay enabled if the driver is removed while runtime-active, since nothing triggers a runtime suspend on unbind. pm_runtime_force_suspend() is not checked because the runtime suspend callback cannot fail. The clock is enabled on the first runtime resume (e.g. during MDIO access). No upstream platform currently enables this driver with a real CSR clock dependency, so the issue is not reachable in mainline today. It was found while using this driver as a reference for the Rockchip XPCS glue driver. Signed-off-by: Coia Prant Link: https://patch.msgid.link/20261023075214.1793922-3-coiaprant@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/pcs/pcs-xpcs-plat.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/drivers/net/pcs/pcs-xpcs-plat.c b/drivers/net/pcs/pcs-xpcs-plat.c index e669a7b248ac..11c4239bfd74 100644 --- a/drivers/net/pcs/pcs-xpcs-plat.c +++ b/drivers/net/pcs/pcs-xpcs-plat.c @@ -275,6 +275,14 @@ static int xpcs_plat_init_res(struct dw_xpcs_plat *pxpcs) return 0; } +static void xpcs_plat_exit_clk(void *data) +{ + struct dw_xpcs_plat *pxpcs = data; + struct device *dev = &pxpcs->pdev->dev; + + pm_runtime_force_suspend(dev); +} + static int xpcs_plat_init_clk(struct dw_xpcs_plat *pxpcs) { struct device *dev = &pxpcs->pdev->dev; @@ -285,10 +293,12 @@ static int xpcs_plat_init_clk(struct dw_xpcs_plat *pxpcs) return dev_err_probe(dev, PTR_ERR(pxpcs->cclk), "Failed to get CSR clock\n"); - pm_runtime_set_active(dev); - ret = devm_pm_runtime_enable(dev); + pm_runtime_set_suspended(dev); + pm_runtime_enable(dev); + + ret = devm_add_action_or_reset(dev, xpcs_plat_exit_clk, pxpcs); if (ret) { - dev_err(dev, "Failed to enable runtime-PM\n"); + dev_err(dev, "Failed to register devm for xpcs_plat_exit_clk: %d\n", ret); return ret; } From eea365ed283cb2a7418b277fef87b462418e2283 Mon Sep 17 00:00:00 2001 From: Coia Prant Date: Fri, 23 Oct 2026 15:52:13 +0800 Subject: [PATCH 094/137] net: pcs: xpcs-plat: fix clock lifecycle for !CONFIG_PM builds With CONFIG_PM disabled, pm_runtime_enable() and the runtime resume path are compiled out, so the CSR clock is never prepared/enabled. On platforms that provide a real "csr" clock this makes register access fail. Handle this by preparing/enabling the CSR clock manually in the !PM case and disabling it in the devm cleanup action. The CONFIG_PM path is left unchanged and still relies on runtime PM to gate the clock. Use RUNTIME_PM_OPS() and pm_ptr() for the PM ops as well, so that in !CONFIG_PM builds the runtime callbacks are still referenced and .pm is NULL rather than pointing at an empty ops structure. This also drops the __maybe_unused annotations from the callbacks, which were only needed because SET_RUNTIME_PM_OPS() expands to nothing when CONFIG_PM is disabled. Both changes are no-ops when CONFIG_PM is enabled. The CSR clock is optional (devm_clk_get_optional()), so platforms without a clock keep working in both cases, and clk_prepare_enable() on a NULL clock is a no-op. Because the !PM case is handled in the driver, there is no need to select or depend on PM in Kconfig for all platforms; keep the current Kconfig as-is to preserve compile coverage. Signed-off-by: Coia Prant Link: https://patch.msgid.link/20261023075214.1793922-4-coiaprant@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/pcs/pcs-xpcs-plat.c | 46 ++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/drivers/net/pcs/pcs-xpcs-plat.c b/drivers/net/pcs/pcs-xpcs-plat.c index 11c4239bfd74..c4404500bac6 100644 --- a/drivers/net/pcs/pcs-xpcs-plat.c +++ b/drivers/net/pcs/pcs-xpcs-plat.c @@ -275,6 +275,7 @@ static int xpcs_plat_init_res(struct dw_xpcs_plat *pxpcs) return 0; } +#ifdef CONFIG_PM static void xpcs_plat_exit_clk(void *data) { struct dw_xpcs_plat *pxpcs = data; @@ -304,6 +305,39 @@ static int xpcs_plat_init_clk(struct dw_xpcs_plat *pxpcs) return 0; } +#else +static void xpcs_plat_exit_clk(void *data) +{ + struct dw_xpcs_plat *pxpcs = data; + + clk_disable_unprepare(pxpcs->cclk); +} + +static int xpcs_plat_init_clk(struct dw_xpcs_plat *pxpcs) +{ + struct device *dev = &pxpcs->pdev->dev; + int ret; + + pxpcs->cclk = devm_clk_get_optional(dev, "csr"); + if (IS_ERR(pxpcs->cclk)) + return dev_err_probe(dev, PTR_ERR(pxpcs->cclk), + "Failed to get CSR clock\n"); + + ret = clk_prepare_enable(pxpcs->cclk); + if (ret) { + dev_err(dev, "Failed to enable CSR clock: %d\n", ret); + return ret; + } + + ret = devm_add_action_or_reset(dev, xpcs_plat_exit_clk, pxpcs); + if (ret) { + dev_err(dev, "Failed to register devm for xpcs_plat_exit_clk: %d\n", ret); + return ret; + } + + return 0; +} +#endif static int xpcs_plat_init_bus(struct dw_xpcs_plat *pxpcs) { @@ -408,7 +442,7 @@ static int xpcs_plat_probe(struct platform_device *pdev) return 0; } -static int __maybe_unused xpcs_plat_pm_runtime_suspend(struct device *dev) +static int xpcs_plat_pm_runtime_suspend(struct device *dev) { struct dw_xpcs_plat *pxpcs = dev_get_drvdata(dev); @@ -417,7 +451,7 @@ static int __maybe_unused xpcs_plat_pm_runtime_suspend(struct device *dev) return 0; } -static int __maybe_unused xpcs_plat_pm_runtime_resume(struct device *dev) +static int xpcs_plat_pm_runtime_resume(struct device *dev) { struct dw_xpcs_plat *pxpcs = dev_get_drvdata(dev); @@ -425,9 +459,9 @@ static int __maybe_unused xpcs_plat_pm_runtime_resume(struct device *dev) } static const struct dev_pm_ops xpcs_plat_pm_ops = { - SET_RUNTIME_PM_OPS(xpcs_plat_pm_runtime_suspend, - xpcs_plat_pm_runtime_resume, - NULL) + RUNTIME_PM_OPS(xpcs_plat_pm_runtime_suspend, + xpcs_plat_pm_runtime_resume, + NULL) }; DW_XPCS_INFO_DECLARE(xpcs_generic, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_ID_NATIVE); @@ -456,7 +490,7 @@ static struct platform_driver xpcs_plat_driver = { .probe = xpcs_plat_probe, .driver = { .name = "dwxpcs", - .pm = &xpcs_plat_pm_ops, + .pm = pm_ptr(&xpcs_plat_pm_ops), .of_match_table = xpcs_of_ids, }, }; From e7cccfde61367452e9a43257a57eef0a95e7a6bf Mon Sep 17 00:00:00 2001 From: Coia Prant Date: Fri, 23 Oct 2026 15:52:14 +0800 Subject: [PATCH 095/137] net: pcs: xpcs-rk: fix cleanup action error message The devm action registered by xpcs_rk_init_clk() force-suspends the device and disables the EEE clock, but the current error message only mentions the EEE clock. Point at the cleanup action by name instead, so the message covers both parts of what the action does. Signed-off-by: Coia Prant Link: https://patch.msgid.link/20261023075214.1793922-5-coiaprant@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/pcs/pcs-xpcs-rk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/pcs/pcs-xpcs-rk.c b/drivers/net/pcs/pcs-xpcs-rk.c index 698a43b75334..6f0a110e32d2 100644 --- a/drivers/net/pcs/pcs-xpcs-rk.c +++ b/drivers/net/pcs/pcs-xpcs-rk.c @@ -365,7 +365,7 @@ static int xpcs_rk_init_clk(struct dw_xpcs_rk *pxpcs) ret = devm_add_action_or_reset(dev, xpcs_rk_exit_clk, pxpcs); if (ret) { - dev_err(dev, "Failed to register devm for EEE clock: %d\n", ret); + dev_err(dev, "Failed to register devm for xpcs_rk_exit_clk: %d\n", ret); return ret; } From 87b88b658b9b983b119f59396c08e4df1a211d59 Mon Sep 17 00:00:00 2001 From: Coia Prant Date: Fri, 23 Oct 2026 15:52:15 +0800 Subject: [PATCH 096/137] MAINTAINERS: list DW XPCS platform driver and binding under XPCS entry The Synopsys DW XPCS entry covers pcs-xpcs.c, pcs-xpcs.h and the public header, but not pcs-xpcs-plat.c or its DT binding. pcs-xpcs-plat.c only matches the ETHERNET PHY LIBRARY entry today, and the binding is only matched by the generic DT and networking rules. Add both files so the entry covers the whole driver. Signed-off-by: Coia Prant Link: https://patch.msgid.link/20261023075214.1793922-6-coiaprant@gmail.com Signed-off-by: Jakub Kicinski --- MAINTAINERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 7336600952c8..4f24656f7653 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -26729,6 +26729,8 @@ F: drivers/net/ethernet/synopsys/ SYNOPSYS DESIGNWARE ETHERNET XPCS DRIVER L: netdev@vger.kernel.org S: Orphan +F: Documentation/devicetree/bindings/net/pcs/snps,dw-xpcs.yaml +F: drivers/net/pcs/pcs-xpcs-plat.c F: drivers/net/pcs/pcs-xpcs.c F: drivers/net/pcs/pcs-xpcs.h F: include/linux/pcs/pcs-xpcs.h From 0de4e69cd021c99d0aa13e1f30aafd13ad74a2a5 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 26 Oct 2026 16:59:27 +0100 Subject: [PATCH 097/137] ppp: linearize and segment bridged packets when required PPP channel bridging hands packets received on one channel directly to the start_xmit() of another. Packets received by PPPoE can be non-linear, GSO since commit 55a5d8fca836 ("net: pppoe: implement GRO/GSO support"), or CHECKSUM_PARTIAL when injected with a virtio_net_hdr. The regular transmit path never sees such packets, and only PPPoE, which uses dev_queue_xmit(), handles them: - PPP Async reads the whole packet through skb->data, transmitting unrelated memory for non-linear packets. - PPP Async and Sync send GSO packets as single frames of up to 64KB, and CHECKSUM_PARTIAL packets with a bad L4 checksum. - PPTP and L2TP cannot segment a TCP GSO packet once their GRE or UDP header is pushed, and do not finish pending checksums. Add a xmit_offload flag to struct ppp_channel, set for PPPoE. For other channels, segment GSO packets in software, using the PPP protocol field as the L2 header, finish CHECKSUM_PARTIAL checksums (including SCTP CRC32c), and linearize packets for channels without direct_xmit, as ppp_push() does. Packets are dropped if any step fails, or if the channel is busy, as before. skb->dev is NULL at this point for PPPoE (cleared by __sk_receive_skb()), and tunnel and IPv6 UFO GSO handlers dereference it: only TCP, UDP L4 and fraglist GSO packets without encapsulation are segmented, others are dropped. Receive paths like L2TP pull headers without updating the checksum state, possibly leaving a CHECKSUM_PARTIAL state with csum_start before skb->data. Reset it to CHECKSUM_NONE, as skb_postpull_rcsum() does, so that skb_checksum_help() does not warn, and so that PPPoE does not offload a checksum at a wrong offset. Tested in a VM by bridging PPPoE (on a TAP with IFF_NAPI_FRAGS, or on veth) to PPP Async on a PTY, PPPoL2TP and PPPoE: non-linear packets no longer leak garbage, GSO packets (including fraglist GRO) are sent as valid segments for IPv4 and IPv6, CHECKSUM_PARTIAL packets get a valid checksum, and PPPoE still receives GSO packets unchanged. Fixes: 4cf476ced45d ("ppp: add PPPIOCBRIDGECHAN and PPPIOCUNBRIDGECHAN ioctls") Fixes: 55a5d8fca836 ("net: pppoe: implement GRO/GSO support") Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260908072135.877364-1-qingfang.deng@linux.dev Co-developed-by: Qingfang Deng Signed-off-by: Qingfang Deng Signed-off-by: Eric Dumazet Cc: Felix Fietkau Cc: Pablo Neira Ayuso Reviewed-by: Simon Horman Link: https://patch.msgid.link/20261026155927.707655-1-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ppp/ppp_generic.c | 95 ++++++++++++++++++++++++++++++++++- drivers/net/ppp/pppoe.c | 1 + include/linux/ppp_channel.h | 1 + 3 files changed, 95 insertions(+), 2 deletions(-) diff --git a/drivers/net/ppp/ppp_generic.c b/drivers/net/ppp/ppp_generic.c index 3998ce4332f4..29847dc4bb45 100644 --- a/drivers/net/ppp/ppp_generic.c +++ b/drivers/net/ppp/ppp_generic.c @@ -45,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -2278,6 +2279,73 @@ static bool ppp_decompress_proto(struct sk_buff *skb) return pskb_may_pull(skb, 2); } +#define PPP_BRIDGE_GSO_TYPES (SKB_GSO_TCPV4 | SKB_GSO_TCPV6 | \ + SKB_GSO_TCP_ECN | SKB_GSO_TCP_ACCECN | \ + SKB_GSO_TCP_FIXEDID | SKB_GSO_UDP_L4 | \ + SKB_GSO_FRAGLIST | SKB_GSO_DODGY) + +/* Segment a GSO packet (built by PPPoE GRO, or injected with a + * virtio_net_hdr) before sending it to a bridged channel that does + * not accept GSO packets. + * skb->data points to the PPP protocol field, which is replicated + * in front of each segment. + * Returns a list of segments, or NULL if the packet was dropped. + */ +static struct sk_buff *ppp_bridge_gso_segment(struct sk_buff *skb, + netdev_features_t features) +{ + struct sk_buff *segs; + + /* skb->dev is NULL here (cleared by __sk_receive_skb() for PPPoE), + * and some GSO handlers (tunnels, IPv6 UFO) dereference it. + */ + if (skb->encapsulation || + (skb_shinfo(skb)->gso_type & ~PPP_BRIDGE_GSO_TYPES)) + goto drop; + + if (!pskb_may_pull(skb, PPP_PROTO_LEN)) + goto drop; + + switch (get_unaligned((__be16 *)skb->data)) { + case htons(PPP_IP): + skb->protocol = htons(ETH_P_IP); + break; + case htons(PPP_IPV6): + skb->protocol = htons(ETH_P_IPV6); + break; + default: + goto drop; + } + skb_set_network_header(skb, PPP_PROTO_LEN); + + /* No checksum offload: skb_segment() computes the checksums. */ + segs = skb_gso_segment(skb, features); + if (IS_ERR_OR_NULL(segs)) + goto drop; + + consume_skb(skb); + return segs; + +drop: + kfree_skb(skb); + return NULL; +} + +static void ppp_bridge_xmit(struct ppp_channel *chan, struct sk_buff *skb) +{ + if (!chan->direct_xmit && skb_linearize(skb)) + goto drop; + + if (!chan->xmit_offload && skb->ip_summed == CHECKSUM_PARTIAL && + skb_csum_hwoffload_help(skb, 0)) + goto drop; + + if (chan->ops->start_xmit(chan, skb)) + return; +drop: + kfree_skb(skb); +} + /* Attempt to handle a frame via. a bridged channel, if one exists. * If the channel is bridged, the frame is consumed by the bridge. * If not, the caller must handle the frame by normal recv mechanisms. @@ -2285,6 +2353,8 @@ static bool ppp_decompress_proto(struct sk_buff *skb) */ static bool ppp_channel_bridge_input(struct channel *pch, struct sk_buff *skb) { + struct sk_buff *segs, *next; + struct ppp_channel *chan; struct channel *pchb; rcu_read_lock(); @@ -2300,8 +2370,29 @@ static bool ppp_channel_bridge_input(struct channel *pch, struct sk_buff *skb) } skb_scrub_packet(skb, !net_eq(pch->chan_net, pchb->chan_net)); - if (!pchb->chan->ops->start_xmit(pchb->chan, skb)) - kfree_skb(skb); + + /* Receive paths such as L2TP pull headers without updating + * the checksum state: drop a stale CHECKSUM_PARTIAL state + * pointing before skb->data, as skb_postpull_rcsum() does. + */ + if (skb->ip_summed == CHECKSUM_PARTIAL && + skb_checksum_start_offset(skb) < 0) + skb->ip_summed = CHECKSUM_NONE; + + chan = pchb->chan; + if (!chan->xmit_offload && skb_is_gso(skb)) { + /* Channels needing linear packets get linear segments, + * others keep references to the original pages. + */ + segs = ppp_bridge_gso_segment(skb, chan->direct_xmit ? + NETIF_F_SG : 0); + skb_list_walk_safe(segs, skb, next) { + skb_mark_not_on_list(skb); + ppp_bridge_xmit(chan, skb); + } + } else { + ppp_bridge_xmit(chan, skb); + } outl: spin_unlock_bh(&pchb->downl); diff --git a/drivers/net/ppp/pppoe.c b/drivers/net/ppp/pppoe.c index 9edf8151bd7d..67c28ac763ef 100644 --- a/drivers/net/ppp/pppoe.c +++ b/drivers/net/ppp/pppoe.c @@ -671,6 +671,7 @@ static int pppoe_connect(struct socket *sock, struct sockaddr_unsized *uservaddr po->chan.private = sk; po->chan.ops = &pppoe_chan_ops; po->chan.direct_xmit = true; + po->chan.xmit_offload = true; error = ppp_register_net_channel(dev_net(dev), &po->chan); if (error) { diff --git a/include/linux/ppp_channel.h b/include/linux/ppp_channel.h index 2f63e9a6cc88..15bc3aa4f171 100644 --- a/include/linux/ppp_channel.h +++ b/include/linux/ppp_channel.h @@ -43,6 +43,7 @@ struct ppp_channel { void *ppp; /* opaque to channel */ int speed; /* transfer rate (bytes/second) */ bool direct_xmit; /* no qdisc, xmit directly */ + bool xmit_offload; /* accepts GSO and CHECKSUM_PARTIAL skbs */ }; #ifdef __KERNEL__ From a8d0451967fc0a34c4b0c21368986a81b4e14862 Mon Sep 17 00:00:00 2001 From: Divyanshu Jha Date: Sat, 24 Oct 2026 17:50:51 +0530 Subject: [PATCH 098/137] net: usb: cdc_ncm: add Apple Silicon MacBook Air (05ac:1903) device ID The Apple Silicon MacBook Air exposes a CDC-NCM interface (05ac:1903) when connected host-to-host over USB-C. The device tested is an Apple MacBook Air (13-inch, M1, 2020), Model A2337 (MacBookAir10,1) with an Apple M1 chip. Unlike commit a5148bc2fa27 ("net: usb: cdc_ncm: add Apple Mac USB-C direct networking quirk") which hypothesized all Apple Silicon Macs share PID 0x1905 ("Mac"), this M1 MacBook Air exposes PID 0x1903 with iProduct string "MacBook Air". Like other Apple CDC-NCM peripherals (0x1902 and 0x1905), Apple's firmware omits the CDC status interrupt endpoint. This causes the standard cdc_ncm probe to fail with a missing endpoint error: cdc_ncm 1-7:1.0: bind() failure cdc_ncm: probe of 1-7:1.0 failed with error -22 Add device ID 05ac:1903 to cdc_devs binding to apple_private_interface_info so the driver bypasses the interrupt check and initializes host-to-host networking successfully: cdc_ncm 1-7:1.0 usb0: register 'cdc_ncm' at usb-..., CDC NCM (Apple Private), ... Relevant 'lsusb -v' descriptor output: Device Descriptor: bcdUSB 2.10 idVendor 0x05ac Apple, Inc. idProduct 0x1903 MacBook Air bcdDevice 10.01 iManufacturer 1 Apple Inc. iProduct 2 MacBook Air Configuration Descriptor: Interface Descriptor: bInterfaceNumber 0 bInterfaceClass 2 Communications bInterfaceSubClass 13 CDC NCM bNumEndpoints 0 Interface Descriptor: bInterfaceNumber 1 bInterfaceClass 10 CDC Data bNumEndpoints 2 Interface Descriptor: bInterfaceNumber 2 bInterfaceClass 2 Communications bInterfaceSubClass 13 CDC NCM bNumEndpoints 0 Interface Descriptor: bInterfaceNumber 3 bInterfaceClass 10 CDC Data bNumEndpoints 2 Reviewed-by: Nicolai Buchwitz Signed-off-by: Divyanshu Jha Link: https://patch.msgid.link/20261024122051.14427-1-divyanshuj2006@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/usb/cdc_ncm.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/usb/cdc_ncm.c b/drivers/net/usb/cdc_ncm.c index 3bd7cb66f3fc..19db5a668dcb 100644 --- a/drivers/net/usb/cdc_ncm.c +++ b/drivers/net/usb/cdc_ncm.c @@ -2022,6 +2022,12 @@ static const struct usb_device_id cdc_devs[] = { { USB_DEVICE_INTERFACE_NUMBER(0x05ac, 0x1902, 2), .driver_info = (unsigned long)&apple_private_interface_info, }, + { USB_DEVICE_INTERFACE_NUMBER(0x05ac, 0x1903, 0), + .driver_info = (unsigned long)&apple_private_interface_info, + }, + { USB_DEVICE_INTERFACE_NUMBER(0x05ac, 0x1903, 2), + .driver_info = (unsigned long)&apple_private_interface_info, + }, { USB_DEVICE_INTERFACE_NUMBER(0x05ac, 0x1905, 0), .driver_info = (unsigned long)&apple_private_interface_info, }, From e8f6ca11167d4753386340ce88444b5445dff831 Mon Sep 17 00:00:00 2001 From: Dharmender Garg Date: Mon, 26 Oct 2026 14:57:56 +0530 Subject: [PATCH 099/137] bnge: cap RSS contexts and RX rings to the RSS table size The hardware supports at most BNGE_RSS_TABLE_MAX_TBL (8) RSS contexts. They are stored in vnic->fw_rss_cos_lb_ctx[8] and back a 512-entry (BNGE_MAX_RSS_TABLE_ENTRIES) indirection table and DMA buffer, so at most 512 RX rings can be served by RSS. bnge_cal_nr_rss_ctxs() has no upper bound, so with more RX rings it returns more than 8 contexts: bnge_setup_vnic() writes past fw_rss_cos_lb_ctx[], bnge_set_dflt_rss_indir_tbl() and bnge_fill_hw_rss_tbl() write past the indirection table and its DMA buffer, and the pad computation in bnge_set_dflt_rss_indir_tbl() underflows into a huge memset(). Cap the number of contexts at BNGE_RSS_TABLE_MAX_TBL and the number of RX rings at BNGE_MAX_RSS_TABLE_ENTRIES, so the sizes derived from the ring count always fit the arrays. Fixes: 627c67f038d2 ("bng_en: Add resource management support") Signed-off-by: Dharmender Garg Reviewed-by: Vikas Gupta Reviewed-by: Simon Horman Link: https://patch.msgid.link/20261026092757.9168-2-vikas.gupta@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnge/bnge_resc.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_resc.c b/drivers/net/ethernet/broadcom/bnge/bnge_resc.c index 7c26943c5cb3..65ef34536e36 100644 --- a/drivers/net/ethernet/broadcom/bnge/bnge_resc.c +++ b/drivers/net/ethernet/broadcom/bnge/bnge_resc.c @@ -182,8 +182,9 @@ int bnge_cal_nr_rss_ctxs(u16 rx_rings) if (!rx_rings) return 0; - return bnge_adjust_pow_two(rx_rings - 1, - BNGE_RSS_TABLE_ENTRIES); + return min_t(int, bnge_adjust_pow_two(rx_rings - 1, + BNGE_RSS_TABLE_ENTRIES), + BNGE_RSS_TABLE_MAX_TBL); } static u16 bnge_rss_ctxs_in_use(struct bnge_dev *bd, @@ -459,6 +460,7 @@ static void _bnge_get_max_rings(struct bnge_dev *bd, u16 *max_rx, } *max_rx = min_t(int, *max_rx, max_ring_grps); + *max_rx = min_t(int, *max_rx, BNGE_MAX_RSS_TABLE_ENTRIES); } static int bnge_get_max_rings(struct bnge_dev *bd, u16 *max_rx, From 8b97091eae744a577573797e5d5abc3e0da10769 Mon Sep 17 00:00:00 2001 From: Dharmender Garg Date: Mon, 26 Oct 2026 14:57:57 +0530 Subject: [PATCH 100/137] bnge: fix RSS table sizing for large RX ring counts bnge_adjust_pow_two() adds one block before rounding up to a power of two only when the quotient is 0 or 1. A larger quotient that is already a power of two is returned unchanged, so 129-192 RX rings get a 128-entry RSS indirection table and 257-320 rings a 256-entry one. The rings above the table size never receive default RSS traffic. bnxt_calc_nr_ring_pages() always adds one, which is correct. Make bnge_adjust_pow_two() always add one block before rounding up. bnge_adjust_pow_two() also sizes the RX, RX aggregation, TX and completion ring pages. For the default 511-entry rings their page counts do not change. The completion ring is sized from more than the default depth, so its page count changes where the old code dropped a partial page. With 8K ring pages, TPA off and the default rings, its budget is 1533 entries and the page count goes from 2 to 4. With 4K ring pages and TPA on, when the firmware reports a max_aggs_supported of 260 to 387, it goes from 16 to 32. In these cases the old completion ring held fewer entries than its budget. Fixes: 627c67f038d2 ("bng_en: Add resource management support") Signed-off-by: Dharmender Garg Reviewed-by: Vikas Gupta Reviewed-by: Simon Horman Link: https://patch.msgid.link/20261026092757.9168-3-vikas.gupta@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnge/bnge_resc.h | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_resc.h b/drivers/net/ethernet/broadcom/bnge/bnge_resc.h index b62a634669f6..c0b6992d7f82 100644 --- a/drivers/net/ethernet/broadcom/bnge/bnge_resc.h +++ b/drivers/net/ethernet/broadcom/bnge/bnge_resc.h @@ -81,13 +81,10 @@ bnge_adjust_pow_two(u32 total_ent, u16 ent_per_blk) { u32 blks = total_ent / ent_per_blk; - if (blks == 0 || blks == 1) - return ++blks; + if (!blks) + return 1; - if (!is_power_of_2(blks)) - blks = roundup_pow_of_two(blks); - - return blks; + return roundup_pow_of_two(blks + 1); } #define BNGE_MAX_ROCE_MSIX 64 From d5d859a6efff473d799d35999a35aa764dcc185b Mon Sep 17 00:00:00 2001 From: Jacob Keller Date: Tue, 27 Oct 2026 17:05:37 -0700 Subject: [PATCH 101/137] ice: use reference counting and SRCU for PTP port access The ice adapter structure maintains a list of ports associated with the adapter. This is used for supporting PTP, where the clock owner must handle many operations that require access to the PTP port structures of the associated PFs. This is implemented using a linked list and a mutex. This sort of works, but a few places within the code do not acquire the mutex when iterating the list. This includes ice_ptp_flush_all_tx_tracker(), ice_ptp_restart_all_phy(), and ice_ptp_prepare_rebuild_sec(). Fixing this is tricky, especially since it is not clear if we can simply acquire the lock around the complete iterations. The pattern of use for the port list is read-mostly with modifications only happening during PF initialization when elements are inserted. This typically only happens during early boot, though a PF could in principle be removed or loaded at arbitrary times via bind and unbind operations. The use of a mutex does mean the driver can sleep while holding it, but it still creates complicates with lock ordering and prevents iterating the list in any code path that *can't* sleep. Instead, use SRCU primitives for the port linked list, along with a reference count on the port. The kref reference counter ensures that a PF will have a valid lifetime and not be removed until the reference is released. Note that this change focuses solely on the port list and does not make an effort to resolve access to ctrl_pf, which is currently being investigated by another developer. Use of sleepable RCU is required because we often iterate the PTP port list and perform operations that might sleep. Attempts at implementing regular RCU have thus far not proven to be acceptable. The remove path first removes the port from the list, and we use kref_get_unless_zero to ensure that such ports are skipped when iterating the list. This ensures that once a port starts removing we will drop references and no longer be able to acquire new ones. This avoids loop iterations chaining together to indefinitely block removal. The ice_ptp_release_port_srcu() function is used as the release function for the kref_put() call. This uses wake_up_var() to wake the removing thread. The waiting thread will block until the final reference has been removed, then it will use synchronize_srcu() to ensure any outstanding SRCU critical sections have had the necessary grace period. This flow ensures that accesses to ports via the adapter port list will remain valid until both the SRCU critical sections have ended and all the references to the ports have been dropped. Strictly speaking, SRCU alone might be sufficient for existing code paths, but the reference count allows the option for passing a pointer to the port on to other functions if necessary in the future. Fixes: e800654e85b5 ("ice: Use ice_adapter for PTP shared data instead of auxdev") Reviewed-by: Maciek Machnikowski Signed-off-by: Jacob Keller Link: https://patch.msgid.link/20261027-reduction-v1-1-81237c67a361@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/ice/ice_adapter.c | 17 ++- drivers/net/ethernet/intel/ice/ice_adapter.h | 17 ++- drivers/net/ethernet/intel/ice/ice_ptp.c | 125 ++++++++++++++----- drivers/net/ethernet/intel/ice/ice_ptp.h | 4 + 4 files changed, 121 insertions(+), 42 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice_adapter.c b/drivers/net/ethernet/intel/ice/ice_adapter.c index 2dc3629d6d0f..536923b6ae97 100644 --- a/drivers/net/ethernet/intel/ice/ice_adapter.c +++ b/drivers/net/ethernet/intel/ice/ice_adapter.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include "ice_adapter.h" #include "ice.h" @@ -54,11 +55,18 @@ static unsigned long ice_adapter_xa_index(struct pci_dev *pdev) static struct ice_adapter *ice_adapter_new(struct pci_dev *pdev) { struct ice_adapter *adapter; + int err; adapter = kzalloc_obj(*adapter); if (!adapter) return NULL; + err = init_srcu_struct(&adapter->ports.srcu); + if (err) { + kfree(adapter); + return NULL; + } + adapter->index = ice_adapter_index(pdev); spin_lock_init(&adapter->ptp_gltsyn_time_lock); spin_lock_init(&adapter->txq_ctx_lock); @@ -66,18 +74,19 @@ static struct ice_adapter *ice_adapter_new(struct pci_dev *pdev) mutex_init(&adapter->cpi_phy_lock[i]); refcount_set(&adapter->refcount, 1); - mutex_init(&adapter->ports.lock); - INIT_LIST_HEAD(&adapter->ports.ports); + spin_lock_init(&adapter->ports.lock); + INIT_LIST_HEAD(&adapter->ports.list); return adapter; } static void ice_adapter_free(struct ice_adapter *adapter) { - WARN_ON(!list_empty(&adapter->ports.ports)); + WARN_ON(!list_empty(&adapter->ports.list)); for (int i = 0; i < ARRAY_SIZE(adapter->cpi_phy_lock); i++) mutex_destroy(&adapter->cpi_phy_lock[i]); - mutex_destroy(&adapter->ports.lock); + + cleanup_srcu_struct(&adapter->ports.srcu); kfree(adapter); } diff --git a/drivers/net/ethernet/intel/ice/ice_adapter.h b/drivers/net/ethernet/intel/ice/ice_adapter.h index 4f695f32da3d..c61bd645e460 100644 --- a/drivers/net/ethernet/intel/ice/ice_adapter.h +++ b/drivers/net/ethernet/intel/ice/ice_adapter.h @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -17,15 +18,19 @@ struct ice_pf; /** * struct ice_port_list - data used to store the list of adapter ports * - * This structure contains data used to maintain a list of adapter ports + * This structure contains data used to maintain a list of adapter ports. + * Writers modifying the list *must* acquire the lock, and use SRCU safe list + * operations. Readers should use srcu_read_lock() on the provided domain. * - * @ports: list of ports - * @lock: protect access to the ports list + * @list: list of ports + * @lock: protect write access to the list + * @srcu: Sleepable RCU domain for this adapter */ struct ice_port_list { - struct list_head ports; - /* To synchronize the ports list operations */ - struct mutex lock; + struct list_head list; + /* To synchronize write operations on the port list */ + spinlock_t lock; + struct srcu_struct srcu; }; /** diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c index b781004f856d..e149a818dcd4 100644 --- a/drivers/net/ethernet/intel/ice/ice_ptp.c +++ b/drivers/net/ethernet/intel/ice/ice_ptp.c @@ -1,6 +1,9 @@ // SPDX-License-Identifier: GPL-2.0 /* Copyright (C) 2021, Intel Corporation. */ +#include +#include +#include #include "ice.h" #include "ice_lib.h" #include "ice_trace.h" @@ -676,20 +679,37 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx) pf->ptp.tx_hwtstamp_good += tstamp_good; } +static void ice_ptp_release_port_srcu(struct kref *ref) +{ + /* wake_up_var() requires full ordering but kref_put only provides + * release ordering. + */ + smp_mb__after_atomic(); + wake_up_var(ref); +} + static void ice_ptp_tx_tstamp_owner(struct ice_pf *pf) { + struct ice_port_list *ports = &pf->adapter->ports; struct ice_ptp_port *port; + int srcu_idx; - mutex_lock(&pf->adapter->ports.lock); - list_for_each_entry(port, &pf->adapter->ports.ports, list_node) { + srcu_idx = srcu_read_lock(&ports->srcu); + list_for_each_entry_srcu(port, &ports->list, list_node, + srcu_read_lock_held(&ports->srcu)) { struct ice_ptp_tx *tx = &port->tx; - if (!tx || !tx->init) + if (!tx->init) + continue; + + if (!kref_get_unless_zero(&port->ref)) continue; ice_ptp_process_tx_tstamp(tx); + + kref_put(&port->ref, ice_ptp_release_port_srcu); } - mutex_unlock(&pf->adapter->ports.lock); + srcu_read_unlock(&ports->srcu, srcu_idx); } /** @@ -809,10 +829,19 @@ ice_ptp_mark_tx_tracker_stale(struct ice_ptp_tx *tx) static void ice_ptp_flush_all_tx_tracker(struct ice_pf *pf) { + struct ice_port_list *ports = &pf->adapter->ports; struct ice_ptp_port *port; + int srcu_idx; - list_for_each_entry(port, &pf->adapter->ports.ports, list_node) + srcu_idx = srcu_read_lock(&ports->srcu); + list_for_each_entry_srcu(port, &ports->list, list_node, + srcu_read_lock_held(&ports->srcu)) { + if (!kref_get_unless_zero(&port->ref)) + continue; ice_ptp_flush_tx_tracker(ptp_port_to_pf(port), &port->tx); + kref_put(&port->ref, ice_ptp_release_port_srcu); + } + srcu_read_unlock(&ports->srcu, srcu_idx); } /** @@ -1427,16 +1456,22 @@ static void ice_ptp_reset_phy_timestamping(struct ice_pf *pf) */ static void ice_ptp_restart_all_phy(struct ice_pf *pf) { - struct list_head *entry; + struct ice_port_list *ports = &pf->adapter->ports; + struct ice_ptp_port *port; + int srcu_idx; - list_for_each(entry, &pf->adapter->ports.ports) { - struct ice_ptp_port *port = list_entry(entry, - struct ice_ptp_port, - list_node); + srcu_idx = srcu_read_lock(&ports->srcu); + list_for_each_entry_srcu(port, &ports->list, list_node, + srcu_read_lock_held(&ports->srcu)) { + if (!kref_get_unless_zero(&port->ref)) + continue; if (port->link_up) ice_ptp_port_phy_restart(port); + + kref_put(&port->ref, ice_ptp_release_port_srcu); } + srcu_read_unlock(&ports->srcu, srcu_idx); } /** @@ -2699,19 +2734,28 @@ static bool ice_port_has_timestamps(struct ice_ptp_tx *tx) static bool ice_any_port_has_timestamps(struct ice_pf *pf) { + struct ice_port_list *ports = &pf->adapter->ports; + bool have_tstamps = false; struct ice_ptp_port *port; + int srcu_idx; - scoped_guard(mutex, &pf->adapter->ports.lock) { - list_for_each_entry(port, &pf->adapter->ports.ports, - list_node) { - struct ice_ptp_tx *tx = &port->tx; + srcu_idx = srcu_read_lock(&ports->srcu); + list_for_each_entry_srcu(port, &ports->list, list_node, + srcu_read_lock_held(&ports->srcu)) { + if (!kref_get_unless_zero(&port->ref)) + continue; - if (ice_port_has_timestamps(tx)) - return true; - } + if (ice_port_has_timestamps(&port->tx)) + have_tstamps = true; + + kref_put(&port->ref, ice_ptp_release_port_srcu); + + if (have_tstamps) + break; } + srcu_read_unlock(&ports->srcu, srcu_idx); - return false; + return have_tstamps; } bool ice_ptp_tx_tstamps_pending(struct ice_pf *pf) @@ -2970,14 +3014,18 @@ void ice_ptp_queue_work(struct ice_pf *pf) static void ice_ptp_prepare_rebuild_sec(struct ice_pf *pf, bool rebuild, enum ice_reset_req reset_type) { - struct list_head *entry; + struct ice_port_list *ports = &pf->adapter->ports; + struct ice_ptp_port *port; + int srcu_idx; - list_for_each(entry, &pf->adapter->ports.ports) { - struct ice_ptp_port *port = list_entry(entry, - struct ice_ptp_port, - list_node); + srcu_idx = srcu_read_lock(&ports->srcu); + list_for_each_entry_srcu(port, &ports->list, list_node, + srcu_read_lock_held(&ports->srcu)) { struct ice_pf *peer_pf = ptp_port_to_pf(port); + if (!kref_get_unless_zero(&port->ref)) + continue; + if (!ice_is_primary(&peer_pf->hw)) { if (rebuild) { /* TODO: When implementing rebuild=true: @@ -2989,7 +3037,10 @@ static void ice_ptp_prepare_rebuild_sec(struct ice_pf *pf, bool rebuild, ice_ptp_prepare_for_reset(peer_pf, reset_type); } } + + kref_put(&port->ref, ice_ptp_release_port_srcu); } + srcu_read_unlock(&ports->srcu, srcu_idx); } /** @@ -3169,11 +3220,11 @@ static int ice_ptp_setup_pf(struct ice_pf *pf) return -ENODEV; INIT_LIST_HEAD(&ptp->port.list_node); - mutex_lock(&pf->adapter->ports.lock); + kref_init(&ptp->port.ref); - list_add(&ptp->port.list_node, - &pf->adapter->ports.ports); - mutex_unlock(&pf->adapter->ports.lock); + spin_lock(&pf->adapter->ports.lock); + list_add_rcu(&ptp->port.list_node, &pf->adapter->ports.list); + spin_unlock(&pf->adapter->ports.lock); /* Seed the per-PHY Tx reference clock usage map for this port. * Only meaningful on E825 (other MAC types don't expose tx-clk @@ -3195,13 +3246,23 @@ static int ice_ptp_setup_pf(struct ice_pf *pf) static void ice_ptp_cleanup_pf(struct ice_pf *pf) { + struct ice_port_list *ports = &pf->adapter->ports; struct ice_ptp *ptp = &pf->ptp; + struct kref *ref; - if (pf->hw.mac_type != ICE_MAC_UNKNOWN) { - mutex_lock(&pf->adapter->ports.lock); - list_del(&ptp->port.list_node); - mutex_unlock(&pf->adapter->ports.lock); - } + if (pf->hw.mac_type == ICE_MAC_UNKNOWN) + return; + + spin_lock(&ports->lock); + list_del_rcu(&ptp->port.list_node); + spin_unlock(&ports->lock); + + ref = &ptp->port.ref; + kref_put(ref, ice_ptp_release_port_srcu); + + wait_var_event(ref, !kref_read(ref)); + + synchronize_srcu(&ports->srcu); } /** diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.h b/drivers/net/ethernet/intel/ice/ice_ptp.h index 0e40fef3b4e8..12a4ffa4f57c 100644 --- a/drivers/net/ethernet/intel/ice/ice_ptp.h +++ b/drivers/net/ethernet/intel/ice/ice_ptp.h @@ -5,6 +5,8 @@ #define _ICE_PTP_H_ #include +#include +#include #include #include "ice_ptp_hw.h" @@ -138,6 +140,7 @@ struct ice_ptp_tx { * and determine when the port's PHY offset is valid. * * @list_node: list member structure + * @ref: reference counter for use with adapter ports list * @tx: Tx timestamp tracking for this port * @ov_work: delayed work task for tracking when PHY offset is valid * @ps_lock: mutex used to protect the overall PTP PHY start procedure @@ -149,6 +152,7 @@ struct ice_ptp_tx { */ struct ice_ptp_port { struct list_head list_node; + struct kref ref; struct ice_ptp_tx tx; struct kthread_delayed_work ov_work; struct mutex ps_lock; /* protects overall PTP PHY start procedure */ From 50870d62116038e6f1e17a6aedcf2a95cec37ec7 Mon Sep 17 00:00:00 2001 From: Jacob Keller Date: Tue, 27 Oct 2026 17:05:38 -0700 Subject: [PATCH 102/137] ice: set in_use only after preparing Tx timestamp index The ice_ptp_request_ts() function is used to request a timestamp index for use with a packet. When reserving an index, it sets the start time and saves a pointer to the skb into the appropriate index. The function marks the in_use bit first before doing any of these steps. The IRQ handler which clears the timestamps reads the in_use bits uses a lockless flow for reading the in_use bits to determine which ones are in-use. This is necessary as actually processing a complete timestamp must be able to sleep so we cannot hold the timestamp tracker lock over the entire sequence. Additionally, blocking the Tx hotpath with such a lock indefinitely would be problematic. However, the existing flow now has a very narrow window where the IRQ handler could see a timestamp as in-use but read a stale value for its "start" time. Fix this by ordering the sequence to mark the in_use bit last, and add a memory barrier to prevent re-ordering of the previous writes to setup the index. This was found and reported by Sashiko while reviewing an unrelated change. Closes: https://sashiko.dev/#/patchset/20260821-jk-e825c-minimized-fixes-v1-0-9d0731eb4858%40intel.com?part=7 Fixes: ea9b847cda64 ("ice: enable transmit timestamps for E810 devices") Signed-off-by: Jacob Keller Link: https://patch.msgid.link/20261027-reduction-v1-2-81237c67a361@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/ice/ice_ptp.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c index e149a818dcd4..10cf60cae81e 100644 --- a/drivers/net/ethernet/intel/ice/ice_ptp.c +++ b/drivers/net/ethernet/intel/ice/ice_ptp.c @@ -595,6 +595,12 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx) bool drop_ts = !link_up; struct sk_buff *skb; + /* Paired with smp_mb__before_atomic() in ice_ptp_request_ts() + * to ensure ordering the in_use bitmap and the indexed fields + * in the tstamps array. + */ + smp_rmb(); + /* Drop packets which have waited for more than 2 seconds */ if (time_is_before_jiffies(tx->tstamps[idx].start + 2 * HZ)) { drop_ts = true; @@ -2678,11 +2684,13 @@ s8 ice_ptp_request_ts(struct ice_ptp_tx *tx, struct sk_buff *skb) * a reference to the skb and the start time to allow discarding old * requests. */ - set_bit(idx, tx->in_use); - clear_bit(idx, tx->stale); tx->tstamps[idx].start = jiffies; tx->tstamps[idx].skb = skb_get(skb); skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; + clear_bit(idx, tx->stale); + /* Ensure index is setup before marking it as used */ + smp_mb__before_atomic(); + set_bit(idx, tx->in_use); ice_trace(tx_tstamp_request, skb, idx); } From 56af436b90acce7eaa7531189c827773983f7d07 Mon Sep 17 00:00:00 2001 From: Karol Kolacinski Date: Tue, 27 Oct 2026 17:05:39 -0700 Subject: [PATCH 103/137] ice: E822: keep Tx timestamps disabled during offset calibration Do not clear the tx.calibrating flag immediately after starting the PHY timer in ice_ptp_port_phy_restart(). Instead, keep Tx timestamps disabled until the offset verification work (ice_ptp_wait_for_offsets) has confirmed that the Tx PHY offset is properly configured. Previously, tx.calibrating was set to true, then immediately back to false right after ice_start_phy_timer_e82x() returned. This allowed Tx timestamp requests to be served during the window where offset verification was still pending. Timestamps produced during this window should have their valid bit cleared, but it is better to prevent requests until the driver has confirmed calibration is complete. Rename the tx.calibrating field to tx.disabled, and change where it is cleared. Only clear it in ice_ptp_wait_for_offsets(), and only after Tx offset configuration has completed successfully. This ensures that new Tx timestamp requests are only accepted after the PHY has properly calibrated the Tx offset. If ice_start_phy_timer_e82x() fails, do not restore disabled to false. The device is in a state where timestamps cannot succeed properly anyways. A dev_err message is already logged on failure to start the timer at the end of the function. Renaming the field improves clarity and will better reflect the nature of the flag in a future change that also applies disabling Tx timestamps in software in a following commit. Log a debug message while offset calibration is still pending, including the specific Tx/Rx error codes to aid debugging stalled calibration. This path is expected on every routine link-up: ov_work is first queued with no delay and the vernier offset cannot be computed until at least one packet has been transmitted, so the first several invocations normally land here. Use dev_dbg() rather than a rate-limited warning to avoid emitting KERN_WARNING on every link-up during normal operation. Log a debug message when calibration completes successfully. Note that sashiko review has previously complained that leaving the disabled flag set "permanently" disables Tx timestamps if vernier calibration never completes. This is true, but its important to realize that timestamps would still fail regardless of whether the flag is set. Until vernier calibration completes the device will not report valid timestamps regardless. Thus, keeping the calibrating flag set simply prevents new timestamp requests from software while it is known that the hardware will not complete them. Fixes: 3a7496234d17 ("ice: implement basic E822 PTP support") Signed-off-by: Karol Kolacinski Reviewed-by: Aleksandr Loktionov Signed-off-by: Arkadiusz Kubalewski Signed-off-by: Przemyslaw Korba Signed-off-by: Jacob Keller Link: https://patch.msgid.link/20261027-reduction-v1-3-81237c67a361@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/ice/ice_ptp.c | 37 +++++++++++++++++------- drivers/net/ethernet/intel/ice/ice_ptp.h | 6 ++-- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c index 10cf60cae81e..7980eaf9ce25 100644 --- a/drivers/net/ethernet/intel/ice/ice_ptp.c +++ b/drivers/net/ethernet/intel/ice/ice_ptp.c @@ -370,7 +370,7 @@ ice_ptp_is_tx_tracker_up(struct ice_ptp_tx *tx) { lockdep_assert_held(&tx->lock); - return tx->init && !tx->calibrating; + return tx->init && !tx->disabled; } /** @@ -1171,6 +1171,7 @@ static int ice_ptp_check_tx_fifo(struct ice_ptp_port *port) static void ice_ptp_wait_for_offsets(struct kthread_work *work) { struct ice_ptp_port *port; + unsigned long flags; struct ice_pf *pf; struct ice_hw *hw; int tx_err; @@ -1191,9 +1192,26 @@ static void ice_ptp_wait_for_offsets(struct kthread_work *work) tx_err = ice_ptp_check_tx_fifo(port); if (!tx_err) tx_err = ice_phy_cfg_tx_offset_e82x(hw, port->port_num); + if (!tx_err) { + /* Tx offset has been configured, re-enable Tx timestamps */ + spin_lock_irqsave(&port->tx.lock, flags); + if (port->tx.disabled) { + port->tx.disabled = false; + dev_dbg(ice_pf_to_dev(pf), "PTP Tx offset valid for port %u, Tx timestamps enabled\n", + port->port_num); + } + spin_unlock_irqrestore(&port->tx.lock, flags); + } + rx_err = ice_phy_cfg_rx_offset_e82x(hw, port->port_num); if (tx_err || rx_err) { - /* Tx and/or Rx offset not yet configured, try again later */ + /* Tx and/or Rx offset not yet configured, try again later. + * This is expected during normal link-up: the vernier offset + * calibration cannot complete until at least one packet has + * been transmitted, so the first retries routinely land here. + */ + dev_dbg(ice_pf_to_dev(pf), "PTP offset not yet valid for port %u (tx_err=%d rx_err=%d)\n", + port->port_num, tx_err, rx_err); kthread_queue_delayed_work(pf->ptp.kworker, &port->ov_work, msecs_to_jiffies(100)); @@ -1271,11 +1289,9 @@ ice_ptp_port_phy_restart(struct ice_ptp_port *ptp_port) /* Start the PHY timer in Vernier mode */ kthread_cancel_delayed_work_sync(&ptp_port->ov_work); - /* temporarily disable Tx timestamps while calibrating - * PHY offset - */ + /* Disable Tx timestamps until PHY calibration completes */ spin_lock_irqsave(&ptp_port->tx.lock, flags); - ptp_port->tx.calibrating = true; + ptp_port->tx.disabled = true; spin_unlock_irqrestore(&ptp_port->tx.lock, flags); ptp_port->tx_fifo_busy_cnt = 0; @@ -1284,11 +1300,10 @@ ice_ptp_port_phy_restart(struct ice_ptp_port *ptp_port) if (err) break; - /* Enable Tx timestamps right away */ - spin_lock_irqsave(&ptp_port->tx.lock, flags); - ptp_port->tx.calibrating = false; - spin_unlock_irqrestore(&ptp_port->tx.lock, flags); - + /* Do not clear disabled flag here. Tx timestamp requests + * remain disabled until ice_ptp_wait_for_offsets() has + * verified that the Tx offset calibration has completed. + */ kthread_queue_delayed_work(pf->ptp.kworker, &ptp_port->ov_work, 0); break; diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.h b/drivers/net/ethernet/intel/ice/ice_ptp.h index 12a4ffa4f57c..f0839d90e9d2 100644 --- a/drivers/net/ethernet/intel/ice/ice_ptp.h +++ b/drivers/net/ethernet/intel/ice/ice_ptp.h @@ -106,8 +106,8 @@ enum ice_tx_tstamp_work { * @offset: offset into timestamp block to get the real index * @len: length of the tstamps and in_use fields. * @init: if true, the tracker is initialized; - * @calibrating: if true, the PHY is calibrating the Tx offset. During this - * window, timestamps are temporarily disabled. + * @disabled: set to true when new Tx timestamp requests are disabled, such as + * while the PHY Tx offset configuration has not yet completed. * @has_ready_bitmap: if true, the hardware has a valid Tx timestamp ready * bitmap register. If false, fall back to verifying new * timestamp values against previously cached copy. @@ -122,7 +122,7 @@ struct ice_ptp_tx { u8 offset; u8 len; u8 init : 1; - u8 calibrating : 1; + u8 disabled : 1; u8 has_ready_bitmap : 1; s8 last_ll_ts_idx_read; }; From fcc00b15cb21a6de76826e5012b2c758505f5e65 Mon Sep 17 00:00:00 2001 From: Linkui Xiao Date: Tue, 27 Oct 2026 16:52:00 +0800 Subject: [PATCH 104/137] net: bridge: netlink: do not clear the vlan info dump error br_fill_ifinfo() assigns the result of br_fill_vlan_tunnel_info() over the result of br_fill_ifvlaninfo() or br_fill_ifvlaninfo_compressed() instead of only running the second helper when the first one succeeded. When the vlan information no longer fits in the skb but the port has no vlan mapped to a tunnel dst, br_fill_vlan_tunnel_info() walks the vlan list and returns 0 without touching the skb. The -EMSGSIZE is lost, br_fill_ifinfo() completes a message whose IFLA_AF_SPEC is truncated, and the dump carries on to the next interface instead of stopping, so userspace silently sees an incomplete vlan list. Only fill the tunnel information when the vlan information was filled successfully. This was flagged as a pre-existing issue in the Sashiko review of the net-next series, which noted that the VLAN fill result is overwritten before it is tested (see the Link tag below). Not triggered or tested. Fixes: efa5356b0d97 ("bridge: per vlan dst_metadata netlink support") Link: https://lore.kernel.org/bridge/ed1de95e-e112-4076-93f0-6f7668e657ad@blackwall.org/T/#u#5 Signed-off-by: Linkui Xiao Reviewed-by: Eric Dumazet Acked-by: Nikolay Aleksandrov Link: https://patch.msgid.link/20261027085200.1501599-1-xiaolinkui@126.com Signed-off-by: Jakub Kicinski --- net/bridge/br_netlink.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/bridge/br_netlink.c b/net/bridge/br_netlink.c index f65b8b6ca97c..79c4a54990e2 100644 --- a/net/bridge/br_netlink.c +++ b/net/bridge/br_netlink.c @@ -540,7 +540,7 @@ static int br_fill_ifinfo(struct sk_buff *skb, else err = br_fill_ifvlaninfo(skb, vg); - if (port && test_bit(BR_VLAN_TUNNEL_BIT, &port->flags)) + if (!err && port && test_bit(BR_VLAN_TUNNEL_BIT, &port->flags)) err = br_fill_vlan_tunnel_info(skb, vg); rcu_read_unlock(); if (err) From fa503935c2d6603856dafff0a0852631ab6e9503 Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Mon, 26 Oct 2026 10:45:03 +0200 Subject: [PATCH 105/137] net/mlx5: Only set num_doorbells on a fully initialized SF The validation for the num_doorbells devlink param rejects values greater than num_channels. For lightweight SFs, this ends up accessing a NULL priv->eq_table, leading to a crash. Fix that by rejecting setting num_doorbells on uninitialized SFs. Fixes: 11bbcfb7668c ("net/mlx5e: Use the 'num_doorbells' devlink param") Signed-off-by: Cosmin Ratiu Reviewed-by: Dragos Tatulea Signed-off-by: Tariq Toukan Reviewed-by: Daniel Machon Link: https://patch.msgid.link/20261026084503.4122732-1-tariqt@nvidia.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/mellanox/mlx5/core/devlink.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/devlink.c b/drivers/net/ethernet/mellanox/mlx5/core/devlink.c index b9026cc64383..30a97e48dda7 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/devlink.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/devlink.c @@ -542,6 +542,12 @@ static int mlx5_devlink_num_doorbells_validate(struct devlink *devlink, u32 id, u32 val32 = val->vu32; u32 max_num_channels; + if (mlx5_dev_is_lightweight(mdev)) { + NL_SET_ERR_MSG_MOD(extack, + "Setting num_doorbells requires full device initialization; reload the device first"); + return -EOPNOTSUPP; + } + max_num_channels = mlx5e_get_max_num_channels(mdev); if (val32 > max_num_channels) { NL_SET_ERR_MSG_FMT_MOD(extack, From d2bfe74a8fe219a86b86c1f126e1d722a5001790 Mon Sep 17 00:00:00 2001 From: Joas Antonio dos Santos Date: Sat, 24 Oct 2026 19:04:28 -0300 Subject: [PATCH 106/137] net: mctp: free reassembly head skb when key_add fails on SOM When a start-of-message fragment creates a new reassembly key on the broadcast/bind path, mctp_frag_queue() stores the skb as key->reasm_head before the key is made observable with mctp_key_add(). If mctp_key_add() fails (a race with another SOM packet for the same source/dest/tag, or the receiving socket going away), the error path only drops the key reference via mctp_key_unref(). The final key destructor frees the key itself but not key->reasm_head, and since the key was never added to any list, neither the expiry worker nor socket teardown can reach it to release the queued fragment. The skb is leaked. Free key->reasm_head on the key_add failure path before releasing the key. Other teardown paths (__mctp_key_done_in(), EOM delivery) already clear reasm_head before the final unref, so this only affects the failure path. Fixes: 4a992bbd3650 ("mctp: Implement message fragmentation & reassembly") Signed-off-by: Joas Antonio dos Santos Reported-by: Guikun Yang Closes: https://lore.kernel.org/all/tencent_43A65D51143E18518EA968DF9323694EFD08@qq.com/ Acked-by: Jeremy Kerr Link: https://patch.msgid.link/179287946888.17431.11219154409333681184@gmail.com Signed-off-by: Paolo Abeni --- net/mctp/route.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/net/mctp/route.c b/net/mctp/route.c index d18ed9d0db1d..96fd75ea791b 100644 --- a/net/mctp/route.c +++ b/net/mctp/route.c @@ -552,8 +552,16 @@ static int mctp_dst_input(struct mctp_dst *dst, struct sk_buff *skb) * can do is drop. */ rc = mctp_key_add(key, msk); - if (!rc) + if (!rc) { trace_mctp_key_acquire(key); + } else { + /* key_add failed: the key is not observable by + * any cleanup path, so free the fragment we + * just queued on it to avoid leaking the skb. + */ + kfree_skb(key->reasm_head); + key->reasm_head = NULL; + } /* we don't need to release key->lock on exit, so * clean up here and suppress the unlock via From 8b2043279dae25483fe1f2d926e76d41ab95c398 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Sat, 24 Oct 2026 10:34:14 -0400 Subject: [PATCH 107/137] net/sched: sch_teql: fix slaves_lock IRQ deadlock This is a followup of commit e5b811fe7931 ("net/sched: sch_teql: Introduce slaves_lock to avoid race condition and UAF") as reported by Sashiko. The issue: master->slaves_lock serializes the master's ring of slave qdiscs. The control paths -- teql_dequeue(), teql_destroy() and teql_qdisc_init() -- take it with spin_lock_bh(), while teql_master_xmit() (the master's ndo_start_xmit) takes it with plain spin_lock(). netpoll_send_skb() wraps the transmit in local_irq_save(), and __netpoll_send_skb() asserts lockdep_assert_irqs_disabled(), so teql_master_xmit() reached from netconsole always acquires slaves_lock with hard IRQs disabled. On PREEMPT_RT spinlock_t is an rtmutex, and Documentation/locking/locktypes.rst names local_irq_disable(); spin_lock(&lock); as a sequence that breaks there because the rtmutex requires a fully preemptible context. This is reachable without any panic: the nbcon printer thread calls netconsole's write_thread() callback on every message. The usual remedy -- folding the IRQ disable into the lock with spin_lock_irqsave() -- is unavailable because the local_irq_save() is netpoll's, not ours. On non-RT the same asymmetry leaves slaves_lock acquirable with hard IRQs disabled on one path and held with hard IRQs enabled on another. That is only exploitable during the panic console flush (netconsole is CON_NBCON_ATOMIC_UNSAFE, so console_is_usable() admits its write_atomic() only from nbcon_atomic_flush_unsafe(), which panic() alone calls), and even then an IRQ-saving lock does not help if the holder oopsed inside the section or was stopped by panic_other_cpus_shutdown(). So the non-RT half is hardening; the PREEMPT_RT half is the bug. The Fix: take slaves_lock as a raw spinlock with IRQs saved at every site. Since skb_queue_purge() takes the queue's spinlock_t and may not nest inside a raw lock on PREEMPT_RT, move its call in teql_destroy() outside the raw section; dat->q is the dying qdisc's own queue and was never serialized by slaves_lock, so the move is behaviour-neutral. The new critical sections contain only RCU pointer assignments and netdev_get_tx_queue(); skb_queue_purge(), netif_wake_queue() and dev_reset_queue() are all outside them. teql_destroy()'s ring walk is O(slaves), two pointer loads and a compare per iteration, so even a large ring is microseconds with IRQs off. Keeping the purge conditional rather than dropping it outright is the conservative call for a backport. Some context: commit 6301f6a34ed8 ("net/sched: sch_teql: move rcu_read_lock()/spin_lock() from _bh variants") hardened the parent fix; together they addressed the master->slaves resurrection use-after-free and the xmit-side BH warning but left the reverse direction -- a hard IRQ re-entering xmit while a control path holds the lock -- unaddressed. Conditions to recreate the bug: - CONFIG_PREEMPT_RT, CONFIG_NET_SCH_TEQL, CONFIG_DUMMY and CONFIG_NETCONSOLE. - Run `ip link add dev teqlslave0 type dummy`, bring teqlslave0 up, attach it with `tc qdisc add dev teqlslave0 root teql0`, and bring teql0 up. - Configure a netconsole target on teql0 with explicit local and remote IP addresses and a remote MAC address, then emit a console message. The nbcon printer thread reaches teql_master_xmit() with hard IRQs disabled by netpoll; taking slaves_lock as a spinlock_t is invalid on PREEMPT_RT. No panic or concurrent qdisc change is required. - Reachable by an admin with CAP_NET_ADMIN; teql devices exist only in the initial netns (no per-netns ops), so a user namespace does not suffice. Fixes: e5b811fe7931 ("net/sched: sch_teql: Introduce slaves_lock to avoid race condition and UAF") Reported-by: Sashiko (gemini) Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260630150922.238714-1-jhs%40mojatatu.com Link: https://lore.kernel.org/netdev/20260630150922.238714-1-jhs@mojatatu.com/ Cc: stable@vger.kernel.org Signed-off-by: Jamal Hadi Salim Reviewed-by: Simon Horman Link: https://patch.msgid.link/QDISC-Y0BY.v2.r0.20261024060625-1@mojatatu.com Signed-off-by: Paolo Abeni --- net/sched/sch_teql.c | 45 +++++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/net/sched/sch_teql.c b/net/sched/sch_teql.c index e9cb7d408ca0..6764f0dd38df 100644 --- a/net/sched/sch_teql.c +++ b/net/sched/sch_teql.c @@ -55,7 +55,7 @@ struct teql_master { struct Qdisc_ops qops; struct net_device *dev; struct Qdisc __rcu *slaves; - spinlock_t slaves_lock; /* serializes writes to ->slaves */ + raw_spinlock_t slaves_lock; /* serializes writes to ->slaves */ struct list_head master_list; unsigned long tx_bytes; unsigned long tx_packets; @@ -94,6 +94,7 @@ teql_dequeue(struct Qdisc *sch) { struct teql_sched_data *dat = qdisc_priv(sch); struct netdev_queue *dat_queue; + unsigned long flags; struct sk_buff *skb; struct Qdisc *q; @@ -104,9 +105,9 @@ teql_dequeue(struct Qdisc *sch) if (skb == NULL) { struct net_device *m = qdisc_dev(q); if (m) { - spin_lock_bh(&dat->m->slaves_lock); + raw_spin_lock_irqsave(&dat->m->slaves_lock, flags); rcu_assign_pointer(dat->m->slaves, sch); - spin_unlock_bh(&dat->m->slaves_lock); + raw_spin_unlock_irqrestore(&dat->m->slaves_lock, flags); netif_wake_queue(m); } } else { @@ -139,11 +140,13 @@ teql_destroy(struct Qdisc *sch) struct teql_master *master = dat->m; struct netdev_queue *txq = NULL; bool reset_master_queue = false; + unsigned long flags; + bool purge = false; if (!master) return; - spin_lock_bh(&master->slaves_lock); + raw_spin_lock_irqsave(&master->slaves_lock, flags); prev = rcu_dereference_protected(master->slaves, lockdep_is_held(&master->slaves_lock)); if (prev) { @@ -171,12 +174,18 @@ teql_destroy(struct Qdisc *sch) reset_master_queue = true; } } - skb_queue_purge(&dat->q); + purge = true; break; } while (prev != rcu_dereference_protected(master->slaves, lockdep_is_held(&master->slaves_lock))); } - spin_unlock_bh(&master->slaves_lock); + raw_spin_unlock_irqrestore(&master->slaves_lock, flags); + + /* skb_queue_purge() takes the queue's spinlock_t, which cannot nest + * inside a raw lock on PREEMPT_RT; purge after the raw section. + */ + if (purge) + skb_queue_purge(&dat->q); if (reset_master_queue) dev_reset_queue(master->dev, txq, NULL); @@ -189,6 +198,7 @@ static int teql_qdisc_init(struct Qdisc *sch, struct nlattr *opt, struct teql_master *m = (struct teql_master *)sch->ops; struct teql_sched_data *q = qdisc_priv(sch); struct Qdisc *first; + unsigned long flags; if (dev->hard_header_len > m->dev->hard_header_len) return -EINVAL; @@ -205,7 +215,7 @@ static int teql_qdisc_init(struct Qdisc *sch, struct nlattr *opt, skb_queue_head_init(&q->q); - spin_lock_bh(&m->slaves_lock); + raw_spin_lock_irqsave(&m->slaves_lock, flags); first = rcu_dereference_protected(m->slaves, lockdep_is_held(&m->slaves_lock)); if (first) { if (m->dev->flags & IFF_UP) { @@ -216,7 +226,7 @@ static int teql_qdisc_init(struct Qdisc *sch, struct nlattr *opt, (m->dev->flags & IFF_MULTICAST && !(dev->flags & IFF_MULTICAST)) || dev->mtu < m->dev->mtu) { - spin_unlock_bh(&m->slaves_lock); + raw_spin_unlock_irqrestore(&m->slaves_lock, flags); return -EINVAL; } } else { @@ -239,7 +249,7 @@ static int teql_qdisc_init(struct Qdisc *sch, struct nlattr *opt, m->dev->mtu = dev->mtu; m->dev->flags = (m->dev->flags&~FMASK)|(dev->flags&FMASK); } - spin_unlock_bh(&m->slaves_lock); + raw_spin_unlock_irqrestore(&m->slaves_lock, flags); return 0; } @@ -315,11 +325,12 @@ static inline int teql_resolve(struct sk_buff *skb, static netdev_tx_t teql_master_xmit(struct sk_buff *skb, struct net_device *dev) { struct teql_master *master = netdev_priv(dev); - struct Qdisc *start, *q; - int busy; - int nores; int subq = skb_get_queue_mapping(skb); struct sk_buff *skb_res = NULL; + struct Qdisc *start, *q; + unsigned long flags; + int nores; + int busy; restart: nores = 0; @@ -355,13 +366,13 @@ static netdev_tx_t teql_master_xmit(struct sk_buff *skb, struct net_device *dev) netdev_start_xmit(skb, slave, slave_txq, false) == NETDEV_TX_OK) { __netif_tx_unlock(slave_txq); - spin_lock(&master->slaves_lock); + raw_spin_lock_irqsave(&master->slaves_lock, flags); if (rcu_dereference_protected(master->slaves, lockdep_is_held(&master->slaves_lock)) == q) rcu_assign_pointer(master->slaves, rcu_dereference_protected(NEXT_SLAVE(q), lockdep_is_held(&master->slaves_lock))); - spin_unlock(&master->slaves_lock); + raw_spin_unlock_irqrestore(&master->slaves_lock, flags); netif_wake_queue(dev); master->tx_packets++; master->tx_bytes += length; @@ -374,13 +385,13 @@ static netdev_tx_t teql_master_xmit(struct sk_buff *skb, struct net_device *dev) busy = 1; break; case 1: - spin_lock(&master->slaves_lock); + raw_spin_lock_irqsave(&master->slaves_lock, flags); if (rcu_dereference_protected(master->slaves, lockdep_is_held(&master->slaves_lock)) == q) rcu_assign_pointer(master->slaves, rcu_dereference_protected(NEXT_SLAVE(q), lockdep_is_held(&master->slaves_lock))); - spin_unlock(&master->slaves_lock); + raw_spin_unlock_irqrestore(&master->slaves_lock, flags); rcu_read_unlock(); return NETDEV_TX_OK; default: @@ -502,7 +513,7 @@ static __init void teql_master_setup(struct net_device *dev) struct teql_master *master = netdev_priv(dev); struct Qdisc_ops *ops = &master->qops; - spin_lock_init(&master->slaves_lock); + raw_spin_lock_init(&master->slaves_lock); master->dev = dev; ops->priv_size = sizeof(struct teql_sched_data); From 782d6bb8f9b8e9d8591181662222bf74cd3ecf79 Mon Sep 17 00:00:00 2001 From: owen kwan Date: Sat, 24 Oct 2026 10:34:15 -0400 Subject: [PATCH 108/137] net/sched: sch_teql: fix use-after-free on lower qdisc drop The issue: teql_master_xmit() tests the slave's ndo_start_xmit() return value against NETDEV_TX_OK alone. A slave that consumes the skb but returns an error code (rc < NET_XMIT_MASK, e.g. NET_XMIT_DROP) is treated as if the skb were still owned, so the loop restores skb->dev and calls __skb_pull() on it. A VLAN slave returns the result of dev_queue_xmit() from its ndo_start_xmit() (propagated since cbbef5e18307), and when the lower qdisc drops the packet that value is NET_XMIT_DROP after the skb has been freed, giving a use-after-free in teql_master_xmit(). The Fix: test for consumption with dev_xmit_complete(), which accepts NETDEV_TX_OK and every rc < NET_XMIT_MASK, matching other callers that need to distinguish consumed results from busy. Only NETDEV_TX_OK keeps the packet counted in tx_packets/tx_bytes and advances the slave; a consumed result that is neither a success nor NET_XMIT_CN ends the attempt and is charged to tx_dropped, so lower-qdisc drops are no longer reported as good transmits. NET_XMIT_CN is counted like success because it can accompany acceptance of the current packet: pfifo_head_drop drops the old head, enqueues the current skb and returns NET_XMIT_CN. Conditions to recreate the bug: With CONFIG_NET_SCH_TEQL, CONFIG_VLAN_8021Q and CONFIG_DUMMY enabled, add lkrdummy0 (dummy) and lkrvlan0 (VLAN id 37 on lkrdummy0); set a pfifo limit 0 root on lkrdummy0 and a teql0 root on lkrvlan0; bring teql0 up with an address and send one UDP packet to it. The VLAN xmit returns NET_XMIT_DROP from the lower qdisc while holding a freed skb. Fixes: 572a9d7b6fc7 ("net: allow to propagate errors through ->ndo_hard_start_xmit()") Link: https://lore.kernel.org/netdev/179248763469.3135.1739559328868319628@kernel.org/ Link: https://lore.kernel.org/netdev/20261023162821.GN708417@horms.kernel.org/ Cc: stable@vger.kernel.org Signed-off-by: owen kwan Signed-off-by: Jamal Hadi Salim Reviewed-by: Simon Horman Link: https://patch.msgid.link/QDISC-Y0BY.v2.r0.20261024060625-2@mojatatu.com Signed-off-by: Paolo Abeni --- net/sched/sch_teql.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/net/sched/sch_teql.c b/net/sched/sch_teql.c index 6764f0dd38df..70c948a26f80 100644 --- a/net/sched/sch_teql.c +++ b/net/sched/sch_teql.c @@ -360,12 +360,18 @@ static netdev_tx_t teql_master_xmit(struct sk_buff *skb, struct net_device *dev) case 0: if (__netif_tx_trylock(slave_txq)) { unsigned int length = qdisc_pkt_len(skb); + int rc = NETDEV_TX_BUSY; skb->dev = slave; - if (!netif_xmit_frozen_or_stopped(slave_txq) && - netdev_start_xmit(skb, slave, slave_txq, false) == - NETDEV_TX_OK) { - __netif_tx_unlock(slave_txq); + if (!netif_xmit_frozen_or_stopped(slave_txq)) + rc = netdev_start_xmit(skb, slave, slave_txq, false); + __netif_tx_unlock(slave_txq); + if (dev_xmit_complete(rc)) { + if (rc != NETDEV_TX_OK && rc != NET_XMIT_CN) { + master->tx_dropped++; + rcu_read_unlock(); + return NETDEV_TX_OK; + } raw_spin_lock_irqsave(&master->slaves_lock, flags); if (rcu_dereference_protected(master->slaves, lockdep_is_held(&master->slaves_lock)) == q) @@ -379,7 +385,6 @@ static netdev_tx_t teql_master_xmit(struct sk_buff *skb, struct net_device *dev) rcu_read_unlock(); return NETDEV_TX_OK; } - __netif_tx_unlock(slave_txq); } if (netif_xmit_stopped(netdev_get_tx_queue(dev, 0))) busy = 1; From 14d01ff1a4952198abae713a4ad428e07819b4aa Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Tue, 27 Oct 2026 20:54:06 +0100 Subject: [PATCH 109/137] octeontx2-pf: fix skb use-after-free on ipsec transmit drop When an SA is removed while packets for it are still queued, the transmit path frees the packet but reports the queue as busy. The stack then requeues the freed skb, and the DMA mappings leak. Fixes: 6a77a158848a ("cn10k-ipsec: Process outbound ipsec crypto offload") Signed-off-by: Nicolai Buchwitz Reviewed-by: Ratheesh Kannoth Link: https://patch.msgid.link/20261027-nb-otx2-ipsec-tx-fixes-net-v1-1-2114c4f10c36@tipi-net.de Signed-off-by: Paolo Abeni --- drivers/net/ethernet/marvell/octeontx2/nic/cn10k_ipsec.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/cn10k_ipsec.c b/drivers/net/ethernet/marvell/octeontx2/nic/cn10k_ipsec.c index 77543d472345..3920cd098583 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/cn10k_ipsec.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/cn10k_ipsec.c @@ -1037,6 +1037,9 @@ bool cn10k_ipsec_transmit(struct otx2_nic *pf, struct netdev_queue *txq, cn10k_cpt_inst_flush(pf, &inst, sizeof(struct cpt_inst_s)); return true; drop: + otx2_dma_unmap_skb_frags(pf, &sq->sg[sq->head]); + sq->sg[sq->head].skb = (u64)NULL; + atomic_long_inc(&pf->hw.dev_stats.tx_discards); dev_kfree_skb_any(skb); - return false; + return true; } From 92cb8653970e4efaa7d2c2e2819c6d050b25e462 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Tue, 27 Oct 2026 20:54:07 +0100 Subject: [PATCH 110/137] octeontx2-pf: pass the skb to otx2_dma_unmap_skb_frags() After a DMA mapping error, the unmap reads an skb pointer that is not set yet. It can be NULL or left over from an earlier packet. Fixes: 195c3d463181 ("octeontx2-pf: map skb data as device writeable") Signed-off-by: Nicolai Buchwitz Acked-by: Ratheesh Kannoth Link: https://patch.msgid.link/20261027-nb-otx2-ipsec-tx-fixes-net-v1-2-2114c4f10c36@tipi-net.de Signed-off-by: Paolo Abeni --- .../net/ethernet/marvell/octeontx2/nic/cn10k_ipsec.c | 2 +- .../net/ethernet/marvell/octeontx2/nic/otx2_common.c | 5 ++--- .../net/ethernet/marvell/octeontx2/nic/otx2_common.h | 3 ++- drivers/net/ethernet/marvell/octeontx2/nic/otx2_txrx.c | 10 +++++----- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/cn10k_ipsec.c b/drivers/net/ethernet/marvell/octeontx2/nic/cn10k_ipsec.c index 3920cd098583..443435608781 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/cn10k_ipsec.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/cn10k_ipsec.c @@ -1037,7 +1037,7 @@ bool cn10k_ipsec_transmit(struct otx2_nic *pf, struct netdev_queue *txq, cn10k_cpt_inst_flush(pf, &inst, sizeof(struct cpt_inst_s)); return true; drop: - otx2_dma_unmap_skb_frags(pf, &sq->sg[sq->head]); + otx2_dma_unmap_skb_frags(pf, &sq->sg[sq->head], skb); sq->sg[sq->head].skb = (u64)NULL; atomic_long_inc(&pf->hw.dev_stats.tx_discards); dev_kfree_skb_any(skb); diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c index 4e418c7456b7..16f73797739d 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c @@ -2134,13 +2134,12 @@ dma_addr_t otx2_dma_map_skb_frag(struct otx2_nic *pfvf, return otx2_dma_map_page(pfvf, page, offset, *len, dir); } -void otx2_dma_unmap_skb_frags(struct otx2_nic *pfvf, struct sg_list *sg) +void otx2_dma_unmap_skb_frags(struct otx2_nic *pfvf, struct sg_list *sg, + struct sk_buff *skb) { enum dma_data_direction dir = DMA_TO_DEVICE; - struct sk_buff *skb = NULL; int seg; - skb = (struct sk_buff *)sg->skb; if (unlikely(xfrm_offload(skb))) dir = DMA_BIDIRECTIONAL; diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.h b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.h index 3c7275d545bb..8417af94a02a 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.h +++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.h @@ -1252,7 +1252,8 @@ static inline int mcam_entry_cmp(const void *a, const void *b) dma_addr_t otx2_dma_map_skb_frag(struct otx2_nic *pfvf, struct sk_buff *skb, int seg, int *len); -void otx2_dma_unmap_skb_frags(struct otx2_nic *pfvf, struct sg_list *sg); +void otx2_dma_unmap_skb_frags(struct otx2_nic *pfvf, struct sg_list *sg, + struct sk_buff *skb); int otx2_read_free_sqe(struct otx2_nic *pfvf, u16 qidx); void otx2_queue_vf_work(struct mbox *mw, struct workqueue_struct *mbox_wq, int first, int mdevs, u64 intr); diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_txrx.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_txrx.c index 8d2d607bc92f..66dc8cc15486 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_txrx.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_txrx.c @@ -160,7 +160,7 @@ static void otx2_snd_pkt_handler(struct otx2_nic *pfvf, *tx_bytes += skb->len; (*tx_pkts)++; - otx2_dma_unmap_skb_frags(pfvf, sg); + otx2_dma_unmap_skb_frags(pfvf, sg, skb); napi_consume_skb(skb, budget); sg->skb = (u64)NULL; } @@ -872,7 +872,7 @@ static int otx2_dma_map_tso_skb(struct otx2_nic *pfvf, } return 0; unmap: - otx2_dma_unmap_skb_frags(pfvf, sg); + otx2_dma_unmap_skb_frags(pfvf, sg, skb); return -EINVAL; } @@ -1252,7 +1252,7 @@ bool otx2_sq_append_skb(void *dev, struct netdev_queue *txq, ret = otx2_sqe_add_sg(pfvf, sq, skb, num_segs, &offset); if (!ret) { - otx2_dma_unmap_skb_frags(pfvf, &sq->sg[sq->head]); + otx2_dma_unmap_skb_frags(pfvf, &sq->sg[sq->head], skb); return false; } @@ -1344,7 +1344,7 @@ void otx2_cleanup_tx_cqes(struct otx2_nic *pfvf, struct otx2_cq_queue *cq) if (skb) { tx_bytes += skb->len; tx_pkts++; - otx2_dma_unmap_skb_frags(pfvf, sg); + otx2_dma_unmap_skb_frags(pfvf, sg, skb); dev_kfree_skb_any(skb); sg->skb = (u64)NULL; } @@ -1399,7 +1399,7 @@ void otx2_free_pending_sqe(struct otx2_nic *pfvf) if (skb) { tx_bytes += skb->len; tx_pkts++; - otx2_dma_unmap_skb_frags(pfvf, sg); + otx2_dma_unmap_skb_frags(pfvf, sg, skb); dev_kfree_skb_any(skb); sg->skb = (u64)NULL; } From 829a09df899dcce860dcf5f70e09c1b2d7d425ab Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Tue, 27 Oct 2026 19:44:09 +0100 Subject: [PATCH 111/137] octeontx2-pf: fix skb use-after-free with ipsec crypto offload otx2_dma_map_skb_frag() calls skb_unclone_full() on its own copy of the skb pointer. For a cloned skb, for example with tcpdump running on the interface, this frees the skb and the new copy is lost on return. The transmit path then uses the freed skb and the copy leaks. Make the skb writable in place in otx2_sq_append_skb() instead, so the pointer stays valid. Also do this for nonlinear skbs, as the hardware writes the encrypted data back into the frags, which may be shared, for example with the TCP retransmit queue. Fixes: 195c3d463181 ("octeontx2-pf: map skb data as device writeable") Signed-off-by: Nicolai Buchwitz Link: https://patch.msgid.link/20261027-nb-otx2-xfrm-unshare-net-v3-1-31dc9d79d11d@tipi-net.de Signed-off-by: Paolo Abeni --- drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c | 4 +--- drivers/net/ethernet/marvell/octeontx2/nic/otx2_txrx.c | 9 +++++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c index 16f73797739d..c85436457286 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c @@ -2115,10 +2115,8 @@ dma_addr_t otx2_dma_map_skb_frag(struct otx2_nic *pfvf, int offset; /* Crypto hardware need write permission for ipsec crypto offload */ - if (unlikely(xfrm_offload(skb))) { + if (unlikely(xfrm_offload(skb))) dir = DMA_BIDIRECTIONAL; - skb = skb_unclone_full(skb, GFP_ATOMIC); - } /* First segment is always skb->data */ if (!seg) { diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_txrx.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_txrx.c index 66dc8cc15486..90af9b9044aa 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_txrx.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_txrx.c @@ -1205,6 +1205,15 @@ bool otx2_sq_append_skb(void *dev, struct netdev_queue *txq, if (free_desc < otx2_get_sqe_count(pfvf, skb)) return false; + /* Crypto hardware need write permission for ipsec crypto offload */ + if (unlikely(xfrm_offload(skb)) && + (skb_cloned(skb) || skb_is_nonlinear(skb)) && + skb_ensure_writable(skb, skb->len)) { + atomic_long_inc(&pfvf->hw.dev_stats.tx_discards); + dev_kfree_skb_any(skb); + return true; + } + num_segs = skb_shinfo(skb)->nr_frags + 1; /* If SKB doesn't fit in a single SQE, linearize it. From 85b4bc79dde77aadda0929a22f3532b208a3e1f8 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Mon, 26 Oct 2026 09:23:45 +0800 Subject: [PATCH 112/137] r8152: Use BMSR to detect the link state r8152 detects carrier from PLA_PHYSTATUS without reading BMSR, so BMSR_LSTATUS can remain latched low after link up. Since commit f6f2e946aa4d ("net: mii: Fix the Speed display when the network cable is not connected"), the first speed query can then report SPEED_UNKNOWN, which applications such as NetworkManager may cache until the next carrier change. Use BMSR_LSTATUS in set_carrier() so a latched link down can turn carrier off after the link has recovered. If BMSR reports down but PLA_PHYSTATUS reports up, reschedule the link work to read the current state without waiting for another notification. Tested on an RTL8153B: NetworkManager reported 1000 Mb/s after 3 of 3 rebinds, instead of an unknown speed. Fixes: f6f2e946aa4d ("net: mii: Fix the Speed display when the network cable is not connected") Cc: stable@vger.kernel.org Suggested-by: Hayes Wang Signed-off-by: Linmao Li Reviewed-by: Hayes Wang Link: https://patch.msgid.link/20261026012345.1902285-1-lilinmao@kylinos.cn Signed-off-by: Paolo Abeni --- drivers/net/usb/r8152.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/net/usb/r8152.c b/drivers/net/usb/r8152.c index af3d7dcb2f14..ae861eb6d9a9 100644 --- a/drivers/net/usb/r8152.c +++ b/drivers/net/usb/r8152.c @@ -6958,11 +6958,8 @@ static void set_carrier(struct r8152 *tp) { struct net_device *netdev = tp->netdev; struct napi_struct *napi = &tp->napi; - u16 speed; - speed = rtl8152_get_speed(tp); - - if (speed & LINK_STATUS) { + if (r8152_mdio_read(tp, MII_BMSR) & BMSR_LSTATUS) { if (!netif_carrier_ok(netdev)) { tp->rtl_ops.enable(tp); netif_stop_queue(netdev); @@ -6988,6 +6985,11 @@ static void set_carrier(struct r8152 *tp) tasklet_enable(&tp->tx_tl); netif_info(tp, link, netdev, "carrier off\n"); } + + if (rtl8152_get_speed(tp) & LINK_STATUS) { + set_bit(RTL8152_LINK_CHG, &tp->flags); + schedule_delayed_work(&tp->schedule, 0); + } } } From 6815a2513effd1c6c0bf9c2f1c8c459e597d985d Mon Sep 17 00:00:00 2001 From: Dong Yibo Date: Mon, 26 Oct 2026 15:54:45 +0800 Subject: [PATCH 113/137] net: rnpgbe: Accept link-down events without speed magic The interface can continue reporting carrier up after the link goes down because the driver ignores the firmware's link-down notification. Firmware reports link-down events with a zero port_magic and no valid speed or duplex information. The driver requires ST_VALID_MAGIC for every link event and returns before updating the cached link state when this check fails. It also writes an invalid driver snapshot to firmware instead of acknowledging the link-down state. Validate the speed magic only for link-up events. This allows link-down events to update the cached state, propagate carrier loss, and update the snapshot exported to firmware. Keep the checks for invalid link-up magic and unsupported speeds. Fixes: 92a671d510a8 ("net: rnpgbe: Add link status handling support") Signed-off-by: Dong Yibo Reviewed-by: Simon Horman Link: https://patch.msgid.link/C2DFBBF04F1DCFD2+20261026075445.920572-1-dong100@mucse.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.c index 683e0ea74113..c6ce9dbee3d0 100644 --- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.c +++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.c @@ -392,8 +392,11 @@ static void mucse_mbx_fw_req_handler(struct mucse_hw *hw, u16 speed = le16_to_cpu(req->link_stat.st.speed); spin_lock_irqsave(&mucse->link_lock, flags); - if (magic != ST_VALID_MAGIC) { - /* Do not change the cached state for an invalid event. + /* Link-down events do not carry valid speed/duplex information + * and may have a zero port_magic. Validate it only for link up. + */ + if (mucse_link_is_up(hw, req) && magic != ST_VALID_MAGIC) { + /* Keep the cached state for an invalid link-up event. * Use an invalid speed encoding to make firmware report * again. */ From d7314b51341c037973ce022f12c6009fd07b021e Mon Sep 17 00:00:00 2001 From: "Noam D. Eliyahu" Date: Tue, 27 Oct 2026 00:09:56 +0200 Subject: [PATCH 114/137] tg3: Invert restart quirk logic to restrict SNP power-down bypass Commit 9fc3bc764334 ("tg3: power down device only on SYSTEM_POWER_OFF") bypassed `tg3_power_down()` on system restarts to prevent reboot hangs and NMIs on servers where NICs were initialized via SNP (such as Dell PowerEdge R650 and R750). However, skipping the power-down flow globally causes the ACPI _PTS method to trigger fatal PCIe AER events and hangs on other models. Commit e0efe83ed325 ("tg3: Disable tg3 PCIe AER on system reboot") attempted to mitigate this by adding an AER-disable DMI quirk table. Maintaining a growing list of models for a secondary symptom misses the root cause, leaving systems like the PowerEdge T440 (sharing the BCM5720 NIC) exposed. Fix this by inverting the quirk logic: 1. Restrict SNP power-down avoidance strictly to `tg3_restart_snp_quirk_table` for the specific systems requiring it (Dell PowerEdge R650 and R750). 2. Restore standard power-down behavior for all other systems, eliminating the separate AER-disable quirk table. For systems affected by the original SNP restart issue, their shutdown flow remains completely untouched. Link: https://lore.kernel.org/netdev/20251221175050.14089-1-noam.d.eliyahu@gmail.com/T/#u Fixes: 9fc3bc764334 ("tg3: power down device only on SYSTEM_POWER_OFF") Fixes: e0efe83ed325 ("tg3: Disable tg3 PCIe AER on system reboot") Cc: stable@vger.kernel.org Signed-off-by: Noam D. Eliyahu Reviewed-by: Pavan Chebbi Link: https://patch.msgid.link/20261026220955.473247-1-noam.d.eliyahu@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/broadcom/tg3.c | 48 +++++++---------------------- 1 file changed, 11 insertions(+), 37 deletions(-) diff --git a/drivers/net/ethernet/broadcom/tg3.c b/drivers/net/ethernet/broadcom/tg3.c index ce108705bbaf..95827df4cf6a 100644 --- a/drivers/net/ethernet/broadcom/tg3.c +++ b/drivers/net/ethernet/broadcom/tg3.c @@ -18256,53 +18256,23 @@ static int tg3_resume(struct device *device) static SIMPLE_DEV_PM_OPS(tg3_pm_ops, tg3_suspend, tg3_resume); -/* Systems where ACPI _PTS (Prepare To Sleep) S5 will result in a fatal - * PCIe AER event on the tg3 device if the tg3 device is not, or cannot - * be, powered down. +/* Systems where powering down a tg3 device on restarts triggers + * NMIs and fatal hangs during reboot if the network adapters were + * initialized under SNP. */ -static const struct dmi_system_id tg3_restart_aer_quirk_table[] = { - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "PowerEdge R440"), - }, - }, - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "PowerEdge R540"), - }, - }, - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "PowerEdge R640"), - }, - }, +static const struct dmi_system_id tg3_restart_snp_quirk_table[] = { { .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), DMI_MATCH(DMI_PRODUCT_NAME, "PowerEdge R650"), }, }, - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "PowerEdge R740"), - }, - }, { .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), DMI_MATCH(DMI_PRODUCT_NAME, "PowerEdge R750"), }, }, - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "datto"), - DMI_MATCH(DMI_PRODUCT_NAME, "SIRIS 5 1U Server"), - }, - }, {} }; @@ -18320,14 +18290,18 @@ static void tg3_shutdown(struct pci_dev *pdev) if (netif_running(dev)) dev_close(dev); - if (system_state == SYSTEM_POWER_OFF) + if (!dmi_first_match(tg3_restart_snp_quirk_table) || + system_state == SYSTEM_POWER_OFF) { tg3_power_down(tp); - else if (system_state == SYSTEM_RESTART && - dmi_first_match(tg3_restart_aer_quirk_table) && + } else if (system_state == SYSTEM_RESTART && pdev->current_state != PCI_D3cold && pdev->current_state != PCI_UNKNOWN) { /* Disable PCIe AER on the tg3 to avoid a fatal * error during this system restart. + * Needed since on restarts for systems in + * tg3_restart_snp_quirk_table skipping on powering down the + * tg3 device causes a fatal AER error during ACPI _PTS + * (Prepare To Sleep). */ pcie_capability_clear_word(pdev, PCI_EXP_DEVCTL, PCI_EXP_DEVCTL_CERE | From db627e71b6da4416324a2eb599d12fa96959d478 Mon Sep 17 00:00:00 2001 From: Anil Kaushik Date: Tue, 27 Oct 2026 13:06:23 +0000 Subject: [PATCH 115/137] gtp: Require 'CAP_NET_ADMIN' when joining the gtp multicast group The gtp generic netlink family sends notifications on its "gtp" multicast group when a PDP context is added or deleted, and for GTP-U echo responses. They carry the same attributes as a GETPDP reply: the MS and peer addresses, and the TEIDs, or the TID and the flow label. GETPDP requires CAP_NET_ADMIN, but generic netlink lets any user join a multicast group by default, so an unprivileged user in the network namespace of the gtp device can read all of this. Found by code inspection. Fix by marking the group with GENL_MCAST_CAP_NET_ADMIN, like the psample "packets" group, and with uns-admin-perm in the gtp netlink spec. Only users with CAP_NET_ADMIN in the user namespace owning the network namespace can then join it. This is still weaker than the check on GETPDP, which needs CAP_NET_ADMIN in the initial user namespace: in a network namespace owned by another user namespace, its root can join the group, but not use GETPDP. Genetlink has no multicast group flag for the initial user namespace, and ioam6 pairs its ops and its group the same way. This fix blocks the unprivileged users. Tested by joining the group as root, as root without CAP_NET_ADMIN, and as an unprivileged user: Before: root: joined root without CAP_NET_ADMIN: joined nobody: joined After: root: joined root without CAP_NET_ADMIN: EPERM nobody: EPERM Fixes: 50aba46c234e ("gtp: add notification mechanism") Reviewed-by: Nicolas Dichtel Signed-off-by: Anil Kaushik Reviewed-by: Simon Horman Link: https://patch.msgid.link/20261027130623.3136658-1-anilkaushikwireless@gmail.com Signed-off-by: Paolo Abeni --- Documentation/netlink/specs/gtp.yaml | 1 + drivers/net/gtp.c | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/netlink/specs/gtp.yaml b/Documentation/netlink/specs/gtp.yaml index 7871f5def054..b65e18ae5df2 100644 --- a/Documentation/netlink/specs/gtp.yaml +++ b/Documentation/netlink/specs/gtp.yaml @@ -194,3 +194,4 @@ mcast-groups: list: - name: gtp + flags: [uns-admin-perm] diff --git a/drivers/net/gtp.c b/drivers/net/gtp.c index 86c8c88b702c..1dd32e979cf5 100644 --- a/drivers/net/gtp.c +++ b/drivers/net/gtp.c @@ -118,7 +118,8 @@ enum gtp_multicast_groups { }; static const struct genl_multicast_group gtp_genl_mcgrps[] = { - [GTP_GENL_MCGRP] = { .name = GTP_GENL_MCGRP_NAME }, + [GTP_GENL_MCGRP] = { .name = GTP_GENL_MCGRP_NAME, + .flags = GENL_MCAST_CAP_NET_ADMIN, }, }; static void pdp_context_delete(struct pdp_ctx *pctx); From 8daacee78b3d2798fcf92398b6dff364eab64466 Mon Sep 17 00:00:00 2001 From: Jamie Nguyen Date: Tue, 27 Oct 2026 09:29:45 -0700 Subject: [PATCH 116/137] selftests: net: txtimestamp: fix out-of-bounds read in raw UDP checksum With -r (SOCK_RAW, IPPROTO_UDP) the test writes only the UDP header and payload, and the kernel adds the IP header. get_udp_csum() still expects the IP source and destination addresses to directly precede the UDP header, and reads 2 * alen bytes before the start of the buffer: 8 bytes for IPv4, 32 bytes for IPv6. ASan reports a heap-buffer-overflow for both -4 -r and -6 -r. Without ASan the read usually goes unnoticed, but it faults when the buffer is the first chunk in the heap. That happens on Ubuntu 26.04, where the kselftest runner wraps the test in uutils stdbuf, and txtimestamp.sh fails with SIGSEGV on every run. Even without a crash, -r packets were sent with a checksum computed over unrelated heap memory, so their UDP checksum was invalid. Build the pseudo-header from the destination address and checksum only the UDP header and payload. -R and -P already write daddr as both the source and destination address, so their checksums are unchanged. With -r the kernel selects the source address, which equals daddr for the loopback addresses used by txtimestamp.sh. Fixes: b52354aa068e ("selftests: expand txtimestamp with ipv6 dgram + raw and pf_packet") Signed-off-by: Jamie Nguyen Reviewed-by: Willem de Bruijn Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20261027162945.2122306-1-jamien@nvidia.com Signed-off-by: Paolo Abeni --- tools/testing/selftests/net/txtimestamp.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/tools/testing/selftests/net/txtimestamp.c b/tools/testing/selftests/net/txtimestamp.c index 170be192f5c7..f7bdf30b78d9 100644 --- a/tools/testing/selftests/net/txtimestamp.c +++ b/tools/testing/selftests/net/txtimestamp.c @@ -418,19 +418,25 @@ static uint16_t get_ip_csum(const uint16_t *start, int num_words, return ~sum; } -static uint16_t get_udp_csum(const struct udphdr *udph, int alen) +static uint16_t get_udp_csum(const struct udphdr *udph, bool is_ipv4) { - unsigned long pseudo_sum, csum_len; - const void *csum_start = udph; + const void *addr = is_ipv4 ? (void *)&daddr.sin_addr : &daddr6.sin6_addr; + int alen = is_ipv4 ? sizeof(struct in_addr) : sizeof(struct in6_addr); + uint16_t addr16[sizeof(struct in6_addr) / sizeof(uint16_t)]; + unsigned long pseudo_sum; + int i; pseudo_sum = htons(IPPROTO_UDP); pseudo_sum += udph->len; - /* checksum ip(v6) addresses + udp header + payload */ - csum_start -= alen * 2; - csum_len = ntohs(udph->len) + alen * 2; + /* pseudo-header source and destination: both are daddr */ + memcpy(addr16, addr, alen); + for (i = 0; i < alen / 2; i++) + pseudo_sum += 2 * addr16[i]; - return get_ip_csum(csum_start, csum_len >> 1, pseudo_sum); + /* checksum udp header + payload */ + return get_ip_csum((const uint16_t *)udph, ntohs(udph->len) >> 1, + pseudo_sum); } static int fill_header_ipv4(void *p) @@ -479,8 +485,7 @@ static void fill_header_udp(void *p, bool is_ipv4) udph->len = ntohs(sizeof(*udph) + cfg_payload_len); udph->check = 0; - udph->check = get_udp_csum(udph, is_ipv4 ? sizeof(struct in_addr) : - sizeof(struct in6_addr)); + udph->check = get_udp_csum(udph, is_ipv4); } static void do_test(int family, unsigned int report_opt) From dda51b73721684637ab8e9bd97c78228b99c4bd2 Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Tue, 27 Oct 2026 12:46:49 +0800 Subject: [PATCH 117/137] tcp: check congestion control restriction first tcp_set_default_congestion_control() takes a reference to an algorithm before checking whether a non-init network namespace may select it. A rejected request returns -EPERM without dropping the reference. This affects module-backed algorithms too; BPF is not required. For example, after root loads tcp_bbr, a user with CAP_NET_ADMIN in a non-init network namespace can repeatedly request it as the default although it is not in tcp_allowed_congestion_control. Each rejected write leaks a module reference. Since module->refcnt is an atomic_t, 2^31 such writes can make it negative; later module_put() calls warn, and rmmod can hit the BUG_ON() in try_release_module_ref(). Check the namespace restriction before acquiring the reference, as tcp_set_congestion_control() already does. Fixes: 8d432592f30f ("net: Only allow init netns to set default tcp cong to a restricted algo") Cc: stable@vger.kernel.org Reported-by: Vega Suggested-by: Eric Dumazet Signed-off-by: Zhiling Zou Reviewed-by: Kuniyuki Iwashima Reviewed-by: Eric Dumazet Reviewed-by: Jiayuan Chen Link: https://patch.msgid.link/71c142b2d45af6a2fe7fc6cd4a5ca52c53afbe23.1793075984.git.zhilinz@nebusec.ai Signed-off-by: Paolo Abeni --- net/ipv4/tcp_cong.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/ipv4/tcp_cong.c b/net/ipv4/tcp_cong.c index 8e83ef81fc18..e3f462f161dd 100644 --- a/net/ipv4/tcp_cong.c +++ b/net/ipv4/tcp_cong.c @@ -288,12 +288,12 @@ int tcp_set_default_congestion_control(struct net *net, const char *name) ca = tcp_ca_find_autoload(name); if (!ca) { ret = -ENOENT; - } else if (!bpf_try_module_get(ca, ca->owner)) { - ret = -EBUSY; } else if (!net_eq(net, &init_net) && !(ca->flags & TCP_CONG_NON_RESTRICTED)) { /* Only init netns can set default to a restricted algorithm */ ret = -EPERM; + } else if (!bpf_try_module_get(ca, ca->owner)) { + ret = -EBUSY; } else { prev = xchg(&net->ipv4.tcp_congestion_control, ca); if (prev) From fd0909e963ed8f36967bd072ed484be7babe46de Mon Sep 17 00:00:00 2001 From: Naman Gulati Date: Mon, 26 Oct 2026 22:13:06 +0000 Subject: [PATCH 118/137] netdev-genl: ignore NETDEV_UNREGISTER rebroadcasts netdev_wait_allrefs_any() re-sends NETDEV_UNREGISTER every second while it waits for the last references on a device to be dropped. By then netdev_run_todo() has already marked the device NETREG_UNREGISTERED and RTNL has been dropped at least once since the initial notification sent by unregister_netdevice_many_notify(). netdev_genl_netdevice_event() does not tell these rebroadcasts apart from the initial notification and sends a NETDEV_CMD_DEV_DEL_NTF multicast for each of them. At best this spams NETDEV_NLGRP_MGMT listeners with duplicate notifications; at worst the pernet state of the netns the device belongs to is already gone and the handler dereferences a NULL pointer. default_device_exit_batch() only unregisters the devices still listed in the exiting netns; it does not wait for in-flight unregistrations of devices that were already unlisted (the barrier doing that was removed by commit 8a4fc54b07d7 ("net: get rid of rtnl_lock_unregistering()")). So when a device is unregistered by something other than the cleanup_net() of its own netns while references on it are still held, e.g. a veth whose peer in another netns gets deleted, and that netns exits while the device is stuck in netdev_wait_allrefs_any(), the remaining pernet exit handlers run and genl_pernet_exit() sets net->genl_sock to NULL. The next rebroadcast then crashes in genl_has_listeners(): BUG: KASAN: null-ptr-deref in netlink_has_listeners+0x3d/0x3f0 net/netlink/af_netlink.c:1388 Read of size 8 at addr 0000000000000578 by task syz.0.0/5325 Call Trace: netlink_has_listeners+0x3d/0x3f0 net/netlink/af_netlink.c:1388 genl_has_listeners include/net/genetlink.h:644 [inline] netdev_genl_dev_notify+0x161/0x450 net/core/netdev-genl.c:93 netdev_genl_netdevice_event+0x98/0x3c0 net/core/netdev-genl.c:1491 notifier_call_chain+0x1a5/0x3d0 kernel/notifier.c:85 netdev_wait_allrefs_any net/core/dev.c:11690 [inline] netdev_run_todo+0x731/0x1060 net/core/dev.c:11808 rtnl_unlock net/core/rtnetlink.c:157 [inline] rtnl_dellink+0x68f/0x810 net/core/rtnetlink.c:3707 syzbot hit this by moving one end of a veth pair into a short-lived netns while netdevsim's deferred FIB event processing still held a reference on it, and then deleting the other end (repro [1]). While the source of this bug is netdevsim deferring FIB processing for and holding onto the device reference for O(seconds) which is being fixed separately [2], any driver may introduce such a delay and trigger the null-ptr-deref. Skip the rebroadcasts, which are the only NETDEV_UNREGISTER events delivered with reg_state == NETREG_UNREGISTERED. The notifications sent by unregister_netdevice_many_notify() and, for a device leaving a netns, by __dev_change_net_namespace() are issued under RTNL in the same critical section that unlists the device, before its netns can be torn down, and keep being handled. This mirrors what ip6_route_dev_notify() already does to cope with the rebroadcasts. This change should not break the UAPI spec as it defines dev-del-ntf as a "Notification about device disappearing." which is a single event. The rebroadcast in netdev_wait_allrefs_any() exists to re-kick in-kernel subsystems holding references; netdev-genl happens to be on the notifier chain and forwards it. [1] https://gist.github.com/NamanGulati/2d1b49b8757f9a88b0bd6e55018c02ae [2] https://lore.kernel.org/r/20261023191517.607834-1-kuniyu@google.com Fixes: d3d854fd6a1d ("netdev-genl: create a simple family for netdev stuff") Reported-by: syzbot+1269fe6cc2016008124e@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6adf9f64.6200f071.51519.0ed1.GAE@google.com/ Reviewed-by: Kuniyuki Iwashima Signed-off-by: Naman Gulati Link: https://patch.msgid.link/20261026221306.3151025-1-namangulati@google.com Signed-off-by: Jakub Kicinski --- net/core/netdev-genl.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/core/netdev-genl.c b/net/core/netdev-genl.c index c3aa367214ac..384fdc5ae514 100644 --- a/net/core/netdev-genl.c +++ b/net/core/netdev-genl.c @@ -1506,6 +1506,12 @@ static int netdev_genl_netdevice_event(struct notifier_block *nb, netdev_unlock_full_to_ops(netdev); break; case NETDEV_UNREGISTER: + /* netdev_wait_allrefs_any() rebroadcasts NETDEV_UNREGISTER + * while waiting for the last references to be dropped; the + * netns may already be torn down by then. Notify only once. + */ + if (READ_ONCE(netdev->reg_state) == NETREG_UNREGISTERED) + break; netdev_lock(netdev); netdev_genl_dev_notify(netdev, NETDEV_CMD_DEV_DEL_NTF); netdev_unlock(netdev); From db2c610df81e34d497f49b68a0a1681c3f203bdb Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Tue, 27 Oct 2026 16:49:36 -0700 Subject: [PATCH 119/137] eth: bnxt: recycle agg buffers when Rx head buffer allocation fails bnxt_rx_pkt() tries to return the aggregation buffers of a packet to the ring when the copybreak allocation fails, but the same logic is missing on the rx_skb_func() failure path. The agg BDs consumed by the device are then never posted again. The agg ring is only refilled one-for-one as completions consume its buffers, so each such failure permanently shrinks it. Under sustained memory pressure the ring drains completely, and the queue stops receiving anything that does not fit into the head buffer, even once memory is available again. Only re-creating the rings (e.g. ifdown / ifup) recovers it. Commit 296d5b541639 ("bnxt_en: Fix aggregation buffer leak under OOM condition.") fixed the copybreak path only. Recently commit bd3beb5c8691 ("eth: bnxt: disable rx-copybreak by default") made the non-copybreak path the default, re-surfacing the issue. With XDP, the agg buffers are attached to the xdp_buff as frags by this point. Their agg ring slots have already been refilled. We need to keep the original head page (and therefore shinfo) around until the xdp_buff cleanup. Note the odd split of ownership here: rx_skb_func() only deals with the head buffer, the agg buffers stay with the caller in whatever state they are in, still on the ring without XDP, or frags of the xdp_buff with XDP. This complicates rx_skb_func() cleaning up all the buffers (head and frags), which would be more intuitive. Discovered in production, severe packet loss would persist after a transient OOM event. Tested on a BCM57504 by failing the head buffer allocations in bnxt_rx_skb() (bpftrace override() of should_failslab() and should_fail_alloc_page()) while receiving 1400B ICMPv6 echo requests. 341k packets were dropped on that queue, enough to drain its 8188 entry agg ring 40 times over, and the queue kept receiving large frames afterwards with no loss and no new HW drops. With a frags-capable XDP_PASS program at MTU 9000, receiving 8000B ICMPv6 echo requests while napi_build_skb() called from bnxt_rx_multi_page_skb() failed, 9.2k packets were dropped, the page pools' inflight count stayed flat and there were no KASAN or DEBUG_PAGEALLOC splats. Fixes: c0c050c58d84 ("bnxt_en: New Broadcom ethernet driver.") Fixes: 4c6c123c9af9 ("bnxt: adding bnxt_rx_agg_pages_xdp for aggregated xdp") Reviewed-by: Michael Chan Link: https://patch.msgid.link/20261027234940.1267225-2-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 33 ++++++++++++++--------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 0682060b048c..2e7276801fdd 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -1215,7 +1215,15 @@ static struct sk_buff *bnxt_rx_multi_page_skb(struct bnxt *bp, bp->rx_dir); skb = napi_build_skb(frag_start, rxr->rx_page_size); if (!skb) { - page_pool_recycle_direct(rxr->page_pool, page); + struct page *new_page; + + new_page = rxr->rx_buf_ring[RING_RX(bp, prod)].data; + /* The caller may still need the XDP frags from shinfo at the + * end of the head page. Release the new buffer instead and + * put the head back on the ring. + */ + page_pool_recycle_direct(rxr->page_pool, new_page); + bnxt_reuse_rx_data(rxr, cons, data); return NULL; } skb_mark_for_recycle(skb); @@ -2313,17 +2321,8 @@ static int bnxt_rx_pkt(struct bnxt *bp, struct bnxt_cp_ring_info *cpr, skb = bnxt_copy_xdp(bnapi, &bnxt_xdp.xdp, len, dma_addr); bnxt_reuse_rx_data(rxr, cons, data); - if (!skb) { - if (agg_bufs) { - if (!xdp_active) - bnxt_reuse_rx_agg_bufs(cpr, cp_cons, 0, - agg_bufs, false); - else - bnxt_xdp_buff_frags_free(rxr, - &bnxt_xdp.xdp); - } - goto oom_next_rx; - } + if (!skb) + goto oom_next_rx_agg; } else { u32 payload; @@ -2334,7 +2333,7 @@ static int bnxt_rx_pkt(struct bnxt *bp, struct bnxt_cp_ring_info *cpr, skb = bp->rx_skb_func(bp, rxr, cons, data, data_ptr, dma_addr, payload | len); if (!skb) - goto oom_next_rx; + goto oom_next_rx_agg; } if (agg_bufs) { @@ -2424,6 +2423,14 @@ static int bnxt_rx_pkt(struct bnxt *bp, struct bnxt_cp_ring_info *cpr, return rc; +oom_next_rx_agg: + if (agg_bufs) { + if (!xdp_active) + bnxt_reuse_rx_agg_bufs(cpr, cp_cons, 0, agg_bufs, + false); + else + bnxt_xdp_buff_frags_free(rxr, &bnxt_xdp.xdp); + } oom_next_rx: cpr->sw_stats->rx.rx_oom_discards += 1; rc = -ENOMEM; From 09f10efee6f93863ea71489ce84563f4d51787f6 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Tue, 27 Oct 2026 16:49:37 -0700 Subject: [PATCH 120/137] eth: bnxt: resize Rx head buffers when rx-copybreak changes Since rx-copybreak became configurable, bnxt_set_ring_params() sizes the head buffers to fit at least rx_copybreak bytes, and the copybreak path in bnxt_copy_data() syncs rx_copybreak bytes of the head buffer. bnxt_set_tunable() however only updates bp->rx_copybreak, and nothing on the open path recalculates the ring parameters. After raising rx-copybreak the head buffers keep their old size until some other setting changes, and bnxt_copy_data() syncs past their end, into the next buffer in the page or past the end of the page: DMA-API: bnxt_en 0000:01:00.0: device driver tries to sync DMA memory it has not allocated [device address=0x00000002aa210c40] [size=1024 bytes] WARNING: kernel/dma/debug.c:1162 at check_sync+0x577/0x1eb0, CPU#22: swapper/22/0 Call Trace: debug_dma_sync_single_for_cpu+0x66/0x90 bnxt_copy_data+0x12f/0x290 bnxt_rx_pkt+0x24ea/0x50f0 __bnxt_poll_work+0x42c/0xc00 bnxt_poll_p5+0x579/0x17a0 __napi_poll+0x8b/0x2c0 net_rx_action+0x58f/0xbb0 Here the head buffers were still sized for the 640B HDS threshold, four 1kB buffers per page, while rx-copybreak was raised to 1024. Without coherent DMA or with swiotlb the oversized sync can corrupt a neighboring buffer owned by the device or by an skb. The device must be down for rx-copybreak to change, so recalculate the ring parameters right away, like bnxt_set_ringparam() does. Hit while testing patch 1, tested on BCM57504 - boot; set rx-copybreak to 1024 with the link down (CONFIG_DMA_API_DEBUG); ifup. Fixes: 152f4da05aee ("bnxt_en: add support for rx-copybreak ethtool command") Reviewed-by: Michael Chan Link: https://patch.msgid.link/20261027234940.1267225-3-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c b/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c index 622e89587e5d..ef4bea4a0f8c 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c @@ -4601,6 +4601,7 @@ static int bnxt_set_tunable(struct net_device *dev, if (netif_running(dev)) return -EBUSY; bp->rx_copybreak = rx_copybreak; + bnxt_set_ring_params(bp); } return 0; case ETHTOOL_PFC_PREVENTION_TOUT: From 1b2f02fb4ba92d01603c8261c94be8d348de65b0 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Tue, 27 Oct 2026 16:49:38 -0700 Subject: [PATCH 121/137] eth: bnxt: don't leak buffers when XDP agg buffer allocation fails If a replacement agg buffer can't be allocated for a packet received with XDP attached, bnxt_rx_pkt() drops the packet without returning its head buffer to the ring. The head buffer leaks, and since rx_prod still advances, the device gets a BD with a stale DMA address which points at a buffer the driver no longer owns. When that BD completes the driver finds no buffer in the slot. The agg buffers consumed before the failing one have already been replaced on the agg ring, they are referenced only by the frags of the xdp_buff, which nobody frees. bnxt_xdp_buff_frags_free() would skip them since the frags flag is set only on success, so set it upfront. Pointed out in LLM review of the first patch of the series. Tested on a BCM57504 at MTU 9000 with a frags-capable XDP_PASS program, receiving 8000B ICMPv6 echo requests (a head and two agg buffers each) while page_pool_alloc_netmems() called from bnxt_rx_agg_netmems_xdp() failed half of the time (bpftrace override()). 44.7k packets were dropped, the page pools' inflight count stayed flat and there were no KASAN or DEBUG_PAGEALLOC splats. Fixes: 4c6c123c9af9 ("bnxt: adding bnxt_rx_agg_pages_xdp for aggregated xdp") Reviewed-by: Michael Chan Link: https://patch.msgid.link/20261027234940.1267225-4-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 2e7276801fdd..4607c31eedf8 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -1427,16 +1427,19 @@ static u32 bnxt_rx_agg_netmems_xdp(struct bnxt *bp, struct skb_shared_info *shinfo = xdp_get_shared_info_from_buff(xdp); u32 total_frag_len = 0; - if (!xdp_buff_has_frags(xdp)) + if (!xdp_buff_has_frags(xdp)) { + xdp_buff_set_frags_flag(xdp); shinfo->nr_frags = 0; + } total_frag_len = __bnxt_rx_agg_netmems(bp, cpr, idx, agg_bufs, tpa, NULL, xdp); - if (total_frag_len) { - xdp_buff_set_frags_flag(xdp); - shinfo->nr_frags = agg_bufs; - shinfo->xdp_frags_size = total_frag_len; + if (!total_frag_len) { + bnxt_xdp_buff_frags_free(cpr->bnapi->rx_ring, xdp); + return 0; } + shinfo->nr_frags = agg_bufs; + shinfo->xdp_frags_size = total_frag_len; return total_frag_len; } @@ -2293,8 +2296,10 @@ static int bnxt_rx_pkt(struct bnxt *bp, struct bnxt_cp_ring_info *cpr, cp_cons, agg_bufs, false); - if (!frag_len) + if (!frag_len) { + bnxt_reuse_rx_data(rxr, cons, data); goto oom_next_rx; + } } xdp_active = true; From c10f3420d59859df107838aa97b49357616d9b47 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Tue, 27 Oct 2026 16:49:39 -0700 Subject: [PATCH 122/137] eth: bnxt: free XDP frags when xdp_do_redirect() fails When xdp_do_redirect() fails bnxt_rx_xdp() recycles the head page but not the frags of a multi-buffer frame. Their agg ring slots have already been refilled, so the pages leak, and the page pool can't be destroyed until they are returned, which will never happen. Redirect to an AF_XDP socket in copy mode fails this way with -ENOMEM whenever the socket's fill ring runs out of buffers. A socket bound without XDP_USE_SG also fails with -ENOSPC for every frame that doesn't fit in a single chunk. All the other drop paths in this function free the frags. Pointed out in LLM review of the first patch of the series. Tested on a BCM57504 at MTU 9000 by redirecting 60k 8000B ICMPv6 echo requests (a head and two agg buffers each) to lo, which can't transmit XDP frames. The page pools' inflight count stayed flat rather than growing by two pages per frame. Fixes: a7559bc8c17c ("bnxt: support transmit and free of aggregation buffers") Reviewed-by: Michael Chan Link: https://patch.msgid.link/20261027234940.1267225-5-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.c b/drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.c index 96845c8646e0..161e0dd62be6 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.c @@ -307,6 +307,7 @@ bool bnxt_rx_xdp(struct bnxt *bp, struct bnxt_rx_ring_info *rxr, u16 cons, if (xdp_do_redirect(bp->dev, xdp, xdp_prog)) { trace_xdp_exception(bp->dev, xdp_prog, act); + bnxt_xdp_buff_frags_free(rxr, xdp); page_pool_recycle_direct(rxr->page_pool, page); return true; } From 364b88b54fc260d22ee318bae862c2d7d8c31dc6 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Tue, 27 Oct 2026 16:49:40 -0700 Subject: [PATCH 123/137] eth: bnxt: hand XDP frags over to the copybreak skb With a multi-buffer XDP program attached, a packet only has agg buffers if it filled its head buffer, which in page mode holds most of a page. The program can still move the start of the packet forward with bpf_xdp_adjust_head() and leave at most rx_copybreak bytes in the head. bnxt_rx_pkt() then copies the head into a new skb, puts the head page back on the ring, and calls bnxt_xdp_build_skb() on the copy. bnxt_xdp_build_skb() only sets nr_frags and the skb's length accounting, because it expects an skb built around the head page, sharing its shinfo with the xdp_buff. The copy ends up with nr_frags set but no frag descriptors, and the stack later reads and releases whatever its frags array happens to contain. The real frags leak. Copy the frag descriptors to the new skb in bnxt_copy_xdp(), so that bnxt_xdp_build_skb() finds the frags where it expects them. The frags now belong to the skb, and the head page goes back on the ring as before. Pointed out in LLM review of the first patch of the series. Confirmed by reading the code, the unfixed kernel was not run with the reproducer below. Tested on a BCM57504 at MTU 9000 with rx-copybreak raised to 1024 and a frags-capable XDP program moving the start of multi-buffer frames 3000B forward with bpf_xdp_adjust_head(), receiving 8000B ICMPv6 echo requests (a head and two agg buffers each). All 60k frames took the copybreak path with frags attached, the sampled skbs reached GRO with len=4998 data_len=4492 nr_frags=2, and were freed by the stack with the page pools' inflight count staying flat, no KASAN or DEBUG_PAGEALLOC splats. Fixes: 1dc4c557bfed ("bnxt: adding bnxt_xdp_build_skb to build skb from multibuffer xdp_buff") Reviewed-by: Michael Chan Link: https://patch.msgid.link/20261027234940.1267225-6-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 4607c31eedf8..757c7266ce15 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -1511,6 +1511,14 @@ static struct sk_buff *bnxt_copy_xdp(struct bnxt_napi *bnapi, __skb_pull(skb, metasize); } + if (unlikely(xdp_buff_has_frags(xdp))) { + struct skb_shared_info *sinfo; + + sinfo = xdp_get_shared_info_from_buff(xdp); + memcpy(skb_shinfo(skb)->frags, sinfo->frags, + sinfo->nr_frags * sizeof(skb_frag_t)); + } + return skb; } From dc4dd12f80e499dc034710cd8195489abcf0b059 Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Wed, 28 Oct 2026 19:14:50 +0100 Subject: [PATCH 124/137] net/packet: check TX_RING fragments before memory accounting tpacket_fill_skb() adds the full non-linear payload length to skb->truesize and sk_wmem_alloc before checking MAX_SKB_FRAGS. With PACKET_VNET_HDR, userspace can submit a ring-sized frame even when it greatly exceeds the device MTU. A near-INT_MAX frame can saturate sk_wmem_alloc before the fragment loop rejects it. A subsequent refcount_add(0x40000001) can temporarily turn REFCOUNT_SATURATED into 1. A concurrent TX completion can then drop that apparent last reference and free the still-live packet socket before refcount_warn_saturate() restores the counter. Check that the remaining payload fits in MAX_SKB_FRAGS pages before accounting it. Include the initial page offset in the calculation, and move the existing -EFAULT handling out of the fragment loop. This preserves the accepted packet sizes and error code while preventing an invalid frame from injecting an oversized byte charge. Fixes: 69e3c75f4d54 ("net: TX_RING and packet mmap") Cc: stable@vger.kernel.org Cc: Willem de Bruijn Signed-off-by: Kyle Zeng [edumazet: use net_warn_ratelimited() instead of pr_err(), add a Fixes: tag] Signed-off-by: Eric Dumazet Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20261028181450.2221013-1-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- net/packet/af_packet.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c index 823e0911c910..cf6429f09477 100644 --- a/net/packet/af_packet.c +++ b/net/packet/af_packet.c @@ -2655,6 +2655,12 @@ static int tpacket_fill_skb(struct packet_sock *po, struct sk_buff *skb, } offset = offset_in_page(data); + if (unlikely(to_write > MAX_SKB_FRAGS * PAGE_SIZE - offset)) { + net_warn_ratelimited("Packet exceed the number of skb frags(%u)\n", + (unsigned int)MAX_SKB_FRAGS); + return -EFAULT; + } + len_max = PAGE_SIZE - offset; len = ((to_write > len_max) ? len_max : to_write); @@ -2666,12 +2672,6 @@ static int tpacket_fill_skb(struct packet_sock *po, struct sk_buff *skb, while (likely(to_write)) { nr_frags = skb_shinfo(skb)->nr_frags; - if (unlikely(nr_frags >= MAX_SKB_FRAGS)) { - pr_err("Packet exceed the number of skb frags(%u)\n", - (unsigned int)MAX_SKB_FRAGS); - return -EFAULT; - } - page = pgv_to_page(data); data += len; flush_dcache_page(page); From de788d8a7032475f900c16e9cc0c1417f4f719eb Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Wed, 28 Oct 2026 18:13:50 +0100 Subject: [PATCH 125/137] gro: do not GRO GSO packets with inconsistent payload tcp_gro_receive() excludes empty non-GSO packets with the unsigned len - 1 check, but its GSO branch only checks that gso_size matches the held packet's MSS. A trusted TCP GSO skb can reach GRO again with no payload after TC pedit increases its TCP data offset. If the skb is fully linear, skb_gro_receive() then enters its fragment transfer path with nr_frags == 0. The do-while loop copies skb_frag_t entries backwards beyond both fragment arrays. An unprivileged process using its own user and network namespaces can trigger a KASAN slab-out-of-bounds write and panic through veth, pedit and GRE. More generally, the payload of such a packet can be shrunk to any value, making it inconsistent with its gso_segs, and a GSO packet with gso_segs == 0 contributes no segment to the GRO packet. Do not aggregate GSO packets with gso_segs == 0, and require the payload of GSO packets to be consistent with gso_segs and gso_size, the last segment being allowed to be short. This rejects the empty skb before either TCP merge helper, and covers both IPv4 and IPv6. Fixes: 5eddb24901ee ("gro: add support of (hw)gro packets to gro stack") Signed-off-by: Kyle Zeng [edumazet: check payload against gso_segs, refuse gso_segs == 0] Signed-off-by: Eric Dumazet Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20261028171350.2205175-1-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- net/core/gro.c | 5 +++-- net/ipv4/tcp_offload.c | 12 +++++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/net/core/gro.c b/net/core/gro.c index ff828a3dce3c..1b033855a20d 100644 --- a/net/core/gro.c +++ b/net/core/gro.c @@ -513,9 +513,10 @@ static enum gro_result dev_gro_receive(struct gro_node *gro, NAPI_GRO_CB(skb)->count = 1; if (unlikely(skb_is_gso(skb))) { NAPI_GRO_CB(skb)->count = skb_shinfo(skb)->gso_segs; - /* Only support TCP and non DODGY users. */ + /* Only support TCP and non DODGY users, with a sane gso_segs. */ if (!skb_is_gso_tcp(skb) || - (skb_shinfo(skb)->gso_type & SKB_GSO_DODGY)) + (skb_shinfo(skb)->gso_type & SKB_GSO_DODGY) || + !NAPI_GRO_CB(skb)->count) NAPI_GRO_CB(skb)->flush = 1; } diff --git a/net/ipv4/tcp_offload.c b/net/ipv4/tcp_offload.c index e74d99ca9fac..b84fcd07c723 100644 --- a/net/ipv4/tcp_offload.c +++ b/net/ipv4/tcp_offload.c @@ -315,14 +315,20 @@ struct sk_buff *tcp_gro_receive(struct list_head *head, struct sk_buff *skb, mss = skb_shinfo(p)->gso_size; - /* If skb is a GRO packet, make sure its gso_size matches prior packet mss. + /* If skb is a GRO packet, make sure its gso_size matches prior packet + * mss, and that its payload is consistent with its gso_segs + * (the last segment can be short). * If it is a single frame, do not aggregate it if its length * is bigger than our mss. */ - if (unlikely(skb_is_gso(skb))) + if (unlikely(skb_is_gso(skb))) { + unsigned int segs = NAPI_GRO_CB(skb)->count; + flush |= (mss != skb_shinfo(skb)->gso_size); - else + flush |= len <= (segs - 1) * mss || len > segs * mss; + } else { flush |= (len - 1) >= mss; + } flush |= (ntohl(th2->seq) + skb_gro_len(p)) ^ ntohl(th->seq); flush |= skb_cmp_decrypted(p, skb); From 4f45e97b3aac2282f2b436d97338c9c2c484d093 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Wed, 28 Oct 2026 09:48:39 -0700 Subject: [PATCH 126/137] net: bgmac: fix TX DMA unmapping and slot cleanup When mapping a fragment fails, bgmac_dma_tx_add() unwinds with the wrong slots. It passes the failed fragment's slot->dma_addr, which holds the mapping error, to dma_unmap_single() for the head. The fragment loop indexes from ring->end instead of ring->end + 1, so it unmaps the head a second time as a page and leaves the last mapped fragment mapped. None of the affected slots get their dma_addr cleared either, which includes the head slot when mapping the head fails. bgmac_dma_tx_ring_free() unmaps every slot with a non-zero dma_addr, so tearing down the ring unmaps these addresses again. It also frees the skbs of pending TX slots but leaves slot->skb and slot->dma_addr set. The slots survive a down/up cycle, and bgmac_dma_tx_add() only sets slot->skb on the last slot of a packet. If a slot with a stale pointer is later used for the head or a fragment of a scatter/gather packet, bgmac_dma_tx_free() frees the old skb a second time when that slot completes. bgmac_dma_tx_ring_free() also picks dma_unmap_single() or dma_unmap_page() by looking at slot->skb, which is set on the last slot of a packet, while the head was mapped with dma_map_single() and the fragments with skb_frag_dma_map(). In the error path, unmap the head and each mapped fragment from their own slots, and clear dma_addr on every slot the failed packet touched. In bgmac_dma_tx_ring_free(), choose the unmap function by the SOF flag of the descriptor like bgmac_dma_tx_free() does, unmap every slot before freeing any skb, since a packet can wrap around the end of the ring with its skb on the last slot, and clear both fields. This was found through code inspection by an LLM while hitting other bugs on an Asus RT-N16. It did not hit these ones specifically. Fixes: 9cde94506eac ("bgmac: implement scatter/gather support") Signed-off-by: Rosen Penev Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20261028164839.28605-1-rosenp@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bgmac.c | 51 +++++++++++++++++---------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bgmac.c b/drivers/net/ethernet/broadcom/bgmac.c index b3a92ae95de3..669eb7ce7d40 100644 --- a/drivers/net/ethernet/broadcom/bgmac.c +++ b/drivers/net/ethernet/broadcom/bgmac.c @@ -208,19 +208,24 @@ static netdev_tx_t bgmac_dma_tx_add(struct bgmac *bgmac, return NETDEV_TX_OK; err_dma: + slot->dma_addr = 0; + + while (i-- > 0) { + index = (ring->end + 1 + i) % BGMAC_TX_RING_SLOTS; + slot = &ring->slots[index]; + dma_unmap_page(dma_dev, slot->dma_addr, + skb_frag_size(&skb_shinfo(skb)->frags[i]), + DMA_TO_DEVICE); + slot->dma_addr = 0; + } + + slot = &ring->slots[ring->end % BGMAC_TX_RING_SLOTS]; dma_unmap_single(dma_dev, slot->dma_addr, skb_headlen(skb), DMA_TO_DEVICE); - while (i-- > 0) { - int index = (ring->end + i) % BGMAC_TX_RING_SLOTS; - struct bgmac_slot_info *slot = &ring->slots[index]; - u32 ctl1 = le32_to_cpu(ring->cpu_base[index].ctl1); - int len = ctl1 & BGMAC_DESC_CTL1_LEN; - - dma_unmap_page(dma_dev, slot->dma_addr, len, DMA_TO_DEVICE); - } - err_dma_head: + slot->dma_addr = 0; + netdev_err(bgmac->net_dev, "Mapping error of skb on ring 0x%X\n", ring->mmio_base); @@ -536,18 +541,26 @@ static void bgmac_dma_tx_ring_free(struct bgmac *bgmac, u32 ctl1 = le32_to_cpu(dma_desc[i].ctl1); unsigned int len = ctl1 & BGMAC_DESC_CTL1_LEN; + slot = &ring->slots[i]; + + if (slot->dma_addr) { + if (le32_to_cpu(dma_desc[i].ctl0) & BGMAC_DESC_CTL0_SOF) + dma_unmap_single(dma_dev, slot->dma_addr, + len, DMA_TO_DEVICE); + else + dma_unmap_page(dma_dev, slot->dma_addr, + len, DMA_TO_DEVICE); + slot->dma_addr = 0; + } + } + + /* A packet can wrap around the end of the ring with its skb on the + * last slot, so free the skbs only once every slot is unmapped. + */ + for (i = 0; i < BGMAC_TX_RING_SLOTS; i++) { slot = &ring->slots[i]; dev_kfree_skb(slot->skb); - - if (!slot->dma_addr) - continue; - - if (slot->skb) - dma_unmap_single(dma_dev, slot->dma_addr, - len, DMA_TO_DEVICE); - else - dma_unmap_page(dma_dev, slot->dma_addr, - len, DMA_TO_DEVICE); + slot->skb = NULL; } } From dbd62ad55a4c898f5a4c9edb20c0c062afb799df Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Wed, 28 Oct 2026 17:08:47 +0100 Subject: [PATCH 127/137] tcp: avoid double destroy in tcp_abort() of an unaccepted child tcp_abort() checks for TCP_CLOSE while only owning the socket lock, then grabs the BH lock before calling tcp_done_with_error(). For a child still sitting in a listener accept queue (which can be reached via SOCK_DESTROY or bpf_sock_destroy()), inet_csk_listen_stop() can run in this window: it only holds the child BH lock, and calls inet_child_forget(), which disconnects, orphans and destroys the child. tcp_abort() then calls tcp_done_with_error() -> tcp_done(), which sees SOCK_DEAD and calls inet_csk_destroy_sock() a second time, leading to a double sock_put() and a use-after-free. Recheck TCP_CLOSE after acquiring the BH lock, unless we just closed a listener ourselves, in which case tcp_done_with_error() must still run to report the error. Note that the WARN_ON(sock_owned_by_user(child)) in inet_csk_listen_stop() can still trigger in this window. Fixes: bac76cf89816 ("tcp: fix forever orphan socket caused by tcp_abort") Signed-off-by: Kyle Zeng [edumazet: recheck TCP_CLOSE under the BH lock instead of moving the test, smaller diff] Signed-off-by: Eric Dumazet Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20261028160847.2170959-1-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- net/ipv4/tcp.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index 76df69aa0115..0871886a34a2 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -5127,6 +5127,8 @@ void tcp_done(struct sock *sk) int tcp_abort(struct sock *sk, int err) { int state = inet_sk_state_load(sk); + bool listener = false; + int ret = 0; if (state == TCP_NEW_SYN_RECV) { struct request_sock *req = inet_reqsk(sk); @@ -5161,21 +5163,31 @@ int tcp_abort(struct sock *sk, int err) if (sk->sk_state == TCP_LISTEN) { tcp_set_state(sk, TCP_CLOSE); inet_csk_listen_stop(sk); + listener = true; } /* Don't race with BH socket closes such as inet_csk_listen_stop. */ local_bh_disable(); bh_lock_sock(sk); + /* An unaccepted child might have been closed by + * inet_csk_listen_stop() while we were not holding its BH lock. + */ + if (!listener && sk->sk_state == TCP_CLOSE) { + ret = -ENOENT; + goto out; + } + if (tcp_need_reset(sk->sk_state)) tcp_send_active_reset(sk, SK_RST_REASON_TCP_STATE); tcp_done_with_error(sk, err); +out: bh_unlock_sock(sk); local_bh_enable(); if (!has_current_bpf_ctx()) release_sock(sk); - return 0; + return ret; } EXPORT_SYMBOL_GPL(tcp_abort); From c1df9c0c0536393de43310f84ca4f8b217271a00 Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Wed, 28 Oct 2026 17:02:26 +0100 Subject: [PATCH 128/137] tcp: fix reordering addition overflow in MTU probing Commit efb8763d7bbb ("net: ipv4: bound TCP reordering sysctl writes and MTU probe sizes") widened size_needed and the MSS multiplication, but tp->reordering + 1 is still evaluated as u32. RTAX_REORDERING accepts an unrestricted u32, and tcp_init_metrics() can install U32_MAX. In that case the extra-data requirement becomes zero. A write queue with probe_size - 1 data bytes and a FIN satisfies the sequence-space check. The coalescing walk can pass the queue head to tcp_skb_can_collapse(), and tcp_clone_payload() can produce an skb whose advertised payload exceeds the data in its fragments. Local delivery followed by TCP_ZEROCOPY_RECEIVE can then walk beyond the fragment array. KASAN reports a slab-out-of-bounds read in can_map_frag(). Cast reordering before incrementing it so the arithmetic handles the entire u32 metric range. For U32_MAX, the required size exceeds the 32-bit queue and window limits, so the existing checks skip the probe. The calculation is unchanged for all other reordering values. Fixes: efb8763d7bbb ("net: ipv4: bound TCP reordering sysctl writes and MTU probe sizes") Cc: stable@vger.kernel.org Cc: Wyatt Feng Cc: Ren Wei Signed-off-by: Kyle Zeng Signed-off-by: Eric Dumazet Reviewed-by: Neal Cardwell Reviewed-by: Kuniyuki Iwashima Reviewed-by: Jiayuan Chen Link: https://patch.msgid.link/20261028160226.2170244-1-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- net/ipv4/tcp_output.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c index c8865205cdc8..39803b874c33 100644 --- a/net/ipv4/tcp_output.c +++ b/net/ipv4/tcp_output.c @@ -2720,7 +2720,7 @@ static int tcp_mtu_probe(struct sock *sk) mss_now = tcp_current_mss(sk); probe_size = tcp_mtu_to_mss(sk, (icsk->icsk_mtup.search_high + icsk->icsk_mtup.search_low) >> 1); - size_needed = probe_size + (tp->reordering + 1) * (u64)tp->mss_cache; + size_needed = probe_size + ((u64)tp->reordering + 1) * tp->mss_cache; interval = icsk->icsk_mtup.search_high - icsk->icsk_mtup.search_low; /* When misfortune happens, we are reprobing actively, * and then reprobe timer has expired. We stick with current From b7ff0e7a9ae3df2f123807a7e670bc348b1e1b9d Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Wed, 28 Oct 2026 16:41:01 +0100 Subject: [PATCH 129/137] tcp: initialize mapped children before route lookup IPv4 passive opens on a dual-stack listener go through tcp_v4_syn_recv_sock() with tcp_v6_mapped_child_init() as the optional initializer. The callback currently runs after the output route lookup. If that lookup fails, tcp_done() destroys a clone whose pinet6 and ipv6_fl_list still refer to the listener's state. With the flowlabel list head in inet_sock, fl6_free_socklist() walks the clone's list and RCU-frees its nodes without unlinking them from the live listener. Subsequent flowlabel operations can use or free the nodes again. Commit 858d2a4f67ff ("tcp: fix potential race in tcp_v6_syn_recv_sock()") moved the initializer before ehash insertion, but did not cover this error path. Invoke the existing initializer after the IPv4 fields it consumes have been set, but before route lookup can fail. It needs neither the destination nor sk_setup_caps(), and all later disposal paths remain covered. Fixes: 1c17f4373d4d ("ipv6: Move ipv6_fl_list from ipv6_pinfo to inet_sock.") Signed-off-by: Kyle Zeng Signed-off-by: Eric Dumazet Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20261028154101.2153360-1-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- net/ipv4/tcp_ipv4.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c index ebe5aa70374a..a7f9242f1e42 100644 --- a/net/ipv4/tcp_ipv4.c +++ b/net/ipv4/tcp_ipv4.c @@ -1723,6 +1723,12 @@ struct sock *tcp_v4_syn_recv_sock(const struct sock *sk, struct sk_buff *skb, if (READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_reflect_tos)) newinet->tos = tcp_rsk(req)->syn_tos & ~INET_ECN_MASK; +#if IS_ENABLED(CONFIG_IPV6) + /* Detach IPv6 state before a route lookup can fail. */ + if (opt_child_init) + opt_child_init(newsk, sk); +#endif + if (!dst) { dst = inet_csk_route_child_sock(sk, newsk, req); if (!dst) @@ -1732,10 +1738,6 @@ struct sock *tcp_v4_syn_recv_sock(const struct sock *sk, struct sk_buff *skb, } sk_setup_caps(newsk, dst); -#if IS_ENABLED(CONFIG_IPV6) - if (opt_child_init) - opt_child_init(newsk, sk); -#endif tcp_ca_openreq_child(newsk, dst); tcp_sync_mss(newsk, dst4_mtu(dst)); From 2519a6fe7a8bba57a961eb804e43e7df35ac40fe Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 28 Oct 2026 13:26:59 +0100 Subject: [PATCH 130/137] net: add eth_type_trans_careful() eth_type_trans() assumes ETH_HLEN bytes are present in skb->head. NIC drivers always provide this, and they should keep using the fast version. Devices looping skbs from their transmit path back to their receive path get skbs built by the stack instead. Their link-layer header might have been popped by tc actions (act_vlan pop_eth, act_ife decode) or OVS actions (pop_eth + pop_mpls / pop_nsh), leaving fewer than ETH_HLEN bytes in the linear part. eth_type_trans() then hits the BUG() in __skb_pull(). Add eth_type_trans_careful(), which first pulls the Ethernet header, and returns a drop reason on failure, so that the few devices exposed to this can drop such packets. Signed-off-by: Eric Dumazet Reviewed-by: Aaron Conole Reviewed-by: Willem de Bruijn Reviewed-by: Jamal Hadi Salim Tested-by: Jamal Hadi Salim Acked-by: Alexei Starovoitov Link: https://patch.msgid.link/20261028122703.2093730-2-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- include/linux/etherdevice.h | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/include/linux/etherdevice.h b/include/linux/etherdevice.h index df8f88f63a70..1f833ebd71ed 100644 --- a/include/linux/etherdevice.h +++ b/include/linux/etherdevice.h @@ -38,6 +38,34 @@ int fwnode_get_mac_address(struct fwnode_handle *fwnode, char *addr); u32 eth_get_headlen(const struct net_device *dev, const void *data, u32 len); __be16 eth_type_trans(struct sk_buff *skb, struct net_device *dev); + +/** + * eth_type_trans_careful - eth_type_trans() for skbs coming from the stack + * @skb: buffer, possibly with less than ETH_HLEN bytes in its linear area + * @dev: receiving network device + * + * eth_type_trans() expects ETH_HLEN bytes in skb->head, which NIC drivers + * provide. Devices looping skbs from their transmit path back to the + * receive path (loopback, OVS internal ports) get skbs built by the stack, + * whose link-layer header might have been popped by tc or OVS actions. + * + * Sets skb->protocol on success. + * + * Return: SKB_NOT_DROPPED_YET on success, or a drop reason. In the latter + * case, the caller must free @skb. + */ +static inline enum skb_drop_reason +eth_type_trans_careful(struct sk_buff *skb, struct net_device *dev) +{ + enum skb_drop_reason reason = pskb_may_pull_reason(skb, ETH_HLEN); + + if (unlikely(reason)) + return reason; + + skb->protocol = eth_type_trans(skb, dev); + return SKB_NOT_DROPPED_YET; +} + extern const struct header_ops eth_header_ops; int eth_header(struct sk_buff *skb, struct net_device *dev, unsigned short type, From 093a28998374f4007ffe67cc661f8192c1b062a9 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 28 Oct 2026 13:27:00 +0100 Subject: [PATCH 131/137] loopback: use eth_type_trans_careful() tc egress actions can leave an skb with fewer than ETH_HLEN bytes in its linear part. loopback_xmit() then crashes in eth_type_trans(): tc qdisc add dev lo clsact tc filter add dev lo egress matchall action vlan pop_eth # AF_PACKET SOCK_RAW: eth / eth / 8192 bytes of payload kernel BUG at include/linux/skbuff.h:2839! RIP: 0010:eth_type_trans (include/linux/etherdevice.h:635 net/ethernet/eth.c:163) Call Trace: loopback_xmit (drivers/net/loopback.c:87) dev_hard_start_xmit (include/linux/netdevice.h:5455 include/linux/netdevice.h:5464 net/core/dev.c:4057 net/core/dev.c:4073) __dev_queue_xmit (net/core/dev.c:5066) packet_sendmsg (net/packet/af_packet.c:3157 net/packet/af_packet.c:3189) The same happens with "action ife decode" on lo egress. Use eth_type_trans_careful(), and drop the packet if the Ethernet header can not be pulled. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: Vega Link: https://lore.kernel.org/netdev/20261005052249.1914367-2-weir@nebusec.ai/ Signed-off-by: Eric Dumazet Reviewed-by: Jamal Hadi Salim Tested-by: Jamal Hadi Salim Acked-by: Alexei Starovoitov Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20261028122703.2093730-3-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/loopback.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/net/loopback.c b/drivers/net/loopback.c index 55ecd5ebac0f..8a6cb34e6b5f 100644 --- a/drivers/net/loopback.c +++ b/drivers/net/loopback.c @@ -70,6 +70,7 @@ EXPORT_SYMBOL(blackhole_netdev); static netdev_tx_t loopback_xmit(struct sk_buff *skb, struct net_device *dev) { + enum skb_drop_reason reason; int len; skb_tx_timestamp(skb); @@ -84,7 +85,12 @@ static netdev_tx_t loopback_xmit(struct sk_buff *skb, */ skb_dst_force(skb); - skb->protocol = eth_type_trans(skb, dev); + reason = eth_type_trans_careful(skb, dev); + if (unlikely(reason)) { + dev_core_stats_tx_dropped_inc(dev); + kfree_skb_reason(skb, reason); + return NETDEV_TX_OK; + } len = skb->len; if (likely(__netif_rx(skb) == NET_RX_SUCCESS)) From 695e1408eb908ac7acf78069933201f92b95727c Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 28 Oct 2026 13:27:01 +0100 Subject: [PATCH 132/137] net: use eth_type_trans_careful() in __dev_forward_skb2() A tc egress BPF program can push a tiny link-layer header with bpf_skb_change_head() on an skb whose head is almost empty, then redirect it to an Ethernet device ingress. __bpf_redirect_common() only checks mac_header < network_header, and __dev_forward_skb2() crashes in eth_type_trans(): tc filter add dev lo egress pref 1 matchall action vlan pop_eth continue tc filter add dev lo egress pref 2 bpf da obj prog.o # prog: bpf_skb_change_head(skb, 1, 0); # return bpf_redirect(veth1_ifindex, BPF_F_INGRESS); # AF_PACKET SOCK_RAW on lo: eth / eth / 8192 bytes of payload kernel BUG at include/linux/skbuff.h:2839! RIP: 0010:eth_type_trans (include/linux/etherdevice.h:635 net/ethernet/eth.c:163) Call Trace: __dev_forward_skb2 (net/core/dev.c:2507) dev_forward_skb_nomtu (net/core/dev.c:2546) __dev_queue_xmit (net/core/dev.c:4738 net/core/dev.c:4998) packet_sendmsg (net/packet/af_packet.c:3157 net/packet/af_packet.c:3189) Use eth_type_trans_careful(), and drop the packet if the Ethernet header can not be pulled. This also covers other dev_forward_skb() users (veth, macvlan, ipvlan, netdevsim, l2tp_eth). Update the empty_skb BPF selftest: at lwt_xmit, BPF_PROG_TEST_RUN strips the Ethernet header, so the "veth ETH_HLEN+1" packet is a 1-byte skb. Redirecting it to veth ingress used to read the Ethernet header past skb->tail and deliver a 1-byte frame; it is now dropped with NET_RX_DROP, as veth_xmit() already does at egress. Fixes: 3a0af8fd61f9 ("bpf: BPF for lightweight tunnel infrastructure") Signed-off-by: Eric Dumazet Reviewed-by: Jamal Hadi Salim Tested-by: Jamal Hadi Salim Acked-by: Alexei Starovoitov Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20261028122703.2093730-4-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- net/core/dev.c | 18 +++++++++++++----- .../selftests/bpf/prog_tests/empty_skb.c | 19 ++++++++++++------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/net/core/dev.c b/net/core/dev.c index 87a79d6004c8..3ac700d37e59 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -2501,14 +2501,22 @@ EXPORT_SYMBOL_GPL(is_skb_forwardable); static int __dev_forward_skb2(struct net_device *dev, struct sk_buff *skb, bool check_mtu) { - int ret = ____dev_forward_skb(dev, skb, check_mtu); + enum skb_drop_reason reason; + int ret; - if (likely(!ret)) { - skb->protocol = eth_type_trans(skb, dev); - skb_postpull_rcsum(skb, eth_hdr(skb), ETH_HLEN); + ret = ____dev_forward_skb(dev, skb, check_mtu); + if (unlikely(ret)) + return ret; + + reason = eth_type_trans_careful(skb, dev); + if (unlikely(reason)) { + dev_core_stats_rx_dropped_inc(dev); + kfree_skb_reason(skb, reason); + return NET_RX_DROP; } + skb_postpull_rcsum(skb, eth_hdr(skb), ETH_HLEN); - return ret; + return 0; } int __dev_forward_skb(struct net_device *dev, struct sk_buff *skb) diff --git a/tools/testing/selftests/bpf/prog_tests/empty_skb.c b/tools/testing/selftests/bpf/prog_tests/empty_skb.c index c9fcb70cbafb..a3938c16557b 100644 --- a/tools/testing/selftests/bpf/prog_tests/empty_skb.c +++ b/tools/testing/selftests/bpf/prog_tests/empty_skb.c @@ -24,7 +24,7 @@ void test_empty_skb(void) int *ifindex; int err; int ret; - int lwt_egress_ret; /* expected retval at lwt/egress */ + int lwt_ret; /* expected retval at lwt */ __be16 h_proto; bool success_on_tc; bool adjust_room; @@ -82,7 +82,7 @@ void test_empty_skb(void) .data_size_in = sizeof(eth_hlen), .ifindex = &veth_ifindex, .ret = -ERANGE, - .lwt_egress_ret = -ERANGE, + .lwt_ret = -ERANGE, .success_on_tc = true, }, { @@ -96,17 +96,23 @@ void test_empty_skb(void) .data_size_in = sizeof(eth_hlen), .ifindex = &ipip_ifindex, .ret = -ERANGE, - .lwt_egress_ret = -ERANGE, + .lwt_ret = -ERANGE, }, - /* ETH_HLEN+1-sized packet should be redirected. */ + /* ETH_HLEN+1-sized packet should be redirected at TC. + * At LWT, the Ethernet header is not part of the skb, and + * a 1-byte packet is too short for an Ethernet device. + */ { .msg = "veth ETH_HLEN+1 packet ingress", .data_in = eth_hlen_pp, .data_size_in = sizeof(eth_hlen_pp), .ifindex = &veth_ifindex, - .lwt_egress_ret = 1, /* veth_xmit NET_XMIT_DROP */ + /* lwt: skb->len=1, dropped by veth_xmit() at egress + * and by __dev_forward_skb2() at ingress. + */ + .lwt_ret = 1, /* NET_XMIT_DROP / NET_RX_DROP */ }, { .msg = "ipip ETH_HLEN+1 packet ingress", @@ -144,7 +150,6 @@ void test_empty_skb(void) eth_hlen.h_proto = tests[i].h_proto; bpf_object__for_each_program(prog, bpf_obj->obj) { - bool at_egress = strstr(bpf_program__name(prog), "egress") != NULL; bool at_tc = !strncmp(bpf_program__section_name(prog), "tc", 2); bool is_adjust_room = !strcmp(bpf_program__name(prog), "tc_adjust_room"); int expected_ret; @@ -153,7 +158,7 @@ void test_empty_skb(void) if (tests[i].adjust_room != is_adjust_room) continue; - expected_ret = at_egress && !at_tc ? tests[i].lwt_egress_ret : tests[i].ret; + expected_ret = at_tc ? tests[i].ret : tests[i].lwt_ret; tattr.data_in = tests[i].data_in; tattr.data_size_in = tests[i].data_size_in; From b55e5a0159c8f53edefcb7848d95eb93db37bce1 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 28 Oct 2026 13:27:02 +0100 Subject: [PATCH 133/137] openvswitch: use eth_type_trans_careful() in internal_dev_recv() OVS actions like pop_eth followed by pop_mpls(ETH_P_TEB) or pop_nsh can expose an inner Ethernet header which is not in the linear part of the skb. Output to an internal port then crashes in eth_type_trans(): flow: in_port(0),eth(),eth_type(0x8847),mpls(lse=0x00010140) actions: pop_eth,pop_mpls(0x6558),0 # AF_PACKET SOCK_RAW on dp0: eth / mpls / eth / 4096 bytes of payload kernel BUG at include/linux/skbuff.h:2839! RIP: 0010:eth_type_trans (include/linux/etherdevice.h:635 net/ethernet/eth.c:163) Call Trace: internal_dev_recv (net/openvswitch/vport-internal_dev.c:202) openvswitch do_execute_actions (net/openvswitch/actions.c:1296) openvswitch ovs_execute_actions (net/openvswitch/actions.c:1606) openvswitch ovs_dp_process_packet (net/openvswitch/datapath.c:310) openvswitch ovs_vport_receive (net/openvswitch/vport.c:448) openvswitch internal_dev_xmit (net/openvswitch/vport-internal_dev.c:42) openvswitch dev_hard_start_xmit (include/linux/netdevice.h:5455 include/linux/netdevice.h:5464 net/core/dev.c:4057 net/core/dev.c:4073) __dev_queue_xmit (net/core/dev.c:5066) packet_sendmsg (net/packet/af_packet.c:3157 net/packet/af_packet.c:3189) Use eth_type_trans_careful(), and drop the packet if the Ethernet header can not be pulled. This covers all OVS actions at the consumer, instead of fixing each producer. Fixes: b2d0f5d5dc53 ("openvswitch: enable NSH support") Fixes: f66b53fdbb22 ("openvswitch: New MPLS actions for layer 2 tunnelling") Signed-off-by: Eric Dumazet Reviewed-by: Aaron Conole Reviewed-by: Jamal Hadi Salim Tested-by: Jamal Hadi Salim Acked-by: Alexei Starovoitov Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20261028122703.2093730-5-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- net/openvswitch/vport-internal_dev.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/net/openvswitch/vport-internal_dev.c b/net/openvswitch/vport-internal_dev.c index 77a16d5ebd54..8f035087f0ee 100644 --- a/net/openvswitch/vport-internal_dev.c +++ b/net/openvswitch/vport-internal_dev.c @@ -188,6 +188,7 @@ static void internal_dev_destroy(struct vport *vport) static int internal_dev_recv(struct sk_buff *skb) { struct net_device *netdev = skb->dev; + enum skb_drop_reason reason; if (unlikely(!(netdev->flags & IFF_UP))) { kfree_skb(skb); @@ -199,7 +200,12 @@ static int internal_dev_recv(struct sk_buff *skb) nf_reset_ct(skb); skb->pkt_type = PACKET_HOST; - skb->protocol = eth_type_trans(skb, netdev); + reason = eth_type_trans_careful(skb, netdev); + if (unlikely(reason)) { + dev_core_stats_rx_dropped_inc(netdev); + kfree_skb_reason(skb, reason); + return NETDEV_TX_OK; + } skb_postpull_rcsum(skb, eth_hdr(skb), ETH_HLEN); dev_sw_netstats_rx_add(netdev, skb->len); From 498c631f9c6d74aad4bf43039fa3ce70ae9a8bb7 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Wed, 28 Oct 2026 13:27:03 +0100 Subject: [PATCH 134/137] net/sched: act_ife: do not consume the inner header on egress The issue: tcf_ife_decode() unconditionally calls eth_type_trans(), which pulls ETH_HLEN. On egress this consumes the inner Ethernet header, and the device receives an skb whose data points to the L3 header: - loopback_xmit() parses L3 data as Ethernet: the "ethertype" comes from the inner IPv4 source address, and the decapsulated packet is dropped or misparsed. - a real NIC transmits the frame without its Ethernet header. (The __skb_pull() BUG this used to trigger in loopback_xmit() when the remaining linear area was short is handled by "loopback: use eth_type_trans_careful()".) The Fix: Parse the inner Ethernet header with eth_type_trans() in both directions, then restore the egress data position and MAC length. On ingress the existing behavior is unchanged. On egress, push the inner Ethernet header back after parsing so the xmit path sees the original frame and later actions see a consistent network header. Conditions to recreate the bug: - egress tc filter on lo: action ife decode pipe; - AF_PACKET SOCK_RAW frame: outer Ethernet with ETH_P_IFE, IFE metadata, inner Ethernet + a valid IPv4 ICMP echo request to a local address; - before this patch, no echo is received; after it, the echo is delivered and answered. Fixes: ef6980b6becb ("introduce IFE action") Cc: stable@vger.kernel.org Signed-off-by: Jamal Hadi Salim [edumazet: reworded changelog, the crash is now handled in loopback] Signed-off-by: Eric Dumazet Acked-by: Alexei Starovoitov Link: https://patch.msgid.link/20261028122703.2093730-6-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- net/sched/act_ife.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/sched/act_ife.c b/net/sched/act_ife.c index 2afd68983ece..3946a9589bb8 100644 --- a/net/sched/act_ife.c +++ b/net/sched/act_ife.c @@ -769,6 +769,10 @@ static int tcf_ife_decode(struct sk_buff *skb, const struct tc_action *a, skb->protocol = eth_type_trans(skb, skb->dev); skb_reset_network_header(skb); + if (!skb_at_tc_ingress(skb)) { + skb_push(skb, ETH_HLEN); + skb_reset_mac_len(skb); + } return action; } From b45d37740cfb4bdfccef3fbd9fd970fd3224438f Mon Sep 17 00:00:00 2001 From: Jhonata Poma-Hansen Date: Tue, 27 Oct 2026 15:04:01 +0100 Subject: [PATCH 135/137] netlink: check sk_rcvbuf before charging the dump skb Since commit ae8f160e7eb2 ("netlink: Fix wraparounds of sk->sk_rmem_alloc."), netlink_dump() charges the dump skb first and queues it only if sk_rmem_alloc is still below sk_rcvbuf. Commit a215b5723922 ("netlink: make sure we allow at least one dump skb") added an exception for an empty socket. A dump that starts while something else is still charged to a socket with a small receive buffer fails with ENOBUFS and stays running. Every recvmsg() retries it, fails again and sets sk_err, so a reader that peeks before reading, as libnl does, gets ENOBUFS on the read after each peek, never consumes the message at the head of the queue and spins forever. I hit this with "iw dev wlan0 scan" hanging at 100% CPU on an arm64 board with a 6.6 based kernel (iw 5.19 sets SO_RCVBUF to 8KiB). A kprobe trace showed the scan dump starting while the NEW_SCAN_RESULTS notification iw had just read was still charged to its socket, because iw had preempted the sender before netlink_broadcast() returned. Check the limit before charging, as before ae8f160e7eb2, which also covers the empty socket. netlink_recvmsg() continues a dump only at or below half of sk_rcvbuf, so the skb is then always queued, and a dump goes over the limit by at most one skb. Refuse a skb that would take sk_rmem_alloc past INT_MAX, so the wraparound stays fixed. Reproduced on net in a KVM guest with a userspace reproducer and the selftest in the next patch; both pass with this fix. Fixes: ae8f160e7eb2 ("netlink: Fix wraparounds of sk->sk_rmem_alloc.") Signed-off-by: Jhonata Poma-Hansen Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20261027-netlink-dump-livelock-v2-1-c4bb05352a33@gmail.com Signed-off-by: Jakub Kicinski --- net/netlink/af_netlink.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/netlink/af_netlink.c b/net/netlink/af_netlink.c index fec2879c497d..1281c42a3456 100644 --- a/net/netlink/af_netlink.c +++ b/net/netlink/af_netlink.c @@ -2322,9 +2322,13 @@ static int netlink_dump(struct sock *sk, bool lock_taken) if (!skb) goto errout_skb; + /* Check the limit before charging the skb: netlink_recvmsg() continues + * the dump at or below half of sk_rcvbuf, and the skb must be queued + * then. Keep sk_rmem_alloc at or below INT_MAX. + */ rcvbuf = READ_ONCE(sk->sk_rcvbuf); rmem = atomic_add_return(skb->truesize, &sk->sk_rmem_alloc); - if (rmem != skb->truesize && rmem >= rcvbuf) { + if (rmem - skb->truesize >= rcvbuf || rmem > INT_MAX) { atomic_sub(skb->truesize, &sk->sk_rmem_alloc); goto errout_skb; } From e615d0a8b20161fd470ca2748001105bcd3c44b1 Mon Sep 17 00:00:00 2001 From: Jhonata Poma-Hansen Date: Tue, 27 Oct 2026 15:04:02 +0100 Subject: [PATCH 136/137] selftests: net: netlink-dumps: start a dump with a notification queued Add a test which starts a link dump on a socket with SO_RCVBUF 2048 while a link notification is still queued, after a large read made the kernel attempt large dump skbs. It reads like libnl does, peeking before each read, and expects the dump to complete with a zero status and at least one link. The notifications come from bringing lo up and down, so the test runs in its own netns and is skipped without the privileges to create one. The test fails without the previous patch. Signed-off-by: Jhonata Poma-Hansen Link: https://patch.msgid.link/20261027-netlink-dump-livelock-v2-2-c4bb05352a33@gmail.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/netlink-dumps.c | 102 ++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/tools/testing/selftests/net/netlink-dumps.c b/tools/testing/selftests/net/netlink-dumps.c index 51129c564d0a..4a94a25167e2 100644 --- a/tools/testing/selftests/net/netlink-dumps.c +++ b/tools/testing/selftests/net/netlink-dumps.c @@ -2,9 +2,12 @@ #define _GNU_SOURCE +#include #include +#include #include #include +#include #include #include #include @@ -260,4 +263,103 @@ TEST(close_with_ref) usleep(100 * 1000); } +static int lo_set_up(int sock, bool up) +{ + struct ifreq ifr = {}; + + strcpy(ifr.ifr_name, "lo"); + if (ioctl(sock, SIOCGIFFLAGS, &ifr)) + return -1; + if (up) + ifr.ifr_flags |= IFF_UP; + else + ifr.ifr_flags &= ~IFF_UP; + return ioctl(sock, SIOCSIFFLAGS, &ifr); +} + +static const struct { + struct nlmsghdr nlhdr; + struct ifinfomsg ifm; +} dump_links = { + .nlhdr = { + .nlmsg_len = sizeof(dump_links), + .nlmsg_type = RTM_GETLINK, + .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP, + .nlmsg_seq = 1, + }, + .ifm = { + .ifi_family = AF_UNSPEC, + }, +}; + +// A dump started while a notification is still queued must make progress +// for a reader which peeks before reading, like libnl does +TEST(dump_with_queued_ntf) +{ + struct sockaddr_nl addr = { + .nl_family = AF_NETLINK, + .nl_groups = RTMGRP_LINK, + }; + int netlink_sock, inet_sock, i, ret, links = 0; + int rcvbuf = 2048; + bool done = false; + char buf[32768]; + ssize_t n; + + ret = unshare(CLONE_NEWNET); + if (ret && errno == EPERM) + SKIP(return, "Needs CAP_SYS_ADMIN for a netns"); + ASSERT_EQ(ret, 0); + + inet_sock = socket(AF_INET, SOCK_DGRAM, 0); + ASSERT_GE(inet_sock, 0); + netlink_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + ASSERT_GE(netlink_sock, 0); + ASSERT_EQ(setsockopt(netlink_sock, SOL_SOCKET, SO_RCVBUF, + &rcvbuf, sizeof(rcvbuf)), 0); + ASSERT_EQ(bind(netlink_sock, (struct sockaddr *)&addr, + sizeof(addr)), 0); + + // A large read makes the kernel attempt large dump skbs + ASSERT_EQ(lo_set_up(inet_sock, true), 0); + n = recv(netlink_sock, buf, sizeof(buf), 0); + ASSERT_GE(n, (ssize_t)sizeof(struct nlmsghdr)); + + // Leave one notification queued, then start the dump + ASSERT_EQ(lo_set_up(inet_sock, false), 0); + n = send(netlink_sock, &dump_links, sizeof(dump_links), 0); + ASSERT_EQ(n, sizeof(dump_links)); + + for (i = 0; i < 100 && !done; i++) { + struct nlmsghdr *nlh; + + n = recv(netlink_sock, buf, sizeof(buf), + MSG_PEEK | MSG_TRUNC | MSG_DONTWAIT); + if (n < 0) + continue; + n = recv(netlink_sock, buf, sizeof(buf), MSG_DONTWAIT); + if (n < 0) + continue; + + for (nlh = (struct nlmsghdr *)buf; NLMSG_OK(nlh, n); + nlh = NLMSG_NEXT(nlh, n)) { + if (nlh->nlmsg_seq != dump_links.nlhdr.nlmsg_seq) + continue; + if (nlh->nlmsg_type == NLMSG_ERROR) + EXPECT_EQ(*(int *)NLMSG_DATA(nlh), 0); + if (nlh->nlmsg_type == RTM_NEWLINK) + links++; + if (nlh->nlmsg_type == NLMSG_DONE) { + EXPECT_EQ(*(int *)NLMSG_DATA(nlh), 0); + done = true; + } + } + } + EXPECT_TRUE(done); + EXPECT_GE(links, 1); + + close(netlink_sock); + close(inet_sock); +} + TEST_HARNESS_MAIN From 8f44631ba02afe9fff2f0a0fb66560ab19df87fd Mon Sep 17 00:00:00 2001 From: Xiao Liang Date: Tue, 27 Oct 2026 18:18:27 +0800 Subject: [PATCH 137/137] tun: drop batched packets while the device is down tun_sendmsg() handles TUN_MSG_PTR batches from vhost-net by calling tun_xdp_one() instead of tun_get_user(). But this path does not check IFF_UP, allowing packets from a guest to be injected into the host receive path while the device is administratively down. Check IFF_UP in tun_xdp_one() before running XDP or building an skb. Drop the packet when the device is down, matching tun_get_user(). Fixes: 043d222f93ab ("tuntap: accept an array of XDP buffs through sendmsg()") Signed-off-by: Xiao Liang Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20261027101830.85327-1-shaw.leon@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/tun.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/tun.c b/drivers/net/tun.c index 770b8fe085c3..dcec7311e601 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -2555,6 +2555,12 @@ static int tun_xdp_one(struct tun_struct *tun, return -EINVAL; } + if (unlikely(!(READ_ONCE(tun->dev->flags) & IFF_UP))) { + put_page(virt_to_head_page(xdp->data)); + dev_core_stats_rx_dropped_inc(tun->dev); + return -EIO; + } + xdp_prog = rcu_dereference(tun->xdp_prog); if (xdp_prog) { if (gso->gso_type) {