From 99985bfa8336fadcc69190ba2dcbd5386af3d661 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Thu, 21 May 2026 07:32:09 -0700 Subject: [PATCH 01/70] nfc: llcp: avoid userspace overflow on invalid optlen nfc_llcp_getsockopt() casts optval to (u32 __user *) for put_user(), so the kernel always stores 4 bytes regardless of the caller-supplied optlen. The existing min_t(u32, len, sizeof(u32)) only clamps the length reported back to userspace; it does not constrain the store. A call with optlen < 4 therefore writes past the user buffer, violating the getsockopt(2) contract for all five supported optnames. Reject any call with optlen < sizeof(u32) up front. 'len' is int, so a plain size comparison would promote a negative optlen to size_t and slip past the check; an explicit 'len < 0' test is added first to catch negative values before the size compare. Fixes: 26fd76cab2e6 ("NFC: llcp: Implement socket options") Signed-off-by: Breno Leitao Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260521-fix_llc-v2-1-ab44cc09179c@debian.org Signed-off-by: David Heidelberg --- net/nfc/llcp_sock.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/nfc/llcp_sock.c b/net/nfc/llcp_sock.c index feab29fc62f4..4b162df0c3fc 100644 --- a/net/nfc/llcp_sock.c +++ b/net/nfc/llcp_sock.c @@ -319,6 +319,12 @@ static int nfc_llcp_getsockopt(struct socket *sock, int level, int optname, if (get_user(len, optlen)) return -EFAULT; + if (len < 0) + return -EINVAL; + + if (len < sizeof(u32)) + return -EINVAL; + local = llcp_sock->local; if (!local) return -ENODEV; From 36812527052c5bfb1ec6c1e292d67a5bf76b750f Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Thu, 21 May 2026 07:32:10 -0700 Subject: [PATCH 02/70] nfc: llcp: read llcp_sock->local under the socket lock in getsockopt nfc_llcp_getsockopt() read llcp_sock->local before lock_sock(sk) and then dereferenced the cached pointer inside the locked region. llcp_sock_bind() assigns and clears llcp_sock->local under the same socket lock, dropping the last reference on its error path. A getsockopt() racing an in-flight bind() can observe the pointer, block on lock_sock(), and then dereference a freed nfc_llcp_local once bind() has unwound. Move the llcp_sock->local read and the NULL check inside the lock_sock(sk) region so bind() cannot mutate or free the pointer between the load and the use. Fixes: 26fd76cab2e6 ("NFC: llcp: Implement socket options") Signed-off-by: Breno Leitao Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260521-fix_llc-v2-2-ab44cc09179c@debian.org Signed-off-by: David Heidelberg --- net/nfc/llcp_sock.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/net/nfc/llcp_sock.c b/net/nfc/llcp_sock.c index 4b162df0c3fc..5558d8a4d48b 100644 --- a/net/nfc/llcp_sock.c +++ b/net/nfc/llcp_sock.c @@ -325,14 +325,16 @@ static int nfc_llcp_getsockopt(struct socket *sock, int level, int optname, if (len < sizeof(u32)) return -EINVAL; - local = llcp_sock->local; - if (!local) - return -ENODEV; - len = min_t(u32, len, sizeof(u32)); lock_sock(sk); + local = llcp_sock->local; + if (!local) { + release_sock(sk); + return -ENODEV; + } + switch (optname) { case NFC_LLCP_RW: rw = llcp_sock->rw > LLCP_MAX_RW ? local->rw : llcp_sock->rw; From 8265a626cc14a48e46e6dc8c47667e72b4232ac2 Mon Sep 17 00:00:00 2001 From: Zhenghang Xiao Date: Tue, 26 May 2026 18:31:21 +0800 Subject: [PATCH 03/70] nfc: nci: fix double completion race in nci_data_exchange_complete nci_close_device() and nci_rx_work can both call nci_data_exchange_complete() concurrently. After commit 4527025d440ce8 ("nfc: nci: fix circular locking dependency in nci_close_device") moved flush_workqueue(ndev->rx_wq) after mutex_unlock(&ndev->req_lock), rx_work is no longer serialized with the explicit completion call in the close path. Both callers read the non-NULL callback pointer and invoke rawsock_data_exchange_complete(), which calls sock_put() -- but only one sock_hold() was taken, so the second sock_put() underflows the refcount and frees the socket while it is still in use. Replace the bare clear_bit(NCI_DATA_EXCHANGE) with test_and_clear_bit() so that only the first caller proceeds to invoke the callback. Fixes: 4527025d440c ("nfc: nci: fix circular locking dependency in nci_close_device") Signed-off-by: Zhenghang Xiao Link: https://patch.msgid.link/20260526103121.47957-1-kipreyyy@gmail.com Signed-off-by: David Heidelberg --- net/nfc/nci/data.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/net/nfc/nci/data.c b/net/nfc/nci/data.c index 5f98c73db5af..4253edea5c8d 100644 --- a/net/nfc/nci/data.c +++ b/net/nfc/nci/data.c @@ -46,11 +46,11 @@ void nci_data_exchange_complete(struct nci_dev *ndev, struct sk_buff *skb, timer_delete_sync(&ndev->data_timer); clear_bit(NCI_DATA_EXCHANGE_TO, &ndev->flags); - /* Mark the exchange as done before calling the callback. - * The callback (e.g. rawsock_data_exchange_complete) may - * want to immediately queue another data exchange. - */ - clear_bit(NCI_DATA_EXCHANGE, &ndev->flags); + /* Claim completion atomically -- both close and rx_work may race here */ + if (!test_and_clear_bit(NCI_DATA_EXCHANGE, &ndev->flags)) { + kfree_skb(skb); + return; + } if (cb) { /* forward skb to nfc core */ From 344a56d7c8e0f3cbaff0bcb1bcd95a1a1db24b16 Mon Sep 17 00:00:00 2001 From: Doruk Tan Ozturk Date: Wed, 3 Jun 2026 16:13:55 +0200 Subject: [PATCH 04/70] nfc: digital: clamp SENSF_RES length to the destination buffer digital_in_recv_sensf_res() memcpy()s resp->len bytes from a remote NFC-F device response into the NFC_SENSF_RES_MAXSIZE-byte target.sensf_res field without an upper-bound check. A nearby malicious NFC-F device can send an oversized SENSF_RES response to overflow the stack-local struct nfc_target. Clamp resp->len to NFC_SENSF_RES_MAXSIZE before the copy. Found by 0sec automated security-research tooling (https://0sec.ai). Fixes: 8c0695e4998d ("NFC Digital: Add NFC-F technology support") Cc: stable@vger.kernel.org Signed-off-by: Doruk Tan Ozturk Reviewed-by: Alexander Lobakin Link: https://patch.msgid.link/20260603141355.68156-1-doruk@0sec.ai Signed-off-by: David Heidelberg --- net/nfc/digital_technology.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/nfc/digital_technology.c b/net/nfc/digital_technology.c index ae63c5eb06fa..ae6487c10a25 100644 --- a/net/nfc/digital_technology.c +++ b/net/nfc/digital_technology.c @@ -778,6 +778,8 @@ static void digital_in_recv_sensf_res(struct nfc_digital_dev *ddev, void *arg, sensf_res = (struct digital_sensf_res *)resp->data; + resp->len = min_t(unsigned int, resp->len, NFC_SENSF_RES_MAXSIZE); + memcpy(target.sensf_res, sensf_res, resp->len); target.sensf_res_len = resp->len; From f4c7f37f0ab990952539dc68d931d65c3657600a Mon Sep 17 00:00:00 2001 From: Doruk Tan Ozturk Date: Tue, 9 Jun 2026 22:25:43 +0200 Subject: [PATCH 05/70] nfc: llcp: bound SNL TLV parsing to the skb and add length checks nfc_llcp_recv_snl() walked the SNL TLV list using a u16 offset/length pair derived from skb->len, without bounding reads to the actual skb data. Three problems followed: - For a short frame (skb->len < LLCP_HEADER_SIZE), tlv_len underflowed. - The per-TLV header (type, length) was read without checking that two bytes remained. - A declared TLV length could run past the end of the buffer, and an SDREQ with length == 0 made "service_name_len = length - 1" underflow (size_t), driving an out-of-bounds read in the following strncmp() / nfc_llcp_sock_from_sn(). The SDRES case likewise read tlv[2]/tlv[3] without a length check. A nearby NFC device can reach this without authentication; LLCP link activation happens automatically after NFC-DEP. Walk the TLV list by pointer, bounded by skb_tail_pointer() over the linear skb data, and validate each TLV declared length before use. Add explicit length checks for SDREQ (>= 1) and SDRES (exactly 2). Found by 0sec automated security-research tooling (https://0sec.ai). Fixes: 19cfe5843e86 ("NFC: Initial SNL support") Signed-off-by: Doruk Tan Ozturk Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260609202543.42282-1-doruk@0sec.ai Signed-off-by: David Heidelberg --- net/nfc/llcp_core.c | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c index dc65c719f35f..aed5fe1afef0 100644 --- a/net/nfc/llcp_core.c +++ b/net/nfc/llcp_core.c @@ -1286,10 +1286,9 @@ static void nfc_llcp_recv_snl(struct nfc_llcp_local *local, { struct nfc_llcp_sock *llcp_sock; u8 dsap, ssap, type, length, tid, sap; - const u8 *tlv; - u16 tlv_len, offset; + const u8 *tlv, *tlv_end; const char *service_name; - size_t service_name_len; + int service_name_len; struct nfc_llcp_sdp_tlv *sdp; HLIST_HEAD(llc_sdres_list); size_t sdres_tlvs_len; @@ -1305,22 +1304,34 @@ static void nfc_llcp_recv_snl(struct nfc_llcp_local *local, return; } + /* + * Walk the SNL TLV list in the linear part of the skb only, + * bounded by skb_tail_pointer(). Each TLV needs a two-byte + * header (type, length) and its declared length must fit before + * the end; this also keeps the walk safe for very short frames. + */ tlv = &skb->data[LLCP_HEADER_SIZE]; - tlv_len = skb->len - LLCP_HEADER_SIZE; - offset = 0; + tlv_end = skb_tail_pointer(skb); sdres_tlvs_len = 0; - while (offset < tlv_len) { + while (tlv + 2 < tlv_end) { type = tlv[0]; length = tlv[1]; + if (tlv + 2 + length > tlv_end) + break; + switch (type) { case LLCP_TLV_SDREQ: + if (length < 1) + break; + tid = tlv[2]; service_name = (char *) &tlv[3]; service_name_len = length - 1; - pr_debug("Looking for %.16s\n", service_name); + pr_debug("Looking for %.*s\n", service_name_len, + service_name); if (service_name_len == strlen("urn:nfc:sn:sdp") && !strncmp(service_name, "urn:nfc:sn:sdp", @@ -1380,6 +1391,9 @@ static void nfc_llcp_recv_snl(struct nfc_llcp_local *local, break; case LLCP_TLV_SDRES: + if (length != 2) + break; + mutex_lock(&local->sdreq_lock); pr_debug("LLCP_TLV_SDRES: searching tid %d\n", tlv[2]); @@ -1408,7 +1422,6 @@ static void nfc_llcp_recv_snl(struct nfc_llcp_local *local, break; } - offset += length + 2; tlv += length + 2; } From 78b20c8eeacd2e44a2d8a4cb5316d3c521d90911 Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Mon, 22 Jun 2026 18:18:02 +0500 Subject: [PATCH 06/70] nfc: llcp: fix OOB read and u8 offset wrap in TLV parsers nfc_llcp_parse_gb_tlv() and nfc_llcp_parse_connection_tlv() contain three related bugs in their TLV parsing loops: 1. 'offset' is declared u8 but tlv_array_len is u16. When TLV data advances offset past 255 it silently wraps to zero, causing infinite loops or double-processing of buffer data. 2. Before reading tlv[0] (type) and tlv[1] (length) there is no check that offset+2 <= tlv_array_len. A truncated TLV causes an OOB read of one byte past the buffer end. 3. After reading the length field, the value bytes are accessed without checking offset+2+length <= tlv_array_len. A crafted length=0xFF on a short buffer causes up to 255 bytes of OOB read past the buffer end. Both functions are reachable without authentication via nfc_llcp_set_remote_gb() which feeds remote LLCP general bytes directly into nfc_llcp_parse_gb_tlv() with no additional validation. Fix all three issues by widening offset from u8 to u16 and adding bounds checks for both the TLV header and value field before each access. Fixes: 3df40eb3a2ea ("nfc: constify several pointers to u8, char and sk_buff") Cc: stable@vger.kernel.org Signed-off-by: Muhammad Bilal Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260622131802.239035-1-meatuni001@gmail.com Signed-off-by: David Heidelberg --- net/nfc/llcp_commands.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/net/nfc/llcp_commands.c b/net/nfc/llcp_commands.c index 291f26facbf3..ca89fe967d6a 100644 --- a/net/nfc/llcp_commands.c +++ b/net/nfc/llcp_commands.c @@ -193,7 +193,8 @@ int nfc_llcp_parse_gb_tlv(struct nfc_llcp_local *local, const u8 *tlv_array, u16 tlv_array_len) { const u8 *tlv = tlv_array; - u8 type, length, offset = 0; + u8 type, length; + u16 offset = 0; pr_debug("TLV array length %d\n", tlv_array_len); @@ -201,9 +202,15 @@ int nfc_llcp_parse_gb_tlv(struct nfc_llcp_local *local, return -ENODEV; while (offset < tlv_array_len) { + if (offset + 2 > tlv_array_len) + return -EINVAL; + type = tlv[0]; length = tlv[1]; + if (offset + 2 + length > tlv_array_len) + return -EINVAL; + pr_debug("type 0x%x length %d\n", type, length); switch (type) { @@ -243,7 +250,8 @@ int nfc_llcp_parse_connection_tlv(struct nfc_llcp_sock *sock, const u8 *tlv_array, u16 tlv_array_len) { const u8 *tlv = tlv_array; - u8 type, length, offset = 0; + u8 type, length; + u16 offset = 0; pr_debug("TLV array length %d\n", tlv_array_len); @@ -251,9 +259,15 @@ int nfc_llcp_parse_connection_tlv(struct nfc_llcp_sock *sock, return -ENOTCONN; while (offset < tlv_array_len) { + if (offset + 2 > tlv_array_len) + return -EINVAL; + type = tlv[0]; length = tlv[1]; + if (offset + 2 + length > tlv_array_len) + return -EINVAL; + pr_debug("type 0x%x length %d\n", type, length); switch (type) { From 0428fa2c22e2ba0cff766d3b80d461e149102045 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 12 Jun 2026 12:50:25 -0500 Subject: [PATCH 07/70] nfc: nci: add data_len bound checks to activation parameter extractors nci_extract_activation_params_iso_dep() and nci_extract_activation_params_nfc_dep() read an inner length byte from the NCI RF_INTF_ACTIVATED_NTF payload and use it to memcpy() into fixed kernel buffers, but neither function receives the caller-validated activation_params_len. A crafted NCI notification with activation_params_len=1 and an inner length byte of up to 20 (NFC-A) or 50 (NFC-B) causes memcpy() to read that many bytes past the one valid byte in the activation params region -- a slab out-of-bounds read of kernel memory adjacent to the NCI skb. The sibling nci_extract_rf_params_*() family was given equivalent protection by commit 571dcbeb8e63 ("net: nfc: nci: Fix parameter validation for packet data"), but the two activation parameter extractors were not updated at that time. Add a data_len parameter to both functions, guard against an empty region before consuming the inner length byte, decrement the remaining count after consuming it, and clamp the copy length to what is actually available. Update both call sites to pass ntf.activation_params_len, which is already validated against the skb at ntf.c:801. Fixes: e8c0dacd9836 ("NFC: Update names and structs to NCI spec 1.0 d18") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Link: https://patch.msgid.link/20260612-b4-disp-6d52d8b0-v3-1-e26221f8826d@proton.me Signed-off-by: David Heidelberg --- net/nfc/nci/ntf.c | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/net/nfc/nci/ntf.c b/net/nfc/nci/ntf.c index c96512bb8653..8bc3adbc5b6b 100644 --- a/net/nfc/nci/ntf.c +++ b/net/nfc/nci/ntf.c @@ -525,15 +525,19 @@ static int nci_rf_discover_ntf_packet(struct nci_dev *ndev, static int nci_extract_activation_params_iso_dep(struct nci_dev *ndev, struct nci_rf_intf_activated_ntf *ntf, - const __u8 *data) + const __u8 *data, __u8 data_len) { struct activation_params_nfca_poll_iso_dep *nfca_poll; struct activation_params_nfcb_poll_iso_dep *nfcb_poll; switch (ntf->activation_rf_tech_and_mode) { case NCI_NFC_A_PASSIVE_POLL_MODE: + if (data_len < 1) + return NCI_STATUS_RF_PROTOCOL_ERROR; nfca_poll = &ntf->activation_params.nfca_poll_iso_dep; nfca_poll->rats_res_len = min_t(__u8, *data++, NFC_ATS_MAXSIZE); + data_len--; + nfca_poll->rats_res_len = min_t(__u8, nfca_poll->rats_res_len, data_len); pr_debug("rats_res_len %d\n", nfca_poll->rats_res_len); if (nfca_poll->rats_res_len > 0) { memcpy(nfca_poll->rats_res, @@ -542,8 +546,12 @@ static int nci_extract_activation_params_iso_dep(struct nci_dev *ndev, break; case NCI_NFC_B_PASSIVE_POLL_MODE: + if (data_len < 1) + return NCI_STATUS_RF_PROTOCOL_ERROR; nfcb_poll = &ntf->activation_params.nfcb_poll_iso_dep; nfcb_poll->attrib_res_len = min_t(__u8, *data++, 50); + data_len--; + nfcb_poll->attrib_res_len = min_t(__u8, nfcb_poll->attrib_res_len, data_len); pr_debug("attrib_res_len %d\n", nfcb_poll->attrib_res_len); if (nfcb_poll->attrib_res_len > 0) { memcpy(nfcb_poll->attrib_res, @@ -562,7 +570,7 @@ static int nci_extract_activation_params_iso_dep(struct nci_dev *ndev, static int nci_extract_activation_params_nfc_dep(struct nci_dev *ndev, struct nci_rf_intf_activated_ntf *ntf, - const __u8 *data) + const __u8 *data, __u8 data_len) { struct activation_params_poll_nfc_dep *poll; struct activation_params_listen_nfc_dep *listen; @@ -570,9 +578,13 @@ static int nci_extract_activation_params_nfc_dep(struct nci_dev *ndev, switch (ntf->activation_rf_tech_and_mode) { case NCI_NFC_A_PASSIVE_POLL_MODE: case NCI_NFC_F_PASSIVE_POLL_MODE: + if (data_len < 1) + return NCI_STATUS_RF_PROTOCOL_ERROR; poll = &ntf->activation_params.poll_nfc_dep; poll->atr_res_len = min_t(__u8, *data++, NFC_ATR_RES_MAXSIZE - 2); + data_len--; + poll->atr_res_len = min_t(__u8, poll->atr_res_len, data_len); pr_debug("atr_res_len %d\n", poll->atr_res_len); if (poll->atr_res_len > 0) memcpy(poll->atr_res, data, poll->atr_res_len); @@ -580,9 +592,13 @@ static int nci_extract_activation_params_nfc_dep(struct nci_dev *ndev, case NCI_NFC_A_PASSIVE_LISTEN_MODE: case NCI_NFC_F_PASSIVE_LISTEN_MODE: + if (data_len < 1) + return NCI_STATUS_RF_PROTOCOL_ERROR; listen = &ntf->activation_params.listen_nfc_dep; listen->atr_req_len = min_t(__u8, *data++, NFC_ATR_REQ_MAXSIZE - 2); + data_len--; + listen->atr_req_len = min_t(__u8, listen->atr_req_len, data_len); pr_debug("atr_req_len %d\n", listen->atr_req_len); if (listen->atr_req_len > 0) memcpy(listen->atr_req, data, listen->atr_req_len); @@ -806,12 +822,14 @@ static int nci_rf_intf_activated_ntf_packet(struct nci_dev *ndev, switch (ntf.rf_interface) { case NCI_RF_INTERFACE_ISO_DEP: err = nci_extract_activation_params_iso_dep(ndev, - &ntf, data); + &ntf, data, + ntf.activation_params_len); break; case NCI_RF_INTERFACE_NFC_DEP: err = nci_extract_activation_params_nfc_dep(ndev, - &ntf, data); + &ntf, data, + ntf.activation_params_len); break; case NCI_RF_INTERFACE_FRAME: From ac200079db50af81e6b04d058b33ec92901d8edd Mon Sep 17 00:00:00 2001 From: Samuel Page Date: Mon, 22 Jun 2026 16:52:43 +0200 Subject: [PATCH 08/70] nfc: nci: fix out-of-bounds write in nci_target_auto_activated() nci_target_auto_activated() appends a target to the fixed-size array ndev->targets[NCI_MAX_DISCOVERED_TARGETS] and increments ndev->n_targets without first checking the array is full; unlike its sibling nci_add_new_target(), which bails out when n_targets already equals NCI_MAX_DISCOVERED_TARGETS. ndev->n_targets is only cleared by nci_clear_target_list(), so an NFCC that repeatedly re-runs discovery (RF_DISCOVER_RSP, which re-enters NCI_DISCOVERY without clearing the target list) and reports an auto-activated target (RF_INTF_ACTIVATED_NTF) drives n_targets past the limit. The append then writes a struct nfc_target past the end of the array (a slab out-of-bounds write), and nfc_targets_found() goes on to walk the array with the inflated count: BUG: KASAN: slab-out-of-bounds in nci_add_new_protocol+0x94/0x2ac [nci] Write of size 2 at addr ffff0000c7299a18 by task kworker/u8:0/12 Workqueue: nfc0_nci_rx_wq nci_rx_work [nci] Call trace: nci_add_new_protocol+0x94/0x2ac [nci] nci_ntf_packet+0xddc/0x11a0 [nci] nci_rx_work+0x15c/0x1e0 [nci] process_one_work+0x2dc/0x500 worker_thread+0x240/0x460 kthread+0x1c0/0x1d0 ret_from_fork+0x10/0x20 The buggy address belongs to the cache kmalloc-2k of size 2048 The buggy address is located 1024 bytes to the right of allocated 1560-byte region [ffff0000c7299000, ffff0000c7299618) Guard nci_target_auto_activated() with the same check used by nci_add_new_target(). Fixes: 019c4fbaa790 ("NFC: Add NCI multiple targets support") Cc: stable@vger.kernel.org Assisted-by: Bynario AI Signed-off-by: Samuel Page Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260622145243.3167276-1-sam@bynar.io Signed-off-by: David Heidelberg --- net/nfc/nci/ntf.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/nfc/nci/ntf.c b/net/nfc/nci/ntf.c index 8bc3adbc5b6b..87f76a29f0ec 100644 --- a/net/nfc/nci/ntf.c +++ b/net/nfc/nci/ntf.c @@ -619,6 +619,12 @@ static void nci_target_auto_activated(struct nci_dev *ndev, struct nfc_target *target; int rc; + /* This is a new target, check if we've enough room */ + if (ndev->n_targets == NCI_MAX_DISCOVERED_TARGETS) { + pr_debug("not enough room, ignoring new target...\n"); + return; + } + target = &ndev->targets[ndev->n_targets]; rc = nci_add_new_protocol(ndev, target, ntf->rf_protocol, From 7ad21dcfeb5181af0c3ee2608808c0c0a5283aa1 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Tue, 16 Jun 2026 23:33:35 -0500 Subject: [PATCH 09/70] nfc: fdp: bound the device-reported read length and fix an skb leak fdp_nci_i2c_read() takes the next packet length from two device-supplied bytes and never validates it. The value is a u16 used as the i2c_master_recv() count into a 261-byte on-stack buffer: a malicious, counterfeit or malfunctioning controller (or an i2c bus interposer) can drive it far past the buffer for a stack out-of-bounds write that clobbers the canary and return address, or below the minimum frame size (directly, or by truncating the computed sum) so the header/LRC strip and the next length read run past a short receive. Reject a length outside [FDP_NCI_I2C_MIN_PAYLOAD, FDP_NCI_I2C_MAX_PAYLOAD], as a corrupted packet already is, and force resynchronization. The same loop allocates one data skb per iteration and assumes a length packet followed by a data packet; a device that sends two data packets in one call leaks the first skb when the second allocation overwrites it. Free a previously allocated skb before allocating the next. Fixes: a06347c04c13 ("NFC: Add Intel Fields Peak NFC solution driver") Cc: stable@vger.kernel.org Suggested-by: Simon Horman Signed-off-by: Bryam Vargas Link: https://patch.msgid.link/20260616-b4-disp-b1f8ab4c-v2-1-2d1fe5955325@proton.me Signed-off-by: David Heidelberg --- drivers/nfc/fdp/i2c.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/drivers/nfc/fdp/i2c.c b/drivers/nfc/fdp/i2c.c index c1896a1d978c..f292e7f37456 100644 --- a/drivers/nfc/fdp/i2c.c +++ b/drivers/nfc/fdp/i2c.c @@ -166,9 +166,36 @@ static int fdp_nci_i2c_read(struct fdp_i2c_phy *phy, struct sk_buff **skb) /* Packet that contains a length */ if (tmp[0] == 0 && tmp[1] == 0) { phy->next_read_size = (tmp[2] << 8) + tmp[3] + 3; + + /* + * next_read_size is taken from the device and is used + * as the i2c_master_recv() count for the next packet + * and as the data skb size. A value above the receive + * buffer overflows tmp[]; one below the minimum frame + * size runs the header/LRC strip and the length-field + * read past a short receive. Either way the packet is + * corrupt: drop it and force resynchronization. + */ + if (phy->next_read_size < FDP_NCI_I2C_MIN_PAYLOAD || + phy->next_read_size > FDP_NCI_I2C_MAX_PAYLOAD) { + dev_dbg(&client->dev, "%s: corrupted packet\n", + __func__); + phy->next_read_size = FDP_NCI_I2C_MIN_PAYLOAD; + goto flush; + } } else { phy->next_read_size = FDP_NCI_I2C_MIN_PAYLOAD; + /* + * Only one data packet is delivered per call; if the + * device sends another, do not overwrite and leak the + * skb allocated for the previous one. + */ + if (*skb) { + kfree_skb(*skb); + *skb = NULL; + } + *skb = alloc_skb(len, GFP_KERNEL); if (*skb == NULL) { r = -ENOMEM; From 8cbe06c1e699c0a165dae5093a2550e65f914818 Mon Sep 17 00:00:00 2001 From: Samuel Page Date: Fri, 26 Jun 2026 10:03:01 +0100 Subject: [PATCH 10/70] nfc: nci: fix uninit-value in the RF discover/activated NTF handlers nci_rf_discover_ntf_packet() and nci_rf_intf_activated_ntf_packet() each parse a notification into an on-stack struct (nci_rf_discover_ntf / nci_rf_intf_activated_ntf) that is not initialised. The RF technology-specific parameters are only extracted when rf_tech_specific_params_len is non-zero, so a notification that reports a zero length leaves the rf_tech_specific_params union uninitialised - and both handlers then pass it to nci_add_new_protocol(), which reads it: - discover: nci_add_new_target() -> nci_add_new_protocol(); - activated: nci_target_auto_activated() -> nci_add_new_protocol(). nci_add_new_protocol() uses nfca_poll->nfcid1_len as both a branch condition and a memcpy() length and copies nfcid1/sens_res/sel_res into ndev->targets, which is later exposed to user space via NFC_CMD_GET_TARGET. BUG: KMSAN: uninit-value in nci_add_new_protocol+0x624/0x6c0 nci_add_new_protocol+0x624/0x6c0 nci_ntf_packet+0x25b2/0x3c30 nci_rx_work+0x318/0x5d0 process_scheduled_works+0x84b/0x17a0 worker_thread+0xc10/0x11b0 kthread+0x376/0x500 Local variable ntf.i created at: nci_ntf_packet+0xbc2/0x3c30 Zero-initialise both on-stack notifications so the union reads back as zero when no technology-specific parameters are present. Fixes: 019c4fbaa790 ("NFC: Add NCI multiple targets support") Fixes: e8c0dacd9836 ("NFC: Update names and structs to NCI spec 1.0 d18") Link: https://lore.kernel.org/netdev/20260623172109.1105965-2-horms@kernel.org/ Cc: stable@vger.kernel.org Assisted-by: Bynario AI Signed-off-by: Samuel Page Link: https://patch.msgid.link/20260626090301.2139500-1-sam@bynar.io Signed-off-by: David Heidelberg --- net/nfc/nci/ntf.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/nfc/nci/ntf.c b/net/nfc/nci/ntf.c index 87f76a29f0ec..f5c9a8ab7ec1 100644 --- a/net/nfc/nci/ntf.c +++ b/net/nfc/nci/ntf.c @@ -440,7 +440,7 @@ void nci_clear_target_list(struct nci_dev *ndev) static int nci_rf_discover_ntf_packet(struct nci_dev *ndev, const struct sk_buff *skb) { - struct nci_rf_discover_ntf ntf; + struct nci_rf_discover_ntf ntf = {}; const __u8 *data; bool add_target = true; @@ -710,7 +710,7 @@ static int nci_rf_intf_activated_ntf_packet(struct nci_dev *ndev, const struct sk_buff *skb) { struct nci_conn_info *conn_info; - struct nci_rf_intf_activated_ntf ntf; + struct nci_rf_intf_activated_ntf ntf = {}; const __u8 *data; int err = NCI_STATUS_OK; From 47792358a624ea066455ef86b744159928cd7716 Mon Sep 17 00:00:00 2001 From: Yinhao Hu Date: Fri, 26 Jun 2026 00:34:34 -0700 Subject: [PATCH 11/70] nfc: pn533: hold a reference to the request skb during send_frame __pn533_send_async() publishes the command and then calls dev->phy_ops->send_frame(). Once dev->cmd is set, an incoming frame can be matched to this command: the I2C threaded IRQ runs pn533_recv_frame(), which queues cmd_complete_work, and pn533_send_async_complete() frees cmd->req with consume_skb(). On the I2C transport, pn533_i2c_send_frame() still dereferences the same skb after i2c_master_send() returns, so a completion that races the send can free the skb while the transport is still using it. The request skb is owned by the command object and may be freed by command completion at any time after dev->cmd is published, so the transport send path must not assume it stays alive. Hold a temporary reference to the request skb across the send_frame() call so the transport always sees a live skb even if completion races the send. Add a pn533_send_cmd_frame() helper and use it from all three send paths. Fixes: 9815c7cf22da ("NFC: pn533: Separate physical layer from the core implementation") Signed-off-by: Yinhao Hu Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260626073434.3977525-1-dddddd@hust.edu.cn Signed-off-by: David Heidelberg --- drivers/nfc/pn533/pn533.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/drivers/nfc/pn533/pn533.c b/drivers/nfc/pn533/pn533.c index d7bdbc82e2ba..55bbfa32d695 100644 --- a/drivers/nfc/pn533/pn533.c +++ b/drivers/nfc/pn533/pn533.c @@ -434,6 +434,18 @@ static int pn533_send_async_complete(struct pn533 *dev) return rc; } +static int pn533_send_cmd_frame(struct pn533 *dev, struct pn533_cmd *cmd) +{ + struct sk_buff *req = cmd->req; + int rc; + + skb_get(req); + dev->cmd = cmd; + rc = dev->phy_ops->send_frame(dev, req); + dev_kfree_skb(req); + return rc; +} + static int __pn533_send_async(struct pn533 *dev, u8 cmd_code, struct sk_buff *req, pn533_send_async_complete_t complete_cb, @@ -458,8 +470,7 @@ static int __pn533_send_async(struct pn533 *dev, u8 cmd_code, mutex_lock(&dev->cmd_lock); if (!dev->cmd_pending) { - dev->cmd = cmd; - rc = dev->phy_ops->send_frame(dev, req); + rc = pn533_send_cmd_frame(dev, cmd); if (rc) { dev->cmd = NULL; goto error; @@ -529,8 +540,7 @@ static int pn533_send_cmd_direct_async(struct pn533 *dev, u8 cmd_code, pn533_build_cmd_frame(dev, cmd_code, req); - dev->cmd = cmd; - rc = dev->phy_ops->send_frame(dev, req); + rc = pn533_send_cmd_frame(dev, cmd); if (rc < 0) { dev->cmd = NULL; kfree(cmd); @@ -569,8 +579,7 @@ static void pn533_wq_cmd(struct work_struct *work) mutex_unlock(&dev->cmd_lock); - dev->cmd = cmd; - rc = dev->phy_ops->send_frame(dev, cmd->req); + rc = pn533_send_cmd_frame(dev, cmd); if (rc < 0) { dev->cmd = NULL; dev_kfree_skb(cmd->req); From 1c7dd70c0adfa58fd66b5cbd03efb747ad6d8d8d Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Fri, 10 Jul 2026 14:12:54 +0800 Subject: [PATCH 12/70] nfc: digital: Do not dump a NULL response in command completion digital_wq_cmd_complete() dumps the response data whenever cmd->resp is not an error pointer. However, a driver can legitimately complete a command with no response skb at all. digital_tg_send_psl_res() is the only caller that passes timeout=0, meaning no response is expected once the command has been transmitted. On that path trf7970a completes the command with trf->rx_skb = ERR_PTR(0); which evaluates to NULL. IS_ERR(NULL) is false, so the NULL response passes the !IS_ERR() check and cmd->resp->data and cmd->resp->len are dereferenced whenever the debug print site is enabled. The driver guards its own dump with "trf->rx_skb && !IS_ERR(trf->rx_skb)"; the digital layer is missing the NULL half of that test. Use IS_ERR_OR_NULL() so that NULL responses are skipped as well. The callback on that path, digital_tg_send_psl_res_complete(), never dereferences resp and dev_kfree_skb() accepts NULL, so only the debug dump needs fixing. Fixes: 59ee2361c924 ("NFC Digital: Implement driver commands mechanism") Signed-off-by: Linmao Li Reviewed-by: Przemek Kitszel Link: https://patch.msgid.link/20260710061254.80975-1-lilinmao@kylinos.cn Signed-off-by: David Heidelberg --- net/nfc/digital_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/nfc/digital_core.c b/net/nfc/digital_core.c index 7cb1e6aaae90..18236221d898 100644 --- a/net/nfc/digital_core.c +++ b/net/nfc/digital_core.c @@ -127,7 +127,7 @@ static void digital_wq_cmd_complete(struct work_struct *work) mutex_unlock(&ddev->cmd_lock); - if (!IS_ERR(cmd->resp)) + if (!IS_ERR_OR_NULL(cmd->resp)) print_hex_dump_debug("DIGITAL RX: ", DUMP_PREFIX_NONE, 16, 1, cmd->resp->data, cmd->resp->len, false); From 95674f506c6376d6722a23144c9acd26609771ed Mon Sep 17 00:00:00 2001 From: Doruk Tan Ozturk Date: Tue, 14 Jul 2026 18:46:31 +0200 Subject: [PATCH 13/70] nfc: llcp: reject PDUs shorter than the LLCP header Every LLCP PDU begins with a two-byte header (DSAP/SSAP + PTYPE), but the receive path never checked that a frame is at least LLCP_HEADER_SIZE bytes before parsing it. nfc_llcp_rx_skb() reads the header via nfc_llcp_ptype()/nfc_llcp_dsap()/ nfc_llcp_ssap(), which dereference pdu->data[0] and pdu->data[1], and a CONNECT or CC PDU then computes tlv_array_len = skb->len - LLCP_HEADER_SIZE; as a size_t and hands it to the TLV walk. When the frame is shorter than the header the subtraction wraps to a huge value and the walk runs far past the buffer, an out-of-bounds read. A nearby NFC device can reach this without authentication; LLCP link activation happens automatically after NFC-DEP. Guard the common receive choke point __nfc_llcp_recv(), shared by both the target (nfc_llcp_data_received()) and initiator (nfc_llcp_recv()) paths, so a short skb is dropped before the rx_work worker parses it. Use pskb_may_pull() rather than a skb->len test so the two header bytes are guaranteed to sit in the skb linear area even for a non-linear skb, matching how the sibling NCI and HCI receive paths validate their headers. Reproduced with a KFENCE out-of-bounds read via /dev/virtual_nci on linux-next. Found by 0sec automated security-research tooling (https://0sec.ai). Fixes: d646960f7986 ("NFC: Initial LLCP support") Cc: stable@vger.kernel.org Suggested-by: David Laight Signed-off-by: Doruk Tan Ozturk Reviewed-by: Vadim Fedorenko Link: https://patch.msgid.link/20260714164631.75068-1-doruk@0sec.ai Signed-off-by: David Heidelberg --- net/nfc/llcp_core.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c index aed5fe1afef0..e3b2627cb089 100644 --- a/net/nfc/llcp_core.c +++ b/net/nfc/llcp_core.c @@ -1565,6 +1565,11 @@ static void nfc_llcp_rx_work(struct work_struct *work) static void __nfc_llcp_recv(struct nfc_llcp_local *local, struct sk_buff *skb) { + if (!pskb_may_pull(skb, LLCP_HEADER_SIZE)) { + kfree_skb(skb); + return; + } + local->rx_pending = skb; timer_delete(&local->link_timer); schedule_work(&local->rx_work); From 55c68ac93e7dacc0f5f608b9c39dd4ff48cf28e8 Mon Sep 17 00:00:00 2001 From: Doruk Tan Ozturk Date: Thu, 9 Jul 2026 15:12:29 +0200 Subject: [PATCH 14/70] nfc: llcp: bound the connect_sn TLV walk to the skb Commit 27256cdb290e ("nfc: llcp: bound SNL TLV parsing to the skb and add length checks") fixed the unbounded TLV walk in nfc_llcp_recv_snl(), and commit d8bd2dedbde5 ("nfc: llcp: fix OOB read and u8 offset wrap in TLV parsers") subsequently bounded nfc_llcp_parse_gb_tlv() and nfc_llcp_parse_connection_tlv(). One sibling parser sharing the same pattern remains unbounded: nfc_llcp_connect_sn(). nfc_llcp_connect_sn() walks a TLV list, reading a two-byte header (type, length) followed by length bytes of value, without checking that the two header bytes or the declared length stay within the buffer. It returns a pointer to a service name of up to 255 bytes that may point past the end of the skb; it is subsequently consumed by memcmp() in nfc_llcp_sock_from_sn(). In addition tlv_array_len was computed as "skb->len - LLCP_HEADER_SIZE" in size_t, so a CONNECT/CC frame shorter than the LLCP header underflows to a huge length and the walk runs far past the buffer. nfc_llcp_connect_sn() is reachable from nfc_llcp_recv_connect() and nfc_llcp_recv_cc(), i.e. from received CONNECT and CC PDUs. A nearby NFC device can reach this without authentication; LLCP link activation happens automatically after NFC-DEP, and the nfc_llcp_rx_skb() dispatcher applies no minimum-length guard. Walk the TLV list by pointer, bounded by skb_tail_pointer(skb), and validate each declared length before use, matching the approach already used for nfc_llcp_recv_snl(). Starting the walk at &skb->data[LLCP_HEADER_SIZE] against the tail pointer also removes the size_t underflow for short frames. Found by 0sec automated security-research tooling (https://0sec.ai). Fixes: d646960f7986 ("NFC: Initial LLCP support") Cc: stable@vger.kernel.org Assisted-by: 0sec:claude-opus-4-8 Signed-off-by: Doruk Tan Ozturk Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260709131229.44477-1-doruk@0sec.ai Signed-off-by: David Heidelberg --- net/nfc/llcp_core.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c index e3b2627cb089..cac1b5487064 100644 --- a/net/nfc/llcp_core.c +++ b/net/nfc/llcp_core.c @@ -849,13 +849,16 @@ static struct nfc_llcp_sock *nfc_llcp_sock_get_sn(struct nfc_llcp_local *local, static const u8 *nfc_llcp_connect_sn(const struct sk_buff *skb, size_t *sn_len) { u8 type, length; - const u8 *tlv = &skb->data[2]; - size_t tlv_array_len = skb->len - LLCP_HEADER_SIZE, offset = 0; + const u8 *tlv = &skb->data[LLCP_HEADER_SIZE]; + const u8 *tlv_end = skb_tail_pointer(skb); - while (offset < tlv_array_len) { + while (tlv + 2 < tlv_end) { type = tlv[0]; length = tlv[1]; + if (tlv + 2 + length > tlv_end) + break; + pr_debug("type 0x%x length %d\n", type, length); if (type == LLCP_TLV_SN) { @@ -863,7 +866,6 @@ static const u8 *nfc_llcp_connect_sn(const struct sk_buff *skb, size_t *sn_len) return &tlv[2]; } - offset += length + 2; tlv += length + 2; } From 5cdcca5d62a66eda6b774110a44cba67bc1a8d1d Mon Sep 17 00:00:00 2001 From: Doruk Tan Ozturk Date: Sat, 11 Jul 2026 09:13:01 +0200 Subject: [PATCH 15/70] nfc: st21nfca: validate ATR_REQ length against the received frame st21nfca_tm_recv_atr_req() checks that the received ATR_REQ frame is at least ST21NFCA_ATR_REQ_MIN_SIZE and that the self-declared atr_req->length is at least sizeof(struct st21nfca_atr_req), but never checks that atr_req->length does not exceed the actual received length (skb->len). st21nfca_tm_send_atr_res() then trusts the declared length: gb_len = atr_req->length - sizeof(struct st21nfca_atr_req); ... memcpy(atr_res->gbi, atr_req->gbi, gb_len); so an RF peer that sends a short frame but sets atr_req->length larger than the frame makes gb_len exceed the general bytes actually present, and the memcpy reads out of bounds past the received skb. Those bytes are placed in the ATR_RES and sent back to the peer (kernel-memory disclosure to a proximity attacker); a larger declared length is an out-of-bounds read (DoS). Reject frames whose declared length exceeds the received length. The adjacent nfc_tm_activated() path in the same function already derives its general-bytes length from skb->len rather than the declared field. Found by 0sec (https://0sec.ai) using automated source analysis; the missing bound is evident from source. Compile-tested. Fixes: 1892bf844ea0 ("NFC: st21nfca: Adding P2P support to st21nfca in Initiator & Target mode") Cc: stable@vger.kernel.org Assisted-by: 0sec:claude-opus-4-8 Signed-off-by: Doruk Tan Ozturk Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260711071301.58071-1-doruk@0sec.ai Signed-off-by: David Heidelberg --- drivers/nfc/st21nfca/dep.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/nfc/st21nfca/dep.c b/drivers/nfc/st21nfca/dep.c index 3425b68f0ddc..a5fab4fd5129 100644 --- a/drivers/nfc/st21nfca/dep.c +++ b/drivers/nfc/st21nfca/dep.c @@ -205,6 +205,9 @@ static int st21nfca_tm_recv_atr_req(struct nfc_hci_dev *hdev, if (atr_req->length < sizeof(struct st21nfca_atr_req)) return -EPROTO; + if (atr_req->length > skb->len) + return -EPROTO; + r = st21nfca_tm_send_atr_res(hdev, atr_req); if (r) return r; From 5718fc62198c38c2de5316020a90506f9e75e0bb Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Mon, 20 Jul 2026 10:14:44 +0800 Subject: [PATCH 16/70] nfc: pn533: purge fragmented skbs during cleanup pn53x_common_clean() purges resp_q before freeing the common PN533 state, but it leaves fragment_skb untouched. The fragmentation helpers queue transmit fragments there while sending large initiator or target-mode frames, and those skbs remain owned by the driver until they are sent or discarded. If the device is removed while fragments are still queued, the common cleanup path frees the PN533 state without releasing the queued fragment skbs, leaking them. Purge fragment_skb during cleanup alongside resp_q. Fixes: 963a82e07d4e ("NFC: pn533: Split large Tx frames in chunks") Cc: stable@vger.kernel.org Signed-off-by: Xu Rao Link: https://patch.msgid.link/2D896607CAE4408E+20260720021444.3362044-1-raoxu@uniontech.com Signed-off-by: David Heidelberg --- drivers/nfc/pn533/pn533.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/nfc/pn533/pn533.c b/drivers/nfc/pn533/pn533.c index 55bbfa32d695..76081a99b450 100644 --- a/drivers/nfc/pn533/pn533.c +++ b/drivers/nfc/pn533/pn533.c @@ -2808,6 +2808,7 @@ void pn53x_common_clean(struct pn533 *priv) destroy_workqueue(priv->wq); skb_queue_purge(&priv->resp_q); + skb_queue_purge(&priv->fragment_skb); list_for_each_entry_safe(cmd, n, &priv->cmd_queue, queue) { list_del(&cmd->queue); From d56575a2595ee1f597f39e8a1cfb67ed3501678d Mon Sep 17 00:00:00 2001 From: Yun Zhou Date: Wed, 27 May 2026 13:26:25 +0800 Subject: [PATCH 17/70] nfc: nci: fix use of uninitialized memory in CORE_INIT_RSP parsing nci_core_init_rsp_packet_v1() and nci_core_init_rsp_packet_v2() parse the CORE_INIT_RSP packet without validating that the skb contains enough data. A malformed response (e.g. injected via virtual_ncidev) can declare a large num_supported_rf_interfaces while providing insufficient data, causing reads of uninitialized slab memory. This is later used in nci_init_complete_req(), triggering a KMSAN uninit-value warning. Add skb length checks before accessing packet fields: - Validate the skb has at least 1 byte for the status field. - Validate the skb can hold the fixed-size header before parsing. - In v2, bounds-check each variable-length rf_interface entry and its extension parameters within the parsing loop. - In v1, verify the skb is large enough for both the variable-length rf_interfaces array and the trailing rsp_2 structure. Reported-by: syzbot+46ca2592193f2fb3debc@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=46ca2592193f2fb3debc Fixes: bcd684aace34 ("net/nfc/nci: Support NCI 2.x initial sequence") Signed-off-by: Yun Zhou Link: https://patch.msgid.link/20260527052625.3309581-1-yun.zhou@windriver.com Signed-off-by: David Heidelberg --- net/nfc/nci/rsp.c | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/net/nfc/nci/rsp.c b/net/nfc/nci/rsp.c index 9eeb862825c5..85c3ff2b78cd 100644 --- a/net/nfc/nci/rsp.c +++ b/net/nfc/nci/rsp.c @@ -50,11 +50,27 @@ static u8 nci_core_init_rsp_packet_v1(struct nci_dev *ndev, const struct nci_core_init_rsp_1 *rsp_1 = (void *)skb->data; const struct nci_core_init_rsp_2 *rsp_2; + /* Ensure that the status field can be accessed. */ + if (skb_headlen(skb) < 1) + return NCI_STATUS_SYNTAX_ERROR; + pr_debug("status 0x%x\n", rsp_1->status); if (rsp_1->status != NCI_STATUS_OK) return rsp_1->status; + /* Success response must contain the full fixed-size header */ + if (skb_headlen(skb) < sizeof(*rsp_1)) + return NCI_STATUS_SYNTAX_ERROR; + + /* Ensure the variable-length rf_interfaces array and trailing + * rsp_2 structure are fully contained within the skb. + */ + if (skb_headlen(skb) < sizeof(*rsp_1) + + rsp_1->num_supported_rf_interfaces + + sizeof(*rsp_2)) + return NCI_STATUS_SYNTAX_ERROR; + ndev->nfcc_features = __le32_to_cpu(rsp_1->nfcc_features); ndev->num_supported_rf_interfaces = rsp_1->num_supported_rf_interfaces; @@ -87,15 +103,25 @@ static u8 nci_core_init_rsp_packet_v2(struct nci_dev *ndev, const struct sk_buff *skb) { const struct nci_core_init_rsp_nci_ver2 *rsp = (void *)skb->data; - const u8 *supported_rf_interface = rsp->supported_rf_interfaces; + const u8 *supported_rf_interface; u8 rf_interface_idx = 0; u8 rf_extension_cnt = 0; + /* Ensure that the status field can be accessed. */ + if (skb_headlen(skb) < 1) + return NCI_STATUS_SYNTAX_ERROR; + pr_debug("status %x\n", rsp->status); if (rsp->status != NCI_STATUS_OK) return rsp->status; + /* Success response must contain the full fixed-size header */ + if (skb_headlen(skb) < sizeof(*rsp)) + return NCI_STATUS_SYNTAX_ERROR; + + supported_rf_interface = rsp->supported_rf_interfaces; + ndev->nfcc_features = __le32_to_cpu(rsp->nfcc_features); ndev->num_supported_rf_interfaces = rsp->num_supported_rf_interfaces; @@ -104,13 +130,22 @@ static u8 nci_core_init_rsp_packet_v2(struct nci_dev *ndev, NCI_MAX_SUPPORTED_RF_INTERFACES); while (rf_interface_idx < ndev->num_supported_rf_interfaces) { - ndev->supported_rf_interfaces[rf_interface_idx++] = *supported_rf_interface++; + /* Each entry: [rf_interface_type (1B)] [ext_count (1B)] [ext...] */ + if (supported_rf_interface + 2 > skb_tail_pointer(skb)) + break; + ndev->supported_rf_interfaces[rf_interface_idx] = *supported_rf_interface++; - /* skip rf extension parameters */ rf_extension_cnt = *supported_rf_interface++; + if (supported_rf_interface + rf_extension_cnt > skb_tail_pointer(skb)) + break; + + /* Only count the entry after full validation */ + rf_interface_idx++; supported_rf_interface += rf_extension_cnt; } + ndev->num_supported_rf_interfaces = rf_interface_idx; + ndev->max_logical_connections = rsp->max_logical_connections; ndev->max_routing_table_size = __le16_to_cpu(rsp->max_routing_table_size); From 2e65bafdfd3a8bba972b3d17b6a57816557530fc Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Tue, 21 Jul 2026 10:35:18 +0800 Subject: [PATCH 18/70] nfc: nci: free destination parameters when closing a connection When a connection is closed, nci_core_conn_close_rsp_packet() frees conn_info but not conn_info->dest_params, which is a separate devm allocation. Each connect/close cycle leaks one dest_params until the NFC device is removed. Free dest_params along with conn_info. Fixes: 9b8d1a4cf2aa ("nfc: nci: Add an additional parameter to identify a connection id") Cc: stable@vger.kernel.org Signed-off-by: Linmao Li Reviewed-by: Vadim Fedorenko Link: https://patch.msgid.link/20260721023518.1697625-1-lilinmao@kylinos.cn Signed-off-by: David Heidelberg --- net/nfc/nci/rsp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/nfc/nci/rsp.c b/net/nfc/nci/rsp.c index 85c3ff2b78cd..b0ab4f5acbce 100644 --- a/net/nfc/nci/rsp.c +++ b/net/nfc/nci/rsp.c @@ -371,6 +371,7 @@ static void nci_core_conn_close_rsp_packet(struct nci_dev *ndev, list_del(&conn_info->list); if (conn_info == ndev->rf_conn_info) ndev->rf_conn_info = NULL; + devm_kfree(&ndev->nfc_dev->dev, conn_info->dest_params); devm_kfree(&ndev->nfc_dev->dev, conn_info); } } From 25519469972ef57c3edb1805dabd6c5612b90211 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Thu, 23 Jul 2026 10:37:20 +0800 Subject: [PATCH 19/70] nfc: microread: validate target discovery payload lengths microread_target_discovered() parses target discovery payloads from skb->data according to the HCI gate. The fixed field offsets and UID copies were checked only against the destination nfc_target buffers, not against the actual skb length. Validate that each gate-specific payload contains the fixed fields and UID bytes before reading or copying them. Fixes: cfad1ba87150 ("NFC: Initial support for Inside Secure microread") Cc: stable@vger.kernel.org Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260723103508.1-microread-v2-pengpeng@iscas.ac.cn Signed-off-by: David Heidelberg --- drivers/nfc/microread/microread.c | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/drivers/nfc/microread/microread.c b/drivers/nfc/microread/microread.c index 4149c5d735bd..dfa2490db545 100644 --- a/drivers/nfc/microread/microread.c +++ b/drivers/nfc/microread/microread.c @@ -483,13 +483,19 @@ static void microread_target_discovered(struct nfc_hci_dev *hdev, u8 gate, switch (gate) { case MICROREAD_GATE_ID_MREAD_ISO_A: + if (skb->len <= MICROREAD_EMCF_A_LEN) { + r = -EINVAL; + goto exit_free; + } + targets->supported_protocols = nfc_hci_sak_to_protocol(skb->data[MICROREAD_EMCF_A_SAK]); targets->sens_res = be16_to_cpu(*(u16 *)&skb->data[MICROREAD_EMCF_A_ATQA]); targets->sel_res = skb->data[MICROREAD_EMCF_A_SAK]; targets->nfcid1_len = skb->data[MICROREAD_EMCF_A_LEN]; - if (targets->nfcid1_len > sizeof(targets->nfcid1)) { + if (targets->nfcid1_len > sizeof(targets->nfcid1) || + targets->nfcid1_len > skb->len - MICROREAD_EMCF_A_UID) { r = -EINVAL; goto exit_free; } @@ -497,13 +503,19 @@ static void microread_target_discovered(struct nfc_hci_dev *hdev, u8 gate, targets->nfcid1_len); break; case MICROREAD_GATE_ID_MREAD_ISO_A_3: + if (skb->len <= MICROREAD_EMCF_A3_LEN) { + r = -EINVAL; + goto exit_free; + } + targets->supported_protocols = nfc_hci_sak_to_protocol(skb->data[MICROREAD_EMCF_A3_SAK]); targets->sens_res = be16_to_cpu(*(u16 *)&skb->data[MICROREAD_EMCF_A3_ATQA]); targets->sel_res = skb->data[MICROREAD_EMCF_A3_SAK]; targets->nfcid1_len = skb->data[MICROREAD_EMCF_A3_LEN]; - if (targets->nfcid1_len > sizeof(targets->nfcid1)) { + if (targets->nfcid1_len > sizeof(targets->nfcid1) || + targets->nfcid1_len > skb->len - MICROREAD_EMCF_A3_UID) { r = -EINVAL; goto exit_free; } @@ -511,11 +523,21 @@ static void microread_target_discovered(struct nfc_hci_dev *hdev, u8 gate, targets->nfcid1_len); break; case MICROREAD_GATE_ID_MREAD_ISO_B: + if (skb->len < MICROREAD_EMCF_B_UID + 4) { + r = -EINVAL; + goto exit_free; + } + targets->supported_protocols = NFC_PROTO_ISO14443_B_MASK; memcpy(targets->nfcid1, &skb->data[MICROREAD_EMCF_B_UID], 4); targets->nfcid1_len = 4; break; case MICROREAD_GATE_ID_MREAD_NFC_T1: + if (skb->len < MICROREAD_EMCF_T1_UID + 4) { + r = -EINVAL; + goto exit_free; + } + targets->supported_protocols = NFC_PROTO_JEWEL_MASK; targets->sens_res = le16_to_cpu(*(u16 *)&skb->data[MICROREAD_EMCF_T1_ATQA]); @@ -523,6 +545,11 @@ static void microread_target_discovered(struct nfc_hci_dev *hdev, u8 gate, targets->nfcid1_len = 4; break; case MICROREAD_GATE_ID_MREAD_NFC_T3: + if (skb->len < MICROREAD_EMCF_T3_UID + 8) { + r = -EINVAL; + goto exit_free; + } + targets->supported_protocols = NFC_PROTO_FELICA_MASK; memcpy(targets->nfcid1, &skb->data[MICROREAD_EMCF_T3_UID], 8); targets->nfcid1_len = 8; From 6959fbdc940f62d8eef2a171d3a3342d7c248855 Mon Sep 17 00:00:00 2001 From: Przemyslaw Korba Date: Fri, 5 Jun 2026 14:06:26 +0200 Subject: [PATCH 20/70] ice: fall back to SBQ when LL PHY timer interface times out The low-latency (LL) PHY timer interface relies on a tight, atomic poll of the PF_SB_ATQBAL register with a 2ms timeout. After an NVM update / EMPR, FW may need significantly longer than 2ms to start responding to ATQBAL commands. The first PHY adjust or incval write issued by ice_ptp_rebuild_owner() fails with -ETIMEDOUT. Fix this by falling back to the existing SBQ-based PHY register write path when LL times out. This makes sure PTP is initialized when FW takes longer than expected to come back online. Steps to reproduce: ./nvmupdate64e -if devlink -f Update E810 card with nvmupdate64e, and observe dmesg errors: Failed to write PHC increment value, status -110 PTP reset failed, error: -110 (-ETIMEDOUT) Fixes: ef9a64c07294 ("ice: implement low latency PHY timer updates") Signed-off-by: Przemyslaw Korba Reviewed-by: Simon Horman Tested-by: Rinitha S (A Contingent worker at Intel) Reviewed-by: Aleksandr Loktionov Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/ice/ice_ptp_hw.c | 38 +++++++++++---------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c index 8e5f97835954..3a41c711e751 100644 --- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c +++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c @@ -4808,15 +4808,12 @@ static int ice_ptp_prep_phy_adj_ll_e810(struct ice_hw *hw, s32 adj) !FIELD_GET(REG_LL_PROXY_H_EXEC, val), 10, REG_LL_PROXY_H_TIMEOUT_US, false, hw, REG_LL_PROXY_H); - if (err) { - ice_debug(hw, ICE_DBG_PTP, "Failed to prepare PHY timer adjustment using low latency interface\n"); - spin_unlock_irq(¶ms->atqbal_wq.lock); - return err; - } - spin_unlock_irq(¶ms->atqbal_wq.lock); - return 0; + if (err) + ice_debug(hw, ICE_DBG_PTP, "Failed to prepare PHY timer adjustment using low latency interface\n"); + + return err; } /** @@ -4837,8 +4834,12 @@ static int ice_ptp_prep_phy_adj_e810(struct ice_hw *hw, s32 adj) u8 tmr_idx; int err; - if (hw->dev_caps.ts_dev_info.ll_phy_tmr_update) - return ice_ptp_prep_phy_adj_ll_e810(hw, adj); + if (hw->dev_caps.ts_dev_info.ll_phy_tmr_update) { + err = ice_ptp_prep_phy_adj_ll_e810(hw, adj); + if (err != -ETIMEDOUT) + return err; + ice_debug(hw, ICE_DBG_PTP, "LL adj timed out, falling back to SBQ\n"); + } tmr_idx = hw->func_caps.ts_func_info.tmr_index_owned; @@ -4901,15 +4902,12 @@ static int ice_ptp_prep_phy_incval_ll_e810(struct ice_hw *hw, u64 incval) !FIELD_GET(REG_LL_PROXY_H_EXEC, val), 10, REG_LL_PROXY_H_TIMEOUT_US, false, hw, REG_LL_PROXY_H); - if (err) { - ice_debug(hw, ICE_DBG_PTP, "Failed to prepare PHY timer increment using low latency interface\n"); - spin_unlock_irq(¶ms->atqbal_wq.lock); - return err; - } - spin_unlock_irq(¶ms->atqbal_wq.lock); - return 0; + if (err) + ice_debug(hw, ICE_DBG_PTP, "Failed to prepare PHY timer increment using low latency interface\n"); + + return err; } /** @@ -4927,8 +4925,12 @@ static int ice_ptp_prep_phy_incval_e810(struct ice_hw *hw, u64 incval) u8 tmr_idx; int err; - if (hw->dev_caps.ts_dev_info.ll_phy_tmr_update) - return ice_ptp_prep_phy_incval_ll_e810(hw, incval); + if (hw->dev_caps.ts_dev_info.ll_phy_tmr_update) { + err = ice_ptp_prep_phy_incval_ll_e810(hw, incval); + if (err != -ETIMEDOUT) + return err; + ice_debug(hw, ICE_DBG_PTP, "LL incval timed out, falling back to SBQ\n"); + } tmr_idx = hw->func_caps.ts_func_info.tmr_index_owned; low = lower_32_bits(incval); From d04287e27bf1c0b879a10d929c163f2da69715b6 Mon Sep 17 00:00:00 2001 From: Petr Oros Date: Mon, 22 Jun 2026 10:10:30 +0200 Subject: [PATCH 21/70] ice: clear the default forwarding VSI rule when releasing a VSI When a VSI is configured as the switch's default forwarding VSI (ICE_SW_LKUP_DFLT) and is then torn down, the rule is left behind in the switch. ice_vsi_release() no longer removes it, and the SR-IOV VF free path (ice_free_vfs() -> ice_free_vf_res() -> ice_vf_vsi_release() -> ice_vsi_release()) does not disable promiscuous mode either, which only happens on VF reset in ice_vf_clear_all_promisc_modes(). A trusted VF that enters unicast promiscuous mode becomes the default forwarding VSI (this is the default mode, when the PF does not have VF true-promiscuous mode enabled). If the VFs are then destroyed without the VF first leaving promiscuous mode, the ICE_SW_LKUP_DFLT rule for the now-freed VSI is leaked. When VFs are recreated, a VSI reuses the freed hw_vsi_id. If it is assigned a different VSI handle than the leaked rule holds, ice_set_dflt_vsi() does not recognize it as already-default, and ice_add_update_vsi_list() folds the dangling (freed) handle into a VSI list, which the firmware rejects. The VSI handle assigned on re-creation varies, so the failure is intermittent rather than every cycle. Reproduce by repeatedly running the cycle below on the two ports of the same card, where $VF0 and $VF1 are the netdevs of vf 15 once they appear. The VF must be brought up so iavf actually pushes the unicast promiscuous request, and the rule must settle before the VFs are torn down again: echo 16 > /sys/class/net/$PF0/device/sriov_numvfs echo 16 > /sys/class/net/$PF1/device/sriov_numvfs ip link set $PF0 vf 15 trust on ip link set $PF1 vf 15 trust on ip link set $VF0 up ip link set $VF1 up ip link set $VF0 promisc on ip link set $VF1 promisc on sleep 1 echo 0 > /sys/class/net/$PF0/device/sriov_numvfs echo 0 > /sys/class/net/$PF1/device/sriov_numvfs Within a few cycles the ice PF and iavf VF log: Failed to set VSI 25 as the default forwarding VSI, error -22 Turning on/off promiscuous mode for VF 63 failed, error: -22 PF returned error -53 (IAVF_ERR_ADMIN_QUEUE_ERROR) to our request 14 This cleanup used to live in ice_vsi_release() but was dropped by the referenced refactor. Restore it. Clear the default forwarding VSI rule in ice_vsi_release() when this VSI owns it, which covers every teardown path. Fixes: 6624e780a577 ("ice: split ice_vsi_setup into smaller functions") Signed-off-by: Petr Oros Reviewed-by: Marcin Szycik Tested-by: Rafal Romanowski Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/ice/ice_lib.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/intel/ice/ice_lib.c b/drivers/net/ethernet/intel/ice/ice_lib.c index 8cdc4fda89e9..9e08db376d3d 100644 --- a/drivers/net/ethernet/intel/ice/ice_lib.c +++ b/drivers/net/ethernet/intel/ice/ice_lib.c @@ -2871,6 +2871,9 @@ int ice_vsi_release(struct ice_vsi *vsi) return -ENODEV; pf = vsi->back; + if (ice_is_vsi_dflt_vsi(vsi)) + ice_clear_dflt_vsi(vsi); + if (test_bit(ICE_FLAG_RSS_ENA, pf->flags)) ice_rss_clean(vsi); From df88d6f1ed653993bd5c8647aef0e6498f4b1647 Mon Sep 17 00:00:00 2001 From: Robert Malz Date: Tue, 4 Aug 2026 10:35:36 +0200 Subject: [PATCH 22/70] ice: acquire NVM lock around each flash read FW caps the NVM read lock at a maximum of 3000ms regardless of the timeout requested via ice_acquire_nvm(). ice_read_flat_nvm() splits a read into multiple ice_aq_read_nvm() commands, one per 4KB sector, all issued under a single lock taken by the caller. Reading a large region can exceed 3000ms, so FW reclaims the lock mid-read and the remaining commands might fail. Move the lock acquire/release into ice_read_flat_nvm() so it brackets each individual ice_aq_read_nvm() command, ensuring the lock is never held across more than one FW read. ice_release_nvm() issues its own AQ command and overwrites hw->adminq.sq_last_status, which some callers inspect after a failed read. Add an optional read_aq_err output parameter to ice_read_flat_nvm() to capture the failing read's AQ error before the release; callers that need it (ice_discover_flash_size() and the ethtool/devlink log paths) use it instead of sq_last_status, others pass NULL. Callers that previously took the lock around ice_read_flat_nvm(), ice_read_sr_word() or ice_read_flash_module() now call them without it. The now-redundant per-block locking in ice_devlink_nvm_snapshot() is dropped. ice_read_sr_word() is now a thin wrapper, so ice_read_sr_word_aq() is folded into it. Fixes: e94509906d6b ("ice: create function to read a section of the NVM and Shadow RAM") Signed-off-by: Robert Malz Reviewed-by: Przemek Kitszel Reviewed-by: Marcin Szycik Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen --- .../net/ethernet/intel/ice/devlink/devlink.c | 32 ++----- drivers/net/ethernet/intel/ice/ice_ethtool.c | 18 ++-- drivers/net/ethernet/intel/ice/ice_nvm.c | 90 ++++++++++--------- drivers/net/ethernet/intel/ice/ice_nvm.h | 2 +- 4 files changed, 59 insertions(+), 83 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/devlink/devlink.c b/drivers/net/ethernet/intel/ice/devlink/devlink.c index 22b7d8e6bd9e..8c2b63eef82b 100644 --- a/drivers/net/ethernet/intel/ice/devlink/devlink.c +++ b/drivers/net/ethernet/intel/ice/devlink/devlink.c @@ -1890,27 +1890,18 @@ static int ice_devlink_nvm_snapshot(struct devlink *devlink, */ for (i = 0; i < num_blks; i++) { u32 read_sz = min_t(u32, ICE_DEVLINK_READ_BLK_SIZE, left); - - status = ice_acquire_nvm(hw, ICE_RES_READ); - if (status) { - dev_dbg(dev, "ice_acquire_nvm failed, err %d aq_err %d\n", - status, hw->adminq.sq_last_status); - NL_SET_ERR_MSG_MOD(extack, "Failed to acquire NVM semaphore"); - vfree(nvm_data); - return -EIO; - } + enum libie_aq_err read_aq_err = LIBIE_AQ_RC_OK; status = ice_read_flat_nvm(hw, i * ICE_DEVLINK_READ_BLK_SIZE, - &read_sz, tmp, read_shadow_ram); + &read_sz, tmp, read_shadow_ram, + &read_aq_err); if (status) { dev_dbg(dev, "ice_read_flat_nvm failed after reading %u bytes, err %d aq_err %d\n", - read_sz, status, hw->adminq.sq_last_status); + read_sz, status, read_aq_err); NL_SET_ERR_MSG_MOD(extack, "Failed to read NVM contents"); - ice_release_nvm(hw); vfree(nvm_data); return -EIO; } - ice_release_nvm(hw); tmp += read_sz; left -= read_sz; @@ -1943,6 +1934,7 @@ static int ice_devlink_nvm_read(struct devlink *devlink, struct netlink_ext_ack *extack, u64 offset, u32 size, u8 *data) { + enum libie_aq_err read_aq_err = LIBIE_AQ_RC_OK; struct ice_pf *pf = devlink_priv(devlink); struct device *dev = ice_pf_to_dev(pf); struct ice_hw *hw = &pf->hw; @@ -1966,24 +1958,14 @@ static int ice_devlink_nvm_read(struct devlink *devlink, return -ERANGE; } - status = ice_acquire_nvm(hw, ICE_RES_READ); - if (status) { - dev_dbg(dev, "ice_acquire_nvm failed, err %d aq_err %d\n", - status, hw->adminq.sq_last_status); - NL_SET_ERR_MSG_MOD(extack, "Failed to acquire NVM semaphore"); - return -EIO; - } - status = ice_read_flat_nvm(hw, (u32)offset, &size, data, - read_shadow_ram); + read_shadow_ram, &read_aq_err); if (status) { dev_dbg(dev, "ice_read_flat_nvm failed after reading %u bytes, err %d aq_err %d\n", - size, status, hw->adminq.sq_last_status); + size, status, read_aq_err); NL_SET_ERR_MSG_MOD(extack, "Failed to read NVM contents"); - ice_release_nvm(hw); return -EIO; } - ice_release_nvm(hw); return 0; } diff --git a/drivers/net/ethernet/intel/ice/ice_ethtool.c b/drivers/net/ethernet/intel/ice/ice_ethtool.c index 7eb380be7ed2..bf9a821c543b 100644 --- a/drivers/net/ethernet/intel/ice/ice_ethtool.c +++ b/drivers/net/ethernet/intel/ice/ice_ethtool.c @@ -853,6 +853,7 @@ static int ice_get_eeprom(struct net_device *netdev, struct ethtool_eeprom *eeprom, u8 *bytes) { + enum libie_aq_err read_aq_err = LIBIE_AQ_RC_OK; struct ice_pf *pf = ice_netdev_to_pf(netdev); struct ice_hw *hw = &pf->hw; struct device *dev; @@ -869,24 +870,15 @@ ice_get_eeprom(struct net_device *netdev, struct ethtool_eeprom *eeprom, if (!buf) return -ENOMEM; - ret = ice_acquire_nvm(hw, ICE_RES_READ); + ret = ice_read_flat_nvm(hw, eeprom->offset, &eeprom->len, buf, + false, &read_aq_err); if (ret) { - dev_err(dev, "ice_acquire_nvm failed, err %d aq_err %s\n", - ret, libie_aq_str(hw->adminq.sq_last_status)); + dev_err(dev, "ice_read_flat_nvm failed, err %d aq_err %s\n", + ret, libie_aq_str(read_aq_err)); goto out; } - ret = ice_read_flat_nvm(hw, eeprom->offset, &eeprom->len, buf, - false); - if (ret) { - dev_err(dev, "ice_read_flat_nvm failed, err %d aq_err %s\n", - ret, libie_aq_str(hw->adminq.sq_last_status)); - goto release; - } - memcpy(bytes, buf, eeprom->len); -release: - ice_release_nvm(hw); out: kfree(buf); return ret; diff --git a/drivers/net/ethernet/intel/ice/ice_nvm.c b/drivers/net/ethernet/intel/ice/ice_nvm.c index 7e187a804dfa..21f3b615dbbf 100644 --- a/drivers/net/ethernet/intel/ice/ice_nvm.c +++ b/drivers/net/ethernet/intel/ice/ice_nvm.c @@ -53,17 +53,27 @@ int ice_aq_read_nvm(struct ice_hw *hw, u16 module_typeid, u32 offset, * @length: (in) number of bytes to read; (out) number of bytes actually read * @data: buffer to return data in (sized to fit the specified length) * @read_shadow_ram: if true, read from shadow RAM instead of NVM + * @read_aq_err: if non-NULL, receives the AQ error status of the failing read * * Reads a portion of the NVM, as a flat memory space. This function correctly * breaks read requests across Shadow RAM sectors and ensures that no single * read request exceeds the maximum 4KB read for a single AdminQ command. * + * FW caps the read lock at a maximum of 3000ms, so a read spanning multiple + * 4KB sectors cannot be done under a single lock without FW reclaiming it + * mid-read. The NVM lock is therefore acquired and released around each AQ + * read, so this function must be called without the lock held. + * + * Since ice_release_nvm() issues an AQ command that overwrites + * hw->adminq.sq_last_status, callers that need the failing read's AQ error + * must use @read_aq_err rather than inspecting sq_last_status afterwards. + * * Returns a status code on failure. Note that the data pointer may be * partially updated if some reads succeed before a failure. */ int ice_read_flat_nvm(struct ice_hw *hw, u32 offset, u32 *length, u8 *data, - bool read_shadow_ram) + bool read_shadow_ram, enum libie_aq_err *read_aq_err) { u32 inlen = *length; u32 bytes_read = 0; @@ -92,12 +102,30 @@ ice_read_flat_nvm(struct ice_hw *hw, u32 offset, u32 *length, u8 *data, last_cmd = !(bytes_read + read_size < inlen); + status = ice_acquire_nvm(hw, ICE_RES_READ); + if (status) { + ice_debug(hw, ICE_DBG_NVM, "Failed to acquire NVM lock, err %d aq_err %s\n", + status, libie_aq_str(hw->adminq.sq_last_status)); + break; + } + status = ice_aq_read_nvm(hw, ICE_AQC_NVM_START_POINT, offset, read_size, data + bytes_read, last_cmd, read_shadow_ram, NULL); - if (status) + if (status) { + /* Capture the read's AQ error before ice_release_nvm() + * issues its own AQ command and overwrites + * sq_last_status. + */ + if (read_aq_err) + *read_aq_err = hw->adminq.sq_last_status; + + ice_release_nvm(hw); break; + } + + ice_release_nvm(hw); bytes_read += read_size; offset += read_size; @@ -177,14 +205,19 @@ int ice_aq_erase_nvm(struct ice_hw *hw, u16 module_typeid, struct ice_sq_cd *cd) } /** - * ice_read_sr_word_aq - Reads Shadow RAM via AQ + * ice_read_sr_word - Reads Shadow RAM word * @hw: pointer to the HW structure * @offset: offset of the Shadow RAM word to read (0x000000 - 0x001FFF) * @data: word read from the Shadow RAM * * Reads one 16 bit word from the Shadow RAM using ice_read_flat_nvm. + * + * The NVM lock is acquired and released internally by ice_read_flat_nvm() + * around the FW read, so this function must be called without the lock held. + * + * Return: zero on success, or a negative error code on failure. */ -static int ice_read_sr_word_aq(struct ice_hw *hw, u16 offset, u16 *data) +int ice_read_sr_word(struct ice_hw *hw, u16 offset, u16 *data) { u32 bytes = sizeof(u16); __le16 data_local; @@ -194,7 +227,7 @@ static int ice_read_sr_word_aq(struct ice_hw *hw, u16 offset, u16 *data) * Shadow RAM sector restrictions necessary when reading from the NVM. */ status = ice_read_flat_nvm(hw, offset * sizeof(u16), &bytes, - (__force u8 *)&data_local, true); + (__force u8 *)&data_local, true, NULL); if (status) return status; @@ -330,13 +363,8 @@ ice_read_flash_module(struct ice_hw *hw, enum ice_bank_select bank, u16 module, return -EINVAL; } - status = ice_acquire_nvm(hw, ICE_RES_READ); - if (status) - return status; - - status = ice_read_flat_nvm(hw, start + offset, &length, data, false); - - ice_release_nvm(hw); + status = ice_read_flat_nvm(hw, start + offset, &length, data, false, + NULL); return status; } @@ -418,27 +446,6 @@ ice_read_netlist_module(struct ice_hw *hw, enum ice_bank_select bank, u32 offset return status; } -/** - * ice_read_sr_word - Reads Shadow RAM word and acquire NVM if necessary - * @hw: pointer to the HW structure - * @offset: offset of the Shadow RAM word to read (0x000000 - 0x001FFF) - * @data: word read from the Shadow RAM - * - * Reads one 16 bit word from the Shadow RAM using the ice_read_sr_word_aq. - */ -int ice_read_sr_word(struct ice_hw *hw, u16 offset, u16 *data) -{ - int status; - - status = ice_acquire_nvm(hw, ICE_RES_READ); - if (!status) { - status = ice_read_sr_word_aq(hw, offset, data); - ice_release_nvm(hw); - } - - return status; -} - /** * ice_get_pfa_module_tlv - Reads sub module TLV from NVM PFA * @hw: pointer to hardware structure @@ -856,20 +863,18 @@ int ice_get_inactive_netlist_ver(struct ice_hw *hw, struct ice_netlist_info *net static int ice_discover_flash_size(struct ice_hw *hw) { u32 min_size = 0, max_size = ICE_AQC_NVM_MAX_OFFSET + 1; - int status; - - status = ice_acquire_nvm(hw, ICE_RES_READ); - if (status) - return status; + int status = 0; while ((max_size - min_size) > 1) { + enum libie_aq_err read_aq_err = LIBIE_AQ_RC_OK; u32 offset = (max_size + min_size) / 2; u32 len = 1; u8 data; - status = ice_read_flat_nvm(hw, offset, &len, &data, false); + status = ice_read_flat_nvm(hw, offset, &len, &data, false, + &read_aq_err); if (status == -EIO && - hw->adminq.sq_last_status == LIBIE_AQ_RC_EINVAL) { + read_aq_err == LIBIE_AQ_RC_EINVAL) { ice_debug(hw, ICE_DBG_NVM, "%s: New upper bound of %u bytes\n", __func__, offset); status = 0; @@ -880,7 +885,7 @@ static int ice_discover_flash_size(struct ice_hw *hw) min_size = offset; } else { /* an unexpected error occurred */ - goto err_read_flat_nvm; + return status; } } @@ -888,9 +893,6 @@ static int ice_discover_flash_size(struct ice_hw *hw) hw->flash.flash_size = max_size; -err_read_flat_nvm: - ice_release_nvm(hw); - return status; } diff --git a/drivers/net/ethernet/intel/ice/ice_nvm.h b/drivers/net/ethernet/intel/ice/ice_nvm.h index 63cdc6bdac58..e1d1a11f5ca4 100644 --- a/drivers/net/ethernet/intel/ice/ice_nvm.h +++ b/drivers/net/ethernet/intel/ice/ice_nvm.h @@ -19,7 +19,7 @@ int ice_aq_read_nvm(struct ice_hw *hw, u16 module_typeid, u32 offset, bool read_shadow_ram, struct ice_sq_cd *cd); int ice_read_flat_nvm(struct ice_hw *hw, u32 offset, u32 *length, u8 *data, - bool read_shadow_ram); + bool read_shadow_ram, enum libie_aq_err *read_aq_err); int ice_get_pfa_module_tlv(struct ice_hw *hw, u16 *module_tlv, u16 *module_tlv_len, u16 module_type); From b802a8c1ca16f9490fa0cb3110c00c10d52b392d Mon Sep 17 00:00:00 2001 From: Willem de Bruijn Date: Mon, 3 Aug 2026 17:06:23 -0400 Subject: [PATCH 23/70] idpf: add missing cpu_to_le32 in idpf_tx_splitq_build_flow_desc idpf_tx_splitq_build_flow_desc performs a 32-bit store to &cmd_dtype to set the 8-bit cmd_dtype and zero the adjacent 3-byte timestamp field in a single operation. Descriptors are in little endian. Add missing cpu_to_le32 and cast to __le32 to ensure the fields are written correctly also on big endian platforms. Fixes: 1a49cf814fe1 ("idpf: add Tx timestamp flows") Signed-off-by: Willem de Bruijn Reviewed-by: Jason Xing Reviewed-by: Aleksandr Loktionov Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/idpf/idpf_txrx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/intel/idpf/idpf_txrx.c b/drivers/net/ethernet/intel/idpf/idpf_txrx.c index c724d429a7aa..91ca75e45463 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_txrx.c +++ b/drivers/net/ethernet/intel/idpf/idpf_txrx.c @@ -2408,7 +2408,7 @@ void idpf_tx_splitq_build_flow_desc(union idpf_tx_flex_desc *desc, struct idpf_tx_splitq_params *params, u16 td_cmd, u16 size) { - *(u32 *)&desc->flow.qw1.cmd_dtype = (u8)(params->dtype | td_cmd); + *(__le32 *)&desc->flow.qw1.cmd_dtype = cpu_to_le32((u8)(params->dtype | td_cmd)); desc->flow.qw1.rxr_bufsize = cpu_to_le16((u16)size); desc->flow.qw1.compl_tag = cpu_to_le16(params->compl_tag); } From 36cdf5d48ca191dcd71c28cadbe0981b1d25318d Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Sat, 8 Aug 2026 02:21:23 -0500 Subject: [PATCH 24/70] net/smc: unregister the connection before draining the rx tasklet smc_conn_free() calls smc_ism_unset_conn() only while the link group is still on its device list, and never sets conn->killed. smc_lgr_terminate_sched() unlinks the group immediately and defers killing its connections to a work item, so a connection freed in that window keeps its smcd->conn[] slot with both gates in smcd_handle_irq() open, and the device can re-arm the receive tasklet after tasklet_kill() has returned. On the DMB-nocopy path the ghost send buffer is freed right after that drain, so the re-armed tasklet dereferences it. Unregister unconditionally and drain before the detach at both teardown sites, mirroring rmb_desc, which smc_buf_unuse() releases after the drain. Clear conn->sndbuf_desc before freeing it as well, so a reader that samples the pointer cannot get one that is already freed. Fixes: ae2be35cbed2 ("net/smc: {at|de}tach sndbuf to peer DMB if supported") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Reviewed-by: Sidraya Jayagond Reviewed-by: Tony Lu Link: https://patch.msgid.link/20260808-b4-disp-22f119e6-v2-1-61647601a6f3@proton.me Signed-off-by: Jakub Kicinski --- net/smc/smc_core.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/net/smc/smc_core.c b/net/smc/smc_core.c index b4208cb186c5..181647982490 100644 --- a/net/smc/smc_core.c +++ b/net/smc/smc_core.c @@ -1209,14 +1209,16 @@ static void smcd_buf_detach(struct smc_connection *conn) { struct smcd_dev *smcd = conn->lgr->smcd; u64 peer_token = conn->peer_token; + struct smc_buf_desc *buf_desc; if (!conn->sndbuf_desc) return; smc_ism_detach_dmb(smcd, peer_token); - kfree(conn->sndbuf_desc); + buf_desc = conn->sndbuf_desc; conn->sndbuf_desc = NULL; + kfree(buf_desc); } static void smc_buf_unuse(struct smc_connection *conn, @@ -1268,11 +1270,10 @@ void smc_conn_free(struct smc_connection *conn) goto lgr_put; if (lgr->is_smcd) { - if (!list_empty(&lgr->list)) - smc_ism_unset_conn(conn); + smc_ism_unset_conn(conn); + tasklet_kill(&conn->rx_tsklet); if (smc_ism_support_dmb_nocopy(lgr->smcd)) smcd_buf_detach(conn); - tasklet_kill(&conn->rx_tsklet); } else { smc_cdc_wait_pend_tx_wr(conn); if (current_work() != &conn->abort_work) @@ -1525,12 +1526,12 @@ static void smc_conn_kill(struct smc_connection *conn, bool soft) smc_sk_wake_ups(smc); if (conn->lgr->is_smcd) { smc_ism_unset_conn(conn); - if (smc_ism_support_dmb_nocopy(conn->lgr->smcd)) - smcd_buf_detach(conn); if (soft) tasklet_kill(&conn->rx_tsklet); else tasklet_unlock_wait(&conn->rx_tsklet); + if (smc_ism_support_dmb_nocopy(conn->lgr->smcd)) + smcd_buf_detach(conn); } else { smc_cdc_wait_pend_tx_wr(conn); } From b395dd319cea422239cb45b998fb38d7e373af87 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Sat, 8 Aug 2026 02:21:24 -0500 Subject: [PATCH 25/70] net/smc: do not dereference an unset send buffer on the SMC-D teardown path smc_close_stream_wait() calls smc_tx_prepared_sends() from inside its sk_wait_event() condition, and sk_wait_event() evaluates that condition once with the socket lock released. smcd_buf_detach() clears conn->sndbuf_desc from smc_conn_kill() under lock_sock(), so a link group terminating while a socket waits there leaves the helper dereferencing NULL, faulting out of close(). SIOCOUTQ reads the field by hand, and smc_close_cancel_work() drops the lock across two cancel_*_sync() calls. Sample the pointer once in the helper, report nothing prepared while it is unset, and bound the ioctl the same way. The receive tasklet dereferences the field directly in smc_cdc_msg_recv_action(), not through this helper; 1/2 is what keeps it from running that late. Fixes: ae2be35cbed2 ("net/smc: {at|de}tach sndbuf to peer DMB if supported") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Reviewed-by: Sidraya Jayagond Reviewed-by: Tony Lu Link: https://patch.msgid.link/20260808-b4-disp-22f119e6-v2-2-61647601a6f3@proton.me Signed-off-by: Jakub Kicinski --- net/smc/af_smc.c | 3 ++- net/smc/smc_tx.h | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/net/smc/af_smc.c b/net/smc/af_smc.c index 00403175b740..cff910cedbfc 100644 --- a/net/smc/af_smc.c +++ b/net/smc/af_smc.c @@ -3233,7 +3233,8 @@ int smc_ioctl(struct socket *sock, unsigned int cmd, return -EINVAL; } if (smc->sk.sk_state == SMC_INIT || - smc->sk.sk_state == SMC_CLOSED) + smc->sk.sk_state == SMC_CLOSED || + !READ_ONCE(smc->conn.sndbuf_desc)) answ = 0; else answ = smc->conn.sndbuf_desc->len - diff --git a/net/smc/smc_tx.h b/net/smc/smc_tx.h index a59f370b8b43..610a945aefd6 100644 --- a/net/smc/smc_tx.h +++ b/net/smc/smc_tx.h @@ -20,11 +20,15 @@ static inline int smc_tx_prepared_sends(struct smc_connection *conn) { + struct smc_buf_desc *sndbuf_desc = READ_ONCE(conn->sndbuf_desc); union smc_host_cursor sent, prep; + if (!sndbuf_desc) + return 0; + smc_curs_copy(&sent, &conn->tx_curs_sent, conn); smc_curs_copy(&prep, &conn->tx_curs_prep, conn); - return smc_curs_diff(conn->sndbuf_desc->len, &sent, &prep); + return smc_curs_diff(sndbuf_desc->len, &sent, &prep); } void smc_tx_pending(struct smc_connection *conn); From 447c9303942c439a117d9b76ce6d6e2116b38ee7 Mon Sep 17 00:00:00 2001 From: Asim Viladi Oglu Manizada Date: Wed, 12 Aug 2026 01:21:53 +0000 Subject: [PATCH 26/70] net: tun: bound receive headroom tun_get_user() uses tun->align both as skb headroom and when choosing how much packet data to keep linear. OVS can propagate an oversized headroom request from another port to TUN or TAP. When align is larger than the usable space in a one-page skb head, SKB_MAX_HEAD(align) underflows and the result becomes negative when stored in good_linear. That value later wraps when assigned to the size_t linear variable, and tun_alloc_skb() can place skb->data outside the allocated head. Bound the headroom stored by TUN to the one-page skb-head budget and the largest non-sentinel 16-bit skb header offset. Leave one linear byte for raw TUN and a complete Ethernet header for TAP, including NET_IP_ALIGN. Also pull the raw-TUN protocol byte and the TAP Ethernet header before accessing them, so these checks remain safe for nonlinear skbs supplied by other allocation paths. Fixes: eaea34b23c46 ("net/tun: implement ndo_set_rx_headroom") Cc: stable@vger.kernel.org Signed-off-by: Asim Viladi Oglu Manizada Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260812012139.2134643-1-manizada@pm.me Signed-off-by: Jakub Kicinski --- drivers/net/tun.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/drivers/net/tun.c b/drivers/net/tun.c index fed9dfdfcc3b..5bbe3123979e 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -1107,11 +1107,16 @@ static netdev_features_t tun_net_fix_features(struct net_device *dev, static void tun_set_headroom(struct net_device *dev, int new_hr) { struct tun_struct *tun = netdev_priv(dev); + size_t max_headroom; - if (new_hr < NET_SKB_PAD) - new_hr = NET_SKB_PAD; + max_headroom = min_t(size_t, SKB_MAX_HEAD(0), U16_MAX - 1); - tun->align = new_hr; + if ((tun->flags & TUN_TYPE_MASK) == IFF_TAP) + max_headroom -= ETH_HLEN + NET_IP_ALIGN; + else + max_headroom -= 1; + + tun->align = clamp_t(int, new_hr, NET_SKB_PAD, max_headroom); } static void @@ -1822,7 +1827,13 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, switch (tun->flags & TUN_TYPE_MASK) { case IFF_TUN: if (tun->flags & IFF_NO_PI) { - u8 ip_version = skb->len ? (skb->data[0] >> 4) : 0; + u8 ip_version; + + if (!pskb_may_pull(skb, 1)) { + err = -EINVAL; + goto drop; + } + ip_version = skb->data[0] >> 4; switch (ip_version) { case 4: @@ -1842,7 +1853,7 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, skb->dev = tun->dev; break; case IFF_TAP: - if (frags && !pskb_may_pull(skb, ETH_HLEN)) { + if (!pskb_may_pull(skb, ETH_HLEN)) { err = -ENOMEM; drop_reason = SKB_DROP_REASON_HDR_TRUNC; goto drop; From 24ef02f934eeb48830cff6b739abc3c62b1d107b Mon Sep 17 00:00:00 2001 From: Jijie Shao Date: Fri, 7 Aug 2026 19:48:30 +0800 Subject: [PATCH 27/70] net: page_pool: fix UAF in __page_pool_release_netmem_dma on xa_cmpxchg race MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This bug was discovered while testing the hns3 driver under channel reconfiguration (`ethtool -L` / `ethtool -G`) with iperf3 traffic on arm64. The race is intermittently triggered when page_pool_destroy() runs page_pool_scrub() concurrently with page return via page_pool_put_netmem() on a different CPU. A WARN in page_pool_clear_pp_info() surfaced the dangling DMA index bits left by the cmpxchg loser, which led to the investigation. page_pool_scrub() iterates pool->dma_mapped via xa_for_each() with no page ref held. __page_pool_release_netmem_dma() currently reads and writes netmem fields (dma_addr, DMA index bits in pp_magic) after xa_cmpxchg() returns. The unref path calls put_page() unconditionally regardless of the cmpxchg outcome; when it loses the cmpxchg, it still frees the page before the scrub winner finishes these netmem accesses, so scrub touches a freed page -- a Use-After-Free. Fix this by splitting the DMA release into two functions: 1. __page_pool_unmap_netmem_dma() caches dma_addr before xa_cmpxchg(), does the cmpxchg to remove the DMA mapping, and calls dma_unmap on the cached address. It never touches netmem fields after the cmpxchg, making it safe for the scrub path which holds no page ref. 2. __page_pool_release_netmem_dma() wraps the above and additionally clears dma_addr and DMA index bits in netmem fields. This is safe only when the caller holds a page ref, so it is used by the return path (page_pool_return_netmem). The scrub path calls __page_pool_unmap_netmem_dma() directly; the return path calls __page_pool_release_netmem_dma(). Fixes: ee62ce7a1d90 ("page_pool: Track DMA-mapped pages and unmap them when destroying the pool") Suggested-by: Mina Almasry Reviewed-by: Mina Almasry Signed-off-by: Jijie Shao Reviewed-by: Toke Høiland-Jørgensen Link: https://patch.msgid.link/20260807114830.344336-1-shaojijie@huawei.com Signed-off-by: Jakub Kicinski --- net/core/page_pool.c | 64 +++++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/net/core/page_pool.c b/net/core/page_pool.c index 21dc4a9c8714..50ee550fef73 100644 --- a/net/core/page_pool.c +++ b/net/core/page_pool.c @@ -500,29 +500,40 @@ static int page_pool_register_dma_index(struct page_pool *pool, return err; } -static int page_pool_release_dma_index(struct page_pool *pool, - netmem_ref netmem) +static void __page_pool_unmap_netmem_dma(struct page_pool *pool, + netmem_ref netmem) { struct page *old, *page = netmem_to_page(netmem); unsigned long id; + dma_addr_t dma; - if (unlikely(!PP_DMA_INDEX_BITS)) - return 0; + if (!pool->dma_map) + return; - id = netmem_get_dma_index(netmem); - if (!id) - return -1; + /* Cache dma_addr before xa_cmpxchg. The scrub path holds no page ref; + * the unref path calls put_page() regardless of cmpxchg outcome, so + * after the cmpxchg we cannot safely touch netmem fields. + */ + dma = page_pool_get_dma_addr_netmem(netmem); - if (in_softirq()) - old = xa_cmpxchg(&pool->dma_mapped, id, page, NULL, 0); - else - old = xa_cmpxchg_bh(&pool->dma_mapped, id, page, NULL, 0); - if (old != page) - return -1; + if (likely(PP_DMA_INDEX_BITS)) { + id = netmem_get_dma_index(netmem); + if (!id) + return; - netmem_set_dma_index(netmem, 0); + if (in_softirq()) + old = xa_cmpxchg(&pool->dma_mapped, + id, page, NULL, 0); + else + old = xa_cmpxchg_bh(&pool->dma_mapped, + id, page, NULL, 0); + if (old != page) + return; + } - return 0; + dma_unmap_page_attrs(pool->p.dev, dma, + PAGE_SIZE << pool->p.order, pool->p.dma_dir, + DMA_ATTR_SKIP_CPU_SYNC | DMA_ATTR_WEAK_ORDERING); } static bool page_pool_dma_map(struct page_pool *pool, netmem_ref netmem, gfp_t gfp) @@ -728,24 +739,16 @@ void page_pool_clear_pp_info(netmem_ref netmem) static __always_inline void __page_pool_release_netmem_dma(struct page_pool *pool, netmem_ref netmem) { - dma_addr_t dma; - + /* Caller must hold a page ref: __page_pool_unmap_netmem_dma() is + * safe without a ref, but the field clears below require it. + */ if (!pool->dma_map) - /* Always account for inflight pages, even if we didn't - * map them - */ return; - if (page_pool_release_dma_index(pool, netmem)) - return; - - dma = page_pool_get_dma_addr_netmem(netmem); - - /* When page is unmapped, it cannot be returned to our pool */ - dma_unmap_page_attrs(pool->p.dev, dma, - PAGE_SIZE << pool->p.order, pool->p.dma_dir, - DMA_ATTR_SKIP_CPU_SYNC | DMA_ATTR_WEAK_ORDERING); + __page_pool_unmap_netmem_dma(pool, netmem); page_pool_set_dma_addr_netmem(netmem, 0); + if (likely(PP_DMA_INDEX_BITS)) + netmem_set_dma_index(netmem, 0); } /* Disconnects a page (from a page_pool). API users can have a need @@ -1171,8 +1174,9 @@ static void page_pool_scrub(struct page_pool *pool) synchronize_net(); } + /* No page ref, dma-unmap only. */ xa_for_each(&pool->dma_mapped, id, ptr) - __page_pool_release_netmem_dma(pool, page_to_netmem((struct page *)ptr)); + __page_pool_unmap_netmem_dma(pool, page_to_netmem((struct page *)ptr)); } /* No more consumers should exist, but producers could still From ad27ed7d2309419a129078d781504f486b1b469a Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Wed, 12 Aug 2026 14:36:11 +0800 Subject: [PATCH 28/70] bpf, xdp: move offload check into dev_xdp_install() bpf_xdp_link_update() calls dev_xdp_install() directly and skips dev_xdp_attach(), so the checks in dev_xdp_attach() do not run. A user can make an XDP link with a normal program and then swap in an offloaded or device-bound program with BPF_LINK_UPDATE, which puts it on the software path. dev_xdp_install() is the one place all three paths go through: "ip link set xdp" and BPF_LINK_CREATE reach it via dev_xdp_attach(), and BPF_LINK_UPDATE calls it directly. So move the program checks (offloaded, bound to another device, device-bound in generic mode, native vs generic, DEVMAP and CPUMAP) there, and keep only the netlink-flag check (XDP_FLAGS_UPDATE_IF_NOEXIST) in dev_xdp_attach(). Fixes: 026a4c28e1db3 ("bpf, xdp: Implement LINK_UPDATE for BPF XDP link") Signed-off-by: Jiayuan Chen Signed-off-by: Jakub Kicinski --- net/core/dev.c | 59 ++++++++++++++++++++++++++------------------------ 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/net/core/dev.c b/net/core/dev.c index ece6700536d9..5ac31370df93 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -10333,6 +10333,37 @@ static int dev_xdp_install(struct net_device *dev, enum bpf_xdp_mode mode, netdev_assert_locked_ops_compat(dev); + if (prog) { + enum bpf_xdp_mode other_mode = mode == XDP_MODE_SKB + ? XDP_MODE_DRV : XDP_MODE_SKB; + bool offload = mode == XDP_MODE_HW; + + if (!offload && dev_xdp_prog(dev, other_mode)) { + NL_SET_ERR_MSG(extack, "Native and generic XDP can't be active at the same time"); + return -EEXIST; + } + if (!offload && bpf_prog_is_offloaded(prog->aux)) { + NL_SET_ERR_MSG(extack, "Using offloaded program without HW_MODE flag is not supported"); + return -EINVAL; + } + if (bpf_prog_is_dev_bound(prog->aux) && !bpf_offload_dev_match(prog, dev)) { + NL_SET_ERR_MSG(extack, "Program bound to different device"); + return -EINVAL; + } + if (bpf_prog_is_dev_bound(prog->aux) && mode == XDP_MODE_SKB) { + NL_SET_ERR_MSG(extack, "Can't attach device-bound programs in generic mode"); + return -EINVAL; + } + if (prog->expected_attach_type == BPF_XDP_DEVMAP) { + NL_SET_ERR_MSG(extack, "BPF_XDP_DEVMAP programs can not be attached to a device"); + return -EINVAL; + } + if (prog->expected_attach_type == BPF_XDP_CPUMAP) { + NL_SET_ERR_MSG(extack, "BPF_XDP_CPUMAP programs can not be attached to a device"); + return -EINVAL; + } + } + if (dev->cfg->hds_config == ETHTOOL_TCP_DATA_SPLIT_ENABLED && prog && !prog->aux->xdp_has_frags) { NL_SET_ERR_MSG(extack, "unable to install XDP to device using tcp-data-split"); @@ -10472,38 +10503,10 @@ static int dev_xdp_attach(struct net_device *dev, struct netlink_ext_ack *extack new_prog = link->link.prog; if (new_prog) { - bool offload = mode == XDP_MODE_HW; - enum bpf_xdp_mode other_mode = mode == XDP_MODE_SKB - ? XDP_MODE_DRV : XDP_MODE_SKB; - if ((flags & XDP_FLAGS_UPDATE_IF_NOEXIST) && cur_prog) { NL_SET_ERR_MSG(extack, "XDP program already attached"); return -EBUSY; } - if (!offload && dev_xdp_prog(dev, other_mode)) { - NL_SET_ERR_MSG(extack, "Native and generic XDP can't be active at the same time"); - return -EEXIST; - } - if (!offload && bpf_prog_is_offloaded(new_prog->aux)) { - NL_SET_ERR_MSG(extack, "Using offloaded program without HW_MODE flag is not supported"); - return -EINVAL; - } - if (bpf_prog_is_dev_bound(new_prog->aux) && !bpf_offload_dev_match(new_prog, dev)) { - NL_SET_ERR_MSG(extack, "Program bound to different device"); - return -EINVAL; - } - if (bpf_prog_is_dev_bound(new_prog->aux) && mode == XDP_MODE_SKB) { - NL_SET_ERR_MSG(extack, "Can't attach device-bound programs in generic mode"); - return -EINVAL; - } - if (new_prog->expected_attach_type == BPF_XDP_DEVMAP) { - NL_SET_ERR_MSG(extack, "BPF_XDP_DEVMAP programs can not be attached to a device"); - return -EINVAL; - } - if (new_prog->expected_attach_type == BPF_XDP_CPUMAP) { - NL_SET_ERR_MSG(extack, "BPF_XDP_CPUMAP programs can not be attached to a device"); - return -EINVAL; - } } /* don't call drivers if the effective program didn't change */ From 4b92a3710d4a1d850ed6421c773e735cdca69b07 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Wed, 12 Aug 2026 08:07:30 +0200 Subject: [PATCH 29/70] octeontx2-af: initialize lmac_bmap in rvu_mcs_set_lmac_bmap() rvu_mcs_set_lmac_bmap() declares lmac_bmap without initializing it and only sets bits for valid lmacs with set_bit(), which ORs into the word without clearing it first. Bits for invalid or skipped ports keep whatever was on the stack, and the garbage is stored into mcs->hw->lmac_bmap. Initialize lmac_bmap to 0 so only valid lmacs are marked. Found with Clang's -Wconditional-uninitialized. Fixes: ca7f49ff8846 ("octeontx2-af: cn10k: Introduce driver for macsec block.") Signed-off-by: Karl Mehltretter Reviewed-by: Ratheesh Kannoth Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260812060730.6181-1-kmehltretter@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/marvell/octeontx2/af/mcs_rvu_if.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/mcs_rvu_if.c b/drivers/net/ethernet/marvell/octeontx2/af/mcs_rvu_if.c index d98b49f47970..fce22e314cac 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/mcs_rvu_if.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/mcs_rvu_if.c @@ -856,7 +856,7 @@ int rvu_mbox_handler_mcs_ctrl_pkt_rule_write(struct rvu *rvu, static void rvu_mcs_set_lmac_bmap(struct rvu *rvu) { struct mcs *mcs = mcs_get_pdata(0); - unsigned long lmac_bmap; + unsigned long lmac_bmap = 0; int cgx, lmac, port; for (port = 0; port < mcs->hw->lmac_cnt; port++) { From 2f1463554d0561a2fead81e3888604e5c1125e29 Mon Sep 17 00:00:00 2001 From: Fan Ye Date: Tue, 11 Aug 2026 13:20:49 +0000 Subject: [PATCH 30/70] net: thunderbolt: Release the Rx HopID that was handed out on mismatch tb_xdomain_alloc_in_hopid() passes the wanted HopID to ida_alloc_range() as the lower bound, so a taken id is not an error there: the allocator returns the next free one above it. tbnet_connected_work() asks for the peer's transmit path, treats any other id as a failure and returns without releasing what it got, so that allocation stays live for the rest of the XDomain connection with nothing left holding a reference to it. Release the id when it is not the one we asked for, the same way the error unwind at the end of the function releases the expected one. Fixes: 180b0689425c ("thunderbolt: Allow multiple DMA tunnels over a single XDomain connection") Cc: stable@vger.kernel.org Signed-off-by: Fan Ye Acked-by: Mika Westerberg Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260811-b4-tbnet-hopid-v3-1-9e75d1b51331@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/thunderbolt/main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/thunderbolt/main.c b/drivers/net/thunderbolt/main.c index 98893732bc6e..e5199a87ea7a 100644 --- a/drivers/net/thunderbolt/main.c +++ b/drivers/net/thunderbolt/main.c @@ -647,6 +647,8 @@ static void tbnet_connected_work(struct work_struct *work) ret = tb_xdomain_alloc_in_hopid(net->xd, net->remote_transmit_path); if (ret != net->remote_transmit_path) { netdev_err(net->dev, "failed to allocate Rx HopID\n"); + if (ret >= 0) + tb_xdomain_release_in_hopid(net->xd, ret); return; } From 3c8b26ebf525ba5960510f48c6e9936a79ebe76f Mon Sep 17 00:00:00 2001 From: Fan Ye Date: Tue, 11 Aug 2026 13:20:50 +0000 Subject: [PATCH 31/70] net: thunderbolt: Mark the connection down when bringing it up fails Every failure path in tbnet_connected_work() undoes its own work and returns without clearing login_sent, so the connection still looks established. The next tbnet_tear_down() therefore takes its main branch and repeats a teardown that already happened: it stops rings that are already stopped, which is a dev_WARN() and fatal under panic_on_warn, and it releases net->remote_transmit_path even on the HopID mismatch path, where this connection never owned that id, silently freeing one that someone else is still using. Clear login_sent on those paths. That is enough for tbnet_tear_down() to leave the unwound state alone, and login_received has to stay set: it records that the peer has logged in and carries the transmit path it gave us, which nothing on this side can make the peer send again. Two things change beyond keeping the teardown out of the way: the logout request in that block is no longer sent, and the peer's next login request now re-queues our login work rather than connected_work, giving the connection a fresh login instead of a retry on stale state. Fixes: e69b6c02b4c3 ("net: Add support for networking over Thunderbolt cable") Cc: # 5.13+ Signed-off-by: Fan Ye Acked-by: Mika Westerberg Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260811-b4-tbnet-hopid-v3-2-9e75d1b51331@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/thunderbolt/main.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/net/thunderbolt/main.c b/drivers/net/thunderbolt/main.c index e5199a87ea7a..2a1728621887 100644 --- a/drivers/net/thunderbolt/main.c +++ b/drivers/net/thunderbolt/main.c @@ -626,6 +626,14 @@ static int tbnet_alloc_tx_buffers(struct tbnet *net) return 0; } +static void tbnet_connect_failed(struct tbnet *net) +{ + /* Leave login_received set: only the peer can make it true again. */ + mutex_lock(&net->connection_lock); + net->login_sent = false; + mutex_unlock(&net->connection_lock); +} + static void tbnet_connected_work(struct work_struct *work) { struct tbnet *net = container_of(work, typeof(*net), connected_work); @@ -649,6 +657,7 @@ static void tbnet_connected_work(struct work_struct *work) netdev_err(net->dev, "failed to allocate Rx HopID\n"); if (ret >= 0) tb_xdomain_release_in_hopid(net->xd, ret); + tbnet_connect_failed(net); return; } @@ -693,6 +702,7 @@ static void tbnet_connected_work(struct work_struct *work) tb_ring_stop(net->rx_ring.ring); tb_ring_stop(net->tx_ring.ring); tb_xdomain_release_in_hopid(net->xd, net->remote_transmit_path); + tbnet_connect_failed(net); } static void tbnet_login_work(struct work_struct *work) From d0c2bed6927cbfa2cb51f240b4812bf6916bce0e Mon Sep 17 00:00:00 2001 From: Fan Gong Date: Tue, 11 Aug 2026 19:43:59 +0800 Subject: [PATCH 32/70] hinic3: Fix skb linearization mismatch and drop skb when skb_checksum_help() failed Previously, hinic3_send_one_skb() cached the skb fragment count before calling hinic3_tx_offload(). If hinic3_tx_csum() falls back to skb_checksum_help() for unsupported tunnel packets, the skb may be linearized. Continuing to build the TX descriptor with the stale fragment count leads to a descriptor mismatch, which can trigger out-of-bounds DMA reads or IOMMU faults. Furthermore, the old code ignored the return value of skb_checksum_help(), transmitting corrupted packets with incomplete checksums upon failure. Fix this by: 1. Moving the hinic3_tx_offload() call before calculating 'num_sge' to ensure the correct fragment count is used if the SKB is linearized. 2. Propagating skb_checksum_help() errors and returning HINIC3_TX_OFFLOAD_INVALID to properly drop the skb. Fixes: 17fcb3dc12bb ("hinic3: module initialization and tx/rx logic") Co-developed-by: Teng Peisen Signed-off-by: Teng Peisen Co-developed-by: Wu Di Signed-off-by: Wu Di Signed-off-by: Fan Gong Reviewed-by: Simon Horman Link: https://patch.msgid.link/78d8c61cab588240948eaddcb437d59add9f77ae.1786448013.git.tengpeisen@huawei.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/huawei/hinic3/hinic3_tx.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c b/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c index 9306bf0020ca..cc541e7a2318 100644 --- a/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c +++ b/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c @@ -261,8 +261,7 @@ static int hinic3_tx_csum(struct hinic3_txq *txq, struct hinic3_sq_task *task, ((struct udphdr *)skb_transport_header(skb))->dest != VXLAN_OFFLOAD_PORT_LE) { /* Unsupported tunnel packet, disable csum offload */ - skb_checksum_help(skb); - return 0; + return skb_checksum_help(skb); } } @@ -412,6 +411,10 @@ static u32 hinic3_tx_offload(struct sk_buff *skb, struct hinic3_sq_task *task, offload |= HINIC3_TX_OFFLOAD_TSO; } else { tso_cs_en = hinic3_tx_csum(txq, task, skb); + if (tso_cs_en < 0) { + offload = HINIC3_TX_OFFLOAD_INVALID; + return offload; + } if (tso_cs_en) offload |= HINIC3_TX_OFFLOAD_CSUM; } @@ -545,6 +548,7 @@ static netdev_tx_t hinic3_send_one_skb(struct sk_buff *skb, skb->len = MIN_SKB_LEN; } + offload = hinic3_tx_offload(skb, &task, &queue_info, txq); num_sge = skb_shinfo(skb)->nr_frags + 1; /* assume normal wqe format + 1 wqebb for task info */ wqebb_cnt = num_sge + 1; @@ -560,7 +564,6 @@ static netdev_tx_t hinic3_send_one_skb(struct sk_buff *skb, return NETDEV_TX_BUSY; } - offload = hinic3_tx_offload(skb, &task, &queue_info, txq); if (unlikely(offload == HINIC3_TX_OFFLOAD_INVALID)) { goto err_drop_pkt; } else if (!offload) { From 43b0213529c6ae2fd4cbf8dbb9baff87a34c27d7 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Tue, 11 Aug 2026 15:08:13 +0800 Subject: [PATCH 33/70] net: ibm: emac: mal: fix NAPI locking Since commit 413f0271f396 ("net: protect NAPI enablement with netdev_lock()"), napi_enable() and napi_disable() take netdev_lock(). mal_register_commac() and mal_unregister_commac() call these helpers while holding mal->lock with interrupts disabled. In the unregister path, napi_disable() may also wait for polling to finish, while the poll completion path takes mal->lock. Take netdev_lock() before mal->lock, use the locked NAPI helpers, and drop mal->lock before napi_disable_locked(). Fixes: 413f0271f396 ("net: protect NAPI enablement with netdev_lock()") Cc: stable@vger.kernel.org Signed-off-by: Runyu Xiao Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260811070813.377573-1-runyu.xiao@seu.edu.cn Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/ibm/emac/mal.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/ibm/emac/mal.c b/drivers/net/ethernet/ibm/emac/mal.c index 74526002d52b..42027665f2a9 100644 --- a/drivers/net/ethernet/ibm/emac/mal.c +++ b/drivers/net/ethernet/ibm/emac/mal.c @@ -35,6 +35,7 @@ int mal_register_commac(struct mal_instance *mal, struct mal_commac *commac) { unsigned long flags; + netdev_lock(mal->napi.dev); spin_lock_irqsave(&mal->lock, flags); MAL_DBG(mal, "reg(%08x, %08x)" NL, @@ -44,18 +45,20 @@ int mal_register_commac(struct mal_instance *mal, struct mal_commac *commac) if ((mal->tx_chan_mask & commac->tx_chan_mask) || (mal->rx_chan_mask & commac->rx_chan_mask)) { spin_unlock_irqrestore(&mal->lock, flags); + netdev_unlock(mal->napi.dev); printk(KERN_WARNING "mal%d: COMMAC channels conflict!\n", mal->index); return -EBUSY; } if (list_empty(&mal->list)) - napi_enable(&mal->napi); + napi_enable_locked(&mal->napi); mal->tx_chan_mask |= commac->tx_chan_mask; mal->rx_chan_mask |= commac->rx_chan_mask; list_add(&commac->list, &mal->list); spin_unlock_irqrestore(&mal->lock, flags); + netdev_unlock(mal->napi.dev); return 0; } @@ -64,7 +67,9 @@ void mal_unregister_commac(struct mal_instance *mal, struct mal_commac *commac) { unsigned long flags; + bool disable_napi; + netdev_lock(mal->napi.dev); spin_lock_irqsave(&mal->lock, flags); MAL_DBG(mal, "unreg(%08x, %08x)" NL, @@ -73,10 +78,12 @@ void mal_unregister_commac(struct mal_instance *mal, mal->tx_chan_mask &= ~commac->tx_chan_mask; mal->rx_chan_mask &= ~commac->rx_chan_mask; list_del_init(&commac->list); - if (list_empty(&mal->list)) - napi_disable(&mal->napi); + disable_napi = list_empty(&mal->list); spin_unlock_irqrestore(&mal->lock, flags); + if (disable_napi) + napi_disable_locked(&mal->napi); + netdev_unlock(mal->napi.dev); } int mal_set_rcbs(struct mal_instance *mal, int channel, unsigned long size) From 273480bb836e515353f30a1e70b21a2382de666e Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Tue, 11 Aug 2026 16:36:14 +0800 Subject: [PATCH 34/70] ptp: netc: skip PEROUT disable if channel is not enabled When userspace calls ioctl(PTP_PEROUT_REQUEST) with period = 0 to disable a PEROUT channel that is not enabled, the driver incorrectly enters the disable path. Since the channel's struct netc_pp was previously zeroed, pp->alarm_id evaluates to 0, causing priv->fs_alarm_bitmap &= ~BIT(0) to silently revoke the alarm 0 allocation from whichever channel is actively using it. This can cause two channels conflict over the same hardware alarm configuration and corrupt their periodic output signals. Therefore, guard the disable path with a check on pp->enabled and return early if the channel is not enabled. Fixes: 671e266835b8 ("ptp: netc: add periodic pulse output support") Reported-by: Sashiko Closes: https://sashiko.dev/#/message/20260809031908.46EBF1F00A3A%40smtp.kernel.org Signed-off-by: Wei Fang Link: https://patch.msgid.link/20260811083614.3589967-1-wei.fang@oss.nxp.com Signed-off-by: Jakub Kicinski --- drivers/ptp/ptp_netc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/ptp/ptp_netc.c b/drivers/ptp/ptp_netc.c index 1c20d7efab92..59db08e189e6 100644 --- a/drivers/ptp/ptp_netc.c +++ b/drivers/ptp/ptp_netc.c @@ -482,6 +482,9 @@ static int net_timer_enable_perout(struct netc_timer *priv, netc_timer_enable_periodic_pulse(priv, channel); } else { + if (!pp->enabled) + goto unlock_spinlock; + netc_timer_disable_periodic_pulse(priv, channel); priv->fs_alarm_bitmap &= ~BIT(pp->alarm_id); memset(pp, 0, sizeof(*pp)); From 621f44af8c5b373de2b2e19a4a8db9e45c3c659a Mon Sep 17 00:00:00 2001 From: Eric Joyner Date: Tue, 11 Aug 2026 12:50:38 -0700 Subject: [PATCH 35/70] ionic: add missing dma_rmb() after the completion publish check Each completion service routine tests a device-written publish flag and then reads the rest of the descriptor with nothing ordering those loads. A control dependency does not order loads, so a weakly ordered CPU may satisfy the payload reads from a cache line state observed before the flag became valid. Add the barrier to all four completion paths. Fixes: 1d062b7b6f64 ("ionic: Add basic adminq support") Fixes: 0f3154e6bcb3 ("ionic: Add Tx and Rx handling") Fixes: 77ceb68e29cc ("ionic: Add notifyq support") Signed-off-by: Eric Joyner Reviewed-by: Brett Creeley Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260811195039.1315045-2-eric.joyner@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/pensando/ionic/ionic_main.c | 4 ++++ drivers/net/ethernet/pensando/ionic/ionic_txrx.c | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/drivers/net/ethernet/pensando/ionic/ionic_main.c b/drivers/net/ethernet/pensando/ionic/ionic_main.c index 6e6f3ed07271..10501be9ef95 100644 --- a/drivers/net/ethernet/pensando/ionic/ionic_main.c +++ b/drivers/net/ethernet/pensando/ionic/ionic_main.c @@ -269,6 +269,8 @@ bool ionic_notifyq_service(struct ionic_cq *cq) if ((s64)(eid - lif->last_eid) <= 0) return false; + dma_rmb(); + lif->last_eid = eid; dev_dbg(lif->ionic->dev, "notifyq event:\n"); @@ -314,6 +316,8 @@ bool ionic_adminq_service(struct ionic_cq *cq) if (!color_match(comp->color, cq->done_color)) return false; + dma_rmb(); + /* check for empty queue */ if (q->tail_idx == q->head_idx) return false; diff --git a/drivers/net/ethernet/pensando/ionic/ionic_txrx.c b/drivers/net/ethernet/pensando/ionic/ionic_txrx.c index 301ebee2fdc5..5b58460350be 100644 --- a/drivers/net/ethernet/pensando/ionic/ionic_txrx.c +++ b/drivers/net/ethernet/pensando/ionic/ionic_txrx.c @@ -734,6 +734,8 @@ static bool __ionic_rx_service(struct ionic_cq *cq, struct bpf_prog *xdp_prog) if (!color_match(comp->pkt_type_color, cq->done_color)) return false; + dma_rmb(); + /* check for empty queue */ if (q->tail_idx == q->head_idx) return false; @@ -1249,6 +1251,8 @@ static bool ionic_tx_service(struct ionic_cq *cq, if (!color_match(comp->color, cq->done_color)) return false; + dma_rmb(); + /* clean the related q entries, there could be * several q entries completed for each cq completion */ From 5da6ec6f06f235166bd084466b3386c638e26675 Mon Sep 17 00:00:00 2001 From: Prabu Thayalan Date: Tue, 11 Aug 2026 12:50:39 -0700 Subject: [PATCH 36/70] ionic: fix completion descriptor access with 2x desc size The old ionic_rx_service() and ionic_tx_service() used array indexing to access completion descriptors: comp = &((struct ionic_rxq_comp *)cq->base)[cq->tail_idx]; This assumes the stride is sizeof(struct ionic_rxq_comp) = 16 bytes. However, when the IONIC_Q_F_2X_CQ_DESC flag is set, the actual completion descriptor size is 32 bytes (2 * sizeof(comp)), and the completion itself is located at the end of that 32-byte slot. Array indexing with a 16-byte stride would access the wrong offset. Use pointer arithmetic that accounts for the actual descriptor size from cq->desc_size: comp = cq->base + cq->desc_size * cq->tail_idx + cq->desc_size - sizeof(*comp); This correctly calculates the completion location regardless of descriptor size. For the common case where desc_size equals sizeof(*comp), use array indexing in a likely() fast path to avoid performance regression. Fixes: 65e548f6b0ff ("ionic: remove the cq_info to save more memory") Signed-off-by: Prabu Thayalan Signed-off-by: Eric Joyner Reviewed-by: Brett Creeley Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260811195039.1315045-3-eric.joyner@amd.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/pensando/ionic/ionic_txrx.c | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/drivers/net/ethernet/pensando/ionic/ionic_txrx.c b/drivers/net/ethernet/pensando/ionic/ionic_txrx.c index 5b58460350be..05938689248d 100644 --- a/drivers/net/ethernet/pensando/ionic/ionic_txrx.c +++ b/drivers/net/ethernet/pensando/ionic/ionic_txrx.c @@ -701,11 +701,7 @@ static void ionic_rx_clean(struct ionic_queue *q, __le64 *cq_desc_hwstamp; u64 hwstamp; - cq_desc_hwstamp = - (void *)comp + - qcq->cq.desc_size - - sizeof(struct ionic_rxq_comp) - - IONIC_HWSTAMP_CQ_NEGOFFSET; + cq_desc_hwstamp = (void *)comp - IONIC_HWSTAMP_CQ_NEGOFFSET; hwstamp = le64_to_cpu(*cq_desc_hwstamp); @@ -729,7 +725,12 @@ static bool __ionic_rx_service(struct ionic_cq *cq, struct bpf_prog *xdp_prog) struct ionic_queue *q = cq->bound_q; struct ionic_rxq_comp *comp; - comp = &((struct ionic_rxq_comp *)cq->base)[cq->tail_idx]; + if (likely(cq->desc_size == sizeof(*comp))) + comp = &((struct ionic_rxq_comp *)cq->base)[cq->tail_idx]; + else + comp = cq->base + + cq->desc_size * cq->tail_idx + + cq->desc_size - sizeof(*comp); if (!color_match(comp->pkt_type_color, cq->done_color)) return false; @@ -1182,7 +1183,6 @@ static void ionic_tx_clean(struct ionic_queue *q, bool in_napi) { struct ionic_tx_stats *stats = q_to_tx_stats(q); - struct ionic_qcq *qcq = q_to_qcq(q); struct sk_buff *skb; if (desc_info->xdpf) { @@ -1207,11 +1207,7 @@ static void ionic_tx_clean(struct ionic_queue *q, __le64 *cq_desc_hwstamp; u64 hwstamp; - cq_desc_hwstamp = - (void *)comp + - qcq->cq.desc_size - - sizeof(struct ionic_txq_comp) - - IONIC_HWSTAMP_CQ_NEGOFFSET; + cq_desc_hwstamp = (void *)comp - IONIC_HWSTAMP_CQ_NEGOFFSET; hwstamp = le64_to_cpu(*cq_desc_hwstamp); @@ -1246,7 +1242,12 @@ static bool ionic_tx_service(struct ionic_cq *cq, unsigned int pkts = 0; u16 index; - comp = &((struct ionic_txq_comp *)cq->base)[cq->tail_idx]; + if (likely(cq->desc_size == sizeof(*comp))) + comp = &((struct ionic_txq_comp *)cq->base)[cq->tail_idx]; + else + comp = cq->base + + cq->desc_size * cq->tail_idx + + cq->desc_size - sizeof(*comp); if (!color_match(comp->color, cq->done_color)) return false; From d0d48d999b0eee6bb176ef4e39d9be868fa80f7e Mon Sep 17 00:00:00 2001 From: Luxiao Xu Date: Wed, 12 Aug 2026 20:54:38 +0800 Subject: [PATCH 37/70] ipv6: fix use-after-free in ip6_finish_output2() ip6_finish_output2() caches a pointer to the IPv6 destination address (daddr) before invoking lwtunnel_xmit(). The LWT-BPF transmit path or other encapsulation operations within lwtunnel_xmit() can reallocate the skb head, freeing the memory that daddr points to. When lwtunnel_xmit() returns LWTUNNEL_XMIT_CONTINUE, the function continues to use the stale daddr pointer to compute the nexthop and to look up or create the neighbour entry. This results in a use-after-free read, which can leak sensitive kernel data, pollute the neighbour table with arbitrary values, misdirect traffic, or crash the system. Fix this by re-fetching the IPv6 header and the destination address pointer after lwtunnel_xmit() returns LWTUNNEL_XMIT_CONTINUE, ensuring that the subsequent nexthop computation and neighbour lookup operate on valid memory. Fixes: e415ed3a4b8b ("ipv6: use skb_expand_head in ip6_finish_output2") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Luxiao Xu Signed-off-by: Ren Wei Reviewed-by: Vadim Fedorenko Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/4aa3f53bc44e79572c6dd2340ec7b68ef1a3d87d.1786516730.git.rakukuip@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv6/ip6_output.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c index 2c44e5ed6171..8fc4766c8da9 100644 --- a/net/ipv6/ip6_output.c +++ b/net/ipv6/ip6_output.c @@ -116,6 +116,8 @@ static int ip6_finish_output2(struct net *net, struct sock *sk, struct sk_buff * if (res != LWTUNNEL_XMIT_CONTINUE) return res; + hdr = ipv6_hdr(skb); + daddr = &hdr->daddr; } IP6_UPD_PO_STATS(net, idev, IPSTATS_MIB_OUT, skb->len); From 87f21b59ddc618eff9670c174842964ad65fdade Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Tue, 11 Aug 2026 21:31:11 +0800 Subject: [PATCH 38/70] ip6_tunnel: use skb_cow_head() in ip6_tnl_xmit() ip6_tnl_xmit() may need to expand headroom before it can push the outer IPv6 and optional encap headers. It currently does that with skb_realloc_headroom(), copies skb->sk ownership, consumes the original skb, and then continues processing with the replacement skb kept only in its local variable. That is safe only if the helper cannot fail afterwards. But this helper still has post-reallocation error exits. collect_md tunnels reject non-NONE encap after the replacement, and ip6_tnl_encap() can also fail later. In those cases the helper returns an error to its callers while the caller still only has the original skb pointer. Both ip6_tnl_start_xmit() and the IPv6 GRE paths free the caller skb on error, so they can end up freeing an skb that ip6_tnl_xmit() already consumed. Use skb_cow_head() instead. It provides the required headroom and writability without privately replacing the caller-owned skb, so later error returns cannot leave callers with a stale pointer. The Ethernet users, ip6gretap and ip6erspan, clear IFF_TX_SKB_SHARING and already call skb_cow_head() before entering ip6_tnl_xmit(). They do not rely on the removed skb_shared() reallocation. This also makes the IPv6 tunnel path consistent with ip_tunnel_xmit(). Fixes: 058214a4d1df ("ip6_tun: Add infrastructure for doing encapsulation") Cc: stable@vger.kernel.org Reported-by: Vega Reviewed-by: Ido Schimmel Signed-off-by: Zhiling Zou Link: https://patch.msgid.link/30807a062ccc5c9c8a5ec2c5eb805ef279c50bdd.1786452593.git.zhilinz@nebusec.ai Signed-off-by: Jakub Kicinski --- net/ipv6/ip6_tunnel.c | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c index ebf83f090376..6a1b901ecc9b 100644 --- a/net/ipv6/ip6_tunnel.c +++ b/net/ipv6/ip6_tunnel.c @@ -1236,19 +1236,8 @@ int ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev, __u8 dsfield, */ max_headroom += LL_RESERVED_SPACE(tdev); - if (skb_headroom(skb) < max_headroom || skb_shared(skb) || - (skb_cloned(skb) && !skb_clone_writable(skb, 0))) { - struct sk_buff *new_skb; - - new_skb = skb_realloc_headroom(skb, max_headroom); - if (!new_skb) - goto tx_err_dst_release; - - if (skb->sk) - skb_set_owner_w(new_skb, skb->sk); - consume_skb(skb); - skb = new_skb; - } + if (skb_cow_head(skb, max_headroom)) + goto tx_err_dst_release; if (t->parms.collect_md) { if (t->encap.type != TUNNEL_ENCAP_NONE) From d5d4a7b538b52db63927773a8905fcd9f78a42e2 Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Mon, 10 Aug 2026 14:41:14 +0000 Subject: [PATCH 39/70] vxlan: keep the last remote linked during FDB flush A non-nexthop FDB entry is expected to have at least one remote while it remains reachable through the FDB hash table. A filtered bulk flush violates this invariant when every remote matches: It unlinks the last remote in vxlan_fdb_dst_destroy() and only afterwards tells vxlan_flush() to destroy the parent FDB entry. An RCU reader can find the parent during this interval. first_remote_rcu() then applies list_entry_rcu() to the empty list head, producing an invalid remote pointer that the receive learning path can read from and write to. When a matching remote is the sole remaining remote, leave it linked and ask the caller to destroy the entire FDB entry. vxlan_fdb_destroy() keeps the remote attached while sending the deletion notification and removing the parent from the lookup structures. Fixes: c499fccb71cb ("vxlan: vxlan_core: Support FDB flushing by destination VNI") Cc: stable@vger.kernel.org Signed-off-by: Kyle Zeng Co-developed-by: David Lee Signed-off-by: David Lee Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260810144115.821654-1-david.lee@trailofbits.com Signed-off-by: Jakub Kicinski --- drivers/net/vxlan/vxlan_core.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index 824144bb7774..fbb6ddbb7f89 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -3058,18 +3058,19 @@ vxlan_fdb_flush_match_remotes(struct vxlan_fdb *f, struct vxlan_dev *vxlan, const struct vxlan_fdb_flush_desc *desc, bool *p_destroy_fdb) { - bool remotes_flushed = false; struct vxlan_rdst *rd, *tmp; list_for_each_entry_safe(rd, tmp, &f->remotes, list) { if (!vxlan_fdb_flush_remote_matches(desc, rd)) continue; - vxlan_fdb_dst_destroy(vxlan, f, rd, true); - remotes_flushed = true; - } + if (list_is_singular(&f->remotes)) { + *p_destroy_fdb = true; + return; + } - *p_destroy_fdb = remotes_flushed && list_empty(&f->remotes); + vxlan_fdb_dst_destroy(vxlan, f, rd, true); + } } /* Purge the forwarding table */ From 33f016b23a219fe034213849b51436b8e79df251 Mon Sep 17 00:00:00 2001 From: Petr Oros Date: Thu, 13 Aug 2026 16:08:17 +0200 Subject: [PATCH 40/70] dpll: fix NULL deref in dpll_device_ops() during teardown race When the last owner of a dpll device unregisters while a foreign driver still holds a pin on it via dpll_pin_on_pin_register(), the dpll object stays alive with an empty registration list. A pin notification queued before the unregister (e.g. ice reacting to zl3073x_i2c removal) then walks pin->dpll_refs into dpll_device_ops(), which trips the WARN_ON and dereferences the missing registration. dpll_lock cannot help because the notification work was queued before the unregistering driver took the lock. Treat the empty registration list as a legitimate transient state. Make dpll_priv() and dpll_device_ops() return NULL in that case and make every pin netlink path that resolves a device from a pin skip such dplls. dpll_cmd_pin_get_one() picks a ref with a live registration and returns -ENODEV when there is none, the pin dumpit skips such a pin instead of aborting the dump, dpll_msg_add_pin_dplls() and the frequency, esync, reference sync and phase adjust set paths skip dead refs, and dpll_pin_parent_device_set() validates the parent with dpll_device_get_by_id(). dpll_pin_register() is the last caller that dereferenced the device ops without a check, so move its frequency monitor validation under dpll_lock and tolerate a missing registration there as well. The empty registration list is equivalent to a cleared DPLL_REGISTERED mark, both transitions happen under dpll_lock in dpll_device_register() and dpll_device_unregister(). A pin notification for a pin whose dplls are all gone is now dropped with -ENODEV instead of crashing, all callers in the core ignore that return value. WARNING: drivers/dpll/dpll_core.c:1092 at dpll_device_ops+0x24/0x40, CPU#83: kworker/u576:3/23471 Modules linked in: ... ice ... zl3073x_i2c(-) ... zl3073x ... Workqueue: ice_dpll_wq ice_dpll_pin_notify_work [ice] RIP: 0010:dpll_device_ops+0x24/0x40 Call Trace: dpll_cmd_pin_get_one+0x336/0x520 dpll_pin_event_send+0x82/0x140 dpll_pin_on_pin_unregister+0xbb/0x160 ice_dpll_pin_notify_work+0x1bc/0x1f0 [ice] process_one_work+0x19e/0x370 worker_thread+0x1a6/0x310 kthread+0xe4/0x120 ret_from_fork+0x1a1/0x270 ret_from_fork_asm+0x1a/0x30 ---[ end trace 0000000000000000 ]--- BUG: kernel NULL pointer dereference, address: 0000000000000010 #PF: supervisor read access in kernel mode #PF: error_code(0x0000) - not-present page Fixes: 9431063ad323 ("dpll: core: Add DPLL framework base functions") Signed-off-by: Petr Oros Tested-by: Ivan Vecera Reviewed-by: Vadim Fedorenko Link: https://patch.msgid.link/20260813140817.1051388-1-poros@redhat.com Signed-off-by: Jakub Kicinski --- drivers/dpll/dpll_core.c | 24 +++++++++------ drivers/dpll/dpll_netlink.c | 59 ++++++++++++++++++++++++++++++++----- 2 files changed, 67 insertions(+), 16 deletions(-) diff --git a/drivers/dpll/dpll_core.c b/drivers/dpll/dpll_core.c index 43d51d942ead..a320eeb829ad 100644 --- a/drivers/dpll/dpll_core.c +++ b/drivers/dpll/dpll_core.c @@ -876,19 +876,25 @@ int dpll_pin_register(struct dpll_device *dpll, struct dpll_pin *pin, const struct dpll_pin_ops *ops, void *priv) { + const struct dpll_device_ops *dev_ops; int ret; if (WARN_ON(!ops) || WARN_ON(!ops->state_on_dpll_get) || WARN_ON(!ops->direction_get) || - WARN_ON(ops->measured_freq_get && - (!dpll_device_ops(dpll)->freq_monitor_get || - !dpll_device_ops(dpll)->freq_monitor_set)) || WARN_ON(ops->supported_ffo && !ops->ffo_get)) return -EINVAL; mutex_lock(&dpll_lock); + dev_ops = dpll_device_ops(dpll); + if (WARN_ON(ops->measured_freq_get && + (!dev_ops || !dev_ops->freq_monitor_get || + !dev_ops->freq_monitor_set))) { + ret = -EINVAL; + goto out_unlock; + } + /* * For pins identified via firmware (pin->fwnode), allow registration * even if the pin's (module, clock_id) differs from the target DPLL. @@ -1081,12 +1087,8 @@ EXPORT_SYMBOL_GPL(dpll_pin_ref_sync_pair_add); static struct dpll_device_registration * dpll_device_registration_first(struct dpll_device *dpll) { - struct dpll_device_registration *reg; - - reg = list_first_entry_or_null((struct list_head *)&dpll->registration_list, - struct dpll_device_registration, list); - WARN_ON(!reg); - return reg; + return list_first_entry_or_null((struct list_head *)&dpll->registration_list, + struct dpll_device_registration, list); } void *dpll_priv(struct dpll_device *dpll) @@ -1094,6 +1096,8 @@ void *dpll_priv(struct dpll_device *dpll) struct dpll_device_registration *reg; reg = dpll_device_registration_first(dpll); + if (!reg) + return NULL; return reg->priv; } @@ -1102,6 +1106,8 @@ const struct dpll_device_ops *dpll_device_ops(struct dpll_device *dpll) struct dpll_device_registration *reg; reg = dpll_device_registration_first(dpll); + if (!reg) + return NULL; return reg->ops; } diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c index afb31c004038..9e55745e33e4 100644 --- a/drivers/dpll/dpll_netlink.c +++ b/drivers/dpll/dpll_netlink.c @@ -66,6 +66,22 @@ static bool dpll_pin_available(struct dpll_pin *pin) return false; } +static bool dpll_device_registered(struct dpll_device *dpll) +{ + return dpll_device_ops(dpll); +} + +static struct dpll_pin_ref *dpll_pin_first_registered_ref(struct dpll_pin *pin) +{ + struct dpll_pin_ref *ref; + unsigned long i; + + xa_for_each(&pin->dpll_refs, i, ref) + if (dpll_device_registered(ref->dpll)) + return ref; + return NULL; +} + /** * dpll_msg_add_pin_handle - attach pin handle attribute to a given message * @msg: pointer to sk_buff message to attach a pin handle @@ -656,6 +672,8 @@ dpll_msg_add_pin_dplls(struct sk_buff *msg, struct dpll_pin *pin, int ret; xa_for_each(&pin->dpll_refs, index, ref) { + if (!dpll_device_registered(ref->dpll)) + continue; attr = nla_nest_start(msg, DPLL_A_PIN_PARENT_DEVICE); if (!attr) return -EMSGSIZE; @@ -700,9 +718,10 @@ dpll_cmd_pin_get_one(struct sk_buff *msg, struct dpll_pin *pin, int ret; ref = dpll_pin_own_dpll_ref_first(pin); + if (!ref || !dpll_device_registered(ref->dpll)) + ref = dpll_pin_first_registered_ref(pin); if (!ref) - ref = dpll_xa_ref_dpll_first(&pin->dpll_refs); - ASSERT_NOT_NULL(ref); + return -ENODEV; ret = dpll_msg_add_pin_handle(msg, pin); if (ret) @@ -1091,6 +1110,8 @@ dpll_pin_freq_set(struct dpll_pin *pin, struct nlattr *a, } xa_for_each(&pin->dpll_refs, i, ref) { + if (!dpll_device_registered(ref->dpll)) + continue; ops = dpll_pin_ops(ref); if ((!ops->frequency_set || !ops->frequency_get) && ref->dpll->module == pin->module && @@ -1101,7 +1122,7 @@ dpll_pin_freq_set(struct dpll_pin *pin, struct nlattr *a, } } ref = dpll_pin_own_dpll_ref_first(pin); - if (!ref) { + if (!ref || !dpll_device_registered(ref->dpll)) { NL_SET_ERR_MSG(extack, "pin owner dpll not found"); return -ENODEV; } @@ -1117,6 +1138,8 @@ dpll_pin_freq_set(struct dpll_pin *pin, struct nlattr *a, return 0; xa_for_each(&pin->dpll_refs, i, ref) { + if (!dpll_device_registered(ref->dpll)) + continue; ops = dpll_pin_ops(ref); if (!ops->frequency_set) continue; @@ -1138,6 +1161,8 @@ dpll_pin_freq_set(struct dpll_pin *pin, struct nlattr *a, xa_for_each(&pin->dpll_refs, i, ref) { if (ref == failed) break; + if (!dpll_device_registered(ref->dpll)) + continue; ops = dpll_pin_ops(ref); if (!ops->frequency_set) continue; @@ -1163,6 +1188,8 @@ dpll_pin_esync_set(struct dpll_pin *pin, struct nlattr *a, int ret; xa_for_each(&pin->dpll_refs, i, ref) { + if (!dpll_device_registered(ref->dpll)) + continue; ops = dpll_pin_ops(ref); if ((!ops->esync_set || !ops->esync_get) && ref->dpll->module == pin->module && @@ -1173,7 +1200,7 @@ dpll_pin_esync_set(struct dpll_pin *pin, struct nlattr *a, } } ref = dpll_pin_own_dpll_ref_first(pin); - if (!ref) { + if (!ref || !dpll_device_registered(ref->dpll)) { NL_SET_ERR_MSG(extack, "pin owner dpll not found"); return -ENODEV; } @@ -1199,6 +1226,8 @@ dpll_pin_esync_set(struct dpll_pin *pin, struct nlattr *a, xa_for_each(&pin->dpll_refs, i, ref) { void *pin_dpll_priv; + if (!dpll_device_registered(ref->dpll)) + continue; ops = dpll_pin_ops(ref); if (!ops->esync_set) continue; @@ -1224,6 +1253,8 @@ dpll_pin_esync_set(struct dpll_pin *pin, struct nlattr *a, if (ref == failed) break; + if (!dpll_device_registered(ref->dpll)) + continue; ops = dpll_pin_ops(ref); if (!ops->esync_set) continue; @@ -1262,7 +1293,7 @@ dpll_pin_ref_sync_state_set(struct dpll_pin *pin, return -EINVAL; } ref = dpll_pin_own_dpll_ref_first(pin); - if (!ref) { + if (!ref || !dpll_device_registered(ref->dpll)) { NL_SET_ERR_MSG(extack, "pin owner dpll not found"); return -ENODEV; } @@ -1283,6 +1314,8 @@ dpll_pin_ref_sync_state_set(struct dpll_pin *pin, if (state == old_state) return 0; xa_for_each(&pin->dpll_refs, i, ref) { + if (!dpll_device_registered(ref->dpll)) + continue; ops = dpll_pin_ops(ref); if (!ops->ref_sync_set) continue; @@ -1307,6 +1340,8 @@ dpll_pin_ref_sync_state_set(struct dpll_pin *pin, xa_for_each(&pin->dpll_refs, i, ref) { if (ref == failed) break; + if (!dpll_device_registered(ref->dpll)) + continue; ops = dpll_pin_ops(ref); if (!ops->ref_sync_set) continue; @@ -1500,6 +1535,8 @@ dpll_pin_phase_adj_set(struct dpll_pin *pin, struct nlattr *phase_adj_attr, } xa_for_each(&pin->dpll_refs, i, ref) { + if (!dpll_device_registered(ref->dpll)) + continue; ops = dpll_pin_ops(ref); if ((!ops->phase_adjust_set || !ops->phase_adjust_get) && ref->dpll->module == pin->module && @@ -1509,7 +1546,7 @@ dpll_pin_phase_adj_set(struct dpll_pin *pin, struct nlattr *phase_adj_attr, } } ref = dpll_pin_own_dpll_ref_first(pin); - if (!ref) { + if (!ref || !dpll_device_registered(ref->dpll)) { NL_SET_ERR_MSG(extack, "pin owner dpll not found"); return -ENODEV; } @@ -1526,6 +1563,8 @@ dpll_pin_phase_adj_set(struct dpll_pin *pin, struct nlattr *phase_adj_attr, return 0; xa_for_each(&pin->dpll_refs, i, ref) { + if (!dpll_device_registered(ref->dpll)) + continue; ops = dpll_pin_ops(ref); if (!ops->phase_adjust_set) continue; @@ -1550,6 +1589,8 @@ dpll_pin_phase_adj_set(struct dpll_pin *pin, struct nlattr *phase_adj_attr, xa_for_each(&pin->dpll_refs, i, ref) { if (ref == failed) break; + if (!dpll_device_registered(ref->dpll)) + continue; ops = dpll_pin_ops(ref); if (!ops->phase_adjust_set) continue; @@ -1581,7 +1622,7 @@ dpll_pin_parent_device_set(struct dpll_pin *pin, struct nlattr *parent_nest, return -EINVAL; } pdpll_idx = nla_get_u32(tb[DPLL_A_PIN_PARENT_ID]); - dpll = xa_load(&dpll_device_xa, pdpll_idx); + dpll = dpll_device_get_by_id(pdpll_idx); if (!dpll) { NL_SET_ERR_MSG(extack, "parent device not found"); return -EINVAL; @@ -1873,6 +1914,10 @@ int dpll_nl_pin_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb) ret = dpll_cmd_pin_get_one(skb, pin, cb->extack); if (ret) { genlmsg_cancel(skb, hdr); + if (ret == -ENODEV) { + ret = 0; + continue; + } break; } genlmsg_end(skb, hdr); From b0346dd64e4905291cc9c479f2e6cf1884ced4e6 Mon Sep 17 00:00:00 2001 From: Junseo Lim Date: Thu, 13 Aug 2026 12:51:36 +0900 Subject: [PATCH 41/70] net: kcm: Hold RCU read lock while running BPF parser kcm_parse_func_strparser() calls bpf_prog_run_pin_on_cpu() which prevents CPU migration, but does not establish an RCU read-side critical section. Consequently, BPF map operations can trigger WARN_ON_ONCE(!bpf_rcu_lock_held()) when called from the KCM strparser program. Hold the RCU read lock while running the program. Fixes: 9b73896a81dc ("kcm: Use stream parser") Reported-by: Sechang Lim Signed-off-by: Junseo Lim Link: https://patch.msgid.link/20260813035136.106167-1-zirajs7@gmail.com Signed-off-by: Jakub Kicinski --- net/kcm/kcmsock.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/kcm/kcmsock.c b/net/kcm/kcmsock.c index d469abcd989b..71af69d442f2 100644 --- a/net/kcm/kcmsock.c +++ b/net/kcm/kcmsock.c @@ -5,6 +5,7 @@ * Copyright (c) 2016 Tom Herbert */ +#include #include #include #include @@ -391,7 +392,9 @@ static int kcm_parse_func_strparser(struct strparser *strp, struct sk_buff *skb) struct bpf_prog *prog = psock->bpf_prog; int res; + rcu_read_lock(); res = bpf_prog_run_pin_on_cpu(prog, skb); + rcu_read_unlock(); return res; } From 4f1d06cf8aaa9d2cb18e5ee8835aff6177256bc6 Mon Sep 17 00:00:00 2001 From: Vladimir Oltean Date: Wed, 12 Aug 2026 23:11:21 +0300 Subject: [PATCH 42/70] net: dsa: b53: fix error propagation from b53_fdb_dump() The blamed commit replaced "return ret" statements in b53_fdb_dump() with "break;" which jumps to the mutex_unlock() -> return 0 section. This is notably problematic because it swallows errors from the b53_fdb_copy() -> cb() path, and this will result in FDB dump truncation when the netlink skb overflows - see commit 21b52fed928e ("net: dsa: sja1105: fix broken backpressure in .port_fdb_dump"). Let's go back to "return ret". We don't need to preinitialize "ret" with 0, because the "do {} while" block guarantees we cannot reach the end of the function without at least once calling b53_arl_search_wait(), which will have initialized ret to some valid value. Fixes: f7eb4a1c0864 ("net: dsa: b53: serialize access to the ARL table") Signed-off-by: Vladimir Oltean Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20260812201121.2012356-1-vladimir.oltean@nxp.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/b53/b53_common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/dsa/b53/b53_common.c b/drivers/net/dsa/b53/b53_common.c index 3f5b9592794d..0880310c9ce3 100644 --- a/drivers/net/dsa/b53/b53_common.c +++ b/drivers/net/dsa/b53/b53_common.c @@ -2219,7 +2219,7 @@ int b53_fdb_dump(struct dsa_switch *ds, int port, mutex_unlock(&priv->arl_mutex); - return 0; + return ret; } EXPORT_SYMBOL(b53_fdb_dump); From 92c1bf630abf0af646562398eaa36f80b5ff677d Mon Sep 17 00:00:00 2001 From: Qingfang Deng Date: Tue, 11 Aug 2026 11:53:10 +0800 Subject: [PATCH 43/70] pppox: drain queued packets on channel handoff PPPIOCGCHAN both returns the channel index and marks a PPPOX socket as bound to generic PPP, despite its getter semantic. Packets received before that transition are queued on sk_receive_queue, but a bound socket is no longer readable. Such packets therefore remain queued until the socket is destroyed. After marking a socket bound, wait for receive paths that observed the old state to finish queueing packets, and then drain the queue into generic PPP. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Qingfang Deng Link: https://patch.msgid.link/20260811035314.302878-1-qingfang.deng@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/ppp/pppox.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/net/ppp/pppox.c b/drivers/net/ppp/pppox.c index 5861a2f6ce3e..a6f72c813bef 100644 --- a/drivers/net/ppp/pppox.c +++ b/drivers/net/ppp/pppox.c @@ -74,7 +74,9 @@ int pppox_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) switch (cmd) { case PPPIOCGCHAN: { + struct sk_buff *skb; int index; + rc = -ENOTCONN; if (!(sk->sk_state & PPPOX_CONNECTED)) break; @@ -85,7 +87,22 @@ int pppox_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) break; rc = 0; + /* PPPIOCGCHAN historically marks the userspace handoff to + * generic PPP; pppd then attaches the returned channel to + * /dev/ppp. + */ sk->sk_state |= PPPOX_BOUND; + /* Let lockless receive paths finish queueing against the old + * state. + */ + synchronize_net(); + /* Drain packets queued before the handoff because a bound + * socket is no longer readable. + */ + while ((skb = skb_dequeue(&sk->sk_receive_queue))) { + skb_orphan(skb); + ppp_input(&po->chan, skb); + } break; } default: From 7f16289b91eb316f170a6bd22d32e6c632f6a5b6 Mon Sep 17 00:00:00 2001 From: Xin Xie Date: Sat, 8 Aug 2026 13:08:14 +0200 Subject: [PATCH 44/70] net: hsr: free learned nodes on device setup failure hsr_dev_finalize() can fail after a lower-device RX handler has already been registered (slave A is added before the failable slave B and interlink adds). RX handlers run in softirq regardless of the master's state, so frames received in that window can learn dynamic nodes into node_db, and the error unwind never releases them. Free both owned dynamic databases in the unwind, mirroring hsr_dellink(). proxy_node_db is provably empty on every current error exit (only interlink RX feeds it, and the interlink add is the last failable step) and is freed for symmetry. The order is safe: hsr_del_port() unregisters each RX handler with synchronize_net() before hsr_del_nodes() runs, which removes remaining entries with list_del_rcu() and defers their release with call_rcu() for readers already under RCU. Fixes: 81ba6afd6e64 ("net/hsr: Switch from dev_add_pack() to netdev_rx_handler_register()") Signed-off-by: Xin Xie Link: https://patch.msgid.link/20260808110814.1637-1-xiexinet@gmail.com Signed-off-by: Jakub Kicinski --- net/hsr/hsr_device.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/hsr/hsr_device.c b/net/hsr/hsr_device.c index 5555b71ab19b..9c3078dd38c2 100644 --- a/net/hsr/hsr_device.c +++ b/net/hsr/hsr_device.c @@ -820,6 +820,8 @@ int hsr_dev_finalize(struct net_device *hsr_dev, struct net_device *slave[2], hsr_del_ports(hsr); err_add_master: hsr_del_self_node(hsr); + hsr_del_nodes(&hsr->node_db); + hsr_del_nodes(&hsr->proxy_node_db); if (unregister) unregister_netdevice(hsr_dev); From d09c98a6da215bce2173a292e4b95c8de6ea5d51 Mon Sep 17 00:00:00 2001 From: Anton Protopopov Date: Mon, 10 Aug 2026 12:07:25 +0000 Subject: [PATCH 45/70] virtio_net: Fix resize of the RX ring When a AF_XDP socket is attached, the virtnet_rx_resize should resize the rq->xsk_buffs XSK buffer array. Otherwise, when the size grows, the virtnet_rx_resume() causes a write past the end of the array. This is easily reproducable with ethtool -G ens3 rx 32 ./xdpsock -i eth0 -q 0 -r -z & ethtool -G eth0 rx 256 Fixes: e9f3962441c0 ("virtio_net: xsk: rx: support fill with xsk buffer") Signed-off-by: Anton Protopopov Link: https://patch.msgid.link/20260810120728.47445-1-a.s.protopopov@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/virtio_net.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c index 3e2a5876c6c8..e34c52d059d3 100644 --- a/drivers/net/virtio_net.c +++ b/drivers/net/virtio_net.c @@ -3444,17 +3444,31 @@ static void virtnet_rx_resume_all(struct virtnet_info *vi) static int virtnet_rx_resize(struct virtnet_info *vi, struct receive_queue *rq, u32 ring_num) { + unsigned int old_ring_num = virtqueue_get_vring_size(rq->vq); + struct xdp_buff **tmp_xsk_buffs = NULL; int err, qindex; qindex = rq - vi->rq; + if (rq->xsk_pool && ring_num > old_ring_num) { + tmp_xsk_buffs = kvzalloc_objs(*tmp_xsk_buffs, ring_num); + if (!tmp_xsk_buffs) + return -ENOMEM; + } + virtnet_rx_pause(vi, rq); err = virtqueue_resize(rq->vq, ring_num, virtnet_rq_unmap_free_buf, NULL); + + /* virtqueue_resize may have changed the size even if err != 0 */ + if (tmp_xsk_buffs && virtqueue_get_vring_size(rq->vq) > old_ring_num) + swap(rq->xsk_buffs, tmp_xsk_buffs); + if (err) netdev_err(vi->dev, "resize rx fail: rx queue index: %d err: %d\n", qindex, err); virtnet_rx_resume(vi, rq, true); + kvfree(tmp_xsk_buffs); return err; } From 1f77af0aaf277413ff32f6ff8c2c4282bd64c897 Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Tue, 11 Aug 2026 18:37:32 +0800 Subject: [PATCH 46/70] net: ravb: avoid dereferencing an invalid PTP clock The PTP clock is unavailable before the first open, so querying its index can dereference a NULL pointer. Registration failures can also leave an error pointer in priv->ptp.clock. Cache the PHC index separately and report -1 while no clock is registered. Normalize registration errors to NULL and preserve the static timestamping capabilities. Fixes: a0d2f20650e8 ("Renesas Ethernet AVB PTP clock driver") Cc: stable@vger.kernel.org Reviewed-by: Vadim Fedorenko Signed-off-by: Xuanqiang Luo Link: https://patch.msgid.link/20260811103733.62599-2-xuanqiang.luo@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/renesas/ravb.h | 1 + drivers/net/ethernet/renesas/ravb_main.c | 3 ++- drivers/net/ethernet/renesas/ravb_ptp.c | 15 +++++++++++++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/renesas/ravb.h b/drivers/net/ethernet/renesas/ravb.h index 5e56ec9b1013..2a4fcb12a63c 100644 --- a/drivers/net/ethernet/renesas/ravb.h +++ b/drivers/net/ethernet/renesas/ravb.h @@ -1028,6 +1028,7 @@ struct ravb_ptp_perout { struct ravb_ptp { struct ptp_clock *clock; struct ptp_clock_info info; + int phc_index; u32 default_addend; u32 current_addend; int extts[N_EXT_TS]; diff --git a/drivers/net/ethernet/renesas/ravb_main.c b/drivers/net/ethernet/renesas/ravb_main.c index 5f88733094d0..db0229e00849 100644 --- a/drivers/net/ethernet/renesas/ravb_main.c +++ b/drivers/net/ethernet/renesas/ravb_main.c @@ -1779,7 +1779,7 @@ static int ravb_get_ts_info(struct net_device *ndev, (1 << HWTSTAMP_FILTER_NONE) | (1 << HWTSTAMP_FILTER_PTP_V2_L2_EVENT) | (1 << HWTSTAMP_FILTER_ALL); - info->phc_index = ptp_clock_index(priv->ptp.clock); + info->phc_index = READ_ONCE(priv->ptp.phc_index); } return 0; @@ -2953,6 +2953,7 @@ static int ravb_probe(struct platform_device *pdev) priv->rstc = rstc; priv->ndev = ndev; priv->pdev = pdev; + priv->ptp.phc_index = -1; priv->num_tx_ring[RAVB_BE] = BE_TX_RING_SIZE; priv->num_rx_ring[RAVB_BE] = BE_RX_RING_SIZE; if (info->nc_queues) { diff --git a/drivers/net/ethernet/renesas/ravb_ptp.c b/drivers/net/ethernet/renesas/ravb_ptp.c index 226c6c0ab945..cbec7c057d71 100644 --- a/drivers/net/ethernet/renesas/ravb_ptp.c +++ b/drivers/net/ethernet/renesas/ravb_ptp.c @@ -315,6 +315,7 @@ void ravb_ptp_interrupt(struct net_device *ndev) void ravb_ptp_init(struct net_device *ndev, struct platform_device *pdev) { struct ravb_private *priv = netdev_priv(ndev); + struct ptp_clock *clock; unsigned long flags; priv->ptp.info = ravb_ptp_info; @@ -327,7 +328,15 @@ void ravb_ptp_init(struct net_device *ndev, struct platform_device *pdev) ravb_modify(ndev, GCCR, GCCR_TCSS, GCCR_TCSS_ADJGPTP); spin_unlock_irqrestore(&priv->lock, flags); - priv->ptp.clock = ptp_clock_register(&priv->ptp.info, &pdev->dev); + clock = ptp_clock_register(&priv->ptp.info, &pdev->dev); + if (IS_ERR(clock)) { + netdev_err(ndev, "failed to register PTP clock: %pe\n", clock); + clock = NULL; + } + + priv->ptp.clock = clock; + if (clock) + WRITE_ONCE(priv->ptp.phc_index, ptp_clock_index(clock)); } void ravb_ptp_stop(struct net_device *ndev) @@ -337,5 +346,7 @@ void ravb_ptp_stop(struct net_device *ndev) ravb_write(ndev, 0, GIC); ravb_write(ndev, 0, GIS); - ptp_clock_unregister(priv->ptp.clock); + WRITE_ONCE(priv->ptp.phc_index, -1); + if (priv->ptp.clock) + ptp_clock_unregister(priv->ptp.clock); } From 1cb9663789c5b7a12fcd419fcca6d6254c398252 Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Tue, 11 Aug 2026 18:37:33 +0800 Subject: [PATCH 47/70] net: ravb: serialize PTP clock teardown ravb_ptp_interrupt() can race with ravb_ptp_stop() and pass the clock to ptp_clock_event() while ptp_clock_unregister() is freeing it. This can lead to a use-after-free. Use READ_ONCE() and WRITE_ONCE() for lockless access to the clock pointer. Atomically detach it with xchg() before disabling PTP interrupts, then synchronize all IRQs which can invoke ravb_ptp_interrupt() before unregistering the detached clock. A handler which read the old pointer completes before the clock is unregistered, while later handlers read NULL and skip the event. Fixes: a0d2f20650e8 ("Renesas Ethernet AVB PTP clock driver") Cc: stable@vger.kernel.org Signed-off-by: Xuanqiang Luo Link: https://patch.msgid.link/20260811103733.62599-3-xuanqiang.luo@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/renesas/ravb.h | 2 ++ drivers/net/ethernet/renesas/ravb_main.c | 6 ++-- drivers/net/ethernet/renesas/ravb_ptp.c | 37 +++++++++++++++++++----- 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/drivers/net/ethernet/renesas/ravb.h b/drivers/net/ethernet/renesas/ravb.h index 2a4fcb12a63c..3ee4c6108189 100644 --- a/drivers/net/ethernet/renesas/ravb.h +++ b/drivers/net/ethernet/renesas/ravb.h @@ -1124,6 +1124,8 @@ struct ravb_private { int msg_enable; int speed; int emac_irq; + int err_irq; + int mgmt_irq; unsigned no_avb_link:1; unsigned avb_link_active_low:1; diff --git a/drivers/net/ethernet/renesas/ravb_main.c b/drivers/net/ethernet/renesas/ravb_main.c index db0229e00849..ea1c7e536791 100644 --- a/drivers/net/ethernet/renesas/ravb_main.c +++ b/drivers/net/ethernet/renesas/ravb_main.c @@ -2885,11 +2885,13 @@ static int ravb_setup_irqs(struct ravb_private *priv) return error; if (info->err_mgmt_irqs) { - error = ravb_setup_irq(priv, "err_a", "err_a", NULL, ravb_multi_interrupt); + error = ravb_setup_irq(priv, "err_a", "err_a", &priv->err_irq, + ravb_multi_interrupt); if (error) return error; - error = ravb_setup_irq(priv, "mgmt_a", "mgmt_a", NULL, ravb_multi_interrupt); + error = ravb_setup_irq(priv, "mgmt_a", "mgmt_a", &priv->mgmt_irq, + ravb_multi_interrupt); if (error) return error; } diff --git a/drivers/net/ethernet/renesas/ravb_ptp.c b/drivers/net/ethernet/renesas/ravb_ptp.c index cbec7c057d71..43218bc15b15 100644 --- a/drivers/net/ethernet/renesas/ravb_ptp.c +++ b/drivers/net/ethernet/renesas/ravb_ptp.c @@ -289,16 +289,17 @@ static const struct ptp_clock_info ravb_ptp_info = { void ravb_ptp_interrupt(struct net_device *ndev) { struct ravb_private *priv = netdev_priv(ndev); + struct ptp_clock *clock = READ_ONCE(priv->ptp.clock); u32 gis = ravb_read(ndev, GIS); gis &= ravb_read(ndev, GIC); - if (gis & GIS_PTCF) { + if ((gis & GIS_PTCF) && clock) { struct ptp_clock_event event; event.type = PTP_CLOCK_EXTTS; event.index = 0; event.timestamp = ravb_read(ndev, GCPT); - ptp_clock_event(priv->ptp.clock, &event); + ptp_clock_event(clock, &event); } if (gis & GIS_PTMF) { struct ravb_ptp_perout *perout = priv->ptp.perout; @@ -334,19 +335,39 @@ void ravb_ptp_init(struct net_device *ndev, struct platform_device *pdev) clock = NULL; } - priv->ptp.clock = clock; + WRITE_ONCE(priv->ptp.clock, clock); if (clock) WRITE_ONCE(priv->ptp.phc_index, ptp_clock_index(clock)); } +static void ravb_ptp_disable(struct net_device *ndev) +{ + ravb_write(ndev, 0, GIC); + ravb_write(ndev, 0, GIS); +} + +static void ravb_ptp_sync_irqs(struct net_device *ndev) +{ + struct ravb_private *priv = netdev_priv(ndev); + + synchronize_irq(ndev->irq); + if (priv->info->err_mgmt_irqs) { + synchronize_irq(priv->err_irq); + synchronize_irq(priv->mgmt_irq); + } +} + void ravb_ptp_stop(struct net_device *ndev) { struct ravb_private *priv = netdev_priv(ndev); - - ravb_write(ndev, 0, GIC); - ravb_write(ndev, 0, GIS); + struct ptp_clock *clock; WRITE_ONCE(priv->ptp.phc_index, -1); - if (priv->ptp.clock) - ptp_clock_unregister(priv->ptp.clock); + clock = xchg(&priv->ptp.clock, NULL); + + ravb_ptp_disable(ndev); + ravb_ptp_sync_irqs(ndev); + + if (clock) + ptp_clock_unregister(clock); } From 984f831dda31b3a18f47454cf64989f65402879e Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Wed, 12 Aug 2026 14:53:41 -0700 Subject: [PATCH 48/70] vxlan: vnifilter: enforce exact length of GROUP/GROUP6 attributes The VXLAN VNI filter entry policy declares the GROUP/GROUP6 address attributes as NLA_BINARY with only a maximum length, so validate_nla() accepts a payload shorter than the address. The GROUP consumer reads it with nla_get_in_addr(), an unconditional 4-byte load, so a short attribute over-reads up to 3 bytes of uninitialised slab data, which are stored into remote_ip and echoed back via RTM_GETTUNNEL, disclosing kernel memory. Switch both entries to NLA_POLICY_EXACT_LEN() so the validator rejects any GROUP/GROUP6 that is not exactly 4 / 16 bytes; a valid address is always sent at full width. Fixes: f9c4bb0b245c ("vxlan: vni filtering support on collect metadata device") Reported-by: Weiming Shi Signed-off-by: Xiang Mei Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260812215341.763123-1-xmei5@asu.edu Signed-off-by: Jakub Kicinski --- drivers/net/vxlan/vxlan_vnifilter.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/net/vxlan/vxlan_vnifilter.c b/drivers/net/vxlan/vxlan_vnifilter.c index 3e76f4e21094..dd94085e0886 100644 --- a/drivers/net/vxlan/vxlan_vnifilter.c +++ b/drivers/net/vxlan/vxlan_vnifilter.c @@ -462,10 +462,8 @@ static int vxlan_vnifilter_dump(struct sk_buff *skb, struct netlink_callback *cb static const struct nla_policy vni_filter_entry_policy[VXLAN_VNIFILTER_ENTRY_MAX + 1] = { [VXLAN_VNIFILTER_ENTRY_START] = { .type = NLA_U32 }, [VXLAN_VNIFILTER_ENTRY_END] = { .type = NLA_U32 }, - [VXLAN_VNIFILTER_ENTRY_GROUP] = { .type = NLA_BINARY, - .len = sizeof_field(struct iphdr, daddr) }, - [VXLAN_VNIFILTER_ENTRY_GROUP6] = { .type = NLA_BINARY, - .len = sizeof(struct in6_addr) }, + [VXLAN_VNIFILTER_ENTRY_GROUP] = NLA_POLICY_EXACT_LEN(sizeof_field(struct iphdr, daddr)), + [VXLAN_VNIFILTER_ENTRY_GROUP6] = NLA_POLICY_EXACT_LEN(sizeof(struct in6_addr)), }; static const struct nla_policy vni_filter_policy[VXLAN_VNIFILTER_MAX + 1] = { From 7b196e27ad58e612ad1c04b347d0c2135045aa14 Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Thu, 13 Aug 2026 23:31:31 +0800 Subject: [PATCH 49/70] net: dsa: mv88e6xxx: Fix PCS link check on CMODE read error mv88e6352_pcs_link_check() ignores errors returned by port_get_cmode(). If the port status register read fails, mv88e6352_port_get_cmode() returns without setting cmode. The link check then compares an uninitialized value and may incorrectly treat the PCS as active. Save the return value and fail the link check after releasing the register lock. marvell_c22_pcs_get_state() initializes the reported link state to down before calling the check, so a read failure is handled safely until a later poll succeeds. This issue was found by a static analysis checker and confirmed by manual source review. Fixes: 85764555442f ("net: dsa: mv88e6xxx: convert 88e6352 to phylink_pcs") Signed-off-by: Ruoyu Wang Reviewed-by: Vladimir Oltean Link: https://patch.msgid.link/20260813153131.3952970-1-ruoyuw560@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/mv88e6xxx/pcs-6352.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/dsa/mv88e6xxx/pcs-6352.c b/drivers/net/dsa/mv88e6xxx/pcs-6352.c index 4228ae5bb9db..437054711a2d 100644 --- a/drivers/net/dsa/mv88e6xxx/pcs-6352.c +++ b/drivers/net/dsa/mv88e6xxx/pcs-6352.c @@ -305,13 +305,16 @@ static bool mv88e6352_pcs_link_check(struct marvell_c22_pcs *mpcs) struct mv88e6xxx_port *port = mpcs->port; struct mv88e6xxx_chip *chip = port->chip; u8 cmode; + int err; /* Port 4 can be in auto-media mode. Check that the port is * associated with the mpcs. */ mv88e6xxx_reg_lock(chip); - chip->info->ops->port_get_cmode(chip, port->port, &cmode); + err = chip->info->ops->port_get_cmode(chip, port->port, &cmode); mv88e6xxx_reg_unlock(chip); + if (err) + return false; return cmode == MV88E6XXX_PORT_STS_CMODE_100BASEX || cmode == MV88E6XXX_PORT_STS_CMODE_1000BASEX || From 9466ef3ec972bee926731a766f73533dec590065 Mon Sep 17 00:00:00 2001 From: Maximilian Immanuel Brandtner Date: Thu, 13 Aug 2026 14:09:44 +0200 Subject: [PATCH 50/70] tls: fix RX desync on overlapping skbs The TCP receive queue can hold adjacent skbs whose sequence ranges overlap. The tls fast-path reads the record header with skb_copy_bits() by byte offset, which assumes skbs do not overlap, so a header split across the overlap is misread and the connection aborts (-EMSGSIZE/-EINVAL). tls_strp_check_queue_ok() detects such overlaps but only ran after the header was parsed, never covering the header itself. Observed with parallel kTLS connections on: - ConnectX-7 + IPsec crypto offload + GRO - VirtIO (8 queues) + GRO Fixes: 84c61fe1a75b ("tls: rx: do not use the standard strparser") Signed-off-by: Maximilian Immanuel Brandtner Link: https://patch.msgid.link/20260813121337.3300688-1-maxbr@linux.ibm.com Signed-off-by: Jakub Kicinski --- net/tls/tls_strp.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/net/tls/tls_strp.c b/net/tls/tls_strp.c index 61b10c697ecc..6cc222008d95 100644 --- a/net/tls/tls_strp.c +++ b/net/tls/tls_strp.c @@ -430,9 +430,10 @@ static int tls_strp_read_copy(struct tls_strparser *strp, bool qshort) return 0; } -static bool tls_strp_check_queue_ok(struct tls_strparser *strp) +static bool tls_strp_check_queue_ok(struct tls_strparser *strp, + unsigned int len) { - unsigned int len = strp->stm.offset + strp->stm.full_len; + unsigned int remaining = strp->stm.offset + len; struct sk_buff *first, *skb; u32 seq; @@ -443,9 +444,9 @@ static bool tls_strp_check_queue_ok(struct tls_strparser *strp) /* Make sure there's no duplicate data in the queue, * and the decrypted status matches. */ - while (skb->len < len) { + while (skb->len < remaining) { seq += skb->len; - len -= skb->len; + remaining -= skb->len; skb = skb->next; if (TCP_SKB_CB(skb)->seq != seq) @@ -525,6 +526,11 @@ static int tls_strp_read_sock(struct tls_strparser *strp) tls_strp_load_anchor_with_queue(strp, inq); if (!strp->stm.full_len) { + if (inq < TLS_HEADER_SIZE) + return tls_strp_read_copy(strp, true); + if (!tls_strp_check_queue_ok(strp, TLS_HEADER_SIZE)) + return tls_strp_read_copy(strp, false); + sz = tls_rx_msg_size(strp, strp->anchor); if (sz < 0) return sz; @@ -535,7 +541,7 @@ static int tls_strp_read_sock(struct tls_strparser *strp) return tls_strp_read_copy(strp, true); } - if (!tls_strp_check_queue_ok(strp)) + if (!tls_strp_check_queue_ok(strp, strp->stm.full_len)) return tls_strp_read_copy(strp, false); WRITE_ONCE(strp->msg_ready, 1); From fb58b6a696b30bcbfbe0cfc0a91b19c816a955fc Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Fri, 14 Aug 2026 13:01:02 +0200 Subject: [PATCH 51/70] net: dsa: realtek: use gpiod_set_value_cansleep for reset GPIO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rtl83xx_reset_assert() and rtl83xx_reset_deassert() are only called from the probe path, which may sleep and is not timing-critical. When the reset GPIO is provided by a sleeping controller such as an I2C I/O expander, gpiod_set_value() warns: WARNING: drivers/gpio/gpiolib.c:4030 at gpiod_set_value+0x44/0x80, CPU#1: kworker/u16:4/61 Hardware name: B&O MAP CA33 Rev f (UNKNOWN) (DT) Workqueue: events_unbound deferred_probe_work_func pc : gpiod_set_value+0x44/0x80 lr : rtl83xx_probe+0x1d8/0x3a0 Call trace: gpiod_set_value+0x44/0x80 (P) rtl83xx_probe+0x1d8/0x3a0 realtek_mdio_probe+0x24/0xa0 mdio_probe+0x38/0x78 really_probe+0xc4/0x3e0 __driver_probe_device+0x15c/0x1b8 driver_probe_device+0xb4/0x120 __device_attach_driver+0xb8/0x1a0 bus_for_each_drv+0x88/0xf0 __device_attach+0xa0/0x1d8 device_initial_probe+0x54/0x68 bus_probe_device+0x38/0xa0 deferred_probe_work_func+0xb8/0x120 process_one_work+0x184/0x4e8 worker_thread+0x188/0x308 kthread+0x130/0x150 ret_from_fork+0x10/0x20 Switch both helpers to gpiod_set_value_cansleep() so such a reset GPIO can be used without triggering the warning. The reset GPIO has been driven with the non-sleeping gpiod_set_value() since the driver was added in v4.19. The call has since been refactored across several files - from realtek-smi.c / realtek-mdio.c into the common rtl83xx.c module and then into the rtl83xx_reset_assert() and rtl83xx_reset_deassert() helpers (both in v6.9). This patch therefore applies as-is only to kernels that carry those helpers (v6.9+); older stable kernels need the same gpiod_set_value_cansleep() conversion at the corresponding open-coded call sites. Fixes: d8652956cf37 ("net: dsa: realtek-smi: Add Realtek SMI driver") Cc: # 6.9.x Signed-off-by: Ahmad Fatoum Co-developed-by: Oleksij Rempel Signed-off-by: Oleksij Rempel Reviewed-by: Alvin Šipraga Reviewed-by: Linus Walleij Reviewed-by: Luiz Angelo Daros de Luca Link: https://patch.msgid.link/20260814110102.2362246-1-o.rempel@pengutronix.de Signed-off-by: Jakub Kicinski --- drivers/net/dsa/realtek/rtl83xx.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/dsa/realtek/rtl83xx.c b/drivers/net/dsa/realtek/rtl83xx.c index 9dd50b20c000..d2e92bab55d2 100644 --- a/drivers/net/dsa/realtek/rtl83xx.c +++ b/drivers/net/dsa/realtek/rtl83xx.c @@ -321,7 +321,7 @@ void rtl83xx_reset_assert(struct realtek_priv *priv) "Failed to assert the switch reset control: %pe\n", ERR_PTR(ret)); - gpiod_set_value(priv->reset, true); + gpiod_set_value_cansleep(priv->reset, true); } void rtl83xx_reset_deassert(struct realtek_priv *priv) @@ -334,7 +334,7 @@ void rtl83xx_reset_deassert(struct realtek_priv *priv) "Failed to deassert the switch reset control: %pe\n", ERR_PTR(ret)); - gpiod_set_value(priv->reset, false); + gpiod_set_value_cansleep(priv->reset, false); } /** From 21040c7f931502070dcc66bb0f1aeed07dec032b Mon Sep 17 00:00:00 2001 From: Nikolay Aleksandrov Date: Fri, 14 Aug 2026 17:16:40 +0300 Subject: [PATCH 52/70] net: bridge: vlan: fix inverted default vlan notification A notification should be emitted only when the vlan delete was successful and not otherwise. The proper check is if br/nbp_vlan_delete returned 0. Fixes: f545923b4a6b ("net: bridge: vlan: notify on vlan add/delete/change flags") Signed-off-by: Nikolay Aleksandrov Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260814141640.64958-1-razor@blackwall.org Signed-off-by: Jakub Kicinski --- net/bridge/br_vlan.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/bridge/br_vlan.c b/net/bridge/br_vlan.c index 31c1b2cf75d9..1e0e436629ec 100644 --- a/net/bridge/br_vlan.c +++ b/net/bridge/br_vlan.c @@ -1136,7 +1136,7 @@ int __br_vlan_set_default_pvid(struct net_bridge *br, u16 pvid, if (err) goto out; - if (br_vlan_delete(br, old_pvid)) + if (!br_vlan_delete(br, old_pvid)) br_vlan_notify(br, NULL, old_pvid, 0, RTM_DELVLAN); br_vlan_notify(br, NULL, pvid, 0, RTM_NEWVLAN); __set_bit(0, changed); @@ -1158,7 +1158,7 @@ int __br_vlan_set_default_pvid(struct net_bridge *br, u16 pvid, &vlchange, extack); if (err) goto err_port; - if (nbp_vlan_delete(p, old_pvid)) + if (!nbp_vlan_delete(p, old_pvid)) br_vlan_notify(br, p, old_pvid, 0, RTM_DELVLAN); br_vlan_notify(p->br, p, pvid, 0, RTM_NEWVLAN); __set_bit(p->port_no, changed); From 29e63b8d9fc150cc191b1c6eb7e16e1247e1b650 Mon Sep 17 00:00:00 2001 From: Qing Ming Date: Fri, 14 Aug 2026 17:54:04 +0800 Subject: [PATCH 53/70] mpls: reload header after pskb_may_pull() mpls_select_multipath() calls mpls_multipath_hash() to choose a nexthop when an MPLS route has multiple nexthops. While walking the MPLS label stack, the hash routine caches hdr for the current label. After finding the bottom-of-stack label, it calls pskb_may_pull() before reading the inner IP header. If an skb is constructed with the inner IP header in nonlinear data and insufficient tailroom in the linear head, pskb_may_pull() calls pskb_expand_head() to replace the skb head and free the old one. This leaves hdr pointing to freed memory. The IPv6 path can invalidate hdr again when it performs a second pull for the larger header. The issue was found through static analysis. A reproducer sending a legal Geneve packet through a bareudp/MPLS multipath setup triggered the same KASAN report in 2 of 2 unpatched runs: BUG: KASAN: slab-use-after-free in mpls_select_multipath Read of size 1 at addr ffff88800ecc6e20 by task ksoftirqd/1/23 Call Trace: mpls_select_multipath mpls_forward __netif_receive_skb_list_core netif_receive_skb_list_internal napi_complete_done gro_cell_poll __napi_poll net_rx_action Freed by task 23: kfree pskb_expand_head __pskb_pull_tail mpls_select_multipath Reload hdr from the current skb head after each successful pull before deriving the inner IPv4 or IPv6 header pointer. Fixes: 9f427a0e474a ("net: mpls: Fix multipath selection for LSR use case") Cc: stable@vger.kernel.org Signed-off-by: Qing Ming Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260814095404.7205-1-a0yami@mailbox.org Signed-off-by: Paolo Abeni --- net/mpls/af_mpls.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/mpls/af_mpls.c b/net/mpls/af_mpls.c index 961be5054a03..17b78dcbf8ab 100644 --- a/net/mpls/af_mpls.c +++ b/net/mpls/af_mpls.c @@ -221,6 +221,7 @@ static u32 mpls_multipath_hash(struct mpls_route *rt, struct sk_buff *skb) if (pskb_may_pull(skb, mpls_hdr_len + sizeof(struct iphdr))) { const struct iphdr *v4hdr; + hdr = mpls_hdr(skb) + label_index; v4hdr = (const struct iphdr *)(hdr + 1); if (v4hdr->version == 4) { hash = jhash_3words(ntohl(v4hdr->saddr), @@ -231,6 +232,7 @@ static u32 mpls_multipath_hash(struct mpls_route *rt, struct sk_buff *skb) sizeof(struct ipv6hdr))) { const struct ipv6hdr *v6hdr; + hdr = mpls_hdr(skb) + label_index; v6hdr = (const struct ipv6hdr *)(hdr + 1); hash = __ipv6_addr_jhash(&v6hdr->saddr, hash); hash = __ipv6_addr_jhash(&v6hdr->daddr, hash); From bc2dc66a6693a78f8c1e6ca2dbebd50f16e2c366 Mon Sep 17 00:00:00 2001 From: Baul Lee Date: Sat, 15 Aug 2026 00:35:47 +0900 Subject: [PATCH 54/70] vxlan: mdb: Fix use-after-free in vxlan_mdb_flush() vxlan_mdb_flush() iterates over the MDB entries using hlist_for_each_entry_safe(), which only tolerates the removal of the current entry. Contrary to the comment above the loop, the removal of an entry can trigger the removal of another entry. Flushing the remotes of a (*, G) entry also removes the (S, G) entries that were created for its source list, once they are left without remotes: vxlan_mdb_remotes_flush() -> vxlan_mdb_remote_del() -> vxlan_mdb_remote_srcs_del() -> vxlan_mdb_remote_src_del() -> vxlan_mdb_remote_src_fwd_del() -> __vxlan_mdb_del() -> vxlan_mdb_entry_put() Such an entry can be located after the (*, G) entry in the list, as vxlan_mdb_entry_get() returns an existing entry without moving it to the head of the list. This order is obtained by adding the (S, G) entry before the (*, G) entry, the latter with NLM_F_REPLACE, as the addition of the source otherwise fails with -EEXIST. The (S, G) entry is then the entry saved by hlist_for_each_entry_safe() and it is freed while the (*, G) entry is processed. The next iteration calls hlist_del() on it again, writing LIST_POISON1 to LIST_POISON2 [1]. Besides device deletion, the flush is also reachable from RTM_DELMDB with NLM_F_BULK. Fix by re-reading the next entry after the remotes were flushed. The current entry cannot be removed by this flush, as source lists can only be configured on (*, G) entries and the removed entries are (S, G) entries. It is therefore still linked and its next pointer reflects the removals. [1] BUG: KASAN: wild-memory-access in vxlan_mdb_entry_put.part.0+0x328/0x588 Write of size 8 at addr dead000000000122 by task ip/327 CPU: 3 UID: 1000 PID: 327 Comm: ip Not tainted 7.2.0-rc7 #2 PREEMPT Call trace: vxlan_mdb_entry_put.part.0+0x328/0x588 vxlan_mdb_flush+0x1d8/0x25c vxlan_mdb_fini+0x8c/0x100 vxlan_uninit+0x1c/0x7c unregister_netdevice_many_notify+0x954/0xd4c rtnl_dellink+0x210/0x530 rtnetlink_rcv_msg+0x434/0x4d0 netlink_rcv_skb+0xc4/0x204 rtnetlink_rcv+0x18/0x24 netlink_unicast+0x4b8/0x548 netlink_sendmsg+0x29c/0x560 ____sys_sendmsg+0x390/0x3ec ___sys_sendmsg+0x114/0x188 __sys_sendmsg+0xf0/0x178 __arm64_sys_sendmsg+0x48/0x60 invoke_syscall.constprop.0+0x58/0x180 el0_svc_common.constprop.0+0x74/0x140 do_el0_svc+0x30/0x40 el0_svc+0x38/0x98 el0t_64_sync_handler+0xa0/0xe4 el0t_64_sync+0x198/0x19c Fixes: a3a48de5eade ("vxlan: mdb: Add MDB control path support") Signed-off-by: Baul Lee Reviewed-by: Nikolay Aleksandrov Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260814153547.29567-1-baul.lee@xbow.com Signed-off-by: Paolo Abeni --- drivers/net/vxlan/vxlan_mdb.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/net/vxlan/vxlan_mdb.c b/drivers/net/vxlan/vxlan_mdb.c index 9a9038ae90c1..d71e1925ecfd 100644 --- a/drivers/net/vxlan/vxlan_mdb.c +++ b/drivers/net/vxlan/vxlan_mdb.c @@ -1428,14 +1428,17 @@ static void vxlan_mdb_flush(struct vxlan_dev *vxlan, struct vxlan_mdb_entry *mdb_entry; struct hlist_node *tmp; - /* The removal of an entry cannot trigger the removal of another entry - * since entries are always added to the head of the list. - */ hlist_for_each_entry_safe(mdb_entry, tmp, &vxlan->mdb_list, mdb_node) { if (desc->src_vni && desc->src_vni != mdb_entry->key.vni) continue; vxlan_mdb_remotes_flush(vxlan, mdb_entry, desc); + /* The flush can remove the (S, G) entries created for the + * source list of this entry, including the one saved by + * hlist_for_each_entry_safe(), so re-read it while this entry + * is still linked. + */ + tmp = mdb_entry->mdb_node.next; /* Entry will only be removed if its remotes list is empty. */ vxlan_mdb_entry_put(vxlan, mdb_entry); } From cb7643b78d35392f0f434774d78b4c77b80f677f Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Mon, 17 Aug 2026 06:30:57 +0200 Subject: [PATCH 55/70] 8139cp: fix Rx and Tx not being disabled in cp_suspend On QEMU rtl8139 model, frames that arrive while the interface is suspended still end up in the stack after resume. With pm_test=devices, which keeps devices suspended for 5s, 200 frames sent to interface during that time and 50 frames after resume, eth0 reports 113 received frames. cp_suspend() is supposed to stop receiver and the transmitter, but the mask is wrong: (~RxOn | ~TxOn) is ~0, nothing is cleared and Cmd still reads 0x0d when cp_suspend() returns. Use ~(RxOn | TxOn) so both bits are actually cleared. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Karl Mehltretter Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260817043057.20099-1-kmehltretter@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/realtek/8139cp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/realtek/8139cp.c b/drivers/net/ethernet/realtek/8139cp.c index 5652da8a178c..9016527e229a 100644 --- a/drivers/net/ethernet/realtek/8139cp.c +++ b/drivers/net/ethernet/realtek/8139cp.c @@ -2066,7 +2066,7 @@ static int __maybe_unused cp_suspend(struct device *device) /* Disable Rx and Tx */ cpw16 (IntrMask, 0); - cpw8 (Cmd, cpr8 (Cmd) & (~RxOn | ~TxOn)); + cpw8 (Cmd, cpr8 (Cmd) & ~(RxOn | TxOn)); spin_unlock_irqrestore (&cp->lock, flags); From 8acf691d8017012e1476c30e7381513c1e929c94 Mon Sep 17 00:00:00 2001 From: Mahanta Jambigi Date: Thu, 13 Aug 2026 09:43:15 +0200 Subject: [PATCH 56/70] net/smc: hash socket only after full initialisation in smc_sk_init() smc_sk_init() calls sk->sk_prot->hash(sk) before several fields are fully initialised: clcsock_release_lock, the saved clcsk_* callbacks, use_fallback/fallback_rsn, and conn.close_work. Once hash() returns the socket is visible to concurrent hash walkers, which can then observe uninitialised state. Move hash(sk) to the end of smc_sk_init() so the socket is published only after it is fully constructed. Fixes: d0e35656d834 ("net/smc: refactoring initialization of smc sock") Reviewed-by: Hidayath Khan Reviewed-by: Sidraya Jayagond Signed-off-by: Mahanta Jambigi Link: https://patch.msgid.link/20260813074315.554926-1-mjambigi@linux.ibm.com Signed-off-by: Paolo Abeni --- net/smc/af_smc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/smc/af_smc.c b/net/smc/af_smc.c index cff910cedbfc..e9f93b3ab435 100644 --- a/net/smc/af_smc.c +++ b/net/smc/af_smc.c @@ -409,13 +409,13 @@ void smc_sk_init(struct net *net, struct sock *sk, int protocol) "sk_lock-AF_SMC", &smc_key); spin_lock_init(&smc->accept_q_lock); spin_lock_init(&smc->conn.send_lock); - sk->sk_prot->hash(sk); mutex_init(&smc->clcsock_release_lock); smc_init_saved_callbacks(smc); smc->limit_smc_hs = net->smc.limit_smc_hs; smc->use_fallback = false; /* assume rdma capability first */ smc->fallback_rsn = 0; smc_close_init(smc); + sk->sk_prot->hash(sk); } static struct sock *smc_sock_alloc(struct net *net, struct socket *sock, From 505b6d296c486ef7d1274f279d4c43a172f63224 Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Thu, 13 Aug 2026 00:22:34 +0800 Subject: [PATCH 57/70] ip6_gre: fix hardware header length for NBMA tunnels ip6gre_tnl_link_config_route() accumulates the lower device's hardware header length into dev->hard_header_len whenever header_ops is set. This is incorrect for both users of header_ops. ip6gretap and ip6erspan have a fixed Ethernet hardware header length. For an NBMA ip6gre tunnel, ip6gre_header() creates only the GRE header, the optional FOU or GUE header, and the outer IPv6 header. The lower device header is headroom needed later, not part of the tunnel device's hardware header. Keep the lower device header in needed_headroom. Set hard_header_len to the tunnel header length only for ARPHRD_IP6GRE devices with header_ops, and leave the fixed Ethernet header length unchanged for tap and erspan devices. Fixes: 832ba596494b ("net: ip6_gre: set dev->hard_header_len when using header_ops") Cc: stable@vger.kernel.org Suggested-by: Ido Schimmel Signed-off-by: Zhiling Zou Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/64b46542bbe1701f07702aaa50273e2a87903db5.1786542637.git.zhilinz@nebusec.ai Signed-off-by: Paolo Abeni --- net/ipv6/ip6_gre.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c index b843116e9b70..70c171091020 100644 --- a/net/ipv6/ip6_gre.c +++ b/net/ipv6/ip6_gre.c @@ -1137,13 +1137,8 @@ static void ip6gre_tnl_link_config_route(struct ip6_tnl *t, int set_mtu, return; if (rt->dst.dev) { - unsigned short dst_len = rt->dst.dev->hard_header_len + - t_hlen; - - if (t->dev->header_ops) - dev->hard_header_len = dst_len; - else - dev->needed_headroom = dst_len; + dev->needed_headroom = rt->dst.dev->hard_header_len + + t_hlen; if (set_mtu) { int mtu = rt->dst.dev->mtu - t_hlen; @@ -1171,8 +1166,8 @@ static int ip6gre_calc_hlen(struct ip6_tnl *tunnel) t_hlen = tunnel->hlen + sizeof(struct ipv6hdr); - if (tunnel->dev->header_ops) - tunnel->dev->hard_header_len = LL_MAX_HEADER + t_hlen; + if (tunnel->dev->header_ops && tunnel->dev->type == ARPHRD_IP6GRE) + tunnel->dev->hard_header_len = t_hlen; else tunnel->dev->needed_headroom = LL_MAX_HEADER + t_hlen; From 6b222adeb9340306e2ff97127c76117abb9b3df8 Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Thu, 13 Aug 2026 00:22:35 +0800 Subject: [PATCH 58/70] net: cap advertised IP tunnel headroom IP tunnel devices derive their advertised needed_headroom from lower output devices. A stack of user-created devices can make the derived value larger than the 16-bit skb header offsets can represent. Once IP output reserves it, skb head expansion can wrap those offsets. The runtime transmit path already caps a growing needed_headroom at 512. Apply the same cap when tunnel configuration publishes needed_headroom derived from a lower output device. Capping the advertised value is safe: IP tunnel transmit still expands the skb when a packet needs more headroom. A nonsensical stacked configuration can therefore incur an extra reallocation, but it cannot publish an unbounded reservation to upper layers. Fixes: 1a37e412a022 ("net: Use 16bits for *_headers fields of struct skbuff") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zhiling Zou Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/ba04a1fd6bfae2377607fad5d8f80f7eb80fd4c4.1786542637.git.zhilinz@nebusec.ai Signed-off-by: Paolo Abeni --- include/net/ip_tunnels.h | 11 +++++++++-- net/ipv4/ip_tunnel.c | 2 +- net/ipv6/ip6_gre.c | 7 +++++-- net/ipv6/ip6_tunnel.c | 7 +++++-- net/ipv6/sit.c | 2 +- 5 files changed, 21 insertions(+), 8 deletions(-) diff --git a/include/net/ip_tunnels.h b/include/net/ip_tunnels.h index d708b66e55cd..85e3455cea25 100644 --- a/include/net/ip_tunnels.h +++ b/include/net/ip_tunnels.h @@ -629,8 +629,7 @@ struct metadata_dst *iptunnel_metadata_reply(struct metadata_dst *md, int skb_tunnel_check_pmtu(struct sk_buff *skb, struct dst_entry *encap_dst, int headroom, bool reply); -static inline void ip_tunnel_adj_headroom(struct net_device *dev, - unsigned int headroom) +static inline unsigned int ip_tunnel_limit_headroom(unsigned int headroom) { /* we must cap headroom to some upperlimit, else pskb_expand_head * will overflow header offsets in skb_headers_offset_update(). @@ -640,6 +639,14 @@ static inline void ip_tunnel_adj_headroom(struct net_device *dev, if (headroom > max_allowed) headroom = max_allowed; + return headroom; +} + +static inline void ip_tunnel_adj_headroom(struct net_device *dev, + unsigned int headroom) +{ + headroom = ip_tunnel_limit_headroom(headroom); + if (headroom > READ_ONCE(dev->needed_headroom)) WRITE_ONCE(dev->needed_headroom, headroom); } diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c index 9d114bd575f9..5b1f180485d4 100644 --- a/net/ipv4/ip_tunnel.c +++ b/net/ipv4/ip_tunnel.c @@ -317,7 +317,7 @@ static int ip_tunnel_bind_dev(struct net_device *dev) mtu = min(tdev->mtu, IP_MAX_MTU); } - dev->needed_headroom = t_hlen + hlen; + dev->needed_headroom = ip_tunnel_limit_headroom(t_hlen + hlen); mtu -= t_hlen + (dev->type == ARPHRD_ETHER ? dev->hard_header_len : 0); if (mtu < IPV4_MIN_MTU) diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c index 70c171091020..200d0ba1a40e 100644 --- a/net/ipv6/ip6_gre.c +++ b/net/ipv6/ip6_gre.c @@ -1137,8 +1137,11 @@ static void ip6gre_tnl_link_config_route(struct ip6_tnl *t, int set_mtu, return; if (rt->dst.dev) { - dev->needed_headroom = rt->dst.dev->hard_header_len + - t_hlen; + unsigned int headroom; + + headroom = rt->dst.dev->hard_header_len + t_hlen; + headroom = ip_tunnel_limit_headroom(headroom); + dev->needed_headroom = headroom; if (set_mtu) { int mtu = rt->dst.dev->mtu - t_hlen; diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c index 6a1b901ecc9b..cc96bb8b706e 100644 --- a/net/ipv6/ip6_tunnel.c +++ b/net/ipv6/ip6_tunnel.c @@ -1514,8 +1514,11 @@ static void ip6_tnl_link_config(struct ip6_tnl *t) tdev = __dev_get_by_index(t->net, p->link); if (tdev) { - dev->needed_headroom = tdev->hard_header_len + - tdev->needed_headroom + t_hlen; + unsigned int headroom; + + headroom = tdev->hard_header_len + tdev->needed_headroom; + headroom += t_hlen; + dev->needed_headroom = ip_tunnel_limit_headroom(headroom); mtu = min_t(unsigned int, tdev->mtu, IP6_MAX_MTU); mtu = mtu - t_hlen; diff --git a/net/ipv6/sit.c b/net/ipv6/sit.c index a38b24fb8384..19b7fa8d1a2a 100644 --- a/net/ipv6/sit.c +++ b/net/ipv6/sit.c @@ -1131,7 +1131,7 @@ static void ipip6_tunnel_bind_dev(struct net_device *dev) WRITE_ONCE(dev->mtu, mtu); hlen = tdev->hard_header_len + tdev->needed_headroom; } - dev->needed_headroom = t_hlen + hlen; + dev->needed_headroom = ip_tunnel_limit_headroom(t_hlen + hlen); } static void ipip6_tunnel_update(struct ip_tunnel *t, From e36ce6e78fe3fc3c071a26750783b7ba081ce10d Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Thu, 13 Aug 2026 00:36:38 +0800 Subject: [PATCH 59/70] ip: orphan prefetched skbs before multicast forwarding IPv4 and IPv6 input preserve an skb->sk association installed by bpf_sk_assign() so that local delivery can use the selected socket under RCU. Both address families can also prefetch a socket in UDP early demux. In both paths (BPF and UDP early demux) a reference is not guaranteed to be held on the socket. When a multicast packet is not locally deliverable, IPv6 hands the original skb to ip6_mr_input(). IPv4's ip_mr_input() similarly keeps the original skb when local delivery is not needed. Either path can put the skb on an unresolved multicast route queue or forward it after the receive-side RCU section ends. After the prefetched socket is destroyed, a later skb free invokes sock_pfree() and dereferences the stale skb->sk. Orphan the skb before each non-local multicast forwarding path. Local delivery retains the original skb; the existing skb_clone() calls provide multicast forwarding with a socket-free clone. Fixes: cf7fbe660f2d ("bpf: Add socket assign support") Fixes: 08842c43d016 ("udp: no longer touch sk->sk_refcnt in early demux") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zhiling Zou Reported-by: Vega Signed-off-by: Zhiling Zou Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/0c52eb3d7532aaf8bccf37e0f7c922143c639735.1786552223.git.zhilinz@nebusec.ai Signed-off-by: Paolo Abeni --- net/ipv4/ipmr.c | 3 +++ net/ipv6/ip6_input.c | 1 + 2 files changed, 4 insertions(+) diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c index 1d9a4ac14fce..e5f2b1c6150d 100644 --- a/net/ipv4/ipmr.c +++ b/net/ipv4/ipmr.c @@ -2213,6 +2213,9 @@ int ip_mr_input(struct sk_buff *skb) if (IPCB(skb)->flags & IPSKB_FORWARDED) goto dont_forward; + if (!local) + skb_orphan(skb); + mrt = ipmr_rt_fib_lookup(net, skb); if (IS_ERR(mrt)) { kfree_skb(skb); diff --git a/net/ipv6/ip6_input.c b/net/ipv6/ip6_input.c index 8972863c93ee..d332ec60f915 100644 --- a/net/ipv6/ip6_input.c +++ b/net/ipv6/ip6_input.c @@ -622,6 +622,7 @@ int ip6_mc_input(struct sk_buff *skb) if (deliver) { skb2 = skb_clone(skb, GFP_ATOMIC); } else { + skb_orphan(skb); skb2 = skb; skb = NULL; } From b8c899cf5e7be29840a172c183dedd8d3e7a0287 Mon Sep 17 00:00:00 2001 From: Nguyen Dinh Phi Date: Fri, 14 Aug 2026 01:30:18 +0800 Subject: [PATCH 60/70] vsock: don't check the listener's sk_err in vsock_accept() Syzbot reported an issue which can be reproduced with these steps: r0 = socket(AF_VSOCK, SOCK_STREAM, 0) bind(r0, {VMADDR_CID_ANY, PORT}) connect(r0, {VMADDR_CID_LOCAL, PORT}) -> -1, EPROTO (self-connect) listen(r0, backlog) -> 0 r1 = socket(AF_VSOCK, SOCK_STREAM, 0) connect(r1, {VMADDR_CID_LOCAL, PORT}) -> 0 accept(r0) -> -1, EPROTO (stale sk_err) Basically, it creates a socket (r0) and triggers a self-connect after binding it. This self-connect fails with EPROTO because it loops back to r0 while the socket is still in the TCP_SYN_SENT state, causing it to be incorrectly dispatched to the connecting-client path. The unexpected packet type encountered there sets sk_err to EPROTO. After that, it invokes a listen() call on the same socket. This listen() call succeeds because the kernel's listening path never inspects or clears sk_err. Then, a new socket (r1) is created as a normal client and connects to r0. However, vsock_accept() rejects this incoming connection because the listener's sk_err still holds the EPROTO error from the earlier failed self-connect. This rejection causes the child socket created for r1's connection to never be freed on virtio or hyperv transports; only the VMCI transport implements pending_work to revisit and clean up a rejected socket. For a non-blocking connect(), vsock_connect() may return -EINPROGRESS immediately, and vsock_connect_timeout() can later set sk->sk_err asynchronously. Since no vsock transport ever sets sk_err on a socket while it is in TCP_LISTEN state, checking it in vsock_accept() serves no purpose and only carries forward errors left behind by earlier, unrelated connection attempts on the same socket. Remove the checks so accept() no longer rejects valid incoming connections because of a stale error, which also avoids the resource leak described above. Fixes: d021c344051a ("VSOCK: Introduce VM Sockets") Reported-by: syzbot+1b2c9c4a0f8708082678@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=1b2c9c4a0f8708082678 Suggested-by: Michal Luczaj Signed-off-by: Nguyen Dinh Phi Reviewed-by: Stefano Garzarella Link: https://patch.msgid.link/20260813173024.2362935-2-phind.uet@gmail.com Signed-off-by: Paolo Abeni --- net/vmw_vsock/af_vsock.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c index 622dbd046799..3cd5c3561be3 100644 --- a/net/vmw_vsock/af_vsock.c +++ b/net/vmw_vsock/af_vsock.c @@ -1893,7 +1893,7 @@ static int vsock_accept(struct socket *sock, struct socket *newsock, timeout = sock_rcvtimeo(listener, arg->flags & O_NONBLOCK); while ((connected = vsock_dequeue_accept(listener)) == NULL && - listener->sk_err == 0 && timeout != 0) { + timeout != 0) { prepare_to_wait(sk_sleep(listener), &wait, TASK_INTERRUPTIBLE); release_sock(listener); timeout = schedule_timeout(timeout); @@ -1906,13 +1906,9 @@ static int vsock_accept(struct socket *sock, struct socket *newsock, } } - if (listener->sk_err) { - err = -listener->sk_err; - } else if (!connected) { + if (!connected) { err = -EAGAIN; - } - - if (connected) { + } else { sk_acceptq_removed(listener); lock_sock_nested(connected, SINGLE_DEPTH_NESTING); From 81fc0f369637ed6ee615d089d3016ba88b2bab71 Mon Sep 17 00:00:00 2001 From: Nguyen Dinh Phi Date: Fri, 14 Aug 2026 01:30:19 +0800 Subject: [PATCH 61/70] vsock: remove the now-unused rejected flag After previous patch, the branch marking a socket rejected in vsock_accept() is unreachable, and nothing ever sets vsk->rejected elsewhere. In fact, since commit d021c344051a ("VSOCK: Introduce VM Sockets"), where `rejected` was introduced, there has never been a path that sets sk_err on a listening socket, so that branch has been dead code since the beginning. Therefore, we can remove the `rejected` field from vsock_sock structure. Suggested-by: Stefano Garzarella Signed-off-by: Nguyen Dinh Phi Reviewed-by: Stefano Garzarella Link: https://patch.msgid.link/20260813173024.2362935-3-phind.uet@gmail.com Signed-off-by: Paolo Abeni --- include/net/af_vsock.h | 5 +---- net/vmw_vsock/af_vsock.c | 46 +++++++++++++--------------------------- 2 files changed, 16 insertions(+), 35 deletions(-) diff --git a/include/net/af_vsock.h b/include/net/af_vsock.h index 30046a3c20f7..3357ee62d10b 100644 --- a/include/net/af_vsock.h +++ b/include/net/af_vsock.h @@ -52,13 +52,10 @@ struct vsock_sock { * The listening socket is the head for both lists. Sockets created * for connection requests are placed in the pending list until they * are connected, at which point they are put in the accept queue list - * so they can be accepted in accept(). If accept() cannot accept the - * connection, it is marked as rejected so the cleanup function knows - * to clean up the socket. + * so they can be accepted in accept(). */ struct list_head pending_links; struct list_head accept_queue; - bool rejected; struct delayed_work connect_work; struct delayed_work pending_work; struct delayed_work close_work; diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c index 3cd5c3561be3..62e22c4b13c0 100644 --- a/net/vmw_vsock/af_vsock.c +++ b/net/vmw_vsock/af_vsock.c @@ -38,10 +38,9 @@ * pending socket. When that socket reaches the connected state, it is removed * from the listener socket's pending list and enqueued in the listener * socket's accept queue. Callers of accept(2) will accept connected sockets - * from the listener socket's accept queue. If the socket cannot be accepted - * for some reason then it is marked rejected. Once the connection is - * accepted, it is owned by the user process and the responsibility for cleanup - * falls with that user process. + * from the listener socket's accept queue. Once the connection is accepted, + * it is owned by the user process and the responsibility for cleanup falls + * with that user process. * * - It is possible that these pending sockets will never reach the connected * state; in fact, we may never receive another packet after the connection @@ -49,9 +48,7 @@ * future, after some amount of time passes where a connection should have been * established. This function ensures that the socket is off all lists so it * cannot be retrieved, then drops all references to the socket so it is cleaned - * up (sock_put() -> sk_free() -> our sk_destruct implementation). Note this - * function will also cleanup rejected sockets, those that reach the connected - * state but leave it before they have been accepted. + * up (sock_put() -> sk_free() -> our sk_destruct implementation). * * - Lock ordering for pending or accept queue sockets is: * @@ -774,11 +771,10 @@ static void vsock_pending_work(struct work_struct *work) if (vsock_is_pending(sk)) { vsock_remove_pending(listener, sk); - } else if (!vsk->rejected) { - /* We are not on the pending list and accept() did not reject - * us, so we must have been accepted by our user process. We - * just need to drop our references to the sockets and be on - * our way. + } else { + /* We are not on the pending list so we must have been accepted + * by our user process. We just need to drop our references to + * the sockets and be on our way. */ cleanup = false; goto out; @@ -942,7 +938,6 @@ static struct sock *__vsock_create(struct net *net, vsk->listener = NULL; INIT_LIST_HEAD(&vsk->pending_links); INIT_LIST_HEAD(&vsk->accept_queue); - vsk->rejected = false; vsk->sent_request = false; vsk->ignore_connecting_rst = false; WRITE_ONCE(vsk->peer_shutdown, 0); @@ -1914,27 +1909,16 @@ static int vsock_accept(struct socket *sock, struct socket *newsock, lock_sock_nested(connected, SINGLE_DEPTH_NESTING); vconnected = vsock_sk(connected); - /* If the listener socket has received an error, then we should - * reject this socket and return. Note that we simply mark the - * socket rejected, drop our reference, and let the cleanup - * function handle the cleanup; the fact that we found it in - * the listener's accept queue guarantees that the cleanup - * function hasn't run yet. - */ - if (err) { - vconnected->rejected = true; - } else { - newsock->state = SS_CONNECTED; - sock_graft(connected, newsock); + newsock->state = SS_CONNECTED; + sock_graft(connected, newsock); - set_bit(SOCK_CUSTOM_SOCKOPT, + set_bit(SOCK_CUSTOM_SOCKOPT, + &connected->sk_socket->flags); + + if (vsock_msgzerocopy_allow(vconnected->transport)) + set_bit(SOCK_SUPPORT_ZC, &connected->sk_socket->flags); - if (vsock_msgzerocopy_allow(vconnected->transport)) - set_bit(SOCK_SUPPORT_ZC, - &connected->sk_socket->flags); - } - release_sock(connected); sock_put(connected); } From 96cbf89993091a163bfedec52a3bd683dc94b3b4 Mon Sep 17 00:00:00 2001 From: Nguyen Dinh Phi Date: Fri, 14 Aug 2026 01:30:20 +0800 Subject: [PATCH 62/70] vsock: use sock_error() to consume sk_err after a failed connect vsock_connect() returns sk_err to userspace but does not clear it: if (sk->sk_err) { err = -sk->sk_err; For a blocking connect() the error has already been delivered as connect()'s return value, so leaving it set causes subsequent operations like poll()/epoll() to keep reporting POLLERR even though the connect failure was already delivered. The error should be consumed once it has been returned to userspace. Switch to sock_error(), which reads and clears sk_err atomically, matching the behavior of other protocol implementations such as __inet_stream_connect(). Fixes: d021c344051a ("VSOCK: Introduce VM Sockets") Tested-by: Wupeng Ma Reviewed-by: Stefano Garzarella Signed-off-by: Nguyen Dinh Phi Link: https://patch.msgid.link/20260813173024.2362935-4-phind.uet@gmail.com Signed-off-by: Paolo Abeni --- net/vmw_vsock/af_vsock.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c index 62e22c4b13c0..e89cb84b8d73 100644 --- a/net/vmw_vsock/af_vsock.c +++ b/net/vmw_vsock/af_vsock.c @@ -1842,12 +1842,10 @@ static int vsock_connect(struct socket *sock, struct sockaddr_unsized *addr, prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE); } - if (sk->sk_err) { - err = -sk->sk_err; + err = sock_error(sk); + if (err) { sk->sk_state = TCP_CLOSE; sock->state = SS_UNCONNECTED; - } else { - err = 0; } out_wait: From 8ccc9bf9afeeb46a437081c07154fbf5964682b2 Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Thu, 13 Aug 2026 23:31:26 +0800 Subject: [PATCH 63/70] bonding: initialize err for empty target lists Empty NLA_NESTED attributes are valid, and bonding uses them to clear the ARP and NS target lists. When either target attribute is empty, nla_for_each_nested() does not execute, so err retains an uninitialized value before it is tested. The request can consequently return an unpredictable error after clearing the targets. Initialize err to zero so an empty target list completes successfully. Non-empty lists still propagate errors from __bond_opt_set() unchanged. This issue was found by a static analysis checker and confirmed by manual source review. Fixes: 4fb0ef585eb2 ("bonding: convert arp_ip_target to use the new option API") Signed-off-by: Ruoyu Wang Reviewed-by: Nikolay Aleksandrov Acked-by: Jay Vosburgh Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20260813153126.3952893-1-ruoyuw560@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/bonding/bond_netlink.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/bonding/bond_netlink.c b/drivers/net/bonding/bond_netlink.c index 4a11572f663d..87d92d3cce4a 100644 --- a/drivers/net/bonding/bond_netlink.c +++ b/drivers/net/bonding/bond_netlink.c @@ -220,7 +220,7 @@ static int bond_changelink(struct net_device *bond_dev, struct nlattr *tb[], struct bonding *bond = netdev_priv(bond_dev); struct bond_opt_value newval; int miimon = 0; - int err; + int err = 0; if (!data) return 0; From c0726f0caf8c6b3208552949e17d23634a2f3129 Mon Sep 17 00:00:00 2001 From: Yong Wang Date: Fri, 14 Aug 2026 01:35:26 +0800 Subject: [PATCH 64/70] ipv4: reject undersized MTUs in ip_do_fragment() ip_do_fragment() subtracts the IPv4 header length from the effective MTU and passes the resulting payload MTU to ip_frag_next(). If the effective MTU is smaller than hlen + 8, ip_frag_next() rounds the fragment payload length down to zero. The fragmentation state then never makes forward progress: state->left, state->ptr and state->offset stay unchanged while ip_do_fragment() keeps allocating and transmitting header-only fragments until the softlockup detector fires. This is reproducible with a route installed using "mtu lock 20", but it is also reproducible without route MTU lock, for example by forwarding a packet to a device whose MTU is 20. Fix it in ip_do_fragment() by rejecting mtu < hlen + 8 with -EMSGSIZE, matching the existing IPv6 fragmentation check. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Yong Wang Signed-off-by: Ren Wei Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/8809ef6314b98913681b0b370a05a85c2b6cd579.1786599079.git.edragain@163.com Signed-off-by: Jakub Kicinski --- net/ipv4/ip_output.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c index e6dd1e5b8c32..74e095b6b7ca 100644 --- a/net/ipv4/ip_output.c +++ b/net/ipv4/ip_output.c @@ -790,6 +790,10 @@ int ip_do_fragment(struct net *net, struct sock *sk, struct sk_buff *skb, */ hlen = iph->ihl * 4; + if (mtu < hlen + 8) { + err = -EMSGSIZE; + goto fail; + } mtu = mtu - hlen; /* Size of data space */ IPCB(skb)->flags |= IPSKB_FRAG_COMPLETE; ll_rs = LL_RESERVED_SPACE(rt->dst.dev); From a5edadbae57e2298a56cf7a4e774a027905a331f Mon Sep 17 00:00:00 2001 From: Abdifatah Suruur Date: Thu, 13 Aug 2026 20:47:07 +0300 Subject: [PATCH 65/70] ptp: vmclock: prevent read-only mappings from becoming writable vmclock_miscdev_mmap() rejects writable mappings of the shared vmclock ABI page with -EROFS, but leaves VM_MAYWRITE set. Userspace can map the page read-only and then upgrade it to writable with mprotect(), after which the guest can corrupt the host-written timekeeping data (sequence counter, UTC time, TSC offset) that the vmclock ABI defines as read-only. Clear VM_MAYWRITE on the read-only path so the mapping cannot be upgraded, as i915 does for its read-only objects and as fixed in drm/vc4 (CVE-2026-68445) and drm/panthor (CVE-2024-53071). Cc: stable@vger.kernel.org Fixes: 205032724226 ("ptp: Add support for the AMZNC10C 'vmclock' device") Signed-off-by: Abdifatah Suruur Link: https://patch.msgid.link/20260813174707.14809-1-suruurism@gmail.com Signed-off-by: Jakub Kicinski --- drivers/ptp/ptp_vmclock.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/ptp/ptp_vmclock.c b/drivers/ptp/ptp_vmclock.c index eebdcd5ebc08..bb0e14bac9f2 100644 --- a/drivers/ptp/ptp_vmclock.c +++ b/drivers/ptp/ptp_vmclock.c @@ -372,6 +372,12 @@ static int vmclock_miscdev_mmap(struct file *fp, struct vm_area_struct *vma) if ((vma->vm_flags & (VM_READ|VM_WRITE)) != VM_READ) return -EROFS; + /* + * Restrict the read-only mapping so it cannot be upgraded to + * writable later with mprotect(). + */ + vm_flags_clear(vma, VM_MAYWRITE); + if (vma->vm_end - vma->vm_start != PAGE_SIZE || vma->vm_pgoff) return -EINVAL; From 47e15a8d12e366d0d261bcbc394394f44418938d Mon Sep 17 00:00:00 2001 From: Hyunwoo Kim Date: Sat, 15 Aug 2026 07:36:18 +0900 Subject: [PATCH 66/70] sctp: stop processing a packet once its association is deleted sctp_endpoint_bh_rcv() looks the association up only when chunk->asoc is NULL, and caches the result in chunk->asoc and chunk->transport without taking a reference. A packet that matches no association is handed to the endpoint, so a peer can bundle COOKIE ECHO, SHUTDOWN and SHUTDOWN ACK in one packet. The COOKIE ECHO creates the association, the SHUTDOWN chunk caches it, and with the outqueue empty the SHUTDOWN ACK reaches sctp_sf_do_9_2_final(), so the association and its transports are freed. The endpoint loop has no counterpart to the asoc->base.dead check in sctp_assoc_bh_rcv(). The next chunk writes to last_time_heard in the freed transport and is then passed to sctp_do_sm() with the freed association. The transport is freed through RCU, so this needs the packet to come off the socket backlog, where the loop runs in task context. The endpoint loop cannot do the same check: it holds no reference on the association, so reading asoc->base.dead would itself be a use-after-free. Mark the packet for discard in the command interpreter, just before it deletes the association. That is also before sctp_inq_free() releases the chunk on the association receive path. sctp_sf_do_5_2_4_dupcook() issues SCTP_CMD_DELETE_TCB for the temporary association, while the one the packet belongs to stays alive. A restarting peer can bundle DATA behind its COOKIE ECHO, so compare against chunk->asoc and leave that case alone. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Hyunwoo Kim Acked-by: Xin Long Link: https://patch.msgid.link/an-YYtoqw1QpTXUL@v4bel Signed-off-by: Jakub Kicinski --- net/sctp/sm_sideeffect.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/sctp/sm_sideeffect.c b/net/sctp/sm_sideeffect.c index 424f10a6fdba..94716406d602 100644 --- a/net/sctp/sm_sideeffect.c +++ b/net/sctp/sm_sideeffect.c @@ -1332,6 +1332,10 @@ static int sctp_cmd_interpreter(enum sctp_event_type event_type, sctp_outq_uncork(&asoc->outqueue, gfp); local_cork = 0; } + /* No chunk left in this packet may use this asoc. */ + if (event_type == SCTP_EVENT_T_CHUNK && + chunk->asoc == asoc) + chunk->pdiscard = 1; /* Delete the current association. */ sctp_cmd_delete_tcb(commands, asoc); asoc = NULL; From 4e30317ff67a2eb12b4d890d39f72fd7e7117d48 Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Sat, 15 Aug 2026 02:58:56 +0200 Subject: [PATCH 67/70] net: openvswitch: fix flow mask use-after-free on flow deletion The commit in the Fixes tag below made so flow->mask free is scheduled via RCU right after it is removed from the flow table. The pointer stays in the flow structure and it can be accessible while in the same RCU critical section. This is done to avoid requiring ovs_mutex for the ovs_flow_free(). However, while removing the flow during processing of CMD_DEL, we do not take RCU read lock before the removal, and ovs_flow_cmd_fill_info() uses the flow->mask pointer afterwards. The RCU read lock is taken, but it's already late at that point. The comment on that line acknowledges that the lock is cosmetic and doesn't serve a real purpose. This leads to use-after-free if the RCU grace period passes between removal and the filling. It is a short race window, but it is there and can lead to a real crash in case memory allocation for the info takes a bit longer: BUG: KASAN: slab-use-after-free in __ovs_nla_put_key net/openvswitch/flow_netlink.c:1996 BUG: KASAN: slab-use-after-free in ovs_nla_put_key+0x2463/0x2e30 net/openvswitch/flow_netlink.c:2250 Read of size 4 at addr ffff88801ee89970 by task ovs_flow_del_ec/9487 Call Trace: __ovs_nla_put_key net/openvswitch/flow_netlink.c:1996 ovs_nla_put_key+0x2463/0x2e30 net/openvswitch/flow_netlink.c:2250 ovs_flow_cmd_fill_info+0x420/0x9c0 net/openvswitch/datapath.c:930 ovs_flow_cmd_del+0x53a/0x970 net/openvswitch/datapath.c:1467 ... netlink_rcv_skb+0x156/0x420 net/netlink/af_netlink.c:2556 Allocated by task 9487: mask_alloc net/openvswitch/flow_table.c:967 flow_mask_insert net/openvswitch/flow_table.c:1012 ovs_flow_tbl_insert+0xea2/0x1a90 net/openvswitch/flow_table.c:1084 ovs_flow_cmd_new+0x7e3/0xd90 net/openvswitch/datapath.c:1086 ... netlink_rcv_skb+0x156/0x420 net/netlink/af_netlink.c:2556 Freed by task 9485: rcu_free_sheaf+0x1e/0x100 mm/slub.c:5978 rcu_do_batch kernel/rcu/tree.c:2645 rcu_core+0x59c/0x10c0 kernel/rcu/tree.c:2897 handle_softirqs+0x1e4/0x9a0 kernel/softirq.c:622 ... instr_sysvec_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1062 ovs_flow_tbl_remove() must be called after the ovs_flow_cmd_fill_info() to avoid this race. This also helps with cleaning up the forced cast and the cosmetic RCU read lock. Before the commit in the Fixes tag the order did not matter as long as the flow object itself was not freed. A wider RCU critical section could be another option, but we have a GFP_KERNEL allocation in the way. Reported by Trend Micro's Zero Day Initiative as ZDI-CAN-32042. Fixes: 56c19868e115 ("openvswitch: Make flow mask removal symmetric.") Cc: stable@vger.kernel.org Signed-off-by: Ilya Maximets Reviewed-by: Aaron Conole Link: https://patch.msgid.link/20260815005915.1097270-1-i.maximets@ovn.org Signed-off-by: Jakub Kicinski --- net/openvswitch/datapath.c | 47 +++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c index ae69b2cabab9..ded46d993a4e 100644 --- a/net/openvswitch/datapath.c +++ b/net/openvswitch/datapath.c @@ -1473,33 +1473,34 @@ static int ovs_flow_cmd_del(struct sk_buff *skb, struct genl_info *info) goto unlock; } + reply = ovs_flow_cmd_alloc_info(ovsl_dereference(flow->sf_acts), + &flow->id, info, false, ufid_flags); + if (IS_ERR(reply)) { + netlink_set_err(sock_net(skb->sk)->genl_sock, 0, 0, + PTR_ERR(reply)); + reply = NULL; + } + + if (likely(reply)) { + err = ovs_flow_cmd_fill_info(flow, ovs_header->dp_ifindex, + reply, info->snd_portid, + info->snd_seq, 0, + OVS_FLOW_CMD_DEL, ufid_flags); + if (WARN_ON_ONCE(err < 0)) { + kfree_skb(reply); + reply = NULL; + } + } + /* Removal has to happen after ovs_flow_cmd_fill_info(), as it uses + * the flow->mask that can be scheduled to be freed by the + * ovs_flow_tbl_remove() and we're not holding the RCU read lock. + */ ovs_flow_tbl_remove(&dp->table, flow); ovs_unlock(); - reply = ovs_flow_cmd_alloc_info((const struct sw_flow_actions __force *) flow->sf_acts, - &flow->id, info, false, ufid_flags); - if (likely(reply)) { - if (!IS_ERR(reply)) { - rcu_read_lock(); /*To keep RCU checker happy. */ - err = ovs_flow_cmd_fill_info(flow, ovs_header->dp_ifindex, - reply, info->snd_portid, - info->snd_seq, 0, - OVS_FLOW_CMD_DEL, - ufid_flags); - rcu_read_unlock(); - if (WARN_ON_ONCE(err < 0)) { - kfree_skb(reply); - goto out_free; - } + if (likely(reply)) + ovs_notify(&dp_flow_genl_family, reply, info); - ovs_notify(&dp_flow_genl_family, reply, info); - } else { - netlink_set_err(sock_net(skb->sk)->genl_sock, 0, 0, - PTR_ERR(reply)); - } - } - -out_free: ovs_flow_free(flow, true); return 0; unlock: From 0b1c2af8a22c35cb099c735c2f63ea3ba757557d Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Sat, 15 Aug 2026 15:50:13 +0900 Subject: [PATCH 68/70] net: add missing ref_tracker_dir_exit() to alloc_netdev_mqs() sashiko is reporting that trying to read /sys/kernel/debug/ref_tracker/* causes use-afer-free crash when either alloc_percpu() or dev_addr_init() in alloc_netdev_mqs() failed, for commit 4d92b95ff2f9 ("net: add net device refcount tracker infrastructure") added ref_tracker_dir_exit() to only free_netdev() path. Closes: https://sashiko.dev/#/patchset/56c707e7-1fb0-43ec-b8fb-cf6f451e513e%40I-love.SAKURA.ne.jp Fixes: 4d92b95ff2f9 ("net: add net device refcount tracker infrastructure") Signed-off-by: Tetsuo Handa Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/b06ce35d-e7bc-47a5-8e0a-e82be7e4dd08@I-love.SAKURA.ne.jp Signed-off-by: Jakub Kicinski --- net/core/dev.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/core/dev.c b/net/core/dev.c index 5ac31370df93..39807b68ff26 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -12167,6 +12167,7 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name, free_percpu(dev->pcpu_refcnt); free_dev: #endif + ref_tracker_dir_exit(&dev->refcnt_tracker); kvfree(dev); return NULL; } From f9de5db270a4c2641de87ee558c16a9bc6eb4cd8 Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Sat, 15 Aug 2026 23:17:29 +0800 Subject: [PATCH 69/70] net: openvswitch: fix nf_connlabels leak in ovs_ct_init ovs_ct_init() acquires a connlabels reference before initializing the conntrack limit state. If ovs_ct_limit_init() fails, its error is returned directly. The pernet core does not invoke the exit callback for the operation whose initialization failed, so ovs_ct_exit() cannot drop the reference. This leaves labels_used elevated when Open vSwitch pernet registration fails for an existing network namespace. Subsequent conntrack entries in that namespace may allocate label extensions even though Open vSwitch failed to register. Drop the connlabels reference before returning a conntrack limit initialization error. ovs_ct_limit_init() already releases its partial state, and the original error remains unchanged. This issue was found by a static analysis checker and confirmed by manual source review. Fixes: 11efd5cb04a1 ("openvswitch: Support conntrack zone limit") Cc: stable@vger.kernel.org Signed-off-by: Ruoyu Wang Reviewed-by: Ilya Maximets Link: https://patch.msgid.link/20260815151729.3757984-1-ruoyuw560@gmail.com Signed-off-by: Jakub Kicinski --- net/openvswitch/conntrack.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/net/openvswitch/conntrack.c b/net/openvswitch/conntrack.c index 95697d4e16e6..38c6f34776c2 100644 --- a/net/openvswitch/conntrack.c +++ b/net/openvswitch/conntrack.c @@ -2001,6 +2001,7 @@ int ovs_ct_init(struct net *net) { unsigned int n_bits = sizeof(struct ovs_key_ct_labels) * BITS_PER_BYTE; struct ovs_net *ovs_net = net_generic(net, ovs_net_id); + int err = 0; if (nf_connlabels_get(net, n_bits - 1)) { ovs_net->xt_label = false; @@ -2010,10 +2011,11 @@ int ovs_ct_init(struct net *net) } #if IS_ENABLED(CONFIG_NETFILTER_CONNCOUNT) - return ovs_ct_limit_init(net, ovs_net); -#else - return 0; + err = ovs_ct_limit_init(net, ovs_net); + if (err && ovs_net->xt_label) + nf_connlabels_put(net); #endif + return err; } void ovs_ct_exit(struct net *net) From e2466392a0b8496000e12181cb1ee1535eb0da25 Mon Sep 17 00:00:00 2001 From: Glenn Judd Date: Sun, 16 Aug 2026 09:42:59 +0300 Subject: [PATCH 70/70] net/mlx5e: do not HW-GRO coalesce small frames When hardware GRO (SHAMPO) coalesces a small IPv4/TCP segment that was padded up to the 60-byte minimum Ethernet frame, the trailing padding is folded into the merged payload causing padding to be delivered to the user as payload. Detecting and reproducing the issue: the selftest tools/testing/selftests/drivers/net/gro.py subtest hw_ipv4_data_lrg_1byte sends {100, 1} expecting to receive {101}. In current code, it receives {106} (100 + 1 payload + 5 pad) instead. This patch avoids giving the user padding as payload by simply not coalescing small packets (which fails the subtest; the same approach and behavior as sw gro). This gains code simplicity at the cost of more computation (passing an extra skb up the stack) for small packets that could be coalesced. The threshold is chosen as ETH_ZLEN + 2 * VLAN_HLEN. This is the largest frame that may still contain minimum-frame padding (+ 2 VLAN tags), so anything larger is safe to consider for coalesce. (We do not include ETH_FCS_LEN in that threshold computation as netdev_fix_features() drops NETIF_F_GRO_HW whenever NETIF_F_RXFCS is set, so retained FCS can't reach this path.) Fixes: 92552d3abd32 ("net/mlx5e: HW_GRO cqe handler implementation") Cc: stable@vger.kernel.org Signed-off-by: Glenn Judd Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260816064259.3279548-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en_rx.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c b/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c index 6fbc0441c4b8..6fc6605d2054 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c @@ -2263,6 +2263,11 @@ static void mlx5e_handle_rx_cqe_mpwrq_shampo(struct mlx5e_rq *rq, struct mlx5_cq data_offset = wqe_offset & (page_size - 1); page_idx = wqe_offset >> rq->mpwqe.page_shift; + if (unlikely(cqe_bcnt <= ETH_ZLEN + 2 * VLAN_HLEN)) { + match = false; + flush = true; + } + if (*skb && !(match && mlx5e_hw_gro_skb_has_enough_space(*skb, data_bcnt, page_size))) {