From dad9f96945d77ecd4708f730c06ef54dcd8cc057 Mon Sep 17 00:00:00 2001 From: Cheng Yongkang Date: Fri, 5 Jun 2026 08:32:10 -0700 Subject: [PATCH 001/234] wifi: ath9k: hif_usb: don't dereference hif_dev after re-arming firmware request MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ath9k_hif_request_firmware() re-arms an asynchronous firmware load via request_firmware_nowait(), passing hif_dev as the completion context, and then still dereferences hif_dev: dev_info(&hif_dev->udev->dev, "ath9k_htc: Firmware %s requested\n", hif_dev->fw_name); The re-armed callback ath9k_hif_usb_firmware_cb() runs on the "events" workqueue and, when the firmware is missing, walks the retry chain into ath9k_hif_usb_firmware_fail() -> complete_all(&hif_dev->fw_done). That releases the wait_for_completion(&hif_dev->fw_done) in a concurrent ath9k_hif_usb_disconnect(), which then kfree()s hif_dev. The trailing dev_info() in the frame that re-armed the request can therefore read freed memory (hif_dev->udev, the first field of struct hif_device_usb): BUG: KASAN: slab-use-after-free in ath9k_hif_request_firmware Read of size 8 ... by task kworker/... ath9k_hif_request_firmware ath9k_hif_usb_firmware_cb drivers/net/wireless/ath/ath9k/hif_usb.c:1247 request_firmware_work_func Allocated by ...: ath9k_hif_usb_probe drivers/net/wireless/ath/ath9k/hif_usb.c Freed by ...: ath9k_hif_usb_disconnect -> kfree drivers/net/wireless/ath/ath9k/hif_usb.c The fw_done barrier only makes disconnect wait for the firmware chain to *terminate*; it does not protect the outer ath9k_hif_request_firmware() frame that re-armed the request and keeps touching hif_dev afterwards. Drop the post-request dev_info(): it is the only use of hif_dev after the async request is armed, and it is purely informational (the dev_err() on the failure path runs only when request_firmware_nowait() did not arm a callback, so hif_dev is still alive there). This was first reported by syzbot as a single, non-reproduced crash that was later auto-obsoleted, and was independently rediscovered by the reFuzz fuzzer, which produced a C reproducer (USB-gadget connect/disconnect of an ath9k_htc device whose firmware download fails). The vulnerable code is unchanged and still present in v7.1-rc6, where the slab-use-after-free reproduces under KASAN once the (sub-microsecond) race window is widened. Fixes: e904cf6fe230 ("ath9k_htc: introduce support for different fw versions") Reported-by: syzbot+50122cbc2874b1eb25b0@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=50122cbc2874b1eb25b0 Signed-off-by: Cheng Yongkang Acked-by: Toke Høiland-Jørgensen Link: https://patch.msgid.link/20260605153210.20471-1-1020691186@qq.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath9k/hif_usb.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/net/wireless/ath/ath9k/hif_usb.c b/drivers/net/wireless/ath/ath9k/hif_usb.c index 515267f48d80..47f904e7e652 100644 --- a/drivers/net/wireless/ath/ath9k/hif_usb.c +++ b/drivers/net/wireless/ath/ath9k/hif_usb.c @@ -1215,15 +1215,10 @@ static int ath9k_hif_request_firmware(struct hif_device_usb *hif_dev, ret = request_firmware_nowait(THIS_MODULE, true, hif_dev->fw_name, &hif_dev->udev->dev, GFP_KERNEL, hif_dev, ath9k_hif_usb_firmware_cb); - if (ret) { + if (ret) dev_err(&hif_dev->udev->dev, "ath9k_htc: Async request for firmware %s failed\n", hif_dev->fw_name); - return ret; - } - - dev_info(&hif_dev->udev->dev, "ath9k_htc: Firmware %s requested\n", - hif_dev->fw_name); return ret; } From ba7debb4dd6427386862220e8335a53a4bfc235d Mon Sep 17 00:00:00 2001 From: Daniel Hodges Date: Fri, 6 Feb 2026 13:52:07 -0500 Subject: [PATCH 002/234] wifi: ath6kl: fix use-after-free in aggr_reset_state() The aggr_reset_state() function uses timer_delete() (non-synchronous) for the aggregation timer before proceeding to delete TID state and before the structure is freed by callers like aggr_module_destroy(). If the timer callback (aggr_timeout) is executing when aggr_reset_state() is called, the callback will continue to access aggr_conn fields like rx_tid[] and stat[] which may be freed immediately after by kfree(aggr_info->aggr_conn) in aggr_module_destroy(). Additionally, the timer callback can re-arm itself via mod_timer() while aggr_reset_state() is running, creating a more complex race condition. Use timer_delete_sync() instead to ensure any running timer callback has completed before returning. Fixes: bdcd81707973 ("Add ath6kl cleaned up driver") Cc: stable@vger.kernel.org Signed-off-by: Daniel Hodges Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20260206185207.30098-1-git@danielhodges.dev Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath6kl/txrx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath6kl/txrx.c b/drivers/net/wireless/ath/ath6kl/txrx.c index 97fdac7237e2..0e268017af52 100644 --- a/drivers/net/wireless/ath/ath6kl/txrx.c +++ b/drivers/net/wireless/ath/ath6kl/txrx.c @@ -1828,7 +1828,7 @@ void aggr_reset_state(struct aggr_info_conn *aggr_conn) return; if (aggr_conn->timer_scheduled) { - timer_delete(&aggr_conn->timer); + timer_delete_sync(&aggr_conn->timer); aggr_conn->timer_scheduled = false; } From e8d85672dd7e2523f774caafba8f858384e18df7 Mon Sep 17 00:00:00 2001 From: Gaole Zhang Date: Tue, 9 Jun 2026 17:06:09 +0800 Subject: [PATCH 003/234] wifi: ath11k: fix NULL pointer dereference in ath11k_hal_srng_access_begin In ATH11K_QMI_EVENT_FW_READY, ATH11K_FLAG_REGISTERED is set unconditionally even when ath11k_core_qmi_firmware_ready() fails. This leaves the driver in an inconsistent state where initialization is considered complete although the firmware ready handling did not finish successfully. During the subsequent SSR, the driver enters the restart path based on this incorrect state and dereferences uninitialized srng members, resulting in a NULL pointer dereference. Call trace: ath11k_hal_srng_access_begin+0xc/0x60 [ath11k] (P) ath11k_ce_cleanup_pipes+0x17c/0x180 [ath11k] ath11k_core_restart+0x40/0x168 [ath11k] Fix this by: - skipping firmware_ready if ATH11K_FLAG_REGISTERED is already set - setting ATH11K_FLAG_REGISTERED only when firmware_ready succeeds - setting ATH11K_FLAG_QMI_FAIL and aborting the FW_READY handling on error Tested-on: WCN6750 hw1.0 AHB WLAN.MSL.2.0.c2-00204-QCAMSLSWPLZ-1 Fixes: 6fe62a8cec51c ("wifi: ath11k: Add cold boot calibration support on WCN6750") Signed-off-by: Gaole Zhang Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260609090609.4041009-1-gaole.zhang@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/qmi.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/ath/ath11k/qmi.c b/drivers/net/wireless/ath/ath11k/qmi.c index 410a7ee076a0..7dc07339b957 100644 --- a/drivers/net/wireless/ath/ath11k/qmi.c +++ b/drivers/net/wireless/ath/ath11k/qmi.c @@ -3294,9 +3294,14 @@ static void ath11k_qmi_driver_event_work(struct work_struct *work) clear_bit(ATH11K_FLAG_CRASH_FLUSH, &ab->dev_flags); clear_bit(ATH11K_FLAG_RECOVERY, &ab->dev_flags); - ath11k_core_qmi_firmware_ready(ab); - set_bit(ATH11K_FLAG_REGISTERED, &ab->dev_flags); - + if (!test_bit(ATH11K_FLAG_REGISTERED, &ab->dev_flags)) { + ret = ath11k_core_qmi_firmware_ready(ab); + if (ret) { + set_bit(ATH11K_FLAG_QMI_FAIL, &ab->dev_flags); + break; + } + set_bit(ATH11K_FLAG_REGISTERED, &ab->dev_flags); + } break; case ATH11K_QMI_EVENT_COLD_BOOT_CAL_DONE: break; From 0e120ee0822b7cc650bd7b29682a34e137cec10d Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Tue, 9 Jun 2026 09:25:28 +0000 Subject: [PATCH 004/234] wifi: ath11k: fix refcount leak in ath11k_ahb_fw_resources_init() of_get_child_by_name() returns a node pointer with refcount incremented, but the error path when ath11k_ahb_setup_msa_resources() fails does not release it. Add the missing of_node_put() to avoid leaking the reference. Cc: stable@vger.kernel.org Fixes: 095cb947490c ("wifi: ath11k: allow missing memory-regions") Signed-off-by: Wentao Liang Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260609092528.220547-1-vulab@iscas.ac.cn Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/ahb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/ath/ath11k/ahb.c b/drivers/net/wireless/ath/ath11k/ahb.c index f566d699d074..1e1dea485760 100644 --- a/drivers/net/wireless/ath/ath11k/ahb.c +++ b/drivers/net/wireless/ath/ath11k/ahb.c @@ -996,6 +996,7 @@ static int ath11k_ahb_fw_resources_init(struct ath11k_base *ab) ret = ath11k_ahb_setup_msa_resources(ab); if (ret) { ath11k_err(ab, "failed to setup msa resources\n"); + of_node_put(node); return ret; } From 7393878255e492515858f751ba4c260f248fb108 Mon Sep 17 00:00:00 2001 From: Manikanta Pubbisetty Date: Tue, 23 Jun 2026 12:13:55 +0530 Subject: [PATCH 005/234] wifi: ath10k: fix skb leak on incomplete msdu during rx pop When ath10k_htt_rx_pop_paddr32_list() or ath10k_htt_rx_pop_paddr64_list() encounters an incomplete frame (RX_ATTENTION_FLAGS_MSDU_DONE not set), it returns -EIO without purging the skb list built up so far, leaking any skbs already queued in the list. Other early-exit paths within these same functions already call __skb_queue_purge() before returning an error. Add it before the -EIO return as well to be consistent and prevent the leak. Tested-on: WCN3990 hw1.0 WLAN.HL.3.2.2.c10-00754-QCAHLSWMTPL-1 Fixes: c545070e404b ("ath10k: implement rx reorder support") Fixes: 3b0b55b19d1d ("ath10k: Add support for 64 bit HTT in-order indication msg") Signed-off-by: Manikanta Pubbisetty Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260623064355.1876743-1-manikanta.pubbisetty@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath10k/htt_rx.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/wireless/ath/ath10k/htt_rx.c b/drivers/net/wireless/ath/ath10k/htt_rx.c index faac359aa9ac..b3f1b7186721 100644 --- a/drivers/net/wireless/ath/ath10k/htt_rx.c +++ b/drivers/net/wireless/ath/ath10k/htt_rx.c @@ -706,6 +706,7 @@ static int ath10k_htt_rx_pop_paddr32_list(struct ath10k_htt *htt, if (!(__le32_to_cpu(rxd_attention->flags) & RX_ATTENTION_FLAGS_MSDU_DONE)) { ath10k_warn(htt->ar, "tried to pop an incomplete frame, oops!\n"); + __skb_queue_purge(list); return -EIO; } } @@ -770,6 +771,7 @@ static int ath10k_htt_rx_pop_paddr64_list(struct ath10k_htt *htt, if (!(__le32_to_cpu(rxd_attention->flags) & RX_ATTENTION_FLAGS_MSDU_DONE)) { ath10k_warn(htt->ar, "tried to pop an incomplete frame, oops!\n"); + __skb_queue_purge(list); return -EIO; } } From 7f11e70629650ff6ea140984e5ce188b775b2683 Mon Sep 17 00:00:00 2001 From: Dmitry Morgun Date: Sat, 30 May 2026 11:42:52 +0000 Subject: [PATCH 006/234] wifi: ath11k: fix potential buffer underflow in ath11k_hal_rx_msdu_list_get() When the first entry in msdu_details has a zero buffer address, the code accesses msdu_details[i - 1] with i == 0, causing a buffer underflow. Fix similarly to ath12k_wifi7_hal_rx_msdu_list_get() by adding a separate check for i == 0 before the main condition to prevent the out-of-bounds access. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: d5c65159f289 ("ath11k: driver for Qualcomm IEEE 802.11ax devices") Signed-off-by: Dmitry Morgun Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260530114252.42615-1-d.morgun@ispras.ru Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/dp_rx.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/ath/ath11k/dp_rx.c b/drivers/net/wireless/ath/ath11k/dp_rx.c index 9e90d8e3f155..8e2abc7b8383 100644 --- a/drivers/net/wireless/ath/ath11k/dp_rx.c +++ b/drivers/net/wireless/ath/ath11k/dp_rx.c @@ -4618,6 +4618,9 @@ static void ath11k_hal_rx_msdu_list_get(struct ath11k *ar, msdu_details = &msdu_link->msdu_link[0]; for (i = 0; i < HAL_RX_NUM_MSDU_DESC; i++) { + if (!i && FIELD_GET(BUFFER_ADDR_INFO0_ADDR, + msdu_details[i].buf_addr_info.info0) == 0) + break; if (FIELD_GET(BUFFER_ADDR_INFO0_ADDR, msdu_details[i].buf_addr_info.info0) == 0) { msdu_desc_info = &msdu_details[i - 1].rx_msdu_info; From 0fe8010fc5b147607fc19ba010ba469afc95f35f Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 23 Jun 2026 16:16:48 +0200 Subject: [PATCH 007/234] wifi: ath11k: Flush the posted write after writing to PCIE_SOC_GLOBAL_RESET ath11k_pci_soc_global_reset() tries to reset the device by writing to the PCIE_SOC_GLOBAL_RESET register. But it doesn't do a read-back to ensure that the write gets flushed to the device before the delay. This may lead to the delay on the host to be insufficient, if the posted write doesn't reach the device before the delay. So add a read-back after writing to the PCIE_SOC_GLOBAL_RESET register and before the delay. Compile tested only. Fixes: f3c603d412b3 ("ath11k: reset MHI during power down and power up") Reported-by: Alex Williamson Closes: https://lore.kernel.org/linux-pci/20260622160822.09350246@shazbot.org Signed-off-by: Manivannan Sadhasivam Reviewed-by: Baochen Qiang Reviewed-by: Raj Kumar Bhagat Link: https://patch.msgid.link/20260623141649.41087-1-manivannan.sadhasivam@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/pci.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/wireless/ath/ath11k/pci.c b/drivers/net/wireless/ath/ath11k/pci.c index 35bb9e7a63a2..a163168f3617 100644 --- a/drivers/net/wireless/ath/ath11k/pci.c +++ b/drivers/net/wireless/ath/ath11k/pci.c @@ -199,6 +199,8 @@ static void ath11k_pci_soc_global_reset(struct ath11k_base *ab) val |= PCIE_SOC_GLOBAL_RESET_V; ath11k_pcic_write32(ab, PCIE_SOC_GLOBAL_RESET, val); + /* Flush the posted write to the device */ + ath11k_pcic_read32(ab, PCIE_SOC_GLOBAL_RESET); /* TODO: exact time to sleep is uncertain */ delay = 10; @@ -208,6 +210,8 @@ static void ath11k_pci_soc_global_reset(struct ath11k_base *ab) val &= ~PCIE_SOC_GLOBAL_RESET_V; ath11k_pcic_write32(ab, PCIE_SOC_GLOBAL_RESET, val); + /* Flush the posted write to the device */ + ath11k_pcic_read32(ab, PCIE_SOC_GLOBAL_RESET); mdelay(delay); From 55f3aa06951cac78b0206bde961c8cf11929a27a Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 23 Jun 2026 16:16:49 +0200 Subject: [PATCH 008/234] wifi: ath12k: Flush the posted write after writing to PCIE_SOC_GLOBAL_RESET ath12k_pci_soc_global_reset() tries to reset the device by writing to the PCIE_SOC_GLOBAL_RESET register. But it doesn't do a read-back to ensure that the write gets flushed to the device before the delay. This may lead to the delay on the host to be insufficient, if the posted write doesn't reach the device before the delay. So add a read-back after writing to the PCIE_SOC_GLOBAL_RESET register and before the delay. Compile tested only. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Fixes: d889913205cf ("wifi: ath12k: driver for Qualcomm Wi-Fi 7 devices") Reported-by: Alex Williamson Closes: https://lore.kernel.org/linux-pci/20260622160822.09350246@shazbot.org Signed-off-by: Manivannan Sadhasivam Reviewed-by: Baochen Qiang Reviewed-by: Raj Kumar Bhagat Tested-by: Raj Kumar Bhagat Link: https://patch.msgid.link/20260623141649.41087-2-manivannan.sadhasivam@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/pci.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/pci.c b/drivers/net/wireless/ath/ath12k/pci.c index d9a22d6afbb0..fee4129ea405 100644 --- a/drivers/net/wireless/ath/ath12k/pci.c +++ b/drivers/net/wireless/ath/ath12k/pci.c @@ -188,6 +188,8 @@ static void ath12k_pci_soc_global_reset(struct ath12k_base *ab) val |= PCIE_SOC_GLOBAL_RESET_V; ath12k_pci_write32(ab, PCIE_SOC_GLOBAL_RESET, val); + /* Flush the posted write to the device */ + ath12k_pci_read32(ab, PCIE_SOC_GLOBAL_RESET); /* TODO: exact time to sleep is uncertain */ delay = 10; @@ -197,6 +199,8 @@ static void ath12k_pci_soc_global_reset(struct ath12k_base *ab) val &= ~PCIE_SOC_GLOBAL_RESET_V; ath12k_pci_write32(ab, PCIE_SOC_GLOBAL_RESET, val); + /* Flush the posted write to the device */ + ath12k_pci_read32(ab, PCIE_SOC_GLOBAL_RESET); mdelay(delay); From a2fe9dc70f3b8d5716fbcfed5fbfb9cf3948d402 Mon Sep 17 00:00:00 2001 From: Yingying Tang Date: Tue, 9 Jun 2026 22:33:15 -0700 Subject: [PATCH 009/234] wifi: ath12k: Fix low MLO RX throughput on WCN7850 Commit [1] introduced a regression causing severely degraded MLO RX throughput on WCN7850. On WCN7850, there is only a single ar instance, but MLO uses two link IDs. ath12k_dp_peer->hw_links[] is indexed using ar->hw_link_id, which causes both MLO link IDs to be stored at the same index. As a result, an incorrect link ID is assigned to MSDUs in ath12k_dp_rx_deliver_msdu(), leading to severe MLO RX throughput loss. Different chipsets identify the per-MSDU link differently: - On QCN9274 / IPQ5332, the host owns multiple ar instances and the per-MSDU hw_link_id from the RX descriptor maps cleanly through dp_peer->hw_links[hw_link_id] to the IEEE link_id. - On single-ar chipsets like WCN7850 / QCC2072, there is only one ar instance for both MLO links, so dp_peer->hw_links[] has just one valid slot and cannot be used to distinguish the two links. To resolve the link, walk dp_peer->link_peers[] and match by rxcb->peer_id, which on the link_peer side identifies the link peer for the MSDU. Add a new hw_op set_rx_link_id() so each chipset resolves the link on the RX fast path using whatever signal it actually has, and let the op itself decide whether to populate rx_status::link_valid and rx_status::link_id: QCN9274 / IPQ5332 : always derive link_id from dp_peer->hw_links[rxcb->hw_link_id] and set link_valid. WCN7850 / QCC2072 : walk the link_peers[] of dp_peer to find the link_peer whose peer_id matches rxcb->peer_id, and set link_valid only when a match is found. Otherwise leave link_valid clear so that mac80211 can fall back to its own link resolution path (via addr2 / deflink). For WCN7850 / QCC2072, walking dp_peer->link_peers[] is bounded by the number of links actually populated, so introduce a link_peers_map bitmap (unsigned long) in struct ath12k_dp_peer that tracks populated slots and use for_each_set_bit() to iterate. Non-MLO clients hit one slot, current MLO clients hit two; the full ATH12K_NUM_MAX_LINKS array is never scanned. The bitmap is maintained with WRITE_ONCE() on the write side (under dp_hw->peer_lock) paired with READ_ONCE() on both the lockless RX read side and the write-side RMW for KCSAN correctness. Also guard the dp_peer dereference in ath12k_mac_peer_cleanup_all() with a NULL check, since peer->dp_peer can be NULL for self-peers or peers not yet fully assigned, the pre-existing rcu_assign_pointer() call there had the same latent issue. This restores the correct link ID on WCN7850 without changing the QCN9274 / IPQ5332 data path, which keeps its O(1) hw_links[] indexing. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Fixes: 11157e0910fd ("wifi: ath12k: Use ath12k_dp_peer in per packet Tx & Rx paths") # [1] Signed-off-by: Yingying Tang Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260610053315.2249912-1-yingying.tang@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/dp_peer.c | 4 +++ drivers/net/wireless/ath/ath12k/dp_peer.h | 1 + drivers/net/wireless/ath/ath12k/dp_rx.c | 7 ++-- drivers/net/wireless/ath/ath12k/hw.h | 16 +++++++++ drivers/net/wireless/ath/ath12k/mac.c | 10 ++++-- drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c | 33 +++++++++++++++++++ drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h | 6 ++++ drivers/net/wireless/ath/ath12k/wifi7/hw.c | 3 ++ 8 files changed, 73 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/dp_peer.c b/drivers/net/wireless/ath/ath12k/dp_peer.c index 47d009a0d61f..2660b4c7449b 100644 --- a/drivers/net/wireless/ath/ath12k/dp_peer.c +++ b/drivers/net/wireless/ath/ath12k/dp_peer.c @@ -570,6 +570,8 @@ int ath12k_dp_link_peer_assign(struct ath12k_dp *dp, struct ath12k_dp_hw *dp_hw, peerid_index = ath12k_dp_peer_get_peerid_index(dp, peer->peer_id); rcu_assign_pointer(dp_peer->link_peers[peer->link_id], peer); + WRITE_ONCE(dp_peer->link_peers_map, + READ_ONCE(dp_peer->link_peers_map) | BIT(peer->link_id)); rcu_assign_pointer(dp_hw->dp_peers[peerid_index], dp_peer); @@ -632,6 +634,8 @@ void ath12k_dp_link_peer_unassign(struct ath12k_dp *dp, struct ath12k_dp_hw *dp_ peerid_index = ath12k_dp_peer_get_peerid_index(dp, peer->peer_id); rcu_assign_pointer(dp_peer->link_peers[peer->link_id], NULL); + WRITE_ONCE(dp_peer->link_peers_map, + READ_ONCE(dp_peer->link_peers_map) & ~BIT(peer->link_id)); rcu_assign_pointer(dp_hw->dp_peers[peerid_index], NULL); diff --git a/drivers/net/wireless/ath/ath12k/dp_peer.h b/drivers/net/wireless/ath/ath12k/dp_peer.h index f5067e66f1e1..7c9709bf717b 100644 --- a/drivers/net/wireless/ath/ath12k/dp_peer.h +++ b/drivers/net/wireless/ath/ath12k/dp_peer.h @@ -140,6 +140,7 @@ struct ath12k_dp_peer { /* Info used in MMIC verification of * RX fragments */ struct ieee80211_key_conf *keys[WMI_MAX_KEY_INDEX + 1]; + unsigned long link_peers_map; struct ath12k_dp_link_peer __rcu *link_peers[ATH12K_NUM_MAX_LINKS]; struct ath12k_reoq_buf reoq_bufs[IEEE80211_NUM_TIDS + 1]; struct ath12k_dp_rx_tid rx_tid[IEEE80211_NUM_TIDS + 1]; diff --git a/drivers/net/wireless/ath/ath12k/dp_rx.c b/drivers/net/wireless/ath/ath12k/dp_rx.c index 06e74124e57e..8fa0e90b4531 100644 --- a/drivers/net/wireless/ath/ath12k/dp_rx.c +++ b/drivers/net/wireless/ath/ath12k/dp_rx.c @@ -1384,10 +1384,9 @@ void ath12k_dp_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct napi_struc pubsta = peer ? peer->sta : NULL; - if (pubsta && pubsta->valid_links) { - status->link_valid = 1; - status->link_id = peer->hw_links[rxcb->hw_link_id]; - } + status->link_valid = 0; + if (pubsta && pubsta->valid_links) + ath12k_hw_set_rx_link_id(dp->hw_params, peer, rxcb, status); ath12k_dbg(dp->ab, ATH12K_DBG_DATA, "rx skb %p len %u peer %pM %d %s sn %u %s%s%s%s%s%s%s%s%s%s rate_idx %u vht_nss %u freq %u band %u flag 0x%x fcs-err %i mic-err %i amsdu-more %i\n", diff --git a/drivers/net/wireless/ath/ath12k/hw.h b/drivers/net/wireless/ath/ath12k/hw.h index d135b2936378..86fb8b719613 100644 --- a/drivers/net/wireless/ath/ath12k/hw.h +++ b/drivers/net/wireless/ath/ath12k/hw.h @@ -13,6 +13,10 @@ #include "wmi.h" #include "hal.h" +struct ath12k_dp_peer; +struct ath12k_skb_rxcb; +struct ieee80211_rx_status; + /* Target configuration defines */ /* Num VDEVS per radio */ @@ -243,6 +247,9 @@ struct ath12k_hw_ops { bool (*dp_srng_is_tx_comp_ring)(int ring_num); bool (*is_frame_link_agnostic)(struct ath12k_link_vif *arvif, struct ieee80211_mgmt *mgmt); + void (*set_rx_link_id)(struct ath12k_dp_peer *dp_peer, + struct ath12k_skb_rxcb *rxcb, + struct ieee80211_rx_status *status); }; static inline @@ -273,6 +280,15 @@ static inline int ath12k_hw_mac_id_to_srng_id(const struct ath12k_hw_params *hw, return 0; } +static inline void ath12k_hw_set_rx_link_id(const struct ath12k_hw_params *hw, + struct ath12k_dp_peer *dp_peer, + struct ath12k_skb_rxcb *rxcb, + struct ieee80211_rx_status *status) +{ + if (hw->hw_ops->set_rx_link_id) + hw->hw_ops->set_rx_link_id(dp_peer, rxcb, status); +} + struct ath12k_fw_ie { __le32 id; __le32 len; diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index af354bef5c0d..51c4df32e716 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -1238,9 +1238,13 @@ void ath12k_mac_peer_cleanup_all(struct ath12k *ar) /* cleanup dp peer */ spin_lock_bh(&dp_hw->peer_lock); dp_peer = peer->dp_peer; - peerid_index = ath12k_dp_peer_get_peerid_index(dp, peer->peer_id); - rcu_assign_pointer(dp_peer->link_peers[peer->link_id], NULL); - rcu_assign_pointer(dp_hw->dp_peers[peerid_index], NULL); + if (dp_peer) { + peerid_index = ath12k_dp_peer_get_peerid_index(dp, peer->peer_id); + rcu_assign_pointer(dp_peer->link_peers[peer->link_id], NULL); + WRITE_ONCE(dp_peer->link_peers_map, + READ_ONCE(dp_peer->link_peers_map) & ~BIT(peer->link_id)); + rcu_assign_pointer(dp_hw->dp_peers[peerid_index], NULL); + } spin_unlock_bh(&dp_hw->peer_lock); ath12k_dp_link_peer_rhash_delete(dp, peer); diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c b/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c index cb9dd8cb28b6..95d87dd67872 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c @@ -5,6 +5,7 @@ */ #include "dp_rx.h" +#include "../dp_peer.h" #include "../dp_tx.h" #include "../peer.h" #include "hal_qcn9274.h" @@ -2315,3 +2316,35 @@ ath12k_wifi7_dp_rxdesc_mpdu_valid(struct ath12k_base *ab, return tlv_tag == HAL_RX_MPDU_START; } + +void +ath12k_wifi7_dp_rx_set_link_id_qcn9274(struct ath12k_dp_peer *dp_peer, + struct ath12k_skb_rxcb *rxcb, + struct ieee80211_rx_status *status) +{ + status->link_valid = 1; + status->link_id = dp_peer->hw_links[rxcb->hw_link_id]; +} + +void +ath12k_wifi7_dp_rx_set_link_id_wcn7850(struct ath12k_dp_peer *dp_peer, + struct ath12k_skb_rxcb *rxcb, + struct ieee80211_rx_status *status) +{ + struct ath12k_dp_link_peer *link_peer; + unsigned long links_map; + int i; + + RCU_LOCKDEP_WARN(!rcu_read_lock_held(), + "ath12k set rx link id called without rcu lock"); + + links_map = READ_ONCE(dp_peer->link_peers_map); + for_each_set_bit(i, &links_map, ATH12K_NUM_MAX_LINKS) { + link_peer = rcu_dereference(dp_peer->link_peers[i]); + if (link_peer && link_peer->peer_id == rxcb->peer_id) { + status->link_valid = 1; + status->link_id = link_peer->link_id; + return; + } + } +} diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h b/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h index 8aa79faf567f..1d3a4788a2dd 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h +++ b/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h @@ -57,4 +57,10 @@ ath12k_wifi7_dp_rxdesc_mpdu_valid(struct ath12k_base *ab, struct hal_rx_desc *rx_desc); int ath12k_wifi7_dp_rx_tid_delete_handler(struct ath12k_base *ab, struct ath12k_dp_rx_tid_rxq *rx_tid); +void ath12k_wifi7_dp_rx_set_link_id_qcn9274(struct ath12k_dp_peer *dp_peer, + struct ath12k_skb_rxcb *rxcb, + struct ieee80211_rx_status *status); +void ath12k_wifi7_dp_rx_set_link_id_wcn7850(struct ath12k_dp_peer *dp_peer, + struct ath12k_skb_rxcb *rxcb, + struct ieee80211_rx_status *status); #endif diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hw.c b/drivers/net/wireless/ath/ath12k/wifi7/hw.c index 3d59fa452ec0..d9fdd2fc8298 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hw.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hw.c @@ -158,6 +158,7 @@ static const struct ath12k_hw_ops qcn9274_ops = { .get_ring_selector = ath12k_wifi7_hw_get_ring_selector_qcn9274, .dp_srng_is_tx_comp_ring = ath12k_wifi7_dp_srng_is_comp_ring_qcn9274, .is_frame_link_agnostic = ath12k_wifi7_is_frame_link_agnostic_qcn9274, + .set_rx_link_id = ath12k_wifi7_dp_rx_set_link_id_qcn9274, }; static const struct ath12k_hw_ops wcn7850_ops = { @@ -168,6 +169,7 @@ static const struct ath12k_hw_ops wcn7850_ops = { .get_ring_selector = ath12k_wifi7_hw_get_ring_selector_wcn7850, .dp_srng_is_tx_comp_ring = ath12k_wifi7_dp_srng_is_comp_ring_wcn7850, .is_frame_link_agnostic = ath12k_wifi7_is_frame_link_agnostic_wcn7850, + .set_rx_link_id = ath12k_wifi7_dp_rx_set_link_id_wcn7850, }; static const struct ath12k_hw_ops qcc2072_ops = { @@ -178,6 +180,7 @@ static const struct ath12k_hw_ops qcc2072_ops = { .get_ring_selector = ath12k_wifi7_hw_get_ring_selector_wcn7850, .dp_srng_is_tx_comp_ring = ath12k_wifi7_dp_srng_is_comp_ring_wcn7850, .is_frame_link_agnostic = ath12k_wifi7_is_frame_link_agnostic_wcn7850, + .set_rx_link_id = ath12k_wifi7_dp_rx_set_link_id_wcn7850, }; #define ATH12K_TX_RING_MASK_0 0x1 From 70231dcd782201579990ded73e0435d18bb524ca Mon Sep 17 00:00:00 2001 From: Jose Ignacio Tornos Martinez Date: Mon, 15 Jun 2026 13:21:03 +0200 Subject: [PATCH 010/234] wifi: ath12k: fix NULL pointer dereference in rhash table destroy When unbinding the ath12k driver, kernel NULL pointer dereferences occur in irq_work_sync() called from rhashtable_destroy(). Two hash tables are affected: 1. ath12k_link_sta hash table in ath12k_base 2. ath12k_dp_link_peer hash table in ath12k_dp The issue happens because the destroy functions are called unconditionally in cleanup paths, but the hash tables are only initialized late in their respective init functions. If the device was never fully started or if the init functions failed before initializing the hash tables, the pointers will be NULL. The issues are always reproducible from a VM because the MSI addressing initialization is failing. Call trace for ath12k_link_sta_rhash_tbl_destroy: RIP: irq_work_sync+0x1e/0x70 rhashtable_destroy+0x12/0x60 ath12k_link_sta_rhash_tbl_destroy+0x19/0x40 [ath12k] ath12k_core_stop+0xe/0x80 [ath12k] ath12k_core_hw_group_cleanup+0x6b/0xb0 [ath12k] ath12k_pci_remove+0x60/0x110 [ath12k] Call trace for ath12k_dp_link_peer_rhash_tbl_destroy: RIP: irq_work_sync+0x1e/0x70 rhashtable_destroy+0x12/0x60 ath12k_dp_link_peer_rhash_tbl_destroy+0x29/0x50 [ath12k] ath12k_dp_cmn_device_deinit+0x21/0x140 [ath12k] ath12k_core_hw_group_cleanup+0x6b/0xb0 [ath12k] ath12k_pci_remove+0x60/0x110 [ath12k] Fix this by adding NULL checks before calling rhashtable_destroy() in both destroy functions. The NULL check approach was chosen because the rhashtable pointer serves as the initialization state indicator. The init can fail at various points, leaving some components uninitialized. Checking the pointer directly is simpler than adding separate state flags that would need synchronization. Fixes: 57ccca410237 ("wifi: ath12k: Add hash table for ath12k_link_sta in ath12k_base") Fixes: a88cf5f71adf ("wifi: ath12k: Add hash table for ath12k_dp_link_peer") Cc: stable@vger.kernel.org Signed-off-by: Jose Ignacio Tornos Martinez Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20260615112103.601982-1-jtornosm@redhat.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/dp_peer.c | 7 +++++-- drivers/net/wireless/ath/ath12k/peer.c | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/dp_peer.c b/drivers/net/wireless/ath/ath12k/dp_peer.c index 2660b4c7449b..a12073afc307 100644 --- a/drivers/net/wireless/ath/ath12k/dp_peer.c +++ b/drivers/net/wireless/ath/ath12k/dp_peer.c @@ -274,11 +274,14 @@ int ath12k_dp_link_peer_rhash_tbl_init(struct ath12k_dp *dp) void ath12k_dp_link_peer_rhash_tbl_destroy(struct ath12k_dp *dp) { - mutex_lock(&dp->link_peer_rhash_tbl_lock); + guard(mutex)(&dp->link_peer_rhash_tbl_lock); + + if (!dp->rhead_peer_addr) + return; + rhashtable_destroy(dp->rhead_peer_addr); kfree(dp->rhead_peer_addr); dp->rhead_peer_addr = NULL; - mutex_unlock(&dp->link_peer_rhash_tbl_lock); } static int ath12k_dp_link_peer_rhash_insert(struct ath12k_dp *dp, diff --git a/drivers/net/wireless/ath/ath12k/peer.c b/drivers/net/wireless/ath/ath12k/peer.c index c222bdaa333c..2681a047d4d5 100644 --- a/drivers/net/wireless/ath/ath12k/peer.c +++ b/drivers/net/wireless/ath/ath12k/peer.c @@ -453,6 +453,9 @@ int ath12k_link_sta_rhash_tbl_init(struct ath12k_base *ab) void ath12k_link_sta_rhash_tbl_destroy(struct ath12k_base *ab) { + if (!ab->rhead_sta_addr) + return; + rhashtable_destroy(ab->rhead_sta_addr); kfree(ab->rhead_sta_addr); ab->rhead_sta_addr = NULL; From 44126b6994eeb28f2103b638e698f40a1244f327 Mon Sep 17 00:00:00 2001 From: Tristan Madani Date: Thu, 2 Jul 2026 00:50:20 +0000 Subject: [PATCH 011/234] wifi: ath6kl: fix OOB access from firmware ADDBA window size aggr_recv_addba_req_evt() logs a debug message when the firmware-supplied win_sz is outside [AGGR_WIN_SZ_MIN, AGGR_WIN_SZ_MAX] but does not return. The out-of-range win_sz is then used in TID_WINDOW_SZ() to compute a kzalloc size and stored in rxtid->hold_q_sz, leading to zero-size or overflowed allocations and subsequent out-of-bounds access. Clean up any previously active aggregation session for the TID first, then return early when win_sz is out of the valid range, instead of proceeding with a broken allocation size. Fixes: bdcd81707973 ("Add ath6kl cleaned up driver") Cc: stable@vger.kernel.org Reviewed-by: Vasanthakumar Thiagarajan Signed-off-by: Tristan Madani Link: https://patch.msgid.link/20260702005020.708717-1-tristmd@gmail.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath6kl/txrx.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/ath/ath6kl/txrx.c b/drivers/net/wireless/ath/ath6kl/txrx.c index 0e268017af52..d81825413906 100644 --- a/drivers/net/wireless/ath/ath6kl/txrx.c +++ b/drivers/net/wireless/ath/ath6kl/txrx.c @@ -1723,13 +1723,15 @@ void aggr_recv_addba_req_evt(struct ath6kl_vif *vif, u8 tid_mux, u16 seq_no, rxtid = &aggr_conn->rx_tid[tid]; - if (win_sz < AGGR_WIN_SZ_MIN || win_sz > AGGR_WIN_SZ_MAX) - ath6kl_dbg(ATH6KL_DBG_WLAN_RX, "%s: win_sz %d, tid %d\n", - __func__, win_sz, tid); - if (rxtid->aggr) aggr_delete_tid_state(aggr_conn, tid); + if (win_sz < AGGR_WIN_SZ_MIN || win_sz > AGGR_WIN_SZ_MAX) { + ath6kl_dbg(ATH6KL_DBG_WLAN_RX, "%s: win_sz %d, tid %d\n", + __func__, win_sz, tid); + return; + } + rxtid->seq_next = seq_no; hold_q_size = TID_WINDOW_SZ(win_sz) * sizeof(struct skb_hold_q); rxtid->hold_q = kzalloc(hold_q_size, GFP_KERNEL); From 3a21c89215cc18f1a97c5e5bfd1da6d4f3d44495 Mon Sep 17 00:00:00 2001 From: Tristan Madani Date: Thu, 25 Jun 2026 23:29:07 +0000 Subject: [PATCH 012/234] wifi: ath6kl: fix OOB read from firmware num_msg in TX complete handler The firmware-controlled num_msg field (u8, 0-255) drives the loop in ath6kl_wmi_tx_complete_event_rx() without validation against the buffer length. This allows out-of-bounds reads of up to 1020 bytes past the WMI event buffer when the firmware sends an inflated num_msg. Add a check that the buffer is large enough to hold the fixed struct and the num_msg variable-length entries. Fixes: bdcd81707973 ("Add ath6kl cleaned up driver") Signed-off-by: Tristan Madani Link: https://patch.msgid.link/20260625232907.3620746-1-tristmd@gmail.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath6kl/wmi.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/net/wireless/ath/ath6kl/wmi.c b/drivers/net/wireless/ath/ath6kl/wmi.c index 72611a2ceb9d..7e65a03be0b7 100644 --- a/drivers/net/wireless/ath/ath6kl/wmi.c +++ b/drivers/net/wireless/ath/ath6kl/wmi.c @@ -484,6 +484,18 @@ static int ath6kl_wmi_tx_complete_event_rx(u8 *datap, int len) evt = (struct wmi_tx_complete_event *) datap; + if (len < sizeof(*evt)) { + ath6kl_dbg(ATH6KL_DBG_WMI, "tx complete: invalid len %d\n", + len); + return -EINVAL; + } + + if (len < sizeof(*evt) + evt->num_msg * sizeof(struct tx_complete_msg_v1)) { + ath6kl_dbg(ATH6KL_DBG_WMI, "tx complete: invalid len %d for %u msgs\n", + len, evt->num_msg); + return -EINVAL; + } + ath6kl_dbg(ATH6KL_DBG_WMI, "comp: %d %d %d\n", evt->num_msg, evt->msg_len, evt->msg_type); From 6b47b29730de3232b919d8362749f6814c5f2a33 Mon Sep 17 00:00:00 2001 From: Tristan Madani Date: Tue, 21 Apr 2026 13:50:08 +0000 Subject: [PATCH 013/234] wifi: ath6kl: fix OOB read from firmware IE lengths in connect event The firmware-controlled beacon_ie_len, assoc_req_len, and assoc_resp_len fields in ath6kl_wmi_connect_event_rx() are not validated against the buffer length. Their sum (up to 765) can exceed the actual WMI event data, causing out-of-bounds reads during IE parsing and state corruption of wmi->is_wmm_enabled. Add a check that the total IE length fits within the buffer. Fixes: bdcd81707973 ("Add ath6kl cleaned up driver") Signed-off-by: Tristan Madani Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20260421135009.348084-3-tristmd@gmail.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath6kl/wmi.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/net/wireless/ath/ath6kl/wmi.c b/drivers/net/wireless/ath/ath6kl/wmi.c index 7e65a03be0b7..2b0c5038ae04 100644 --- a/drivers/net/wireless/ath/ath6kl/wmi.c +++ b/drivers/net/wireless/ath/ath6kl/wmi.c @@ -874,6 +874,14 @@ static int ath6kl_wmi_connect_event_rx(struct wmi *wmi, u8 *datap, int len, ev = (struct wmi_connect_event *) datap; + if (len < sizeof(*ev) + ev->beacon_ie_len + + ev->assoc_req_len + ev->assoc_resp_len) { + ath6kl_dbg(ATH6KL_DBG_WMI, + "connect event: IE lengths %u+%u+%u exceed buffer %d\n", + ev->beacon_ie_len, ev->assoc_req_len, + ev->assoc_resp_len, len); + return -EINVAL; + } if (vif->nw_type == AP_NETWORK) { /* AP mode start/STA connected event */ struct net_device *dev = vif->ndev; From 4cde55b2feff9504d1f993ab80e84e7ccb62791c Mon Sep 17 00:00:00 2001 From: Tristan Madani Date: Tue, 21 Apr 2026 13:49:26 +0000 Subject: [PATCH 014/234] wifi: carl9170: bound memcpy length in cmd callback to prevent OOB read When the firmware sends a command response with a length mismatch, carl9170_cmd_callback() logs the mismatch and calls carl9170_restart() but then falls through to memcpy(ar->readbuf, buffer + 4, len - 4). Since len comes from the firmware and can exceed ar->readlen, this copies more data than the readbuf was allocated for. Bound the memcpy to min(len - 4, ar->readlen) so that the response is still completed -- avoiding repeated restarts from queued garbage -- while preventing an overread past the response buffer. Fixes: a84fab3cbfdc ("carl9170: 802.11 rx/tx processing and usb backend") Signed-off-by: Tristan Madani Acked-by: Christian Lamparter Closes: https://syzkaller.appspot.com/bug?extid=5c1ca6ccaa1215781cac Link: https://patch.msgid.link/20260421134929.325662-2-tristmd@gmail.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/carl9170/rx.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/carl9170/rx.c b/drivers/net/wireless/ath/carl9170/rx.c index 6833430130f4..f6855efc05c0 100644 --- a/drivers/net/wireless/ath/carl9170/rx.c +++ b/drivers/net/wireless/ath/carl9170/rx.c @@ -150,7 +150,8 @@ static void carl9170_cmd_callback(struct ar9170 *ar, u32 len, void *buffer) spin_lock(&ar->cmd_lock); if (ar->readbuf) { if (len >= 4) - memcpy(ar->readbuf, buffer + 4, len - 4); + memcpy(ar->readbuf, buffer + 4, + min_t(u32, len - 4, ar->readlen)); ar->readbuf = NULL; } From a3f42f1049ad80c65560d2b078ad426c3134f78d Mon Sep 17 00:00:00 2001 From: Tristan Madani Date: Tue, 21 Apr 2026 13:49:27 +0000 Subject: [PATCH 015/234] wifi: carl9170: fix OOB read from off-by-two in TX status handler The bounds check in carl9170_tx_process_status() uses `i > ((cmd->hdr.len / 2) + 1)` which is off by two, allowing 2 extra iterations past valid _tx_status entries when the firmware- controlled hdr.ext exceeds hdr.len/2. Fix by using the correct comparison `i >= (cmd->hdr.len / 2)`. Fixes: a84fab3cbfdc ("carl9170: 802.11 rx/tx processing and usb backend") Signed-off-by: Tristan Madani Acked-by: Christian Lamparter Closes: https://syzkaller.appspot.com/bug?extid=5c1ca6ccaa1215781cac Link: https://patch.msgid.link/20260421134929.325662-3-tristmd@gmail.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/carl9170/tx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/carl9170/tx.c b/drivers/net/wireless/ath/carl9170/tx.c index 59caf1e4b158..06aaf281655b 100644 --- a/drivers/net/wireless/ath/carl9170/tx.c +++ b/drivers/net/wireless/ath/carl9170/tx.c @@ -692,7 +692,7 @@ void carl9170_tx_process_status(struct ar9170 *ar, unsigned int i; for (i = 0; i < cmd->hdr.ext; i++) { - if (WARN_ON(i > ((cmd->hdr.len / 2) + 1))) { + if (WARN_ON(i >= (cmd->hdr.len / 2))) { print_hex_dump_bytes("UU:", DUMP_PREFIX_NONE, (void *) cmd, cmd->hdr.len + 4); break; From a1a21995c2e1cc2ca6b2226cfe4f5f018370182a Mon Sep 17 00:00:00 2001 From: Tristan Madani Date: Tue, 21 Apr 2026 13:49:28 +0000 Subject: [PATCH 016/234] wifi: carl9170: fix buffer overflow in rx_stream failover path The failover continuation in carl9170_rx_stream() copies the full tlen from the second USB transfer instead of capping at rx_failover_missing bytes. When both transfers are near maximum size, the total exceeds the 65535-byte failover SKB, triggering skb_over_panic. Limit the copy size to the missing byte count. Fixes: a84fab3cbfdc ("carl9170: 802.11 rx/tx processing and usb backend") Signed-off-by: Tristan Madani Acked-by: Christian Lamparter Closes: https://syzkaller.appspot.com/bug?extid=5c1ca6ccaa1215781cac Link: https://patch.msgid.link/20260421134929.325662-4-tristmd@gmail.com [Fix checkpatch CHECK:PARENTHESIS_ALIGNMENT] Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/carl9170/rx.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/carl9170/rx.c b/drivers/net/wireless/ath/carl9170/rx.c index f6855efc05c0..0383d5c9698b 100644 --- a/drivers/net/wireless/ath/carl9170/rx.c +++ b/drivers/net/wireless/ath/carl9170/rx.c @@ -918,7 +918,9 @@ static void carl9170_rx_stream(struct ar9170 *ar, void *buf, unsigned int len) } } - skb_put_data(ar->rx_failover, tbuf, tlen); + skb_put_data(ar->rx_failover, tbuf, + min_t(unsigned int, tlen, + ar->rx_failover_missing)); ar->rx_failover_missing -= tlen; if (ar->rx_failover_missing <= 0) { From 1826215eb63b57a4ac8cb973785a84d703ff23f5 Mon Sep 17 00:00:00 2001 From: Avraham Stern Date: Tue, 14 Jul 2026 14:19:50 +0300 Subject: [PATCH 017/234] wifi: iwlwifi: mvm: verify scan id reported by firmware The scan id reported by firmware in scan complete notification is used as an index to the scan status array. Verify the reported id does not exceed the array size. Signed-off-by: Avraham Stern Link: https://patch.msgid.link/20260714141909.fdf31f494f1c.I70d01ed2023f6584fb23ea8ab344a93d222cc4c0@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mvm/scan.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/scan.c b/drivers/net/wireless/intel/iwlwifi/mvm/scan.c index 79829f775c89..42f9d9a713b8 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/scan.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/scan.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2012-2014, 2018-2025 Intel Corporation + * Copyright (C) 2012-2014, 2018-2026 Intel Corporation * Copyright (C) 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH */ @@ -3217,6 +3217,10 @@ void iwl_mvm_rx_umac_scan_complete_notif(struct iwl_mvm *mvm, mvm->mei_scan_filter.is_mei_limited_scan = false; + if (IWL_FW_CHECK(mvm, uid >= ARRAY_SIZE(mvm->scan_uid_status), + "FW reports out-of-range scan UID %d\n", uid)) + return; + IWL_DEBUG_SCAN(mvm, "Scan completed: uid=%u type=%u, status=%s, EBS=%s\n", uid, mvm->scan_uid_status[uid], From d77aff138c9ec6c8562f4c2c9f262d3d9c4b4cb8 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Tue, 14 Jul 2026 14:19:51 +0300 Subject: [PATCH 018/234] wifi: iwlwifi: mvm: fix an off-by-1 boundary check Before looking at the 11th byte, check the length is big enough. Signed-off-by: Emmanuel Grumbach Reviewed-by: Ilan Peer Link: https://patch.msgid.link/20260714141909.d22bf52a18d0.If0ef6612a67cca671428b06dbdeec68549e50ae6@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c index 74bd4038fd56..48cc10db7b96 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c @@ -3490,7 +3490,7 @@ static void iwl_mvm_check_he_obss_narrow_bw_ru_iter(struct wiphy *wiphy, elem = cfg80211_find_elem(WLAN_EID_EXT_CAPABILITY, ies->data, ies->len); - if (!elem || elem->datalen < 10 || + if (!elem || elem->datalen < 11 || !(elem->data[10] & WLAN_EXT_CAPA10_OBSS_NARROW_BW_RU_TOLERANCE_SUPPORT)) { data->tolerated = false; From 402620cdcf4d4ee311551906dfae832b33a0cc60 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Tue, 14 Jul 2026 14:19:52 +0300 Subject: [PATCH 019/234] wifi: iwlwifi: mld: fix an off-by-1 boundary check Before looking at the 11th byte, check the length is big enough. Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260714141909.336b527e3fc6.I6fe839f4e70d673632fd7ca757e81827af87b029@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/mac80211.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c index 17286b3341c0..9ac17be30400 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c @@ -1708,7 +1708,7 @@ static void iwl_mld_check_he_obss_narrow_bw_ru_iter(struct wiphy *wiphy, elem = cfg80211_find_elem(WLAN_EID_EXT_CAPABILITY, ies->data, ies->len); - if (!elem || elem->datalen < 10 || + if (!elem || elem->datalen < 11 || !(elem->data[10] & WLAN_EXT_CAPA10_OBSS_NARROW_BW_RU_TOLERANCE_SUPPORT)) { *tolerated = false; From 119c353467d802865e2f5da210b64727c5e334b4 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Tue, 14 Jul 2026 14:19:53 +0300 Subject: [PATCH 020/234] wifi: iwlwifi: mld: don't parse a notif before checking its length In order to compure the size of the iwl_mcc_update_resp which has a variable length, we need to know the number of channels. In order to read the number of channels, we must first check the payload is long enough to read at least that. Add this check. Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260714141909.c2f644919011.Ic579e9935b92a674c96ccc44713140b5b4bc5d10@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/mcc.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mcc.c b/drivers/net/wireless/intel/iwlwifi/mld/mcc.c index 8502129abe49..830c251f43af 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mcc.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mcc.c @@ -18,9 +18,15 @@ static struct iwl_mcc_update_resp_v8 * iwl_mld_copy_mcc_resp(const struct iwl_rx_packet *pkt) { const struct iwl_mcc_update_resp_v8 *mcc_resp_v8 = (const void *)pkt->data; - int n_channels = __le32_to_cpu(mcc_resp_v8->n_channels); struct iwl_mcc_update_resp_v8 *resp_cp; - int notif_len = struct_size(resp_cp, channels, n_channels); + int n_channels; + int notif_len; + + if (iwl_rx_packet_payload_len(pkt) < sizeof(*mcc_resp_v8)) + return ERR_PTR(-EINVAL); + + n_channels = __le32_to_cpu(mcc_resp_v8->n_channels); + notif_len = struct_size(resp_cp, channels, n_channels); if (iwl_rx_packet_payload_len(pkt) != notif_len) return ERR_PTR(-EINVAL); From 7da7162652a9e254f2c6055cb8612bbe8a49a554 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Tue, 14 Jul 2026 14:19:54 +0300 Subject: [PATCH 021/234] wifi: iwlwifi: mvm: fix the FCS truncation logic in d3 Fix a harmless mistake in the wake packet management code in the d3 wakeup flow. If the FCS is truncated, we want to detect it, but we cleared the icvlen before updating the truncated variable that holds the number of bytes having been truncated. Fix that. Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260714141909.369588f93c6b.I1a4d13f276c7e75514ab2032ae387873337470b8@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mvm/d3.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/d3.c b/drivers/net/wireless/intel/iwlwifi/mvm/d3.c index 9a74f60c9185..d7ceb385ae0b 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/d3.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/d3.c @@ -1539,8 +1539,8 @@ static void iwl_mvm_report_wakeup_reasons(struct iwl_mvm *mvm, /* if truncated, FCS/ICV is (partially) gone */ if (truncated >= icvlen) { - icvlen = 0; truncated -= icvlen; + icvlen = 0; } else { icvlen -= truncated; truncated = 0; From 219292e73e40d131925a1caf413203671092dd7a Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Tue, 14 Jul 2026 14:19:55 +0300 Subject: [PATCH 022/234] wifi: iwlwifi: mld: validate reorder BAID Reject BAIDs >= IWL_MAX_BAID before indexing fw_id_to_ba. This prevents out-of-bounds access on malformed notifications. Assisted-by: GitHubCopilot:gpt-5.3-codex Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260714141909.07ea823b8eea.Ica915fa0cce0427bf5e3420ae933f57118fedf86@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/agg.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/agg.c b/drivers/net/wireless/intel/iwlwifi/mld/agg.c index e3627ad0321c..a464ebdec57f 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/agg.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/agg.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2024-2025 Intel Corporation + * Copyright (C) 2024-2026 Intel Corporation */ #include "agg.h" #include "sta.h" @@ -222,6 +222,11 @@ iwl_mld_reorder(struct iwl_mld *mld, struct napi_struct *napi, if (baid == IWL_RX_REORDER_DATA_INVALID_BAID) return IWL_MLD_PASS_SKB; + if (IWL_FW_CHECK(mld, baid >= ARRAY_SIZE(mld->fw_id_to_ba), + "Got out-of-range BAID %u in reorder_data=0x%x\n", + baid, reorder)) + return IWL_MLD_PASS_SKB; + /* no sta yet */ if (WARN_ONCE(!sta, "Got valid BAID without a valid station assigned\n")) From b77c6f50b1f80414cb3f542ae72e532ed90fc7f7 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Tue, 14 Jul 2026 14:19:56 +0300 Subject: [PATCH 023/234] wifi: iwlwifi: mvm: parse beacon notif per layout The beacon TX notification can arrive in different layouts, and fields must be read only after selecting the expected format. Parse gp2 and TSF from the matching notification structure in each branch, and keep using the parsed gp2 for CSA countdown and debug output. Drop the obsolete cached gp2 field. Assisted-by: GitHub Copilot:gpt-5.3-codex Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260714141909.cc8aa937f8e5.I921f8dadcb20cb73e8283e1b8546e1778205411f@changeid Signed-off-by: Miri Korenblit --- .../net/wireless/intel/iwlwifi/mvm/mac-ctxt.c | 25 +++++++++++-------- .../net/wireless/intel/iwlwifi/mvm/mac80211.c | 1 - drivers/net/wireless/intel/iwlwifi/mvm/mvm.h | 3 --- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mac-ctxt.c b/drivers/net/wireless/intel/iwlwifi/mvm/mac-ctxt.c index d6a8624b1ae5..b2c5be22c293 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/mac-ctxt.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mac-ctxt.c @@ -1493,49 +1493,54 @@ void iwl_mvm_rx_beacon_notif(struct iwl_mvm *mvm, { struct iwl_rx_packet *pkt = rxb_addr(rxb); unsigned int pkt_len = iwl_rx_packet_payload_len(pkt); - struct iwl_extended_beacon_notif *beacon = (void *)pkt->data; - struct iwl_extended_beacon_notif_v5 *beacon_v5 = (void *)pkt->data; struct ieee80211_vif *csa_vif; struct ieee80211_vif *tx_blocked_vif; struct agg_tx_status *agg_status; + u32 beacon_gp2; u16 status; lockdep_assert_held(&mvm->mutex); - mvm->ap_last_beacon_gp2 = le32_to_cpu(beacon->gp2); - if (!iwl_mvm_is_short_beacon_notif_supported(mvm)) { + struct iwl_extended_beacon_notif_v5 *beacon = (void *)pkt->data; struct iwl_tx_resp *beacon_notify_hdr = - &beacon_v5->beacon_notify_hdr; + &beacon->beacon_notify_hdr; - if (unlikely(pkt_len < sizeof(*beacon_v5))) + if (unlikely(pkt_len < sizeof(*beacon))) return; - mvm->ibss_manager = beacon_v5->ibss_mgr_status != 0; + beacon_gp2 = le32_to_cpu(beacon->gp2); + + mvm->ibss_manager = beacon->ibss_mgr_status != 0; agg_status = iwl_mvm_get_agg_status(mvm, beacon_notify_hdr); status = le16_to_cpu(agg_status->status) & TX_STATUS_MSK; IWL_DEBUG_RX(mvm, "beacon status %#x retries:%d tsf:0x%016llX gp2:0x%X rate:%d\n", status, beacon_notify_hdr->failure_frame, le64_to_cpu(beacon->tsf), - mvm->ap_last_beacon_gp2, + beacon_gp2, le32_to_cpu(beacon_notify_hdr->initial_rate)); } else { + const struct iwl_extended_beacon_notif *beacon = + (void *)pkt->data; + if (unlikely(pkt_len < sizeof(*beacon))) return; + beacon_gp2 = le32_to_cpu(beacon->gp2); + mvm->ibss_manager = beacon->ibss_mgr_status != 0; status = le32_to_cpu(beacon->status) & TX_STATUS_MSK; IWL_DEBUG_RX(mvm, "beacon status %#x tsf:0x%016llX gp2:0x%X\n", status, le64_to_cpu(beacon->tsf), - mvm->ap_last_beacon_gp2); + beacon_gp2); } csa_vif = rcu_dereference_protected(mvm->csa_vif, lockdep_is_held(&mvm->mutex)); if (unlikely(csa_vif && csa_vif->bss_conf.csa_active)) - iwl_mvm_csa_count_down(mvm, csa_vif, mvm->ap_last_beacon_gp2, + iwl_mvm_csa_count_down(mvm, csa_vif, beacon_gp2, (status == TX_STATUS_SUCCESS)); tx_blocked_vif = rcu_dereference_protected(mvm->csa_tx_blocked_vif, diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c index 48cc10db7b96..f4f4446bef54 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c @@ -3061,7 +3061,6 @@ void iwl_mvm_stop_ap_ibss_common(struct iwl_mvm *mvm, } mvmvif->ap_ibss_active = false; - mvm->ap_last_beacon_gp2 = 0; if (vif->type == NL80211_IFTYPE_AP && !vif->p2p) { iwl_mvm_vif_set_low_latency(mvmvif, false, diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h b/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h index 683cac56822c..31912f4d0175 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h @@ -1168,9 +1168,6 @@ struct iwl_mvm { struct ieee80211_vif __rcu *csa_tx_blocked_vif; u8 csa_tx_block_bcn_timeout; - /* system time of last beacon (for AP/GO interface) */ - u32 ap_last_beacon_gp2; - /* indicates that we transmitted the last beacon */ bool ibss_manager; From 77f33bed0cb49a11f03427f2fa368830c1cae3c2 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Tue, 14 Jul 2026 14:19:57 +0300 Subject: [PATCH 024/234] wifi: iwlwifi: mvm: validate MCC header before n_channels MCC response parsing read n_channels from v8/v4/v3 response variants before ensuring the payload contained the fixed response header. Add a minimum payload-length check for each response version before reading n_channels, and keep the existing exact-size validation for the channels array payload. Assisted-by: GitHub Copilot:gpt-5.3-codex Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260714141909.cb2cef3d3e7e.Iee7b48614289da576de842157ad3730b7589a4b1@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mvm/nvm.c | 43 ++++++++++++++++---- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/nvm.c b/drivers/net/wireless/intel/iwlwifi/mvm/nvm.c index 953218f1e025..f76e57399c1f 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/nvm.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/nvm.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2012-2014, 2018-2019, 2021-2025 Intel Corporation + * Copyright (C) 2012-2014, 2018-2019, 2021-2026 Intel Corporation * Copyright (C) 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH */ @@ -416,6 +416,7 @@ iwl_mvm_update_mcc(struct iwl_mvm *mvm, const char *alpha2, int ret, resp_ver; u32 status; int resp_len, n_channels; + unsigned int pkt_len; u16 mcc; if (WARN_ON_ONCE(!iwl_mvm_is_lar_supported(mvm))) @@ -431,6 +432,7 @@ iwl_mvm_update_mcc(struct iwl_mvm *mvm, const char *alpha2, return ERR_PTR(ret); pkt = cmd.resp_pkt; + pkt_len = iwl_rx_packet_payload_len(pkt); resp_ver = iwl_fw_lookup_notif_ver(mvm->fw, IWL_ALWAYS_LONG_GROUP, MCC_UPDATE_CMD, 0); @@ -439,9 +441,18 @@ iwl_mvm_update_mcc(struct iwl_mvm *mvm, const char *alpha2, if (resp_ver >= 8) { struct iwl_mcc_update_resp_v8 *mcc_resp_v8 = (void *)pkt->data; + if (IWL_FW_CHECK(mvm, pkt_len < sizeof(*mcc_resp_v8), + "MCC v8 response too short: %u\n", pkt_len)) { + resp_cp = ERR_PTR(-EINVAL); + goto exit; + } + n_channels = __le32_to_cpu(mcc_resp_v8->n_channels); - if (iwl_rx_packet_payload_len(pkt) != - struct_size(mcc_resp_v8, channels, n_channels)) { + if (IWL_FW_CHECK(mvm, + pkt_len != + struct_size(mcc_resp_v8, channels, n_channels), + "invalid MCC v8 response size: %u (n_channels=%d)\n", + pkt_len, n_channels)) { resp_cp = ERR_PTR(-EINVAL); goto exit; } @@ -464,9 +475,18 @@ iwl_mvm_update_mcc(struct iwl_mvm *mvm, const char *alpha2, IWL_UCODE_TLV_CAPA_MCC_UPDATE_11AX_SUPPORT)) { struct iwl_mcc_update_resp_v4 *mcc_resp_v4 = (void *)pkt->data; + if (IWL_FW_CHECK(mvm, pkt_len < sizeof(*mcc_resp_v4), + "MCC v4 response too short: %u\n", pkt_len)) { + resp_cp = ERR_PTR(-EINVAL); + goto exit; + } + n_channels = __le32_to_cpu(mcc_resp_v4->n_channels); - if (iwl_rx_packet_payload_len(pkt) != - struct_size(mcc_resp_v4, channels, n_channels)) { + if (IWL_FW_CHECK(mvm, + pkt_len != + struct_size(mcc_resp_v4, channels, n_channels), + "invalid MCC v4 response size: %u (n_channels=%d)\n", + pkt_len, n_channels)) { resp_cp = ERR_PTR(-EINVAL); goto exit; } @@ -489,9 +509,18 @@ iwl_mvm_update_mcc(struct iwl_mvm *mvm, const char *alpha2, } else { struct iwl_mcc_update_resp_v3 *mcc_resp_v3 = (void *)pkt->data; + if (IWL_FW_CHECK(mvm, pkt_len < sizeof(*mcc_resp_v3), + "MCC v3 response too short: %u\n", pkt_len)) { + resp_cp = ERR_PTR(-EINVAL); + goto exit; + } + n_channels = __le32_to_cpu(mcc_resp_v3->n_channels); - if (iwl_rx_packet_payload_len(pkt) != - struct_size(mcc_resp_v3, channels, n_channels)) { + if (IWL_FW_CHECK(mvm, + pkt_len != + struct_size(mcc_resp_v3, channels, n_channels), + "invalid MCC v3 response size: %u (n_channels=%d)\n", + pkt_len, n_channels)) { resp_cp = ERR_PTR(-EINVAL); goto exit; } From 3ed8d1705d3aa5fbec918b8e241b41c483706cc2 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Tue, 14 Jul 2026 14:19:58 +0300 Subject: [PATCH 025/234] wifi: iwlwifi: mvm: validate sta_id in TLC notif TLC_MNG_UPDATE_NOTIF uses firmware-provided sta_id to index fw_id_to_link_sta[] and fw_id_to_mac_id[]. Validate sta_id before array access to avoid out-of-bounds indexing. Assisted-by: GitHubCopilot:gpt-5.3-codex Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260714141909.1ce54794c1f8.I275fd4c1165bf42fb17516c550dd8813a2b8286e@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mvm/rs-fw.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/rs-fw.c b/drivers/net/wireless/intel/iwlwifi/mvm/rs-fw.c index 89ac4c6b3e54..e2382be8edd7 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/rs-fw.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/rs-fw.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* * Copyright (C) 2017 Intel Deutschland GmbH - * Copyright (C) 2018-2025 Intel Corporation + * Copyright (C) 2018-2026 Intel Corporation */ #include "rs.h" #include "fw-api.h" @@ -423,9 +423,14 @@ void iwl_mvm_tlc_update_notif(struct iwl_mvm *mvm, struct iwl_lq_sta_rs_fw *lq_sta; u32 flags; + notif = (void *)pkt->data; + if (IWL_FW_CHECK(mvm, notif->sta_id >= mvm->fw->ucode_capa.num_stations, + "Invalid sta id (%d) in TLC notification\n", + notif->sta_id)) + return; + rcu_read_lock(); - notif = (void *)pkt->data; link_sta = rcu_dereference(mvm->fw_id_to_link_sta[notif->sta_id]); sta = rcu_dereference(mvm->fw_id_to_mac_id[notif->sta_id]); if (IS_ERR_OR_NULL(sta) || !link_sta) { From 6aa77efaea9efea92e3090c35ad348fd759a3cf3 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Tue, 14 Jul 2026 14:19:59 +0300 Subject: [PATCH 026/234] wifi: iwlwifi: mvm: validate sta_id in BA window status notif BA_WINDOW_STATUS_NOTIFICATION_ID extracts a 5-bit sta_id from the firmware notification and uses it to index fw_id_to_mac_id[] without bounds checking. Validate sta_id before array access to prevent out-of-bounds indexing. Assisted-by: GitHubCopilot:gpt-5.3-codex Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260714141909.2e97f337f3cb.Ic3f0f404082ccdea13809a3c0b70e0f5417e1037@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mvm/rx.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/rx.c b/drivers/net/wireless/intel/iwlwifi/mvm/rx.c index 269c4b45de80..ab1eb2eb0c3c 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/rx.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/rx.c @@ -1227,6 +1227,11 @@ void iwl_mvm_window_status_notif(struct iwl_mvm *mvm, /* get the station */ sta_id = (ratid & BA_WINDOW_STATUS_STA_ID_MSK) >> BA_WINDOW_STATUS_STA_ID_POS; + if (IWL_FW_CHECK(mvm, + sta_id >= mvm->fw->ucode_capa.num_stations, + "Invalid sta id (%d) in BA window status notification\n", + sta_id)) + continue; sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]); if (IS_ERR_OR_NULL(sta)) continue; From 71245daf7d58a3c407c7e1422facce13ff6a584b Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Tue, 14 Jul 2026 14:20:00 +0300 Subject: [PATCH 027/234] wifi: iwlwifi: mvm: validate mac_link_id in session protect notif Check the mac_id before accessing the vif_id_to_mac array. Assisted-by: GitHubCopilot:gpt-5.3-codex Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260714141909.547ea470e686.I931445ae6f37bf0e1ef6f112c811712fc48af9c9@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mvm/time-event.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/time-event.c b/drivers/net/wireless/intel/iwlwifi/mvm/time-event.c index 1692b6e75f57..93b46c9e2333 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/time-event.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/time-event.c @@ -951,6 +951,11 @@ void iwl_mvm_rx_session_protect_notif(struct iwl_mvm *mvm, struct ieee80211_vif *vif; struct iwl_mvm_vif *mvmvif; + if (IWL_FW_CHECK(mvm, id >= ARRAY_SIZE(mvm->vif_id_to_mac), + "Invalid mac_link_id (%d) in session protect notif\n", + id)) + return; + rcu_read_lock(); /* note we use link ID == MAC ID */ From 5c55827ef5c74a5d56939ed0e93df21e6f1f864e Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Tue, 14 Jul 2026 14:20:01 +0300 Subject: [PATCH 028/234] wifi: iwlwifi: mld: clear tzone on fail iwl_mld_thermal_zone_register() stores the thermal zone pointer in mld->tzone before calling thermal_zone_device_enable(). If enable fails, the code unregisters the zone but leaves mld->tzone stale, so iwl_mld_thermal_zone_unregister() can unregister it again. Clear mld->tzone after unregister in the error path. While at it remove a pointless if in iwl_mld_thermal_zone_unregister after we've alredy checked the tzone pointer is not NULL. Assisted-by: GitHubCopilot:gpt-5.3-codex Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260714141909.595dcb8cb7fe.I8125e4a2eeb0390798e3f4074c62c00443eda8e8@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/thermal.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/thermal.c b/drivers/net/wireless/intel/iwlwifi/mld/thermal.c index f8a8c35066be..e445b1d7d4b0 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/thermal.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/thermal.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2024-2025 Intel Corporation + * Copyright (C) 2024-2026 Intel Corporation */ #ifdef CONFIG_THERMAL #include @@ -272,6 +272,7 @@ static void iwl_mld_thermal_zone_register(struct iwl_mld *mld) if (ret) { IWL_DEBUG_TEMP(mld, "Failed to enable thermal zone\n"); thermal_zone_device_unregister(mld->tzone); + mld->tzone = NULL; } } @@ -385,10 +386,8 @@ static void iwl_mld_thermal_zone_unregister(struct iwl_mld *mld) return; IWL_DEBUG_TEMP(mld, "Thermal zone device unregister\n"); - if (mld->tzone) { - thermal_zone_device_unregister(mld->tzone); - mld->tzone = NULL; - } + thermal_zone_device_unregister(mld->tzone); + mld->tzone = NULL; } static void iwl_mld_cooling_device_unregister(struct iwl_mld *mld) From 4f155d262b31b9b17e0f9856bdabe0968eb4930f Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Tue, 14 Jul 2026 14:20:02 +0300 Subject: [PATCH 029/234] wifi: iwlwifi: mvm: fix sched scan IE sizing Scheduled scan built the probe request before iwl_mvm_scan_fits(), so oversized IEs could be copied into the fixed preq buffer before length validation. Move iwl_mvm_build_scan_probe() after the fits check. Also advertise max_sched_scan_ie_len using iwl_mvm_max_scan_ie_len() so userspace limits account for driver-inserted DS/TPC bytes. Assisted-by: GitHubCopilot:gpt-5.3-codex Signed-off-by: Emmanuel Grumbach Reviewed-by: Ilan Peer Link: https://patch.msgid.link/20260714141909.53d2722c79e7.Iebb922efa6173c92f14cd8aa8b4e7f372c0a0fb7@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c | 4 +--- drivers/net/wireless/intel/iwlwifi/mvm/scan.c | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c index f4f4446bef54..3e73a6195fd9 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c @@ -625,9 +625,7 @@ int iwl_mvm_mac_setup_register(struct iwl_mvm *mvm) hw->wiphy->max_sched_scan_reqs = 1; hw->wiphy->max_sched_scan_ssids = PROBE_OPTION_MAX; hw->wiphy->max_match_sets = iwl_umac_scan_get_max_profiles(mvm->fw); - /* we create the 802.11 header and zero length SSID IE. */ - hw->wiphy->max_sched_scan_ie_len = - SCAN_OFFLOAD_PROBE_REQ_SIZE - 24 - 2; + hw->wiphy->max_sched_scan_ie_len = iwl_mvm_max_scan_ie_len(mvm); hw->wiphy->max_sched_scan_plans = IWL_MAX_SCHED_SCAN_PLANS; hw->wiphy->max_sched_scan_plan_interval = U16_MAX; diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/scan.c b/drivers/net/wireless/intel/iwlwifi/mvm/scan.c index 42f9d9a713b8..3831b3c27e0f 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/scan.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/scan.c @@ -3144,8 +3144,6 @@ int iwl_mvm_sched_scan_start(struct iwl_mvm *mvm, if (ret) return ret; - iwl_mvm_build_scan_probe(mvm, vif, ies, ¶ms); - /* for 6 GHZ band only PSC channels need to be added */ for (i = 0; i < params.n_channels; i++) { struct ieee80211_channel *channel = params.channels[i]; @@ -3179,6 +3177,8 @@ int iwl_mvm_sched_scan_start(struct iwl_mvm *mvm, goto out; } + iwl_mvm_build_scan_probe(mvm, vif, ies, ¶ms); + uid = iwl_mvm_build_scan_cmd(mvm, vif, &hcmd, ¶ms, type); if (uid < 0) { ret = uid; From 2c79d7a7b583050c9f58041465cb46fe3483ab5d Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Tue, 14 Jul 2026 14:20:03 +0300 Subject: [PATCH 030/234] wifi: iwlwifi: pcie: null RX pointers after free When iwl_pcie_tx_init() fails after RX init, nic init unwinds via iwl_pcie_rx_free(). The freed RX members stayed non-NULL on the live transport object, so later teardown or retry could touch stale RX state. Set rx_pool, global_table, rxq, and alloc_page to NULL after free to make repeated cleanup and retry paths safe. Assisted-by: GitHubCopilot:gpt-5.3-codex Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260714141909.33e8978d8b36.Ibaedd4b0ce01405b940de7b90223b6d2c5136ffd@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/rx.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/rx.c b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/rx.c index fe263cdc2e4f..4631e11f2a96 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/rx.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/rx.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2003-2014, 2018-2024 Intel Corporation + * Copyright (C) 2003-2014, 2018-2024, 2026 Intel Corporation * Copyright (C) 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH */ @@ -1238,11 +1238,16 @@ void iwl_pcie_rx_free(struct iwl_trans *trans) } } kfree(trans_pcie->rx_pool); + trans_pcie->rx_pool = NULL; kfree(trans_pcie->global_table); + trans_pcie->global_table = NULL; kfree(trans_pcie->rxq); + trans_pcie->rxq = NULL; - if (trans_pcie->alloc_page) + if (trans_pcie->alloc_page) { __free_pages(trans_pcie->alloc_page, trans_pcie->rx_page_order); + trans_pcie->alloc_page = NULL; + } } static void iwl_pcie_rx_move_to_allocator(struct iwl_rxq *rxq, From c1a1dc162870a5447cb0fbcf81983473744772d2 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Tue, 14 Jul 2026 14:20:04 +0300 Subject: [PATCH 031/234] wifi: iwlwifi: mvm: d3: validate D3 resume notification payloads D3 resume notification handlers read firmware notification fields before validating that the payload contains the complete fixed structure. This causes buffer underread on malformed or truncated notifications. Move payload length validation to occur before any field access in: - iwl_mvm_parse_wowlan_info_notif: validate before reading num_mlo_link_keys - iwl_mvm_wait_d3_notif D3_END handler: validate before reading flags Assisted-by: GitHub Copilot Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260714141909.762193753434.I148991b8136cc5042fa08b5faf7b57d38aa2fb47@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mvm/d3.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/d3.c b/drivers/net/wireless/intel/iwlwifi/mvm/d3.c index d7ceb385ae0b..3429d9a10e42 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/d3.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/d3.c @@ -2123,16 +2123,16 @@ static void iwl_mvm_parse_wowlan_info_notif(struct iwl_mvm *mvm, struct iwl_wowlan_status_data *status, u32 len) { - if (IWL_FW_CHECK(mvm, data->num_mlo_link_keys, - "MLO is not supported, shouldn't receive MLO keys\n")) - return; - if (len < sizeof(*data)) { IWL_ERR(mvm, "Invalid WoWLAN info notification!\n"); status = NULL; return; } + if (IWL_FW_CHECK(mvm, data->num_mlo_link_keys, + "MLO is not supported, shouldn't receive MLO keys\n")) + return; + if (mvm->fast_resume) return; @@ -2942,6 +2942,11 @@ static bool iwl_mvm_wait_d3_notif(struct iwl_notif_wait_data *notif_wait, case WIDE_ID(PROT_OFFLOAD_GROUP, D3_END_NOTIFICATION): { struct iwl_d3_end_notif *notif = (void *)pkt->data; + if (len < sizeof(*notif)) { + IWL_ERR(mvm, "Invalid D3 end notification size\n"); + break; + } + d3_data->d3_end_flags = __le32_to_cpu(notif->flags); d3_data->notif_received |= IWL_D3_NOTIF_D3_END_NOTIF; From e66ddfd94b829b8ecaaac932d9487fb4de6d267c Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Wed, 15 Jul 2026 21:57:01 +0300 Subject: [PATCH 032/234] wifi: iwlwifi: mld: validate txq_id in TX response handler Validate txq_id from TX response notification before passing to iwl_trans_reclaim(). Other reclaim paths in this file perform this check to prevent out-of-bounds access on malformed notifications. Assisted-by: GitHubCopilot:claude-haiku-4.5 Signed-off-by: Emmanuel Grumbach Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715215523.b938c2dcf08d.I8a88ec359e229f1c41ac9c49fd9ce28a2b62b274@changeid --- drivers/net/wireless/intel/iwlwifi/mld/tx.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tx.c b/drivers/net/wireless/intel/iwlwifi/mld/tx.c index 2185dade95f8..2df6643a5aa8 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/tx.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/tx.c @@ -1120,6 +1120,10 @@ void iwl_mld_handle_tx_resp_notif(struct iwl_mld *mld, ssn = le32_to_cpup((__le32 *)agg_status + tx_resp->frame_count) & 0xFFFF; + if (IWL_FW_CHECK(mld, txq_id >= ARRAY_SIZE(mld->fw_id_to_txq), + "Invalid txq id %d\n", txq_id)) + return; + __skb_queue_head_init(&skbs); /* we can free until ssn % q.n_bd not inclusive */ From 7786233bffdd88b7bab816012a41597c2065470c Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Wed, 15 Jul 2026 21:57:02 +0300 Subject: [PATCH 033/234] wifi: iwlwifi: add support for additional channels in NVM_GET_INFO We need to expect more channels on devices that support UNII-9. Since iwl_ext_nvm_channels and iwl_uhb_nvm_channels are just a prefix of iwl_unii9_nvm_channels just use iwl_unii9_nvm_channels and modify the number of channels if the device does not support UNII-9 channels. Signed-off-by: Emmanuel Grumbach Reviewed-by: Johannes Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715215523.473f48e6135d.I0e93cda753558aa4d9e3efcdd52cbc2eb9302c4a@changeid --- .../net/wireless/intel/iwlwifi/cfg/rf-pe.c | 1 + .../wireless/intel/iwlwifi/fw/api/nvm-reg.h | 33 +++++++++- .../net/wireless/intel/iwlwifi/iwl-config.h | 2 + .../wireless/intel/iwlwifi/iwl-nvm-parse.c | 64 ++++++++++++------- 4 files changed, 74 insertions(+), 26 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/rf-pe.c b/drivers/net/wireless/intel/iwlwifi/cfg/rf-pe.c index 7a04cb120b1b..6080f5f23e69 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/rf-pe.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/rf-pe.c @@ -15,6 +15,7 @@ .non_shared_ant = ANT_B, \ .vht_mu_mimo_supported = true, \ .uhb_supported = true, \ + .unii9_supported = true, \ .eht_supported = true, \ .uhr_supported = true, \ .num_rbds = IWL_NUM_RBDS_EHT, \ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h b/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h index 443a9a416325..0172c0747a47 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h @@ -205,6 +205,7 @@ struct iwl_nvm_get_info_phy { #define IWL_NUM_CHANNELS_V1 51 #define IWL_NUM_CHANNELS_V2 110 +#define IWL_NUM_CHANNELS_V3 115 /** * struct iwl_nvm_get_info_regulatory_v1 - regulatory information @@ -219,12 +220,12 @@ struct iwl_nvm_get_info_regulatory_v1 { } __packed; /* REGULATORY_NVM_GET_INFO_REGULATORY_S_VER_1 */ /** - * struct iwl_nvm_get_info_regulatory - regulatory information + * struct iwl_nvm_get_info_regulatory_v2 - regulatory information * @lar_enabled: is LAR enabled * @n_channels: number of valid channels in the array * @channel_profile: regulatory data of this channel */ -struct iwl_nvm_get_info_regulatory { +struct iwl_nvm_get_info_regulatory_v2 { __le32 lar_enabled; __le32 n_channels; __le32 channel_profile[IWL_NUM_CHANNELS_V2]; @@ -244,6 +245,32 @@ struct iwl_nvm_get_info_rsp_v3 { struct iwl_nvm_get_info_regulatory_v1 regulatory; } __packed; /* REGULATORY_NVM_GET_INFO_RSP_API_S_VER_3 */ +/** + * struct iwl_nvm_get_info_rsp_v4 - response to get NVM data + * @general: general NVM data + * @mac_sku: data relating to MAC sku + * @phy_sku: data relating to PHY sku + * @regulatory: regulatory data + */ +struct iwl_nvm_get_info_rsp_v4 { + struct iwl_nvm_get_info_general general; + struct iwl_nvm_get_info_sku mac_sku; + struct iwl_nvm_get_info_phy phy_sku; + struct iwl_nvm_get_info_regulatory_v2 regulatory; +} __packed; /* REGULATORY_NVM_GET_INFO_RSP_API_S_VER_4 */ + +/** + * struct iwl_nvm_get_info_regulatory - regulatory information + * @lar_enabled: is LAR enabled + * @n_channels: number of valid channels in the array + * @channel_profile: regulatory data of this channel + */ +struct iwl_nvm_get_info_regulatory { + __le32 lar_enabled; + __le32 n_channels; + __le32 channel_profile[IWL_NUM_CHANNELS_V3]; +} __packed; /* REGULATORY_NVM_GET_INFO_REGULATORY_S_VER_3 */ + /** * struct iwl_nvm_get_info_rsp - response to get NVM data * @general: general NVM data @@ -256,7 +283,7 @@ struct iwl_nvm_get_info_rsp { struct iwl_nvm_get_info_sku mac_sku; struct iwl_nvm_get_info_phy phy_sku; struct iwl_nvm_get_info_regulatory regulatory; -} __packed; /* REGULATORY_NVM_GET_INFO_RSP_API_S_VER_4 */ +} __packed; /* REGULATORY_NVM_GET_INFO_RSP_API_S_VER_5 */ /** * struct iwl_nvm_access_complete_cmd - NVM_ACCESS commands are completed diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-config.h b/drivers/net/wireless/intel/iwlwifi/iwl-config.h index 6a3539ad7331..3ac7f000ede4 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-config.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-config.h @@ -415,6 +415,7 @@ struct iwl_mac_cfg { * @vht_mu_mimo_supported: VHT MU-MIMO support * @nvm_type: see &enum iwl_nvm_type * @uhb_supported: ultra high band channels supported + * @unii9_supported: UNII-9 channels supported * @eht_supported: EHT supported * @uhr_supported: UHR supported * @num_rbds: number of receive buffer descriptors to use @@ -450,6 +451,7 @@ struct iwl_rf_cfg { lp_xtal_workaround:1, vht_mu_mimo_supported:1, uhb_supported:1, + unii9_supported:1, eht_supported:1, uhr_supported:1; u8 valid_tx_ant; diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c index d47b4ae2f486..bd5353fdde81 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c @@ -85,16 +85,7 @@ static const u16 iwl_nvm_channels[] = { 149, 153, 157, 161, 165 }; -static const u16 iwl_ext_nvm_channels[] = { - /* 2.4 GHz */ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - /* 5 GHz */ - 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, - 96, 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144, - 149, 153, 157, 161, 165, 169, 173, 177, 181 -}; - -static const u16 iwl_uhb_nvm_channels[] = { +static const u16 iwl_unii9_nvm_channels[] = { /* 2.4 GHz */ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /* 5 GHz */ @@ -105,12 +96,16 @@ static const u16 iwl_uhb_nvm_channels[] = { 1, 5, 9, 13, 17, 21, 25, 29, 33, 37, 41, 45, 49, 53, 57, 61, 65, 69, 73, 77, 81, 85, 89, 93, 97, 101, 105, 109, 113, 117, 121, 125, 129, 133, 137, 141, 145, 149, 153, 157, 161, 165, 169, 173, 177, 181, 185, - 189, 193, 197, 201, 205, 209, 213, 217, 221, 225, 229, 233 + 189, 193, 197, 201, 205, 209, 213, 217, 221, 225, 229, 233, + + /* UNII-9 */ + 237, 241, 245, 249, 253 }; #define IWL_NVM_NUM_CHANNELS ARRAY_SIZE(iwl_nvm_channels) -#define IWL_NVM_NUM_CHANNELS_EXT ARRAY_SIZE(iwl_ext_nvm_channels) -#define IWL_NVM_NUM_CHANNELS_UHB ARRAY_SIZE(iwl_uhb_nvm_channels) +#define IWL_NVM_NUM_CHANNELS_EXT 51 +#define IWL_NVM_NUM_CHANNELS_UHB 110 +#define IWL_NVM_NUM_CHANNELS_UNII9 ARRAY_SIZE(iwl_unii9_nvm_channels) #define NUM_2GHZ_CHANNELS 14 #define NUM_5GHZ_CHANNELS 37 #define FIRST_2GHZ_HT_MINUS 5 @@ -351,12 +346,15 @@ static int iwl_init_channel_map(struct iwl_trans *trans, int num_of_ch; const u16 *nvm_chan; - if (cfg->uhb_supported) { + if (cfg->unii9_supported) { + num_of_ch = IWL_NVM_NUM_CHANNELS_UNII9; + nvm_chan = iwl_unii9_nvm_channels; + } else if (cfg->uhb_supported) { num_of_ch = IWL_NVM_NUM_CHANNELS_UHB; - nvm_chan = iwl_uhb_nvm_channels; + nvm_chan = iwl_unii9_nvm_channels; } else if (cfg->nvm_type == IWL_NVM_EXT) { num_of_ch = IWL_NVM_NUM_CHANNELS_EXT; - nvm_chan = iwl_ext_nvm_channels; + nvm_chan = iwl_unii9_nvm_channels; } else { num_of_ch = IWL_NVM_NUM_CHANNELS; nvm_chan = iwl_nvm_channels; @@ -1441,7 +1439,9 @@ iwl_parse_mei_nvm_data(struct iwl_trans *trans, const struct iwl_rf_cfg *cfg, u8 rx_chains = fw->valid_rx_ant; u8 tx_chains = fw->valid_rx_ant; - if (cfg->uhb_supported) + if (cfg->unii9_supported) + data = kzalloc_flex(*data, channels, IWL_NVM_NUM_CHANNELS_UNII9); + else if (cfg->uhb_supported) data = kzalloc_flex(*data, channels, IWL_NVM_NUM_CHANNELS_UHB); else data = kzalloc_flex(*data, channels, IWL_NVM_NUM_CHANNELS_EXT); @@ -1506,7 +1506,9 @@ iwl_parse_nvm_data(struct iwl_trans *trans, const struct iwl_rf_cfg *cfg, u16 lar_config; const __le16 *ch_section; - if (cfg->uhb_supported) + if (cfg->unii9_supported) + data = kzalloc_flex(*data, channels, IWL_NVM_NUM_CHANNELS_UNII9); + else if (cfg->uhb_supported) data = kzalloc_flex(*data, channels, IWL_NVM_NUM_CHANNELS_UHB); else if (cfg->nvm_type != IWL_NVM_EXT) data = kzalloc_flex(*data, channels, IWL_NVM_NUM_CHANNELS); @@ -1727,12 +1729,15 @@ iwl_parse_nvm_mcc_info(struct iwl_trans *trans, int max_num_ch; struct iwl_reg_capa reg_capa; - if (cfg->uhb_supported) { + if (cfg->unii9_supported) { + max_num_ch = IWL_NVM_NUM_CHANNELS_UNII9; + nvm_chan = iwl_unii9_nvm_channels; + } else if (cfg->uhb_supported) { max_num_ch = IWL_NVM_NUM_CHANNELS_UHB; - nvm_chan = iwl_uhb_nvm_channels; + nvm_chan = iwl_unii9_nvm_channels; } else if (cfg->nvm_type == IWL_NVM_EXT) { max_num_ch = IWL_NVM_NUM_CHANNELS_EXT; - nvm_chan = iwl_ext_nvm_channels; + nvm_chan = iwl_unii9_nvm_channels; } else { max_num_ch = IWL_NVM_NUM_CHANNELS; nvm_chan = iwl_nvm_channels; @@ -2087,13 +2092,26 @@ struct iwl_nvm_data *iwl_get_nvm(struct iwl_trans *trans, struct iwl_nvm_get_info_rsp_v3 *rsp_v3; bool v4 = fw_has_api(&fw->ucode_capa, IWL_UCODE_TLV_API_REGULATORY_NVM_INFO); - size_t rsp_size = v4 ? sizeof(*rsp) : sizeof(*rsp_v3); + size_t rsp_size; void *channel_profile; ret = iwl_trans_send_cmd(trans, &hcmd); if (ret) return ERR_PTR(ret); + switch (iwl_fw_lookup_notif_ver(fw, REGULATORY_AND_NVM_GROUP, + NVM_GET_INFO, 0)) { + case 5: + rsp_size = sizeof(struct iwl_nvm_get_info_rsp); + break; + case 4: + rsp_size = sizeof(struct iwl_nvm_get_info_rsp_v4); + break; + default: + rsp_size = sizeof(struct iwl_nvm_get_info_rsp_v3); + break; + } + if (WARN(iwl_rx_packet_payload_len(hcmd.resp_pkt) != rsp_size, "Invalid payload len in NVM response from FW %d", iwl_rx_packet_payload_len(hcmd.resp_pkt))) { @@ -2107,7 +2125,7 @@ struct iwl_nvm_data *iwl_get_nvm(struct iwl_trans *trans, if (empty_otp) IWL_INFO(trans, "OTP is empty\n"); - nvm = kzalloc_flex(*nvm, channels, IWL_NUM_CHANNELS_V2); + nvm = kzalloc_flex(*nvm, channels, IWL_NUM_CHANNELS_V3); if (!nvm) { ret = -ENOMEM; goto out; From 8d70881707b47353359df57df12f6de67fdacdd2 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Wed, 15 Jul 2026 21:57:03 +0300 Subject: [PATCH 034/234] wifi: iwlwifi: mvm: validate TX_CMD response layout TX_CMD parsing uses frame_count to walk status entries and then read the trailing SCD SSN. Make the minimum-length check follow that exact runtime layout calculation before parsing the payload. For new TX API, reject TX_CMD responses with frame_count != 1 and warn/return in the aggregation handler to document that aggregated accounting is expected via BA notifications. Assisted-by: GitHubCopilot:gpt-5.3-codex Signed-off-by: Emmanuel Grumbach Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715215523.0474ee89bab9.I84f151aabecb8921b587da092f29f78c47128f0f@changeid --- drivers/net/wireless/intel/iwlwifi/mvm/tx.c | 34 +++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/tx.c b/drivers/net/wireless/intel/iwlwifi/mvm/tx.c index dc69c71faa76..d8b088e7c250 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/tx.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/tx.c @@ -1544,6 +1544,17 @@ static inline u32 iwl_mvm_get_scd_ssn(struct iwl_mvm *mvm, return val & 0xFFF; } +static inline size_t iwl_mvm_tx_resp_min_len(struct iwl_mvm *mvm, + struct iwl_tx_resp *tx_resp) +{ + struct agg_tx_status *agg_status = + iwl_mvm_get_agg_status(mvm, tx_resp); + + /* The aggregate response ends with a trailing SCD SSN __le32 word. */ + return (u8 *)(agg_status + tx_resp->frame_count) - (u8 *)tx_resp + + sizeof(__le32); +} + static void iwl_mvm_rx_tx_cmd_single(struct iwl_mvm *mvm, struct iwl_rx_packet *pkt) { @@ -1847,6 +1858,9 @@ static void iwl_mvm_rx_tx_cmd_agg(struct iwl_mvm *mvm, int queue = SEQ_TO_QUEUE(sequence); struct ieee80211_sta *sta; + if (WARN_ON_ONCE(iwl_mvm_has_new_tx_api(mvm))) + return; + if (WARN_ON_ONCE(queue < IWL_MVM_DQA_MIN_DATA_QUEUE && (queue != IWL_MVM_DQA_BSS_CLIENT_QUEUE))) return; @@ -1881,6 +1895,26 @@ void iwl_mvm_rx_tx_cmd(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb) { struct iwl_rx_packet *pkt = rxb_addr(rxb); struct iwl_tx_resp *tx_resp = (void *)pkt->data; + size_t min_len; + + if (IWL_FW_CHECK(mvm, !tx_resp->frame_count, + "invalid TX_CMD frame_count %u\n", + tx_resp->frame_count)) + return; + + if (IWL_FW_CHECK(mvm, + iwl_mvm_has_new_tx_api(mvm) && + tx_resp->frame_count != 1, + "invalid TX_CMD frame_count %u for new TX API\n", + tx_resp->frame_count)) + return; + + min_len = iwl_mvm_tx_resp_min_len(mvm, tx_resp); + if (IWL_FW_CHECK(mvm, iwl_rx_packet_payload_len(pkt) < min_len, + "invalid TX_CMD len %u (frame_count %u, min %zu)\n", + iwl_rx_packet_payload_len(pkt), tx_resp->frame_count, + min_len)) + return; if (tx_resp->frame_count == 1) iwl_mvm_rx_tx_cmd_single(mvm, pkt); From 408d7da38272ce48e2db79b8a9895999f94d7655 Mon Sep 17 00:00:00 2001 From: Pagadala Yesu Anjaneyulu Date: Wed, 15 Jul 2026 21:57:04 +0300 Subject: [PATCH 035/234] wifi: iwlwifi: mvm: validate SAR GEO response payload size The SAR GEO command response is cast to iwl_geo_tx_power_profiles_resp without verifying the payload length. A malformed or unexpected firmware response can lead to reading an invalid structure layout. Add an explicit size check before accessing the response data and return -EIO when the payload size is wrong. Fixes: f604324eefec ("iwlwifi: remove iwl_validate_sar_geo_profile() export") Signed-off-by: Pagadala Yesu Anjaneyulu Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715215523.7e749b7d374a.I4ef54548bff6c6e7c7a57bee771ac12508aad677@changeid --- drivers/net/wireless/intel/iwlwifi/mvm/fw.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/fw.c b/drivers/net/wireless/intel/iwlwifi/mvm/fw.c index 6e507d6dcdd2..fa523be91d8a 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/fw.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/fw.c @@ -964,12 +964,22 @@ int iwl_mvm_get_sar_geo_profile(struct iwl_mvm *mvm) return ret; } + if (IWL_FW_CHECK(mvm, + iwl_rx_packet_payload_len(cmd.resp_pkt) != + sizeof(*resp), + "Wrong size for iwl_geo_tx_power_profiles_resp: %d\n", + iwl_rx_packet_payload_len(cmd.resp_pkt))) { + ret = -EIO; + goto out; + } + resp = (void *)cmd.resp_pkt->data; ret = le32_to_cpu(resp->profile_idx); if (WARN_ON(ret > BIOS_GEO_MAX_PROFILE_NUM)) ret = -EIO; +out: iwl_free_resp(&cmd); return ret; } From a31b0e535fd11219556c7382ee9f63b2438c3769 Mon Sep 17 00:00:00 2001 From: Shahar Tzarfati Date: Wed, 15 Jul 2026 21:57:05 +0300 Subject: [PATCH 036/234] wifi: iwlwifi: fw: validate SMEM response size The SMEM parsers cast firmware response payloads directly to shared memory configuration structures. A short response can leave fields outside the received payload while the driver still dereferences them. Check the response payload length before reading the base fields in both parser variants. Require the full legacy extended layout before reading internal TX FIFO data. Valid responses keep the same parsed values. Signed-off-by: Shahar Tzarfati Reviewed-by: Johannes Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715215523.fbdb0016a91d.I5f6c6e04589a24a233559191170ccb43372dee63@changeid --- drivers/net/wireless/intel/iwlwifi/fw/smem.c | 28 ++++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/smem.c b/drivers/net/wireless/intel/iwlwifi/fw/smem.c index 344ddde85b18..20ed26a1bb00 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/smem.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/smem.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2012-2014, 2018-2021, 2025 Intel Corporation + * Copyright (C) 2012-2014, 2018-2021, 2025-2026 Intel Corporation * Copyright (C) 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH */ @@ -14,9 +14,17 @@ static void iwl_parse_shared_mem_22000(struct iwl_fw_runtime *fwrt, { struct iwl_shared_mem_cfg *mem_cfg = (void *)pkt->data; int i, lmac; - int lmac_num = le32_to_cpu(mem_cfg->lmac_num); - u8 api_ver = iwl_fw_lookup_notif_ver(fwrt->fw, SYSTEM_GROUP, - SHARED_MEM_CFG_CMD, 0); + int lmac_num; + u8 api_ver; + + if (IWL_FW_CHECK(fwrt, iwl_rx_packet_payload_len(pkt) < + offsetofend(struct iwl_shared_mem_cfg, lmac_smem[1]), + "bad shared mem notification size\n")) + return; + + lmac_num = le32_to_cpu(mem_cfg->lmac_num); + api_ver = iwl_fw_lookup_notif_ver(fwrt->fw, SYSTEM_GROUP, + SHARED_MEM_CFG_CMD, 0); /* Note: notification has 3 entries, but we only expect 2 */ if (IWL_FW_CHECK(fwrt, lmac_num > ARRAY_SIZE(fwrt->smem_cfg.lmac), @@ -30,7 +38,7 @@ static void iwl_parse_shared_mem_22000(struct iwl_fw_runtime *fwrt, if (api_ver >= 4 && !IWL_FW_CHECK(fwrt, iwl_rx_packet_payload_len(pkt) < sizeof(*mem_cfg), - "bad shared mem notification size\n")) { + "bad shared mem notification size (v4)\n")) { fwrt->smem_cfg.rxfifo2_control_size = le32_to_cpu(mem_cfg->rxfifo2_control_size); } @@ -53,6 +61,11 @@ static void iwl_parse_shared_mem(struct iwl_fw_runtime *fwrt, struct iwl_shared_mem_cfg_v2 *mem_cfg = (void *)pkt->data; int i; + if (IWL_FW_CHECK(fwrt, iwl_rx_packet_payload_len(pkt) < + offsetof(struct iwl_shared_mem_cfg_v2, rxfifo_addr), + "bad shared mem notification size\n")) + return; + fwrt->smem_cfg.num_lmacs = 1; fwrt->smem_cfg.num_txfifo_entries = ARRAY_SIZE(mem_cfg->txfifo_size); @@ -67,6 +80,11 @@ static void iwl_parse_shared_mem(struct iwl_fw_runtime *fwrt, /* new API has more data, from rxfifo_addr field and on */ if (fw_has_capa(&fwrt->fw->ucode_capa, IWL_UCODE_TLV_CAPA_EXTEND_SHARED_MEM_CFG)) { + if (IWL_FW_CHECK(fwrt, iwl_rx_packet_payload_len(pkt) < + sizeof(*mem_cfg), + "bad shared mem notification size (extend)\n")) + return; + BUILD_BUG_ON(sizeof(fwrt->smem_cfg.internal_txfifo_size) != sizeof(mem_cfg->internal_txfifo_size)); From bc796f84ec9a95b356959ec7caf1d4fce33f3a76 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Wed, 15 Jul 2026 21:57:06 +0300 Subject: [PATCH 037/234] wifi: iwlwifi: fix pointer arithmetic in iwl_add_mcc_to_tas_block_list The expression list[*size++] increments the pointer 'size' rather than the u8 value it points to (operator precedence: ++ binds to the pointer before the dereference). As a result the block-list entry is written at the correct index but *size is never incremented, so the caller's count stays at zero and subsequent calls overwrite slot 0 every time. Change to list[(*size)++] so that the value pointed to by size is incremented after use as the array index. Fixes: 5f4656610edb ("wifi: iwlwifi: extend TAS_CONFIG cmd support for v5") Assisted-by: GitHubCopilot:gpt-5.3-codex Signed-off-by: Emmanuel Grumbach Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715215523.d2cd92242582.Ife4140a4e27be2a1cd9f886c5a9b376ce182a019@changeid --- drivers/net/wireless/intel/iwlwifi/fw/regulatory.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c b/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c index 8d9ff36e30f5..1d6d38ee55b4 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c @@ -389,7 +389,7 @@ bool iwl_add_mcc_to_tas_block_list(u16 *list, u8 *size, u16 mcc) if (*size >= IWL_WTAS_BLACK_LIST_MAX) return false; - list[*size++] = mcc; + list[(*size)++] = mcc; return true; } IWL_EXPORT_SYMBOL(iwl_add_mcc_to_tas_block_list); From daec24a5ed5da77a108e246ad77aa8b889911f93 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Wed, 15 Jul 2026 21:57:07 +0300 Subject: [PATCH 038/234] wifi: iwlwifi: validate payload length in iwl_pnvm_complete_fn iwl_pnvm_complete_fn() casts pkt->data directly to struct iwl_pnvm_init_complete_ntfy and reads the status field without first verifying that the firmware notification payload is large enough to contain that structure. Add a WARN_ON_ONCE check against sizeof(*pnvm_ntf) and return early without reading uninitialised memory if the payload is too short. Fixes: b3e4c0f34c17 ("iwlwifi: move PNVM implementation to common code") Assisted-by: GitHubCopilot:gpt-5.3-codex Signed-off-by: Emmanuel Grumbach Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715215523.7f2a669e5c75.I00465dcfcbccb250ae9af2d9bb305e24de1ba394@changeid --- drivers/net/wireless/intel/iwlwifi/fw/pnvm.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/pnvm.c b/drivers/net/wireless/intel/iwlwifi/fw/pnvm.c index afff8d51ca95..ec0ff58ab312 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/pnvm.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/pnvm.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright(c) 2020-2025 Intel Corporation + * Copyright(c) 2020-2026 Intel Corporation */ #include "iwl-drv.h" @@ -12,6 +12,7 @@ #include "fw/api/alive.h" #include "fw/uefi.h" #include "fw/img.h" +#include "fw/dbg.h" #define IWL_PNVM_REDUCED_CAP_BIT BIT(25) @@ -26,6 +27,12 @@ static bool iwl_pnvm_complete_fn(struct iwl_notif_wait_data *notif_wait, struct iwl_trans *trans = (struct iwl_trans *)data; struct iwl_pnvm_init_complete_ntfy *pnvm_ntf = (void *)pkt->data; + if (IWL_FW_CHECK(trans, + iwl_rx_packet_payload_len(pkt) < sizeof(*pnvm_ntf), + "Bad notif len: %d\n", + iwl_rx_packet_payload_len(pkt))) + return true; + IWL_DEBUG_FW(trans, "PNVM complete notification received with status 0x%0x\n", le32_to_cpu(pnvm_ntf->status)); From 9d7657aae8c1579584c67b0b66114a6a98db8b2f Mon Sep 17 00:00:00 2001 From: Shahar Tzarfati Date: Wed, 15 Jul 2026 21:57:08 +0300 Subject: [PATCH 039/234] wifi: iwlwifi: mvm: fix read in wake packet notification handler In iwl_mvm_wowlan_store_wake_pkt(), packet_len was initialized from notif->wake_packet_length before the explicit check that len >= sizeof(*notif). Move the assignment of packet_len to after the size check so that notif->wake_packet_length is only accessed once the payload length has been validated. Fixes: 219ed58feda9 ("wifi: iwlwifi: mvm: Add support for wowlan wake packet notification") Signed-off-by: Shahar Tzarfati Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715215523.99d5cf85a528.Ic4aa736011d4fe88e0cd19723d1d48bb24642198@changeid --- drivers/net/wireless/intel/iwlwifi/mvm/d3.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/d3.c b/drivers/net/wireless/intel/iwlwifi/mvm/d3.c index 3429d9a10e42..4eaba0bc4a1e 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/d3.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/d3.c @@ -2756,7 +2756,7 @@ static int iwl_mvm_wowlan_store_wake_pkt(struct iwl_mvm *mvm, struct iwl_wowlan_status_data *status, u32 len) { - u32 data_size, packet_len = le32_to_cpu(notif->wake_packet_length); + u32 data_size, packet_len; if (len < sizeof(*notif)) { IWL_ERR(mvm, "Invalid WoWLAN wake packet notification!\n"); @@ -2775,6 +2775,7 @@ static int iwl_mvm_wowlan_store_wake_pkt(struct iwl_mvm *mvm, return -EIO; } + packet_len = le32_to_cpu(notif->wake_packet_length); data_size = len - offsetof(struct iwl_wowlan_wake_pkt_notif, wake_packet); /* data_size got the padding from the notification, remove it. */ From 7455ba7b4d6d869e337db063c466033cf9392fb5 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Wed, 15 Jul 2026 21:57:09 +0300 Subject: [PATCH 040/234] wifi: iwlwifi: mvm: ptp: free response on success path Release CMD_WANT_SKB response buffer after successful timestamp parsing to avoid leaking response allocations. Assisted-by: GitHubCopilot:GPT-5.3-Codex Signed-off-by: Emmanuel Grumbach Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715215523.31b38bef398b.Ib6a5a8bdd800779c8911da6859fd450d3d19c9e9@changeid --- drivers/net/wireless/intel/iwlwifi/mvm/ptp.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/ptp.c b/drivers/net/wireless/intel/iwlwifi/mvm/ptp.c index f39eb48864eb..49dcb1388007 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/ptp.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/ptp.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2021 - 2023, 2025 Intel Corporation + * Copyright (C) 2021 - 2023, 2025-2026 Intel Corporation */ #include "mvm.h" @@ -121,6 +121,8 @@ iwl_mvm_get_crosstimestamp_fw(struct iwl_mvm *mvm, u32 *gp2, u64 *sys_time) *sys_time = (u64)le32_to_cpu(resp->platform_timestamp_hi) << 32 | le32_to_cpu(resp->platform_timestamp_lo); + iwl_free_resp(&cmd); + return ret; } From 7b271df210848e8d0dd9c0d7b032050b3f3affbf Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Wed, 15 Jul 2026 21:57:10 +0300 Subject: [PATCH 041/234] wifi: iwlwifi: pcie: validate FW section counts in iwl_pcie_init_fw_sec iwl_pcie_init_fw_sec() iterates over LMAC, UMAC, and paging firmware sections and writes to ctxt_dram->lmac_img[i], ctxt_dram->umac_img[i], and ctxt_dram->virtual_img[i] without first verifying that the counts derived from the firmware image do not exceed the array size. An oversized firmware image could cause out-of-bounds writes into the fixed-size context-info DRAM arrays. Add explicit WARN_ON checks for all three section counts and return -EINVAL if any is exceeded. Assisted-by: GitHubCopilot:gpt-5.3-codex Signed-off-by: Emmanuel Grumbach Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715215523.34db46ca12f3.I1aa225492a62f25293c147aa7293afa80a5d4215@changeid --- drivers/net/wireless/intel/iwlwifi/pcie/ctxt-info.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/ctxt-info.c b/drivers/net/wireless/intel/iwlwifi/pcie/ctxt-info.c index d5eb895144ef..7f886200d693 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/ctxt-info.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/ctxt-info.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* * Copyright (C) 2017 Intel Deutschland GmbH - * Copyright (C) 2018-2025 Intel Corporation + * Copyright (C) 2018-2026 Intel Corporation */ #include "iwl-trans.h" #include "iwl-fh.h" @@ -99,6 +99,11 @@ int iwl_pcie_init_fw_sec(struct iwl_trans *trans, /* add 2 due to separators */ paging_cnt = iwl_pcie_get_num_sections(fw, lmac_cnt + umac_cnt + 2); + if (WARN_ON(lmac_cnt > ARRAY_SIZE(ctxt_dram->lmac_img) || + umac_cnt > ARRAY_SIZE(ctxt_dram->umac_img) || + paging_cnt > ARRAY_SIZE(ctxt_dram->virtual_img))) + return -EINVAL; + dram->fw = kzalloc_objs(*dram->fw, umac_cnt + lmac_cnt); if (!dram->fw) return -ENOMEM; From 94d3982806c7f194b23484befde12934dda23064 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Wed, 15 Jul 2026 21:57:11 +0300 Subject: [PATCH 042/234] wifi: iwlwifi: mvm: fix out-of-bounds tid_data access in BA notif mvmsta->tid_data was indexed by the TFD loop counter 'i' instead of the actual TID value 'tid'. This writes lq_color into a random tid_data slot unrelated to the BA entry. Since multi-TID blockack is not really in use, 'i' was always 0 and no harm was done. Add a out-of-bound check before accessing the array. Assisted-by: GitHubCopilot:gpt-5.3-codex Signed-off-by: Emmanuel Grumbach Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715215523.919edee567eb.Ie85c350e3afe2b39709d0039072740d86660f8ae@changeid --- drivers/net/wireless/intel/iwlwifi/mvm/tx.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/tx.c b/drivers/net/wireless/intel/iwlwifi/mvm/tx.c index d8b088e7c250..e02c376296c4 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/tx.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/tx.c @@ -2134,8 +2134,14 @@ void iwl_mvm_rx_ba_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb) if (tid == IWL_MGMT_TID) tid = IWL_MAX_TID_COUNT; + if (IWL_FW_CHECK(mvm, tid >= + ARRAY_SIZE(mvmsta->tid_data), + "invalid TID %d in compressed BA\n", + tid)) + continue; + if (mvmsta) - mvmsta->tid_data[i].lq_color = lq_color; + mvmsta->tid_data[tid].lq_color = lq_color; iwl_mvm_tx_reclaim(mvm, sta_id, tid, (int)(le16_to_cpu(ba_tfd->q_num)), From 0e4c0d83267261cf67ec9690856edf4a56bb7dfc Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Wed, 15 Jul 2026 21:57:12 +0300 Subject: [PATCH 043/234] wifi: iwlwifi: mvm: add a check on the tid coming from the firmware ba_notif->tid is a firmware-controlled u8 that is used directly as an array index into tid_data[] without any validation. Add a bounds check against IWL_MAX_TID_COUNT before dereferencing the array. Assisted-by: GitHubCopilot:gpt-5.3-codex Signed-off-by: Emmanuel Grumbach Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715215523.d7c3e75d47af.If88948108cfc8b5fb3ce5531d927855d1b3b6b30@changeid --- drivers/net/wireless/intel/iwlwifi/mvm/tx.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/tx.c b/drivers/net/wireless/intel/iwlwifi/mvm/tx.c index e02c376296c4..d3c2fe830477 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/tx.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/tx.c @@ -2161,6 +2161,9 @@ void iwl_mvm_rx_ba_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb) ba_notif = (void *)pkt->data; sta_id = ba_notif->sta_id; tid = ba_notif->tid; + if (IWL_FW_CHECK(mvm, tid >= ARRAY_SIZE(mvmsta->tid_data), + "invalid TID %d in BA notif\n", tid)) + return; /* "flow" corresponds to Tx queue */ txq = le16_to_cpu(ba_notif->scd_flow); /* "ssn" is start of block-ack Tx window, corresponds to index From 8cb6c719fa3c0812bc0d151a7074f328a61a3062 Mon Sep 17 00:00:00 2001 From: Avraham Stern Date: Wed, 15 Jul 2026 21:57:13 +0300 Subject: [PATCH 044/234] wifi: iwlwifi: mvm: copy the correct TK length for ranging When setting the TK for ranging with an associated peer, 32 bytes of TK are copied from the vif key without verifying the actual key length which may be only 16 bytes if CCMP-128 is used. Fix it by setting the copy length according to the key cipher. Signed-off-by: Avraham Stern Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715215523.e339570086bd.Iafff5aaf0e25f7d29e06f0ef456107c4062bbc7d@changeid --- .../intel/iwlwifi/mvm/ftm-initiator.c | 55 +++++++++++++------ 1 file changed, 38 insertions(+), 17 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/ftm-initiator.c b/drivers/net/wireless/intel/iwlwifi/mvm/ftm-initiator.c index 3a14ca5e512a..8d7f60dcd027 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/ftm-initiator.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/ftm-initiator.c @@ -646,7 +646,8 @@ static void iter(struct ieee80211_hw *hw, WARN_ON(*target->cipher == IWL_LOCATION_CIPHER_INVALID); } -static void +/* The tk buffer is assumed to be exactly TK_11AZ_LEN bytes */ +static int iwl_mvm_ftm_set_secured_ranging(struct iwl_mvm *mvm, struct ieee80211_vif *vif, u8 *bssid, u8 *cipher, u8 *hltk, u8 *tk, u8 *rx_pn, u8 *tx_pn, __le32 *flags) @@ -656,12 +657,12 @@ iwl_mvm_ftm_set_secured_ranging(struct iwl_mvm *mvm, struct ieee80211_vif *vif, struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); if (mvmvif->ftm_unprotected) - return; + return 0; #endif if (!(le32_to_cpu(*flags) & (IWL_INITIATOR_AP_FLAGS_NON_TB | IWL_INITIATOR_AP_FLAGS_TB))) - return; + return 0; lockdep_assert_held(&mvm->mutex); @@ -679,14 +680,31 @@ iwl_mvm_ftm_set_secured_ranging(struct iwl_mvm *mvm, struct ieee80211_vif *vif, if (vif->cfg.assoc && !memcmp(vif->bss_conf.bssid, bssid, ETH_ALEN)) { struct iwl_mvm_ftm_iter_data target; + u32 key_len; target.bssid = bssid; target.cipher = cipher; target.tk = NULL; ieee80211_iter_keys(mvm->hw, vif, iter, &target); - if (!WARN_ON(!target.tk)) - memcpy(tk, target.tk, TK_11AZ_LEN); + if (WARN_ON(!target.tk)) + return -EINVAL; + + switch (*target.cipher) { + case IWL_LOCATION_CIPHER_CCMP_128: + case IWL_LOCATION_CIPHER_GCMP_128: + key_len = WLAN_KEY_LEN_CCMP; + break; + case IWL_LOCATION_CIPHER_GCMP_256: + key_len = WLAN_KEY_LEN_GCMP_256; + break; + default: + WARN_ON(1); + return -EINVAL; + } + + memset(tk, 0, TK_11AZ_LEN); + memcpy(tk, target.tk, key_len); } else { memcpy(tk, entry->tk, sizeof(entry->tk)); } @@ -695,8 +713,10 @@ iwl_mvm_ftm_set_secured_ranging(struct iwl_mvm *mvm, struct ieee80211_vif *vif, memcpy(tx_pn, entry->tx_pn, sizeof(entry->tx_pn)); FTM_SET_FLAG(SECURED); - return; + return 0; } + + return 0; } static int @@ -708,12 +728,11 @@ iwl_mvm_ftm_put_target_v7(struct iwl_mvm *mvm, struct ieee80211_vif *vif, if (err) return err; - iwl_mvm_ftm_set_secured_ranging(mvm, vif, target->bssid, - &target->cipher, target->hltk, - target->tk, target->rx_pn, - target->tx_pn, - &target->initiator_ap_flags); - return err; + return iwl_mvm_ftm_set_secured_ranging(mvm, vif, target->bssid, + &target->cipher, target->hltk, + target->tk, target->rx_pn, + target->tx_pn, + &target->initiator_ap_flags); } static int iwl_mvm_ftm_start_v11(struct iwl_mvm *mvm, @@ -881,11 +900,13 @@ iwl_mvm_ftm_put_target_v10(struct iwl_mvm *mvm, struct ieee80211_vif *vif, iwl_mvm_ftm_set_target_flags(mvm, peer, &target->initiator_ap_flags); iwl_mvm_ftm_set_sta(mvm, vif, peer, &target->sta_id, &target->initiator_ap_flags); - iwl_mvm_ftm_set_secured_ranging(mvm, vif, target->bssid, - &target->cipher, target->hltk, - target->tk, target->rx_pn, - target->tx_pn, - &target->initiator_ap_flags); + ret = iwl_mvm_ftm_set_secured_ranging(mvm, vif, target->bssid, + &target->cipher, target->hltk, + target->tk, target->rx_pn, + target->tx_pn, + &target->initiator_ap_flags); + if (ret) + return ret; i2r_max_sts = IWL_MVM_FTM_I2R_MAX_STS > 1 ? 1 : IWL_MVM_FTM_I2R_MAX_STS; From 0cb5260a1027a43f8cdb961e128f2ddd42e46832 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Wed, 15 Jul 2026 21:57:14 +0300 Subject: [PATCH 045/234] wifi: iwlwifi: mvm: fix a possible underflow We shouldn't trust the firmware about the length of the wowlan packet. Signed-off-by: Emmanuel Grumbach Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715215523.fbd989cc85e2.If68de403bfa4943732c025961154c20b01b09e83@changeid --- drivers/net/wireless/intel/iwlwifi/mvm/d3.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/d3.c b/drivers/net/wireless/intel/iwlwifi/mvm/d3.c index 4eaba0bc4a1e..109265149963 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/d3.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/d3.c @@ -1509,6 +1509,10 @@ static void iwl_mvm_report_wakeup_reasons(struct iwl_mvm *mvm, if (WARN_ON_ONCE(truncated < 0)) truncated = 0; + /* this would be a firmware bug */ + if (WARN_ON_ONCE(pktsize < sizeof(*hdr))) + return; + if (ieee80211_is_data(hdr->frame_control)) { int hdrlen = ieee80211_hdrlen(hdr->frame_control); int ivlen = 0, icvlen = 4; /* also FCS */ @@ -1517,10 +1521,6 @@ static void iwl_mvm_report_wakeup_reasons(struct iwl_mvm *mvm, if (!pkt) goto report; - skb_put_data(pkt, pktdata, hdrlen); - pktdata += hdrlen; - pktsize -= hdrlen; - if (ieee80211_has_protected(hdr->frame_control)) { /* * This is unlocked and using gtk_i(c)vlen, @@ -1546,6 +1546,17 @@ static void iwl_mvm_report_wakeup_reasons(struct iwl_mvm *mvm, truncated = 0; } + if (IWL_FW_CHECK(mvm, + pktsize <= hdrlen + ivlen + icvlen, + "pktsize is too small %d\n", + pktsize)) { + kfree_skb(pkt); + return; + } + + skb_put_data(pkt, pktdata, hdrlen); + pktdata += hdrlen; + pktsize -= hdrlen; pktsize -= ivlen + icvlen; pktdata += ivlen; From a426d3227c669ef0ad9855b311a68129f10ac8bf Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Wed, 15 Jul 2026 21:57:15 +0300 Subject: [PATCH 046/234] wifi: iwlwifi: adapt ND match notif sizing to fixed matches array Switch iwl_scan_offload_match_info::matches to a fixed-size array and adjust D3 netdetect size handling accordingly. In MVM D3 paths, compute expected payload size as offsetof(struct iwl_scan_offload_match_info, matches) + matches_len to preserve previous behavior after the struct layout change. In MLD D3 netdetect handling, keep the simple full-notification size assumption and validate against sizeof(*notif) before accessing data. This keeps scan offload / netdetect functionality unchanged while making length checks consistent with the new struct definition. Assisted-by: GitHubCopilot:gpt-5.3-codex Signed-off-by: Emmanuel Grumbach Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260715215523.4c4346140bcc.I44313ac41daca352e6aecdba09a1c3570c3eea06@changeid --- drivers/net/wireless/intel/iwlwifi/fw/api/scan.h | 4 ++-- drivers/net/wireless/intel/iwlwifi/mvm/d3.c | 9 ++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/scan.h b/drivers/net/wireless/intel/iwlwifi/fw/api/scan.h index 08f4cc3ea1c3..ee78371082b0 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/scan.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/scan.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2012-2014, 2018-2025 Intel Corporation + * Copyright (C) 2012-2014, 2018-2026 Intel Corporation * Copyright (C) 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH */ @@ -1319,7 +1319,7 @@ struct iwl_scan_offload_match_info { u8 resume_while_scanning; u8 self_recovery; __le16 reserved; - struct iwl_scan_offload_profile_match matches[]; + struct iwl_scan_offload_profile_match matches[IWL_SCAN_MAX_PROFILES_V2]; } __packed; /* SCAN_OFFLOAD_PROFILES_QUERY_RSP_S_VER_3 and * SCAN_OFFLOAD_MATCH_INFO_NOTIFICATION_S_VER_1 */ diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/d3.c b/drivers/net/wireless/intel/iwlwifi/mvm/d3.c index 109265149963..6b11fa32ea5c 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/d3.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/d3.c @@ -2459,13 +2459,15 @@ iwl_mvm_netdetect_query_results(struct iwl_mvm *mvm, if (fw_has_api(&mvm->fw->ucode_capa, IWL_UCODE_TLV_API_SCAN_OFFLOAD_CHANS)) { - query_len = sizeof(struct iwl_scan_offload_match_info); matches_len = sizeof(struct iwl_scan_offload_profile_match) * max_profiles; + query_len = offsetof(struct iwl_scan_offload_match_info, + matches) + matches_len; } else { - query_len = sizeof(struct iwl_scan_offload_profiles_query_v1); matches_len = sizeof(struct iwl_scan_offload_profile_match_v1) * max_profiles; + query_len = sizeof(struct iwl_scan_offload_profiles_query_v1) + + matches_len; } len = iwl_rx_packet_payload_len(cmd.resp_pkt); @@ -2819,7 +2821,8 @@ static void iwl_mvm_nd_match_info_handler(struct iwl_mvm *mvm, if (IS_ERR_OR_NULL(vif)) return; - if (len < sizeof(struct iwl_scan_offload_match_info) + matches_len) { + if (len < offsetof(struct iwl_scan_offload_match_info, matches) + + matches_len) { IWL_ERR(mvm, "Invalid scan match info notification\n"); return; } From acad742714bdc70e7fd7f234323807c596828213 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Fri, 17 Jul 2026 17:33:36 +0300 Subject: [PATCH 047/234] wifi: iwlwifi: bound aligned TLV advance in FW parser Validate ALIGN(tlv_len, 4) against remaining parser length before consuming bytes from the firmware image. This avoids length underflow on malformed TLVs. Assisted-by: GitHubCopilot:GPT-5.3-Codex Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260717173215.393c286488f9.Ia39144dc3ca334325ee4eacb7420901e2446fc23@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/iwl-drv.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-drv.c b/drivers/net/wireless/intel/iwlwifi/iwl-drv.c index 842586d4fc5c..001895f4f4bd 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-drv.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-drv.c @@ -804,6 +804,7 @@ static int iwl_parse_tlv_firmware(struct iwl_drv *drv, u32 build, paging_mem_size; int num_of_cpus; bool usniffer_req = false; + size_t aligned_tlv_len; if (len < sizeof(*ucode)) { IWL_ERR(drv, "uCode has invalid length: %zd\n", len); @@ -852,8 +853,16 @@ static int iwl_parse_tlv_firmware(struct iwl_drv *drv, len, tlv_len); return -EINVAL; } - len -= ALIGN(tlv_len, 4); - data += sizeof(*tlv) + ALIGN(tlv_len, 4); + + aligned_tlv_len = ALIGN(tlv_len, 4); + if (len < aligned_tlv_len) { + IWL_ERR(drv, "invalid aligned TLV len: %zd/%zu\n", + len, aligned_tlv_len); + return -EINVAL; + } + + len -= aligned_tlv_len; + data += sizeof(*tlv) + aligned_tlv_len; switch (tlv_type) { case IWL_UCODE_TLV_INST: From 856ab29632c507d51d7bfe86100389d05d4d9a7f Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Fri, 17 Jul 2026 17:33:37 +0300 Subject: [PATCH 048/234] wifi: iwlwifi: dbg-tlv: bound aligned TLV walk length Validate ALIGN(tlv_len, 4) before advancing through external debug TLVs to prevent parser length underflow. Assisted-by: GitHubCopilot:GPT-5.3-Codex Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260717173215.e08d6550c6ec.Iad64190a7d5cded553aff41973120396aef1b557@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/iwl-dbg-tlv.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-dbg-tlv.c b/drivers/net/wireless/intel/iwlwifi/iwl-dbg-tlv.c index d021b24d04d6..b1a55909f0d4 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-dbg-tlv.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-dbg-tlv.c @@ -475,6 +475,7 @@ static int iwl_dbg_tlv_parse_bin(struct iwl_trans *trans, const u8 *data, { const struct iwl_ucode_tlv *tlv; u32 tlv_len; + size_t aligned_tlv_len; while (len >= sizeof(*tlv)) { len -= sizeof(*tlv); @@ -487,8 +488,16 @@ static int iwl_dbg_tlv_parse_bin(struct iwl_trans *trans, const u8 *data, len, tlv_len); return -EINVAL; } - len -= ALIGN(tlv_len, 4); - data += sizeof(*tlv) + ALIGN(tlv_len, 4); + + aligned_tlv_len = ALIGN(tlv_len, 4); + if (len < aligned_tlv_len) { + IWL_ERR(trans, "invalid aligned TLV len: %zd/%zu\n", + len, aligned_tlv_len); + return -EINVAL; + } + + len -= aligned_tlv_len; + data += sizeof(*tlv) + aligned_tlv_len; iwl_dbg_tlv_alloc(trans, tlv, true); } From 954e821f42aaca56073ca830c5fd4bcf1a89048c Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Fri, 17 Jul 2026 17:33:38 +0300 Subject: [PATCH 049/234] wifi: iwlwifi: acpi: validate WGDS table revision index Check tbl_rev bounds before BIT(tbl_rev) to avoid undefined shifts when firmware reports an invalid revision value. Assisted-by: GitHubCopilot:GPT-5.3-Codex Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260717173215.52a01f841f2a.Ic0131eaac31d9ff71b169138d9b0865cb39b44a9@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/fw/acpi.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/acpi.c b/drivers/net/wireless/intel/iwlwifi/fw/acpi.c index bf0f851a9075..430a7642e01a 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/acpi.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/acpi.c @@ -865,6 +865,11 @@ int iwl_acpi_get_wgds_table(struct iwl_fw_runtime *fwrt) min_size, max_size, &tbl_rev); if (!IS_ERR(wifi_pkg)) { + if (tbl_rev < 0 || + tbl_rev >= BITS_PER_BYTE * + sizeof(rev_data[idx].revisions)) + continue; + if (!(BIT(tbl_rev) & rev_data[idx].revisions)) continue; From 9ba1e0a4060e5a4802ccb9c9defa946c1aa6c629 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Fri, 17 Jul 2026 17:33:39 +0300 Subject: [PATCH 050/234] wifi: iwlwifi: uefi: bound PPAG revision bitmap shift Validate revision is below 32 before BIT(revision) in PPAG parsing. Assisted-by: GitHubCopilot:GPT-5.3-Codex Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260717173215.d116dd2efdc1.I3c6cae5cb9d0acc2d94544bc755b0754a91b10ba@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/fw/uefi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/uefi.c b/drivers/net/wireless/intel/iwlwifi/fw/uefi.c index 2ef0a7a920ad..014f22c42f41 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/uefi.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/uefi.c @@ -700,7 +700,7 @@ int iwl_uefi_get_ppag_table(struct iwl_fw_runtime *fwrt) return -EINVAL; parse_table: - if (!(BIT(data->revision) & valid_rev)) { + if (data->revision >= 32 || !(BIT(data->revision) & valid_rev)) { ret = -EINVAL; IWL_DEBUG_RADIO(fwrt, "Unsupported UEFI PPAG revision:%d\n", From d13d5d299c11b7bd3362d5692c56225d9e176664 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Fri, 17 Jul 2026 17:33:40 +0300 Subject: [PATCH 051/234] wifi: iwlwifi: validate SEC_RT TLV minimum size Reject firmware section TLVs that are shorter than the offset field before subtracting sizeof(offset) from the section size. This prevents size underflow for malformed TLVs. Assisted-by: GitHubCopilot:GPT-5.3-Codex Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260717173215.17b040b27edc.I6b32d1e9ad707417e2e604f08a63582456209372@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/iwl-drv.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-drv.c b/drivers/net/wireless/intel/iwlwifi/iwl-drv.c index 001895f4f4bd..7f1515dfc5bf 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-drv.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-drv.c @@ -492,7 +492,7 @@ static void set_sec_offset(struct iwl_firmware_pieces *pieces, * Gets uCode section from tlv. */ static int iwl_store_ucode_sec(struct fw_img_parsing *img, - const void *data, int size) + const void *data, size_t size) { struct fw_sec *sec; const struct fw_sec_parsing *sec_parse; @@ -501,6 +501,9 @@ static int iwl_store_ucode_sec(struct fw_img_parsing *img, if (WARN_ON(!img || !data)) return -EINVAL; + if (size < sizeof(sec_parse->offset)) + return -EINVAL; + sec_parse = (const struct fw_sec_parsing *)data; alloc_size = sizeof(*img->sec) * (img->sec_counter + 1); From b52c5103f64ee825996ca1ab8df7283cde8c5f86 Mon Sep 17 00:00:00 2001 From: Qing Ming Date: Sat, 23 May 2026 16:15:43 +0800 Subject: [PATCH 052/234] ovpn: avoid putting unrelated P2P peer on socket release ovpn_peer_release_p2p() is called when an OVPN UDP socket is being destroyed. It checks the currently published P2P peer and releases it only if that peer still uses the socket being destroyed. A peer replacement can publish a new peer before the old UDP socket is destroyed. When the old socket destruction path runs afterwards, ovpn_peer_release_p2p() observes the new peer through ovpn->peer. Since the new peer uses a different socket, the function takes the socket mismatch branch. That branch still calls ovpn_peer_put(peer). At this point, however, peer is the currently published replacement peer, not the peer associated with the socket being destroyed. Dropping its reference can free it while ovpn->peer still points to it, leading to later use-after-free accesses from the peer and socket cleanup paths. KASAN reports this as a slab-use-after-free on the kmalloc-1k ovpn_peer object. In the reproducer, the object is allocated from ovpn_peer_new() via ovpn_nl_peer_new_doit(), and freed through ovpn_peer_release_rcu() from RCU callback processing. Observed access sites include ovpn_peer_remove(), ovpn_socket_release(), ovpn_nl_peer_del_notify(), and unlock_ovpn(). Fix this by returning from the socket mismatch branch without putting the peer. Fixes: f6226ae7a0cd ("ovpn: introduce the ovpn_socket object") Signed-off-by: Qing Ming Reviewed-by: Simon Horman Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/peer.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index a09d61296425..1844d97154ce 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -1167,7 +1167,6 @@ static void ovpn_peer_release_p2p(struct ovpn_priv *ovpn, struct sock *sk, ovpn_sock = rcu_access_pointer(peer->sock); if (!ovpn_sock || ovpn_sock->sk != sk) { spin_unlock_bh(&ovpn->lock); - ovpn_peer_put(peer); return; } } From 63bbe18fc03062f483c627838a566a707b62da79 Mon Sep 17 00:00:00 2001 From: Pavitra Jha Date: Sat, 23 May 2026 05:02:43 -0400 Subject: [PATCH 053/234] ovpn: fix peer refcount leak in TCP error paths When either the TCP RX or TX error path calls ovpn_peer_hold() followed by schedule_work(&peer->tcp.defer_del_work), and the work item is already pending from the other path, schedule_work() returns false and the work runs only once. Since ovpn_tcp_peer_del_work() calls ovpn_peer_put() exactly once, the extra reference taken by the losing path is never dropped, leaking the peer object. The race window: CPU0 (strparser/RX error): CPU1 (tcp_tx_work/TX error): ovpn_peer_hold() <- refcnt+1 ovpn_peer_hold() <- refcnt+2 schedule_work() <- queued schedule_work() <- NO-OP (work already pending) ovpn_tcp_peer_del_work runs: ovpn_peer_del() ovpn_peer_put() <- refcnt+1 <- peer never freed Fix by checking the return value of schedule_work() in both paths and calling ovpn_peer_put() to drop the extra reference if the work was already pending. ovpn_peer_hold() is kept unconditional in the TX path as it cannot fail at that point. Fixes: a6a5e87b3ee4 ("ovpn: avoid sleep in atomic context in TCP RX error path") Cc: stable@vger.kernel.org Signed-off-by: Pavitra Jha Reviewed-by: Sabrina Dubroca Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/tcp.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/ovpn/tcp.c b/drivers/net/ovpn/tcp.c index 433bd07a4f1b..0af14055c39a 100644 --- a/drivers/net/ovpn/tcp.c +++ b/drivers/net/ovpn/tcp.c @@ -151,7 +151,8 @@ static void ovpn_tcp_rcv(struct strparser *strp, struct sk_buff *skb) /* take reference for deferred peer deletion. should never fail */ if (WARN_ON(!ovpn_peer_hold(peer))) goto err_nopeer; - schedule_work(&peer->tcp.defer_del_work); + if (!schedule_work(&peer->tcp.defer_del_work)) + ovpn_peer_put(peer); ovpn_dev_dstats_rx_dropped(peer->ovpn->dev); err_nopeer: kfree_skb(skb); @@ -283,7 +284,8 @@ static void ovpn_tcp_send_sock(struct ovpn_peer *peer, struct sock *sk) * stream therefore we abort the connection */ ovpn_peer_hold(peer); - schedule_work(&peer->tcp.defer_del_work); + if (!schedule_work(&peer->tcp.defer_del_work)) + ovpn_peer_put(peer); /* we bail out immediately and keep tx_in_progress set * to true. This way we prevent more TX attempts From a4710ae2e7e322fdaefb4be8604228279cfaf48c Mon Sep 17 00:00:00 2001 From: Shuvam Pandey Date: Sat, 23 May 2026 20:38:27 +0545 Subject: [PATCH 054/234] ovpn: hold peer before scheduling keepalive work ovpn_peer_keepalive_send() passes its peer reference to ovpn_xmit_special(), which ultimately drops it. The keepalive scheduler currently queues the work first and takes the reference only after schedule_work() reports that the work was queued. Once schedule_work() queues the item, another CPU may run the worker before the caller gets to ovpn_peer_hold(). In that case the worker can consume a reference that was not acquired for it, corrupting the peer lifetime accounting. Take the peer reference before queueing the work and drop it again when the work was already pending. Fixes: 3ecfd9349f40 ("ovpn: implement keepalive mechanism") Cc: stable@vger.kernel.org Signed-off-by: Shuvam Pandey Reviewed-by: Sabrina Dubroca Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/peer.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index 1844d97154ce..2b6096d8b1cc 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -1284,8 +1284,10 @@ static time64_t ovpn_peer_keepalive_work_single(struct ovpn_peer *peer, netdev_dbg(peer->ovpn->dev, "sending keepalive to peer %u\n", peer->id); - if (schedule_work(&peer->keepalive_work)) - ovpn_peer_hold(peer); + if (WARN_ON(!ovpn_peer_hold(peer))) + return 0; + if (!schedule_work(&peer->keepalive_work)) + ovpn_peer_put(peer); } if (next_run1 < next_run2) From 0bd9cfebc1c91e1066e56d6261b99691b9df6008 Mon Sep 17 00:00:00 2001 From: longlong yan Date: Wed, 3 Jun 2026 15:27:41 +0800 Subject: [PATCH 055/234] selftests/net: ovpn: fix getaddrinfo memory leak in ovpn_parse_remote() The ovpn_parse_remote() function has two memory management issues: 1. When both 'host' and 'vpnip' are non-NULL, the first getaddrinfo() allocation is leaked because 'result' is overwritten by the second getaddrinfo() call without freeing the first allocation. 2. When both 'host' and 'vpnip' are NULL, 'result' is an uninitialized stack variable passed to freeaddrinfo(), which is undefined behavior. Fix by initializing 'result' to NULL and calling freeaddrinfo() after the first getaddrinfo() result is consumed. Fixes: 959bc330a439 ("testing/selftests: add test tool and scripts for ovpn module") Signed-off-by: longlong yan Signed-off-by: Antonio Quartulli --- tools/testing/selftests/net/ovpn/ovpn-cli.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/net/ovpn/ovpn-cli.c b/tools/testing/selftests/net/ovpn/ovpn-cli.c index d40953375c86..f4effa7580c0 100644 --- a/tools/testing/selftests/net/ovpn/ovpn-cli.c +++ b/tools/testing/selftests/net/ovpn/ovpn-cli.c @@ -1785,7 +1785,7 @@ static int ovpn_parse_remote(struct ovpn_ctx *ovpn, const char *host, const char *service, const char *vpnip) { int ret; - struct addrinfo *result; + struct addrinfo *result = NULL; struct addrinfo hints = { .ai_family = ovpn->sa_family, .ai_socktype = SOCK_DGRAM, @@ -1809,6 +1809,8 @@ static int ovpn_parse_remote(struct ovpn_ctx *ovpn, const char *host, } memcpy(&ovpn->remote, result->ai_addr, result->ai_addrlen); + freeaddrinfo(result); + result = NULL; } if (vpnip) { From e1ad6fe5db719874efa45b2caf9934552e09fc43 Mon Sep 17 00:00:00 2001 From: Marco Baffo Date: Mon, 8 Jun 2026 16:04:46 +0200 Subject: [PATCH 056/234] ovpn: fix use after free in unlock_ovpn() unlock_ovpn() iterates over the release_list using llist_for_each_entry() and drops the peer reference inside the loop body via ovpn_peer_put(). If this drops the last reference, the peer is eventually freed. However, llist_for_each_entry() reads peer->release_entry.next in the loop advance expression, which runs after the body. By that time the peer may have already been freed, resulting in a use after free when advancing to the next list entry. Fix this by using llist_for_each_entry_safe(), which caches the next pointer before executing the loop body. Fixes: 80747caef33d ("ovpn: introduce the ovpn_peer object") Signed-off-by: Marco Baffo Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/peer.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index 2b6096d8b1cc..8fdbb5050690 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -26,11 +26,12 @@ static void unlock_ovpn(struct ovpn_priv *ovpn, struct llist_head *release_list) __releases(&ovpn->lock) { - struct ovpn_peer *peer; + struct ovpn_peer *peer, *next; spin_unlock_bh(&ovpn->lock); - llist_for_each_entry(peer, release_list->first, release_entry) { + llist_for_each_entry_safe(peer, next, release_list->first, + release_entry) { ovpn_socket_release(peer); ovpn_peer_put(peer); } From f7e6287ccd3abeed9e638b581dc3fdf742106ba3 Mon Sep 17 00:00:00 2001 From: Marco Baffo Date: Mon, 8 Jun 2026 16:06:42 +0200 Subject: [PATCH 057/234] ovpn: use monotonic clock for peer keepalive timeouts Replace ktime_get_real_seconds() with the monotonic ktime_get_boottime_seconds() to ensure the keepalive mechanism is robust against system clock modifications. Right now, the driver uses ktime_get_real_seconds() to track peer timeouts, relying on the system wall-clock. An administrative time adjustment or an NTP sync that steps the clock forward can cause `now' to instantly exceed `last_recv + timeout'. When this occurs, the driver artificially expires healthy peers. Depending on the OpenVPN user-space configuration, this triggers a premature tunnel restart (if --keepalive or --ping-restart is used) or a complete disconnection of the client (if --ping-exit is used). Fixes: 3ecfd9349f40 ("ovpn: implement keepalive mechanism") Signed-off-by: Marco Baffo Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/io.c | 4 ++-- drivers/net/ovpn/peer.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index a6b777a9c2d9..9a66d693039a 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -142,7 +142,7 @@ void ovpn_decrypt_post(void *data, int ret) } /* keep track of last received authenticated packet for keepalive */ - WRITE_ONCE(peer->last_recv, ktime_get_real_seconds()); + WRITE_ONCE(peer->last_recv, ktime_get_boottime_seconds()); rcu_read_lock(); sock = rcu_dereference(peer->sock); @@ -294,7 +294,7 @@ void ovpn_encrypt_post(void *data, int ret) ovpn_peer_stats_increment_tx(&peer->link_stats, orig_len); /* keep track of last sent packet for keepalive */ - WRITE_ONCE(peer->last_sent, ktime_get_real_seconds()); + WRITE_ONCE(peer->last_sent, ktime_get_boottime_seconds()); /* skb passed down the stack - don't free it */ skb = NULL; err_unlock: diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index 8fdbb5050690..a21d02ac715e 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -45,7 +45,7 @@ static void unlock_ovpn(struct ovpn_priv *ovpn, */ void ovpn_peer_keepalive_set(struct ovpn_peer *peer, u32 interval, u32 timeout) { - time64_t now = ktime_get_real_seconds(); + time64_t now = ktime_get_boottime_seconds(); netdev_dbg(peer->ovpn->dev, "scheduling keepalive for peer %u: interval=%u timeout=%u\n", @@ -1359,7 +1359,7 @@ void ovpn_peer_keepalive_work(struct work_struct *work) { struct ovpn_priv *ovpn = container_of(work, struct ovpn_priv, keepalive_work.work); - time64_t next_run = 0, now = ktime_get_real_seconds(); + time64_t next_run = 0, now = ktime_get_boottime_seconds(); LLIST_HEAD(release_list); spin_lock_bh(&ovpn->lock); From e9027ffbf5a0f3c12ca8900822e884eae9f0821b Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Mon, 20 Jul 2026 00:24:27 +0800 Subject: [PATCH 058/234] Bluetooth: hci_sync: Protect UUID list traversal The hci_sync conversion moved class-of-device and EIR generation from an HCI request built under hdev->lock to asynchronous command sync work. The worker holds hdev->req_lock, but that lock does not serialize access to hdev->uuids against add_uuid() and remove_uuid(), which update the list under hdev->lock. The following interleaving can therefore occur: CPU0 (command sync work) CPU1 (management socket) fetch uuid from the list list_del(&uuid->list) kfree(uuid) read uuid->size KASAN reports the resulting use-after-free: BUG: KASAN: slab-use-after-free in eir_create+0xb8f/0xee0 Read of size 1 at addr ffff88810dbd8620 by task kworker/u17:0/87 Workqueue: hci0 hci_cmd_sync_work Call Trace: eir_create+0xb8f/0xee0 hci_update_eir_sync+0x1c0/0x330 hci_cmd_sync_work+0x13c/0x290 process_one_work+0x63a/0x1070 worker_thread+0x45b/0xd10 Allocated by task 86: __kasan_kmalloc+0x8f/0xa0 add_uuid+0x18a/0x4b0 hci_sock_sendmsg+0x1033/0x1ea0 Freed by task 92: __kasan_slab_free+0x43/0x70 kfree+0x131/0x3c0 remove_uuid+0x25e/0x560 hci_sock_sendmsg+0x1033/0x1ea0 Hold hdev->lock while generating and committing the class-of-device and EIR snapshots. Release it before sending an HCI command, so controller waits do not happen under the device lock. This protects all UUID list walks in these paths and restores the serialization lost in the command sync conversion. Fixes: 161510ccf91c ("Bluetooth: hci_sync: Make use of hci_cmd_sync_queue set 1") Cc: stable@vger.kernel.org Signed-off-by: Chengfeng Ye Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sync.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index 532534bc601c..c0b1fc293b49 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -929,12 +929,16 @@ int hci_update_eir_sync(struct hci_dev *hdev) memset(&cp, 0, sizeof(cp)); + hci_dev_lock(hdev); eir_create(hdev, cp.data); - if (memcmp(cp.data, hdev->eir, sizeof(cp.data)) == 0) + if (memcmp(cp.data, hdev->eir, sizeof(cp.data)) == 0) { + hci_dev_unlock(hdev); return 0; + } memcpy(hdev->eir, cp.data, sizeof(cp.data)); + hci_dev_unlock(hdev); return __hci_cmd_sync_status(hdev, HCI_OP_WRITE_EIR, sizeof(cp), &cp, HCI_CMD_TIMEOUT); @@ -966,6 +970,7 @@ int hci_update_class_sync(struct hci_dev *hdev) if (hci_dev_test_flag(hdev, HCI_SERVICE_CACHE)) return 0; + hci_dev_lock(hdev); cod[0] = hdev->minor_class; cod[1] = hdev->major_class; cod[2] = get_service_classes(hdev); @@ -973,8 +978,12 @@ int hci_update_class_sync(struct hci_dev *hdev) if (hci_dev_test_flag(hdev, HCI_LIMITED_DISCOVERABLE)) cod[1] |= 0x20; - if (memcmp(cod, hdev->dev_class, 3) == 0) + if (memcmp(cod, hdev->dev_class, 3) == 0) { + hci_dev_unlock(hdev); return 0; + } + + hci_dev_unlock(hdev); return __hci_cmd_sync_status(hdev, HCI_OP_WRITE_CLASS_OF_DEV, sizeof(cod), cod, HCI_CMD_TIMEOUT); From c783399efc22d035443f1dfbf2a09bf9562aaa5e Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Mon, 20 Jul 2026 00:03:11 +0800 Subject: [PATCH 059/234] Bluetooth: RFCOMM: Fix session UAF in set_termios rfcomm_tty_set_termios() tests dlc->session without rfcomm_mutex and later passes the pointer to rfcomm_send_rpn(). The latter dereferences both session->initiator and session->sock. Meanwhile, krfcommd can unlink the DLC and free the session while holding rfcomm_mutex. The race can proceed as follows: TTY ioctl task krfcommd -------------- -------- load dlc->session enter rfcomm_send_rpn() lock rfcomm_mutex clear dlc->session free session unlock rfcomm_mutex read session->initiator KASAN reported: BUG: KASAN: slab-use-after-free in rfcomm_send_rpn+0x297/0x2a0 Read of size 4 at addr ffff88810012a850 by task poc/92 Call Trace: rfcomm_send_rpn+0x297/0x2a0 rfcomm_tty_set_termios+0x50d/0x850 tty_set_termios+0x596/0x950 set_termios+0x46a/0x6e0 tty_mode_ioctl+0x152/0xbd0 tty_ioctl+0x915/0x1240 __x64_sys_ioctl+0x134/0x1c0 Allocated by task 92: rfcomm_session_add+0x9e/0x2e0 rfcomm_dlc_open+0x8b1/0xe00 rfcomm_dev_activate+0x85/0x1a0 rfcomm_tty_open+0x90/0x280 Freed by task 68: kfree+0x131/0x3c0 rfcomm_session_del+0x119/0x180 rfcomm_run+0x737/0x4710 Add rfcomm_dlc_send_rpn(), which holds rfcomm_mutex while it verifies that the DLC is still attached and sends the RPN frame. Have the TTY path use the helper and drop its unlocked session check. This keeps the session valid through both the frame construction and socket send. Fixes: 3a5e903c09ae ("[Bluetooth]: Implement RFCOMM remote port negotiation") Cc: stable@vger.kernel.org Signed-off-by: Chengfeng Ye Signed-off-by: Luiz Augusto von Dentz --- include/net/bluetooth/rfcomm.h | 3 +++ net/bluetooth/rfcomm/core.c | 17 +++++++++++++++++ net/bluetooth/rfcomm/tty.c | 7 +++---- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/include/net/bluetooth/rfcomm.h b/include/net/bluetooth/rfcomm.h index feb6b3ae5e69..102c278e3584 100644 --- a/include/net/bluetooth/rfcomm.h +++ b/include/net/bluetooth/rfcomm.h @@ -226,6 +226,9 @@ int rfcomm_send_rpn(struct rfcomm_session *s, int cr, u8 dlci, u8 bit_rate, u8 data_bits, u8 stop_bits, u8 parity, u8 flow_ctrl_settings, u8 xon_char, u8 xoff_char, u16 param_mask); +int rfcomm_dlc_send_rpn(struct rfcomm_dlc *d, u8 bit_rate, u8 data_bits, + u8 stop_bits, u8 parity, u8 flow_ctrl_settings, + u8 xon_char, u8 xoff_char, u16 param_mask); /* ---- RFCOMM DLCs (channels) ---- */ struct rfcomm_dlc *rfcomm_dlc_alloc(gfp_t prio); diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c index ebeae17b71d1..75f7512dec54 100644 --- a/net/bluetooth/rfcomm/core.c +++ b/net/bluetooth/rfcomm/core.c @@ -1028,6 +1028,23 @@ int rfcomm_send_rpn(struct rfcomm_session *s, int cr, u8 dlci, return rfcomm_send_frame(s, buf, ptr - buf); } +int rfcomm_dlc_send_rpn(struct rfcomm_dlc *d, u8 bit_rate, u8 data_bits, + u8 stop_bits, u8 parity, u8 flow_ctrl_settings, + u8 xon_char, u8 xoff_char, u16 param_mask) +{ + int err = -ENOTCONN; + + rfcomm_lock(); + if (d->session) + err = rfcomm_send_rpn(d->session, 1, d->dlci, bit_rate, + data_bits, stop_bits, parity, + flow_ctrl_settings, xon_char, xoff_char, + param_mask); + rfcomm_unlock(); + + return err; +} + static int rfcomm_send_rls(struct rfcomm_session *s, int cr, u8 dlci, u8 status) { struct rfcomm_hdr *hdr; diff --git a/net/bluetooth/rfcomm/tty.c b/net/bluetooth/rfcomm/tty.c index 4b9a699ec59b..b2c1060394e6 100644 --- a/net/bluetooth/rfcomm/tty.c +++ b/net/bluetooth/rfcomm/tty.c @@ -858,7 +858,7 @@ static void rfcomm_tty_set_termios(struct tty_struct *tty, BT_DBG("tty %p termios %p", tty, old); - if (!dev || !dev->dlc || !dev->dlc->session) + if (!dev || !dev->dlc) return; /* Handle turning off CRTSCTS */ @@ -979,9 +979,8 @@ static void rfcomm_tty_set_termios(struct tty_struct *tty, } if (changes) - rfcomm_send_rpn(dev->dlc->session, 1, dev->dlc->dlci, baud, - data_bits, stop_bits, parity, - RFCOMM_RPN_FLOW_NONE, x_on, x_off, changes); + rfcomm_dlc_send_rpn(dev->dlc, baud, data_bits, stop_bits, parity, + RFCOMM_RPN_FLOW_NONE, x_on, x_off, changes); } static void rfcomm_tty_throttle(struct tty_struct *tty) From df541cd485ff80a5ddc579d99687bc7506df9851 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Mon, 20 Jul 2026 19:47:17 +0800 Subject: [PATCH 060/234] Bluetooth: btusb: validate Realtek vendor event length btusb_recv_event_realtek() reads the event code at data[0] and the Realtek subevent code at data[2] before deciding whether to consume a vendor event as a coredump. For example, the two-byte event ff 00 contains a complete vendor-event header declaring zero parameters. The old classifier still reads a nonexistent third byte and can misclassify the event as a coredump if the adjacent byte is 0x34. Require the HCI event header and first parameter to be present before inspecting the Realtek subevent code. Short events continue through the normal HCI receive path, which owns their protocol validation. Fixes: 044014ce85a1 ("Bluetooth: btrtl: Add Realtek devcoredump support") Signed-off-by: Pengpeng Hou Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btusb.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 08c0a99a62c5..8f7ed469cac6 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -2782,7 +2782,9 @@ static int btusb_setup_realtek(struct hci_dev *hdev) static int btusb_recv_event_realtek(struct hci_dev *hdev, struct sk_buff *skb) { - if (skb->data[0] == HCI_VENDOR_PKT && skb->data[2] == RTK_SUB_EVENT_CODE_COREDUMP) { + if (skb->len >= HCI_EVENT_HDR_SIZE + 1 && + skb->data[0] == HCI_VENDOR_PKT && + skb->data[2] == RTK_SUB_EVENT_CODE_COREDUMP) { struct rtk_dev_coredump_hdr hdr = { .code = RTK_DEVCOREDUMP_CODE_MEMDUMP, }; From 7d8ca62d6a9ef593780161586b4efc811ac094fe Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Mon, 6 Jul 2026 14:06:27 +0200 Subject: [PATCH 061/234] net: phy: marvell: fix return code Return the correct error code, not the value written to the register. Fixes: a219912e0fec ("net: phy: marvell: implement config_inband() method") Signed-off-by: Michael Walle Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260706120637.1947685-1-mwalle@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/phy/marvell.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c index 7a578b5aa2ed..f71cffa88406 100644 --- a/drivers/net/phy/marvell.c +++ b/drivers/net/phy/marvell.c @@ -753,7 +753,7 @@ static int m88e1111_config_inband(struct phy_device *phydev, unsigned int modes) err = phy_modify(phydev, MII_M1111_PHY_EXT_SR, MII_M1111_HWCFG_SERIAL_AN_BYPASS, extsr); if (err < 0) - return extsr; + return err; return phy_modify_paged(phydev, MII_MARVELL_FIBER_PAGE, MII_BMCR, BMCR_ANENABLE, bmcr); From ef01724fa235a228e3d3e8b117e89403cd8feb25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20B=2E=20Marli=C3=A8re?= Date: Mon, 6 Jul 2026 13:24:04 -0300 Subject: [PATCH 062/234] selftests/net: Fix tun IPv6 test addresses to avoid 6to4 range MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The IPv6 addresses used for the tun_vnet_udptnl fixture currently fall in the 2002::/16 prefix, which is reserved for the 6to4 transition mechanism (RFC 3056). On systems where the sit module is loaded, the kernel automatically claims 2002::/16 as a 6to4 tunnel prefix. When the test assigns a 2002:: address to a TUN interface, sit registers a competing local route for the same address. This ambiguity breaks the GENEVE decapsulation path: packets injected via the TUN fd are not delivered to the test socket, causing the IPv6-outer gtgso send_gso_packet variants to fail. Replace all four IPv6 test addresses with addresses from the fd00:db8::/32 range, which is part of the ULA space (fc00::/7, RFC 4193) and carries no special kernel semantics. Fixes: 24e59f26eef2 ("selftest: tun: Add helpers for GSO over UDP tunnel") Signed-off-by: Ricardo B. Marlière Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260706-b4-net_tun_addr-v1-1-3d3cb2473560@suse.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/tun.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/testing/selftests/net/tun.c b/tools/testing/selftests/net/tun.c index cf106a49b55e..abe488bac50b 100644 --- a/tools/testing/selftests/net/tun.c +++ b/tools/testing/selftests/net/tun.c @@ -42,19 +42,19 @@ static struct in_addr param_ipaddr4_inner_src = { }; static struct in6_addr param_ipaddr6_outer_dst = { - { { 0x20, 0x02, 0x0d, 0xb8, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 } }, + { { 0xfd, 0x00, 0x0d, 0xb8, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 } }, }; static struct in6_addr param_ipaddr6_outer_src = { - { { 0x20, 0x02, 0x0d, 0xb8, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 } }, + { { 0xfd, 0x00, 0x0d, 0xb8, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 } }, }; static struct in6_addr param_ipaddr6_inner_dst = { - { { 0x20, 0x02, 0x0d, 0xb8, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 } }, + { { 0xfd, 0x00, 0x0d, 0xb8, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 } }, }; static struct in6_addr param_ipaddr6_inner_src = { - { { 0x20, 0x02, 0x0d, 0xb8, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 } }, + { { 0xfd, 0x00, 0x0d, 0xb8, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 } }, }; #ifndef BIT From f6e3b21608e974c4aaa4cfd73a239dacf1d8a9a3 Mon Sep 17 00:00:00 2001 From: Danielle Ratson Date: Tue, 7 Jul 2026 11:03:04 +0300 Subject: [PATCH 063/234] netlink: specs: rt-link: convert bridge port flag attributes to u8 A number of IFLA_BRPORT_* attributes are documented in the rt-link spec as having the "flag" type, i.e. a payload-less NLA_FLAG attribute whose meaning is presence-only. This does not match the kernel, which emits these attributes with nla_put_u8() and validates them as NLA_U8 in br_port_policy[]. The values are not mere presence flags but carry a u8 payload (0/1). Convert these bridge port attributes from "flag" to "u8" so the spec reflects the actual wire format. Fixes: 077b6022d24b ("doc/netlink/specs: Add sub-message type to rt_link family") Reviewed-by: Petr Machata Acked-by: Nikolay Aleksandrov Reviewed-by: Ido Schimmel Signed-off-by: Danielle Ratson Link: https://patch.msgid.link/a57cdfcfc4a6dcb92106c25b4dde5059fde2bd44.1783236731.git.danieller@nvidia.com Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/rt-link.yaml | 40 ++++++++++++------------ 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/Documentation/netlink/specs/rt-link.yaml b/Documentation/netlink/specs/rt-link.yaml index 892979da098e..68c26a70bb64 100644 --- a/Documentation/netlink/specs/rt-link.yaml +++ b/Documentation/netlink/specs/rt-link.yaml @@ -1585,31 +1585,31 @@ attribute-sets: type: u32 - name: mode - type: flag + type: u8 - name: guard - type: flag + type: u8 - name: protect - type: flag + type: u8 - name: fast-leave - type: flag + type: u8 - name: learning - type: flag + type: u8 - name: unicast-flood - type: flag + type: u8 - name: proxyarp - type: flag + type: u8 - name: learning-sync - type: flag + type: u8 - name: proxyarp-wifi - type: flag + type: u8 - name: root-id type: binary @@ -1656,34 +1656,34 @@ attribute-sets: type: pad - name: mcast-flood - type: flag + type: u8 - name: mcast-to-ucast - type: flag + type: u8 - name: vlan-tunnel - type: flag + type: u8 - name: bcast-flood - type: flag + type: u8 - name: group-fwd-mask type: u16 - name: neigh-suppress - type: flag + type: u8 - name: isolated - type: flag + type: u8 - name: backup-port type: u32 - name: mrp-ring-open - type: flag + type: u8 - name: mrp-in-open - type: flag + type: u8 - name: mcast-eht-hosts-limit type: u32 @@ -1692,10 +1692,10 @@ attribute-sets: type: u32 - name: locked - type: flag + type: u8 - name: mab - type: flag + type: u8 - name: mcast-n-groups type: u32 @@ -1704,7 +1704,7 @@ attribute-sets: type: u32 - name: neigh-vlan-suppress - type: flag + type: u8 - name: backup-nhid type: u32 From 50aff80475abd3533eef4320477037e6fcc6b56e Mon Sep 17 00:00:00 2001 From: David Lee Date: Tue, 7 Jul 2026 10:44:37 +0000 Subject: [PATCH 064/234] net/packet: avoid fanout hook re-registration after unregister packet_set_ring() temporarily detaches a socket from packet delivery while reconfiguring its ring. It records the previous running state, clears po->num, unregisters the protocol hook when needed, drops po->bind_lock, and later restores po->num and re-registers the hook from the saved was_running value. That unlocked window can race with NETDEV_UNREGISTER. The notifier can observe the socket as not running, skip __unregister_prot_hook(), and invalidate the per-socket binding by setting po->ifindex to -1 and clearing po->prot_hook.dev. A one-member fanout group can still retain its shared fanout hook device pointer. When packet_set_ring() resumes, re-registering solely from the stale was_running state can re-add the fanout hook after the device has been unregistered. Treat po->ifindex == -1 as an invalidated binding after reacquiring po->bind_lock. This is distinct from ifindex 0, the normal unbound/wildcard state: ifindex -1 marks an existing device binding that was invalidated when the device was unregistered. Restore po->num as before, but do not re-register the hook if device unregister already detached the socket. Fixes: dc99f600698d ("packet: Add fanout support.") Link: https://lore.kernel.org/netdev/20260701113947.23180-1-david.lee@trailofbits.com/ Signed-off-by: David Lee Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260707104440.833129-1-david.lee@trailofbits.com Signed-off-by: Jakub Kicinski --- net/packet/af_packet.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c index 8e6f3a734ba0..e75d2932475a 100644 --- a/net/packet/af_packet.c +++ b/net/packet/af_packet.c @@ -4561,7 +4561,11 @@ static int packet_set_ring(struct sock *sk, union tpacket_req_u *req_u, spin_lock(&po->bind_lock); WRITE_ONCE(po->num, num); - if (was_running) + /* + * NETDEV_UNREGISTER may have invalidated the binding while bind_lock + * was dropped above. Do not re-add a fanout hook to a dead device. + */ + if (was_running && READ_ONCE(po->ifindex) != -1) register_prot_hook(sk); spin_unlock(&po->bind_lock); From 3f4920d165b29052255527d8ae7619e7ec132ece Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Mon, 6 Jul 2026 20:56:07 +0200 Subject: [PATCH 065/234] bpf: Reject redirect helpers without a bpf_net_context The bpf_redirect*() helpers and skb_do_redirect() obtain the per-task bpf_redirect_info via bpf_net_ctx_get_ri(), which dereferences the current->bpf_net_context unconditionally. That context is established on the paths that run tc BPF such as sch_handle_{ingress,egress}(), *except* for the case where {cls,act}_bpf was attached to a proper qdisc. A program running from there reaches the NULL deref in two ways: * It calls bpf_redirect() directly, which dereferences the context at the top of the helper: tc qdisc add dev eth0 root handle 1: red limit 1MB min 10KB max 20KB \ avpkt 1000 burst 100 qevent early_drop block 10 tc filter add block 10 pref 1 bpf obj redirect.o * It simply returns TC_ACT_REDIRECT without helper call: tcf_qevent_handle() then dispatches to skb_do_redirect(), which dereferences the context Rather than extending bpf_net_context management into the qdisc path, make the redirect helpers refuse to operate when no context exists, and have tcf_qevent_handle() drop a TC_ACT_REDIRECT verdict instead of calling skb_do_redirect(). Previous behaviour was a crash, so nothing regresses by not supporting it. Fixes: 401cb7dae813 ("net: Reference bpf_redirect_info via task_struct on PREEMPT_RT.") Fixes: 3625750f05ec ("net: sched: Introduce helpers for qevent blocks") Signed-off-by: Daniel Borkmann Reviewed-by: Sebastian Andrzej Siewior Link: https://patch.msgid.link/20260706185609.330006-2-daniel@iogearbox.net Signed-off-by: Jakub Kicinski --- net/core/filter.c | 17 +++++++++++------ net/sched/cls_api.c | 6 ++---- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/net/core/filter.c b/net/core/filter.c index b446aa8be5c3..11bb0d236822 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -2552,11 +2552,13 @@ int skb_do_redirect(struct sk_buff *skb) BPF_CALL_2(bpf_redirect, u32, ifindex, u64, flags) { - struct bpf_redirect_info *ri = bpf_net_ctx_get_ri(); + struct bpf_redirect_info *ri; - if (unlikely(flags & (~(BPF_F_INGRESS) | BPF_F_REDIRECT_INTERNAL))) + if (unlikely(!bpf_net_ctx_get() || + (flags & (~(BPF_F_INGRESS) | BPF_F_REDIRECT_INTERNAL)))) return TC_ACT_SHOT; + ri = bpf_net_ctx_get_ri(); ri->flags = flags; ri->tgt_index = ifindex; @@ -2573,11 +2575,12 @@ static const struct bpf_func_proto bpf_redirect_proto = { BPF_CALL_2(bpf_redirect_peer, u32, ifindex, u64, flags) { - struct bpf_redirect_info *ri = bpf_net_ctx_get_ri(); + struct bpf_redirect_info *ri; - if (unlikely(flags)) + if (unlikely(!bpf_net_ctx_get() || flags)) return TC_ACT_SHOT; + ri = bpf_net_ctx_get_ri(); ri->flags = BPF_F_PEER; ri->tgt_index = ifindex; @@ -2595,11 +2598,13 @@ static const struct bpf_func_proto bpf_redirect_peer_proto = { BPF_CALL_4(bpf_redirect_neigh, u32, ifindex, struct bpf_redir_neigh *, params, int, plen, u64, flags) { - struct bpf_redirect_info *ri = bpf_net_ctx_get_ri(); + struct bpf_redirect_info *ri; - if (unlikely((plen && plen < sizeof(*params)) || flags)) + if (unlikely((plen && plen < sizeof(*params)) || + !bpf_net_ctx_get() || flags)) return TC_ACT_SHOT; + ri = bpf_net_ctx_get_ri(); ri->flags = BPF_F_NEIGH | (plen ? BPF_F_NEXTHOP : 0); ri->tgt_index = ifindex; diff --git a/net/sched/cls_api.c b/net/sched/cls_api.c index ffeea6db8337..523cf2a8bd1d 100644 --- a/net/sched/cls_api.c +++ b/net/sched/cls_api.c @@ -4046,6 +4046,8 @@ struct sk_buff *tcf_qevent_handle(struct tcf_qevent *qe, struct Qdisc *sch, stru fl = rcu_dereference_bh(qe->filter_chain); switch (tcf_classify(skb, NULL, fl, &cl_res, false)) { + case TC_ACT_REDIRECT: + fallthrough; case TC_ACT_SHOT: qdisc_qstats_drop(sch); __qdisc_drop(skb, to_free); @@ -4057,10 +4059,6 @@ struct sk_buff *tcf_qevent_handle(struct tcf_qevent *qe, struct Qdisc *sch, stru __qdisc_drop(skb, to_free); *ret = __NET_XMIT_STOLEN; return NULL; - case TC_ACT_REDIRECT: - skb_do_redirect(skb); - *ret = __NET_XMIT_STOLEN; - return NULL; case TC_ACT_CONSUMED: *ret = __NET_XMIT_STOLEN; return NULL; From ec48b3be2c8595dd290be883dbd4fb8b2f9f5d5e Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Mon, 6 Jul 2026 20:56:08 +0200 Subject: [PATCH 066/234] net/sched: Handle TC_ACT_REDIRECT from qdisc filter chains When a TC filter attached to a qdisc filter chain returns TC_ACT_REDIRECT (ex: via an eBPF program calling bpf_redirect() or an act_bpf action), the redirect was silently lost i.e no qdisc classify function handled TC_ACT_REDIRECT, so the packet fell through the switch and was enqueued normally instead of being redirected. This has been broken since bpf_redirect() was introduced for TC in commit 27b29f63058d ("bpf: add bpf_redirect() helper"). We got lucky for a long time because bpf_net_context was a per-CPU variable that was always available. commit 401cb7dae813 ("net: Reference bpf_redirect_info via task_struct on PREEMPT_RT.") turned bpf_net_context into a task_struct member that is only set up by explicit callers. Without a caller setting it up, bpf_redirect() itself crashes with a NULL pointer dereference in bpf_net_ctx_get_ri(). However, even with bpf_net_context available, TC_ACT_REDIRECT from qdisc filter chains cannot be honored without adding skb_do_redirect() calls to every qdisc classify function, which would require changes across net/sched/. Isolate it to ebpf core where it belongs. Instead, add a tcf_classify_qdisc() inline helper in pkt_cls.h, as a wrapper around tcf_classify() for use by qdisc classify functions and tcf_qevent_handle(). When the classify verdict is TC_ACT_REDIRECT, the wrapper converts it to TC_ACT_SHOT, dropping the packet rather than letting it continue silently. Dropping is preferred over letting the packet through because the user immediately sees packet loss. Silently passing the packet through would hide the problem and leave the user wondering why their redirect is not working. The clsact fast path, tc_run() continues to call tcf_classify() directly and is unaffected: TC_ACT_REDIRECT is returned as-is and handled by sch_handle_egress/ingress() calling skb_do_redirect() as before. Fixes: 27b29f63058d ("bpf: add bpf_redirect() helper") Fixes: 401cb7dae813 ("net: Reference bpf_redirect_info via task_struct on PREEMPT_RT.") Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Signed-off-by: Daniel Borkmann Reviewed-by: Sebastian Andrzej Siewior Link: https://patch.msgid.link/20260706185609.330006-3-daniel@iogearbox.net Signed-off-by: Jakub Kicinski --- include/net/pkt_cls.h | 14 +++++++++++++- net/sched/cls_api.c | 4 +--- net/sched/sch_cake.c | 2 +- net/sched/sch_drr.c | 2 +- net/sched/sch_dualpi2.c | 2 +- net/sched/sch_ets.c | 2 +- net/sched/sch_fq_codel.c | 2 +- net/sched/sch_fq_pie.c | 2 +- net/sched/sch_hfsc.c | 2 +- net/sched/sch_htb.c | 2 +- net/sched/sch_multiq.c | 2 +- net/sched/sch_prio.c | 2 +- net/sched/sch_qfq.c | 2 +- net/sched/sch_sfb.c | 2 +- net/sched/sch_sfq.c | 2 +- 15 files changed, 27 insertions(+), 17 deletions(-) diff --git a/include/net/pkt_cls.h b/include/net/pkt_cls.h index 3bd08d7f39c1..5f5cb36439fe 100644 --- a/include/net/pkt_cls.h +++ b/include/net/pkt_cls.h @@ -156,8 +156,20 @@ static inline int tcf_classify(struct sk_buff *skb, { return TC_ACT_UNSPEC; } - #endif +static inline int tcf_classify_qdisc(struct sk_buff *skb, + const struct tcf_proto *tp, + struct tcf_result *res, bool compat_mode) +{ + int ret = tcf_classify(skb, NULL, tp, res, compat_mode); + + /* TC_ACT_REDIRECT from qdisc filter chains is not supported. + * Use BPF via tcx or mirred redirect instead. + */ + if (unlikely(ret == TC_ACT_REDIRECT)) + ret = TC_ACT_SHOT; + return ret; +} static inline unsigned long __cls_set_class(unsigned long *clp, unsigned long cl) diff --git a/net/sched/cls_api.c b/net/sched/cls_api.c index 523cf2a8bd1d..fee4524adc98 100644 --- a/net/sched/cls_api.c +++ b/net/sched/cls_api.c @@ -4045,9 +4045,7 @@ struct sk_buff *tcf_qevent_handle(struct tcf_qevent *qe, struct Qdisc *sch, stru fl = rcu_dereference_bh(qe->filter_chain); - switch (tcf_classify(skb, NULL, fl, &cl_res, false)) { - case TC_ACT_REDIRECT: - fallthrough; + switch (tcf_classify_qdisc(skb, fl, &cl_res, false)) { case TC_ACT_SHOT: qdisc_qstats_drop(sch); __qdisc_drop(skb, to_free); diff --git a/net/sched/sch_cake.c b/net/sched/sch_cake.c index f78f8e950776..505f63fecf64 100644 --- a/net/sched/sch_cake.c +++ b/net/sched/sch_cake.c @@ -1727,7 +1727,7 @@ static u32 cake_classify(struct Qdisc *sch, struct cake_tin_data **t, goto hash; *qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS; - result = tcf_classify(skb, NULL, filter, &res, false); + result = tcf_classify_qdisc(skb, filter, &res, false); if (result >= 0) { #ifdef CONFIG_NET_CLS_ACT diff --git a/net/sched/sch_drr.c b/net/sched/sch_drr.c index 020657f959b5..91b1ef824afa 100644 --- a/net/sched/sch_drr.c +++ b/net/sched/sch_drr.c @@ -312,7 +312,7 @@ static struct drr_class *drr_classify(struct sk_buff *skb, struct Qdisc *sch, *qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS; fl = rcu_dereference_bh(q->filter_list); - result = tcf_classify(skb, NULL, fl, &res, false); + result = tcf_classify_qdisc(skb, fl, &res, false); if (result >= 0) { #ifdef CONFIG_NET_CLS_ACT switch (result) { diff --git a/net/sched/sch_dualpi2.c b/net/sched/sch_dualpi2.c index 27088760eff4..4f678d4ff10e 100644 --- a/net/sched/sch_dualpi2.c +++ b/net/sched/sch_dualpi2.c @@ -364,7 +364,7 @@ static int dualpi2_skb_classify(struct dualpi2_sched_data *q, if (!fl) return NET_XMIT_SUCCESS; - result = tcf_classify(skb, NULL, fl, &res, false); + result = tcf_classify_qdisc(skb, fl, &res, false); if (result >= 0) { #ifdef CONFIG_NET_CLS_ACT switch (result) { diff --git a/net/sched/sch_ets.c b/net/sched/sch_ets.c index cb8cf437ce87..25fcf4079fec 100644 --- a/net/sched/sch_ets.c +++ b/net/sched/sch_ets.c @@ -391,7 +391,7 @@ static struct ets_class *ets_classify(struct sk_buff *skb, struct Qdisc *sch, *qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS; if (TC_H_MAJ(skb->priority) != sch->handle) { fl = rcu_dereference_bh(q->filter_list); - err = tcf_classify(skb, NULL, fl, &res, false); + err = tcf_classify_qdisc(skb, fl, &res, false); #ifdef CONFIG_NET_CLS_ACT switch (err) { case TC_ACT_STOLEN: diff --git a/net/sched/sch_fq_codel.c b/net/sched/sch_fq_codel.c index cafd1f943d99..6cce86ba383c 100644 --- a/net/sched/sch_fq_codel.c +++ b/net/sched/sch_fq_codel.c @@ -91,7 +91,7 @@ static unsigned int fq_codel_classify(struct sk_buff *skb, struct Qdisc *sch, return fq_codel_hash(q, skb) + 1; *qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS; - result = tcf_classify(skb, NULL, filter, &res, false); + result = tcf_classify_qdisc(skb, filter, &res, false); if (result >= 0) { #ifdef CONFIG_NET_CLS_ACT switch (result) { diff --git a/net/sched/sch_fq_pie.c b/net/sched/sch_fq_pie.c index 72f48fa4010b..069e1facd413 100644 --- a/net/sched/sch_fq_pie.c +++ b/net/sched/sch_fq_pie.c @@ -96,7 +96,7 @@ static unsigned int fq_pie_classify(struct sk_buff *skb, struct Qdisc *sch, return fq_pie_hash(q, skb) + 1; *qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS; - result = tcf_classify(skb, NULL, filter, &res, false); + result = tcf_classify_qdisc(skb, filter, &res, false); if (result >= 0) { #ifdef CONFIG_NET_CLS_ACT switch (result) { diff --git a/net/sched/sch_hfsc.c b/net/sched/sch_hfsc.c index 7e537295b8b6..e87f5021a199 100644 --- a/net/sched/sch_hfsc.c +++ b/net/sched/sch_hfsc.c @@ -1143,7 +1143,7 @@ hfsc_classify(struct sk_buff *skb, struct Qdisc *sch, int *qerr) *qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS; head = &q->root; tcf = rcu_dereference_bh(q->root.filter_list); - while (tcf && (result = tcf_classify(skb, NULL, tcf, &res, false)) >= 0) { + while (tcf && (result = tcf_classify_qdisc(skb, tcf, &res, false)) >= 0) { #ifdef CONFIG_NET_CLS_ACT switch (result) { case TC_ACT_QUEUED: diff --git a/net/sched/sch_htb.c b/net/sched/sch_htb.c index 908b9ba9ba2e..fdac0dc8f35a 100644 --- a/net/sched/sch_htb.c +++ b/net/sched/sch_htb.c @@ -243,7 +243,7 @@ static struct htb_class *htb_classify(struct sk_buff *skb, struct Qdisc *sch, } *qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS; - while (tcf && (result = tcf_classify(skb, NULL, tcf, &res, false)) >= 0) { + while (tcf && (result = tcf_classify_qdisc(skb, tcf, &res, false)) >= 0) { #ifdef CONFIG_NET_CLS_ACT switch (result) { case TC_ACT_QUEUED: diff --git a/net/sched/sch_multiq.c b/net/sched/sch_multiq.c index a467dd122369..66df30939aa5 100644 --- a/net/sched/sch_multiq.c +++ b/net/sched/sch_multiq.c @@ -36,7 +36,7 @@ multiq_classify(struct sk_buff *skb, struct Qdisc *sch, int *qerr) int err; *qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS; - err = tcf_classify(skb, NULL, fl, &res, false); + err = tcf_classify_qdisc(skb, fl, &res, false); #ifdef CONFIG_NET_CLS_ACT switch (err) { case TC_ACT_STOLEN: diff --git a/net/sched/sch_prio.c b/net/sched/sch_prio.c index e4dd56a89072..79437c587e7e 100644 --- a/net/sched/sch_prio.c +++ b/net/sched/sch_prio.c @@ -39,7 +39,7 @@ prio_classify(struct sk_buff *skb, struct Qdisc *sch, int *qerr) *qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS; if (TC_H_MAJ(skb->priority) != sch->handle) { fl = rcu_dereference_bh(q->filter_list); - err = tcf_classify(skb, NULL, fl, &res, false); + err = tcf_classify_qdisc(skb, fl, &res, false); #ifdef CONFIG_NET_CLS_ACT switch (err) { case TC_ACT_STOLEN: diff --git a/net/sched/sch_qfq.c b/net/sched/sch_qfq.c index cb56787e1d25..6f3b7273cb16 100644 --- a/net/sched/sch_qfq.c +++ b/net/sched/sch_qfq.c @@ -709,7 +709,7 @@ static struct qfq_class *qfq_classify(struct sk_buff *skb, struct Qdisc *sch, *qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS; fl = rcu_dereference_bh(q->filter_list); - result = tcf_classify(skb, NULL, fl, &res, false); + result = tcf_classify_qdisc(skb, fl, &res, false); if (result >= 0) { #ifdef CONFIG_NET_CLS_ACT switch (result) { diff --git a/net/sched/sch_sfb.c b/net/sched/sch_sfb.c index b1d465094276..ed39869199c0 100644 --- a/net/sched/sch_sfb.c +++ b/net/sched/sch_sfb.c @@ -260,7 +260,7 @@ static bool sfb_classify(struct sk_buff *skb, struct tcf_proto *fl, struct tcf_result res; int result; - result = tcf_classify(skb, NULL, fl, &res, false); + result = tcf_classify_qdisc(skb, fl, &res, false); if (result >= 0) { #ifdef CONFIG_NET_CLS_ACT switch (result) { diff --git a/net/sched/sch_sfq.c b/net/sched/sch_sfq.c index 758b88f21865..77675f9a4c46 100644 --- a/net/sched/sch_sfq.c +++ b/net/sched/sch_sfq.c @@ -171,7 +171,7 @@ static unsigned int sfq_classify(struct sk_buff *skb, struct Qdisc *sch, return sfq_hash(q, skb) + 1; *qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS; - result = tcf_classify(skb, NULL, fl, &res, false); + result = tcf_classify_qdisc(skb, fl, &res, false); if (result >= 0) { #ifdef CONFIG_NET_CLS_ACT switch (result) { From f789afed9448e17f12e0dffe84c2d57acd206d42 Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Mon, 6 Jul 2026 20:56:09 +0200 Subject: [PATCH 067/234] selftests/bpf: Add test for redirect from qdisc qevent block Add a regression test for the NULL current->bpf_net_context deref hit when a BPF classifier attached to a qdisc qevent block asks for a redirect. The classifier runs from tcf_qevent_handle() on the qdisc enqueue path, outside any bpf_net_context. # LDLIBS=-static PKG_CONFIG='pkg-config --static' ./vmtest.sh -- ./test_progs -t qevent [...] + /etc/rcS.d/S50-startup ./test_progs -t qevent #496/1 tc_qevent/redirect_verdict:OK #496/2 tc_qevent/redirect_helper:OK #496 tc_qevent:OK Summary: 1/2 PASSED, 0 SKIPPED, 0 FAILED Signed-off-by: Daniel Borkmann Reviewed-by: Sebastian Andrzej Siewior Link: https://patch.msgid.link/20260706185609.330006-4-daniel@iogearbox.net Signed-off-by: Jakub Kicinski --- tools/testing/selftests/bpf/config | 1 + .../selftests/bpf/prog_tests/tc_qevent.c | 113 ++++++++++++++++++ .../selftests/bpf/progs/test_tc_qevent.c | 23 ++++ 3 files changed, 137 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/tc_qevent.c create mode 100644 tools/testing/selftests/bpf/progs/test_tc_qevent.c diff --git a/tools/testing/selftests/bpf/config b/tools/testing/selftests/bpf/config index adb25146e88c..ea7044f30adc 100644 --- a/tools/testing/selftests/bpf/config +++ b/tools/testing/selftests/bpf/config @@ -82,6 +82,7 @@ CONFIG_NET_SCH_BPF=y CONFIG_NET_SCH_FQ=y CONFIG_NET_SCH_INGRESS=y CONFIG_NET_SCH_HTB=y +CONFIG_NET_SCH_RED=y CONFIG_NET_SCHED=y CONFIG_NETDEVSIM=y CONFIG_NETFILTER=y diff --git a/tools/testing/selftests/bpf/prog_tests/tc_qevent.c b/tools/testing/selftests/bpf/prog_tests/tc_qevent.c new file mode 100644 index 000000000000..67e1d17567ab --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/tc_qevent.c @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +#include +#include +#include + +#include "test_tc_qevent.skel.h" + +#define NS_TX "tc_qevent_tx" +#define NS_RX "tc_qevent_rx" +#define IP_TX "10.255.0.1" +#define IP_RX "10.255.0.2" +#define PIN_PATH "/sys/fs/bpf/tc_qevent_redirect" + +static void blast_udp(void) +{ + struct sockaddr_in dst = {}; + char buf[1400] = {}; + int fd, i; + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (!ASSERT_GE(fd, 0, "udp socket")) + return; + + dst.sin_family = AF_INET; + dst.sin_port = htons(12345); + inet_pton(AF_INET, IP_RX, &dst.sin_addr); + + /* + * Push far more than the RED queue can hold. Once qavg crosses qth_min + * every further packet hits the congestion_drop / early_drop qevent. + */ + for (i = 0; i < 50000; i++) + sendto(fd, buf, sizeof(buf), MSG_DONTWAIT, + (struct sockaddr *)&dst, sizeof(dst)); + + close(fd); +} + +static void run_qevent_redirect(struct bpf_program *prog, __u64 *counter) +{ + struct nstoken *tok = NULL; + int err; + + SYS_NOFAIL("ip netns del %s", NS_TX); + SYS_NOFAIL("ip netns del %s", NS_RX); + unlink(PIN_PATH); + + err = bpf_program__pin(prog, PIN_PATH); + if (!ASSERT_OK(err, "pin prog")) + return; + + SYS(unpin, "ip netns add %s", NS_TX); + SYS(del_tx, "ip netns add %s", NS_RX); + SYS(del_rx, "ip -n %s link add veth0 type veth peer name veth1 netns %s", NS_TX, NS_RX); + SYS(del_rx, "ip -n %s addr add %s/24 dev veth0", NS_TX, IP_TX); + SYS(del_rx, "ip -n %s link set veth0 up", NS_TX); + SYS(del_rx, "ip -n %s addr add %s/24 dev veth1", NS_RX, IP_RX); + SYS(del_rx, "ip -n %s link set veth1 up", NS_RX); + + tok = open_netns(NS_TX); + if (!ASSERT_OK_PTR(tok, "open_netns")) + goto del_rx; + + SYS(close_ns, "tc qdisc add dev veth0 root handle 1: htb default 1"); + SYS(close_ns, "tc class add dev veth0 parent 1: classid 1:1 htb rate 1mbit ceil 1mbit"); + + if (system("tc qdisc add dev veth0 parent 1:1 handle 11: red " + "limit 500000 avpkt 1000 probability 1 min 5000 max 6000 " + "burst 6 qevent early_drop block 10 2>/dev/null")) { + test__skip(); + goto close_ns; + } + + if (system("tc filter add block 10 bpf da object-pinned " + PIN_PATH " 2>/dev/null")) { + test__skip(); + goto close_ns; + } + + blast_udp(); + ASSERT_GT(*counter, 0, "qevent classifier ran"); +close_ns: + close_netns(tok); +del_rx: + SYS_NOFAIL("ip netns del %s", NS_RX); +del_tx: + SYS_NOFAIL("ip netns del %s", NS_TX); +unpin: + bpf_program__unpin(prog, PIN_PATH); +} + +void test_tc_qevent(void) +{ + struct test_tc_qevent *skel; + + skel = test_tc_qevent__open_and_load(); + if (!ASSERT_OK_PTR(skel, "open_and_load")) + return; + + if (test__start_subtest("redirect_verdict")) + run_qevent_redirect(skel->progs.qevent_redirect_verdict, + &skel->bss->verdict_calls); + if (test__start_subtest("redirect_helper")) + run_qevent_redirect(skel->progs.qevent_redirect_helper, + &skel->bss->helper_calls); + + test_tc_qevent__destroy(skel); +} diff --git a/tools/testing/selftests/bpf/progs/test_tc_qevent.c b/tools/testing/selftests/bpf/progs/test_tc_qevent.c new file mode 100644 index 000000000000..1529c111f4aa --- /dev/null +++ b/tools/testing/selftests/bpf/progs/test_tc_qevent.c @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "vmlinux.h" +#include + +int redirect_ifindex = 1; +__u64 verdict_calls = 0; +__u64 helper_calls = 0; + +SEC("tc") +int qevent_redirect_verdict(struct __sk_buff *skb) +{ + __sync_fetch_and_add(&verdict_calls, 1); + return TCX_REDIRECT; +} + +SEC("tc") +int qevent_redirect_helper(struct __sk_buff *skb) +{ + __sync_fetch_and_add(&helper_calls, 1); + return bpf_redirect(redirect_ifindex, 0); +} + +char _license[] SEC("license") = "GPL"; From 1c975de3343cdef506f2eecc833cc1f14b0401c4 Mon Sep 17 00:00:00 2001 From: Zhaolong Zhang Date: Tue, 7 Jul 2026 09:06:22 +0800 Subject: [PATCH 068/234] bonding: fix devconf_all NULL dereference when IPv6 is disabled When booting with the 'ipv6.disable=1' parameter, the devconf_all is never initialized because inet6_init() exits before addrconf_init() is called which initializes it. bond_send_validate(), however, will still call bond_ns_send_all() even ipv6 is indeed disabled. It will lead to NULL derefence of net->ipv6.devconf_all in ip6_pol_route(). BUG: kernel NULL pointer dereference, address: 000000000000000c [...] Workqueue: bond0 bond_arp_monitor [bonding] RIP: 0010:ip6_pol_route+0x69/0x480 [...] Call Trace: ? srso_return_thunk+0x5/0x5f ? __pfx_ip6_pol_route_output+0x10/0x10 fib6_rule_lookup+0xfe/0x260 ? wakeup_preempt+0x8a/0x90 ? srso_return_thunk+0x5/0x5f ? srso_return_thunk+0x5/0x5f ? sched_balance_rq+0x369/0x810 ip6_route_output_flags+0xd7/0x170 bond_ns_send_all+0xde/0x280 [bonding] bond_ab_arp_probe+0x296/0x320 [bonding] ? srso_return_thunk+0x5/0x5f bond_activebackup_arp_mon+0xb4/0x2c0 [bonding] process_one_work+0x196/0x370 worker_thread+0x1af/0x320 ? srso_return_thunk+0x5/0x5f ? __pfx_worker_thread+0x10/0x10 kthread+0xe3/0x120 ? __pfx_kthread+0x10/0x10 ret_from_fork+0x199/0x260 ? __pfx_kthread+0x10/0x10 ret_from_fork_asm+0x1a/0x30 Fix this by adding ipv6_mod_enabled() condition check in the caller. Fixes: 4e24be018eb9 ("bonding: add new parameter ns_targets") Signed-off-by: Qianheng Peng Signed-off-by: Zhaolong Zhang Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20260707010622.487333-1-zhangzl2013@126.com Signed-off-by: Jakub Kicinski --- drivers/net/bonding/bond_main.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index e044fc733b8c..522eab060f9e 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -3455,7 +3455,8 @@ static void bond_send_validate(struct bonding *bond, struct slave *slave) { bond_arp_send_all(bond, slave); #if IS_ENABLED(CONFIG_IPV6) - bond_ns_send_all(bond, slave); + if (likely(ipv6_mod_enabled())) + bond_ns_send_all(bond, slave); #endif } From be7cc4656eb1f54029610e82d1f0fdd3f9b5ec0a Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Tue, 7 Jul 2026 02:00:54 -0500 Subject: [PATCH 069/234] net/iucv: fix use-after-free of a severed iucv_path af_iucv queues not-yet-received message notifications on iucv->message_q, each holding a raw pointer to the connection's iucv_path. When the peer severs the connection, iucv_sever_path() frees that path with iucv_path_free() but leaves the notifications queued. A later recvmsg() drains message_q via iucv_process_message_q() and hands the stale path to message_receive() -- a use-after-free of the freed iucv_path. Drop the queued notifications when the path is severed; once the path is gone they can no longer be received. This also frees the notifications leaked when a socket is closed with messages still queued. Fixes: f0703c80e515 ("[AF_IUCV]: postpone receival of iucv-packets") Closes: https://sashiko.dev/#/patchset/20260705-b4-disp-fc79c0dc-v1-1-d2cdcb57afa9@proton.me?part=1 Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Link: https://patch.msgid.link/20260707-b4-disp-783fedbb-v1-1-463b9dbda2ea@proton.me Signed-off-by: Paolo Abeni --- net/iucv/af_iucv.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/net/iucv/af_iucv.c b/net/iucv/af_iucv.c index b85fb9767dec..e3e71d168c47 100644 --- a/net/iucv/af_iucv.c +++ b/net/iucv/af_iucv.c @@ -337,6 +337,7 @@ static void iucv_sever_path(struct sock *sk, int with_user_data) unsigned char user_data[16]; struct iucv_sock *iucv = iucv_sk(sk); struct iucv_path *path = iucv->path; + struct sock_msg_q *p, *n; /* Whoever resets the path pointer, must sever and free it. */ if (xchg(&iucv->path, NULL)) { @@ -348,6 +349,19 @@ static void iucv_sever_path(struct sock *sk, int with_user_data) } else pr_iucv->path_sever(path, NULL); iucv_path_free(path); + + /* + * Message notifications queued on message_q still reference + * the now freed path; drop them, otherwise a later recvmsg() + * would pass the freed iucv_path to message_receive() via + * iucv_process_message_q(). + */ + spin_lock_bh(&iucv->message_q.lock); + list_for_each_entry_safe(p, n, &iucv->message_q.list, list) { + list_del(&p->list); + kfree(p); + } + spin_unlock_bh(&iucv->message_q.lock); } } From d163725af84ad958bb74ef4f6eb906a04daa8902 Mon Sep 17 00:00:00 2001 From: Aniket Negi Date: Tue, 7 Jul 2026 20:56:39 +0530 Subject: [PATCH 070/234] net: airoha: fix MIB stats collection to be lossless REG_FE_GDM_MIB_CLEAR after every read creates a race window where packets arriving between read and clear are lost from statistics. Switch to a delta-based approach instead: - 64-bit H+L registers (ok pkts/bytes, E64..L1023): read absolute hardware total directly into a local variable; clamp with max(new, old) to prevent torn-read regression when the counter carries between the two reads. - 32-bit registers (drops, bc, mc, errors, runt, long): accumulate (u32)(curr - prev) into a 64-bit software counter; unsigned subtraction handles wrap-around transparently. - tx/rx_len[0] ([0,64] bucket): combines RUNT_CNT (32-bit, delta via tx_runt/rx_runt) and E64_CNT (64-bit, absolute) into a single local accumulator; max(new, old) applied here too to guard against a torn read of E64 when the RUNT accumulator is unchanged between polls. MIB counters are zeroed by the SCU FE reset (EN7581_FE_RST) asserted in airoha_hw_init() at module load, so no explicit MIB clear is needed in airoha_fe_init(). Merge airoha_dev_get_hw_stats() into airoha_update_hw_stats() and move stats_lock inside. Plain spin_lock() is correct: the function is only called from ndo_get_stats64() in process context. Each dev refreshes only its own MIB counters; sibling devs on a shared GDM3/4 port are polled when their own netdev is queried. Fixes: 8f4695fb67b2 ("net: airoha: better handle MIBs for GDM ports with multiple devs attached") Signed-off-by: Aniket Negi Acked-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260707152639.105628-1-aniket.negi03@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/airoha/airoha_eth.c | 171 ++++++++++++++--------- drivers/net/ethernet/airoha/airoha_eth.h | 27 ++++ 2 files changed, 132 insertions(+), 66 deletions(-) diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c index 59001fd4b6f7..90aa8b0210bd 100644 --- a/drivers/net/ethernet/airoha/airoha_eth.c +++ b/drivers/net/ethernet/airoha/airoha_eth.c @@ -1686,11 +1686,14 @@ static void airoha_qdma_stop_napi(struct airoha_qdma *qdma) } } -static void airoha_dev_get_hw_stats(struct airoha_gdm_dev *dev) +static void airoha_update_hw_stats(struct airoha_gdm_dev *dev) { struct airoha_gdm_port *port = dev->port; struct airoha_eth *eth = dev->eth; u32 val, i = 0; + u64 data; + + spin_lock(&port->stats_lock); /* Read relevant MIB for GDM with multiple port attached */ if (port->id == AIROHA_GDM3_IDX || port->id == AIROHA_GDM4_IDX) @@ -1701,152 +1704,188 @@ static void airoha_dev_get_hw_stats(struct airoha_gdm_dev *dev) u64_stats_update_begin(&dev->stats.syncp); - /* TX */ + /* TX - 64-bit H+L registers: hw accumulates the total, read directly. + * Use local variable to prevent readers from seeing intermediate values. + * Clamp to prevent regression from torn reads between H and L. + */ val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_PKT_CNT_H(port->id)); - dev->stats.tx_ok_pkts += ((u64)val << 32); + data = (u64)val << 32; val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_PKT_CNT_L(port->id)); - dev->stats.tx_ok_pkts += val; + data += val; + dev->stats.tx_ok_pkts = max(data, dev->stats.tx_ok_pkts); val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_BYTE_CNT_H(port->id)); - dev->stats.tx_ok_bytes += ((u64)val << 32); + data = (u64)val << 32; val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_BYTE_CNT_L(port->id)); - dev->stats.tx_ok_bytes += val; + data += val; + dev->stats.tx_ok_bytes = max(data, dev->stats.tx_ok_bytes); + /* TX - 32-bit registers: accumulate delta to handle wrap-around. */ val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_DROP_CNT(port->id)); - dev->stats.tx_drops += val; + dev->stats.tx_drops += (u32)(val - dev->stats.mib_prev.tx_drops); + dev->stats.mib_prev.tx_drops = val; val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_BC_CNT(port->id)); - dev->stats.tx_broadcast += val; + dev->stats.tx_broadcast += (u32)(val - dev->stats.mib_prev.tx_broadcast); + dev->stats.mib_prev.tx_broadcast = val; val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_MC_CNT(port->id)); - dev->stats.tx_multicast += val; + dev->stats.tx_multicast += (u32)(val - dev->stats.mib_prev.tx_multicast); + dev->stats.mib_prev.tx_multicast = val; val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_RUNT_CNT(port->id)); - dev->stats.tx_len[i] += val; + dev->stats.mib_prev.tx_runt64 += + (u32)(val - dev->stats.mib_prev.tx_runt); + dev->stats.mib_prev.tx_runt = val; + /* tx_len[0]: RUNT (32-bit, delta) + E64 (64-bit, absolute). */ + data = dev->stats.mib_prev.tx_runt64; val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_E64_CNT_H(port->id)); - dev->stats.tx_len[i] += ((u64)val << 32); + data += (u64)val << 32; val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_E64_CNT_L(port->id)); - dev->stats.tx_len[i++] += val; + data += val; + dev->stats.tx_len[i] = max(data, dev->stats.tx_len[i]); + i++; val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L64_CNT_H(port->id)); - dev->stats.tx_len[i] += ((u64)val << 32); + data = (u64)val << 32; val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L64_CNT_L(port->id)); - dev->stats.tx_len[i++] += val; + data += val; + dev->stats.tx_len[i] = max(data, dev->stats.tx_len[i]); + i++; val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L127_CNT_H(port->id)); - dev->stats.tx_len[i] += ((u64)val << 32); + data = (u64)val << 32; val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L127_CNT_L(port->id)); - dev->stats.tx_len[i++] += val; + data += val; + dev->stats.tx_len[i] = max(data, dev->stats.tx_len[i]); + i++; val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L255_CNT_H(port->id)); - dev->stats.tx_len[i] += ((u64)val << 32); + data = (u64)val << 32; val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L255_CNT_L(port->id)); - dev->stats.tx_len[i++] += val; + data += val; + dev->stats.tx_len[i] = max(data, dev->stats.tx_len[i]); + i++; val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L511_CNT_H(port->id)); - dev->stats.tx_len[i] += ((u64)val << 32); + data = (u64)val << 32; val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L511_CNT_L(port->id)); - dev->stats.tx_len[i++] += val; + data += val; + dev->stats.tx_len[i] = max(data, dev->stats.tx_len[i]); + i++; val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L1023_CNT_H(port->id)); - dev->stats.tx_len[i] += ((u64)val << 32); + data = (u64)val << 32; val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L1023_CNT_L(port->id)); - dev->stats.tx_len[i++] += val; + data += val; + dev->stats.tx_len[i] = max(data, dev->stats.tx_len[i]); + i++; val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_LONG_CNT(port->id)); - dev->stats.tx_len[i++] += val; + dev->stats.tx_len[i++] += (u32)(val - dev->stats.mib_prev.tx_long); + dev->stats.mib_prev.tx_long = val; /* RX */ val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_PKT_CNT_H(port->id)); - dev->stats.rx_ok_pkts += ((u64)val << 32); + data = (u64)val << 32; val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_PKT_CNT_L(port->id)); - dev->stats.rx_ok_pkts += val; + data += val; + dev->stats.rx_ok_pkts = max(data, dev->stats.rx_ok_pkts); val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_BYTE_CNT_H(port->id)); - dev->stats.rx_ok_bytes += ((u64)val << 32); + data = (u64)val << 32; val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_BYTE_CNT_L(port->id)); - dev->stats.rx_ok_bytes += val; + data += val; + dev->stats.rx_ok_bytes = max(data, dev->stats.rx_ok_bytes); val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_DROP_CNT(port->id)); - dev->stats.rx_drops += val; + dev->stats.rx_drops += (u32)(val - dev->stats.mib_prev.rx_drops); + dev->stats.mib_prev.rx_drops = val; val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_BC_CNT(port->id)); - dev->stats.rx_broadcast += val; + dev->stats.rx_broadcast += (u32)(val - dev->stats.mib_prev.rx_broadcast); + dev->stats.mib_prev.rx_broadcast = val; val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_MC_CNT(port->id)); - dev->stats.rx_multicast += val; + dev->stats.rx_multicast += (u32)(val - dev->stats.mib_prev.rx_multicast); + dev->stats.mib_prev.rx_multicast = val; val = airoha_fe_rr(eth, REG_FE_GDM_RX_ERROR_DROP_CNT(port->id)); - dev->stats.rx_errors += val; + dev->stats.rx_errors += (u32)(val - dev->stats.mib_prev.rx_errors); + dev->stats.mib_prev.rx_errors = val; val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_CRC_ERR_CNT(port->id)); - dev->stats.rx_crc_error += val; + dev->stats.rx_crc_error += (u32)(val - dev->stats.mib_prev.rx_crc_error); + dev->stats.mib_prev.rx_crc_error = val; val = airoha_fe_rr(eth, REG_FE_GDM_RX_OVERFLOW_DROP_CNT(port->id)); - dev->stats.rx_over_errors += val; + dev->stats.rx_over_errors += (u32)(val - dev->stats.mib_prev.rx_over_errors); + dev->stats.mib_prev.rx_over_errors = val; val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_FRAG_CNT(port->id)); - dev->stats.rx_fragment += val; + dev->stats.rx_fragment += (u32)(val - dev->stats.mib_prev.rx_fragment); + dev->stats.mib_prev.rx_fragment = val; val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_JABBER_CNT(port->id)); - dev->stats.rx_jabber += val; + dev->stats.rx_jabber += (u32)(val - dev->stats.mib_prev.rx_jabber); + dev->stats.mib_prev.rx_jabber = val; i = 0; val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_RUNT_CNT(port->id)); - dev->stats.rx_len[i] += val; + dev->stats.mib_prev.rx_runt64 += + (u32)(val - dev->stats.mib_prev.rx_runt); + dev->stats.mib_prev.rx_runt = val; + /* rx_len[0]: RUNT (32-bit, delta) + E64 (64-bit, absolute). */ + data = dev->stats.mib_prev.rx_runt64; val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_E64_CNT_H(port->id)); - dev->stats.rx_len[i] += ((u64)val << 32); + data += (u64)val << 32; val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_E64_CNT_L(port->id)); - dev->stats.rx_len[i++] += val; + data += val; + dev->stats.rx_len[i] = max(data, dev->stats.rx_len[i]); + i++; val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L64_CNT_H(port->id)); - dev->stats.rx_len[i] += ((u64)val << 32); + data = (u64)val << 32; val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L64_CNT_L(port->id)); - dev->stats.rx_len[i++] += val; + data += val; + dev->stats.rx_len[i] = max(data, dev->stats.rx_len[i]); + i++; val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L127_CNT_H(port->id)); - dev->stats.rx_len[i] += ((u64)val << 32); + data = (u64)val << 32; val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L127_CNT_L(port->id)); - dev->stats.rx_len[i++] += val; + data += val; + dev->stats.rx_len[i] = max(data, dev->stats.rx_len[i]); + i++; val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L255_CNT_H(port->id)); - dev->stats.rx_len[i] += ((u64)val << 32); + data = (u64)val << 32; val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L255_CNT_L(port->id)); - dev->stats.rx_len[i++] += val; + data += val; + dev->stats.rx_len[i] = max(data, dev->stats.rx_len[i]); + i++; val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L511_CNT_H(port->id)); - dev->stats.rx_len[i] += ((u64)val << 32); + data = (u64)val << 32; val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L511_CNT_L(port->id)); - dev->stats.rx_len[i++] += val; + data += val; + dev->stats.rx_len[i] = max(data, dev->stats.rx_len[i]); + i++; val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L1023_CNT_H(port->id)); - dev->stats.rx_len[i] += ((u64)val << 32); + data = (u64)val << 32; val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L1023_CNT_L(port->id)); - dev->stats.rx_len[i++] += val; + data += val; + dev->stats.rx_len[i] = max(data, dev->stats.rx_len[i]); + i++; val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_LONG_CNT(port->id)); - dev->stats.rx_len[i++] += val; + dev->stats.rx_len[i] += (u32)(val - dev->stats.mib_prev.rx_long); + dev->stats.mib_prev.rx_long = val; u64_stats_update_end(&dev->stats.syncp); -} - -static void airoha_update_hw_stats(struct airoha_gdm_dev *dev) -{ - struct airoha_gdm_port *port = dev->port; - int i; - - spin_lock(&port->stats_lock); - - for (i = 0; i < ARRAY_SIZE(port->devs); i++) { - if (port->devs[i]) - airoha_dev_get_hw_stats(port->devs[i]); - } - - /* Reset MIB counters */ - airoha_fe_set(dev->eth, REG_FE_GDM_MIB_CLEAR(port->id), - FE_GDM_MIB_RX_CLEAR_MASK | FE_GDM_MIB_TX_CLEAR_MASK); spin_unlock(&port->stats_lock); } diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h index f6d01a8e8da1..fe934f9ffe8a 100644 --- a/drivers/net/ethernet/airoha/airoha_eth.h +++ b/drivers/net/ethernet/airoha/airoha_eth.h @@ -245,6 +245,33 @@ struct airoha_hw_stats { u64 rx_fragment; u64 rx_jabber; u64 rx_len[7]; + + struct { + /* Previous HW register values for 32-bit counter delta + * tracking. Storing the last seen value and accumulating + * (u32)(curr - prev) into the 64-bit software counter + * handles wrap-around transparently via unsigned arithmetic. + * tx_runt64/rx_runt64 hold the running sum of runt deltas. + * These fields are never reported to userspace. + */ + u32 tx_drops; + u32 tx_broadcast; + u32 tx_multicast; + u32 tx_runt; + u32 tx_long; + u64 tx_runt64; + u32 rx_drops; + u32 rx_broadcast; + u32 rx_multicast; + u32 rx_errors; + u32 rx_crc_error; + u32 rx_over_errors; + u32 rx_fragment; + u32 rx_jabber; + u32 rx_runt; + u32 rx_long; + u64 rx_runt64; + } mib_prev; }; enum { From 4032f8ed10fcb84d41c508dfb04be96589f78dfe Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Tue, 7 Jul 2026 15:16:35 -0700 Subject: [PATCH 071/234] openvswitch: fix GSO userspace truncation underflow OVS_ACTION_ATTR_TRUNC currently stores a delta from the original skb length in OVS_CB(skb)->cutlen. When a later userspace action segments a GSO skb, queue_gso_packets() reuses that delta for each smaller segment. A segment can then reach queue_userspace_packet() with cutlen greater than skb->len, underflowing the length passed to skb_zerocopy(). Store the maximum preserved length instead and bound each consumer against the current skb length. Use U32_MAX as the no-truncation sentinel so the value remains valid if skb geometry changes before a consumer handles it. Fixes: f2a4d086ed4c ("openvswitch: Add packet truncation support.") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.5 Signed-off-by: Kyle Zeng Reviewed-by: Ilya Maximets Reviewed-by: Aaron Conole Link: https://patch.msgid.link/20260707221635.27489-1-kylebot@openai.com Signed-off-by: Paolo Abeni --- net/openvswitch/actions.c | 19 +++++++------------ net/openvswitch/datapath.c | 25 ++++++++++++++----------- net/openvswitch/datapath.h | 2 +- net/openvswitch/vport.c | 2 +- 4 files changed, 23 insertions(+), 25 deletions(-) diff --git a/net/openvswitch/actions.c b/net/openvswitch/actions.c index 140388a18ae0..513fca6a8e8a 100644 --- a/net/openvswitch/actions.c +++ b/net/openvswitch/actions.c @@ -837,12 +837,8 @@ static void do_output(struct datapath *dp, struct sk_buff *skb, int out_port, u16 mru = OVS_CB(skb)->mru; u32 cutlen = OVS_CB(skb)->cutlen; - if (unlikely(cutlen > 0)) { - if (skb->len - cutlen > ovs_mac_header_len(key)) - pskb_trim(skb, skb->len - cutlen); - else - pskb_trim(skb, ovs_mac_header_len(key)); - } + if (unlikely(cutlen < skb->len)) + pskb_trim(skb, max(cutlen, ovs_mac_header_len(key))); if (likely(!mru || (skb->len <= mru + vport->dev->hard_header_len))) { @@ -1234,7 +1230,7 @@ static void execute_psample(struct datapath *dp, struct sk_buff *skb, psample_group.net = ovs_dp_get_net(dp); md.in_ifindex = OVS_CB(skb)->input_vport->dev->ifindex; - md.trunc_size = skb->len - OVS_CB(skb)->cutlen; + md.trunc_size = min(skb->len, OVS_CB(skb)->cutlen); md.rate_as_probability = 1; rate = OVS_CB(skb)->probability ? OVS_CB(skb)->probability : U32_MAX; @@ -1284,22 +1280,21 @@ static int do_execute_actions(struct datapath *dp, struct sk_buff *skb, clone = skb_clone(skb, GFP_ATOMIC); if (clone) do_output(dp, clone, port, key); - OVS_CB(skb)->cutlen = 0; + OVS_CB(skb)->cutlen = U32_MAX; break; } case OVS_ACTION_ATTR_TRUNC: { struct ovs_action_trunc *trunc = nla_data(a); - if (skb->len > trunc->max_len) - OVS_CB(skb)->cutlen = skb->len - trunc->max_len; + OVS_CB(skb)->cutlen = trunc->max_len; break; } case OVS_ACTION_ATTR_USERSPACE: output_userspace(dp, skb, key, a, attr, len, OVS_CB(skb)->cutlen); - OVS_CB(skb)->cutlen = 0; + OVS_CB(skb)->cutlen = U32_MAX; if (nla_is_last(a, rem)) { consume_skb(skb); return 0; @@ -1453,7 +1448,7 @@ static int do_execute_actions(struct datapath *dp, struct sk_buff *skb, case OVS_ACTION_ATTR_PSAMPLE: execute_psample(dp, skb, a); - OVS_CB(skb)->cutlen = 0; + OVS_CB(skb)->cutlen = U32_MAX; if (nla_is_last(a, rem)) { consume_skb(skb); return 0; diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c index f0164817d9b7..eaf332b156d7 100644 --- a/net/openvswitch/datapath.c +++ b/net/openvswitch/datapath.c @@ -276,7 +276,7 @@ void ovs_dp_process_packet(struct sk_buff *skb, struct sw_flow_key *key) upcall.portid = ovs_vport_find_upcall_portid(p, skb); upcall.mru = OVS_CB(skb)->mru; - error = ovs_dp_upcall(dp, skb, key, &upcall, 0); + error = ovs_dp_upcall(dp, skb, key, &upcall, U32_MAX); switch (error) { case 0: case -EAGAIN: @@ -457,7 +457,8 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb, struct sk_buff *nskb = NULL; struct sk_buff *user_skb = NULL; /* to be queued to userspace */ struct nlattr *nla; - size_t len; + size_t msg_size; + size_t skb_len; unsigned int hlen; int err, dp_ifindex; u64 hash; @@ -478,7 +479,8 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb, skb = nskb; } - if (nla_attr_size(skb->len) > USHRT_MAX) { + skb_len = min(skb->len, cutlen); + if (nla_attr_size(skb_len) > USHRT_MAX) { err = -EFBIG; goto out; } @@ -493,13 +495,13 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb, * padding logic. Only perform zerocopy if padding is not required. */ if (dp->user_features & OVS_DP_F_UNALIGNED) - hlen = skb_zerocopy_headlen(skb); + hlen = min(skb_zerocopy_headlen(skb), cutlen); else - hlen = skb->len; + hlen = skb_len; - len = upcall_msg_size(upcall_info, hlen - cutlen, - OVS_CB(skb)->acts_origlen); - user_skb = genlmsg_new(len, GFP_ATOMIC); + msg_size = upcall_msg_size(upcall_info, hlen, + OVS_CB(skb)->acts_origlen); + user_skb = genlmsg_new(msg_size, GFP_ATOMIC); if (!user_skb) { err = -ENOMEM; goto out; @@ -560,7 +562,7 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb, } /* Add OVS_PACKET_ATTR_LEN when packet is truncated */ - if (cutlen > 0 && + if (skb_len < skb->len && nla_put_u32(user_skb, OVS_PACKET_ATTR_LEN, skb->len)) { err = -ENOBUFS; goto out; @@ -585,9 +587,9 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb, err = -ENOBUFS; goto out; } - nla->nla_len = nla_attr_size(skb->len - cutlen); + nla->nla_len = nla_attr_size(skb_len); - err = skb_zerocopy(user_skb, skb, skb->len - cutlen, hlen); + err = skb_zerocopy(user_skb, skb, skb_len, hlen); if (err) goto out; @@ -644,6 +646,7 @@ static int ovs_packet_cmd_execute(struct sk_buff *skb, struct genl_info *info) packet->ignore_df = 1; } OVS_CB(packet)->mru = mru; + OVS_CB(packet)->cutlen = U32_MAX; if (a[OVS_PACKET_ATTR_HASH]) { hash = nla_get_u64(a[OVS_PACKET_ATTR_HASH]); diff --git a/net/openvswitch/datapath.h b/net/openvswitch/datapath.h index db0c3e69d66c..696640e88fa7 100644 --- a/net/openvswitch/datapath.h +++ b/net/openvswitch/datapath.h @@ -118,7 +118,7 @@ struct datapath { * @mru: The maximum received fragement size; 0 if the packet is not * fragmented. * @acts_origlen: The netlink size of the flow actions applied to this skb. - * @cutlen: The number of bytes from the packet end to be removed. + * @cutlen: The number of bytes in the packet to preserve on output. * @probability: The sampling probability that was applied to this skb; 0 means * no sampling has occurred; U32_MAX means 100% probability. * @upcall_pid: Netlink socket PID to use for sending this packet to userspace; diff --git a/net/openvswitch/vport.c b/net/openvswitch/vport.c index 56b2e2d1a749..12741485c939 100644 --- a/net/openvswitch/vport.c +++ b/net/openvswitch/vport.c @@ -502,7 +502,7 @@ int ovs_vport_receive(struct vport *vport, struct sk_buff *skb, OVS_CB(skb)->input_vport = vport; OVS_CB(skb)->mru = 0; - OVS_CB(skb)->cutlen = 0; + OVS_CB(skb)->cutlen = U32_MAX; OVS_CB(skb)->probability = 0; OVS_CB(skb)->upcall_pid = 0; if (unlikely(dev_net(skb->dev) != ovs_dp_get_net(vport->dp))) { From e32649b4bad90a6216d8e93cd7dd050af8ac9740 Mon Sep 17 00:00:00 2001 From: Manjunath Patil Date: Tue, 7 Jul 2026 16:39:11 -0700 Subject: [PATCH 072/234] net/mlx5e: Use sender devcom for MPV master-up After PCIe DPC recovery, mlx5 reloads the affected functions and replays multiport affiliation events. In the reported failure, the first relevant device error was: pcieport 0000:10:01.1: DPC: containment event pcieport 0000:10:01.1: PCIe Bus Error: severity=Uncorrected (Fatal) pcieport 0000:10:01.1: [ 5] SDES (First) mlx5 recovered the PCI functions and resumed 0000:11:00.1. During that resume, RDMA multiport binding replayed MLX5_DRIVER_EVENT_AFFILIATION_DONE and mlx5e sent MPV_DEVCOM_MASTER_UP. The host then panicked with: BUG: kernel NULL pointer dereference, address: 0000000000000010 RIP: mlx5_devcom_comp_set_ready+0x5/0x40 [mlx5_core] RDI: 0000000000000000 Call trace included: mlx5_devcom_comp_set_ready mlx5e_devcom_event_mpv mlx5_devcom_send_event mlx5_ib_bind_slave_port mlx5r_mp_probe mlx5_pci_resume MPV devcom registration publishes mlx5e private data to the component peer list before mlx5e_devcom_init_mpv() stores the returned component device in priv->devcom. A concurrent master-up event can therefore reach a peer whose private data is visible but whose priv->devcom backpointer is still NULL. MPV_DEVCOM_MASTER_UP already carries the sender/master mlx5e private data as event_data. The ready bit is stored on the shared devcom component, not on an individual peer. Use the sender devcom when marking the MPV component ready. This preserves the readiness transition while avoiding a NULL dereference of the peer devcom pointer during affiliation replay after PCI error recovery. Fixes: bf11485f8419 ("net/mlx5: Register mlx5e priv to devcom in MPV mode") Assisted-by: Codex:gpt-5 Signed-off-by: Manjunath Patil Cc: stable@vger.kernel.org # 6.7+ Reviewed-by: Tariq Toukan Link: https://patch.msgid.link/20260707233911.3651139-1-manjunath.b.patil@oracle.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/mellanox/mlx5/core/en_main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c index aa8610cedaa8..c1acb9012d3f 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c @@ -211,11 +211,11 @@ static void mlx5e_disable_async_events(struct mlx5e_priv *priv) static int mlx5e_devcom_event_mpv(int event, void *my_data, void *event_data) { - struct mlx5e_priv *slave_priv = my_data; + struct mlx5e_priv *master_priv = event_data; switch (event) { case MPV_DEVCOM_MASTER_UP: - mlx5_devcom_comp_set_ready(slave_priv->devcom, true); + mlx5_devcom_comp_set_ready(master_priv->devcom, true); break; case MPV_DEVCOM_MASTER_DOWN: /* no need for comp set ready false since we unregister after From 5521ae71e32a8069ed4ca6e792179dc57bc43ab2 Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Tue, 7 Jul 2026 19:43:14 -0700 Subject: [PATCH 073/234] rds: drop incoming messages that cross network namespace boundaries rds_find_bound() looks up the destination socket using a global rhashtable keyed solely on (addr, port, scope_id). Network namespaces are not part of the key, so a sender in netns A can deliver an incoming message (inc) to a socket that lives in a different netns B. When this happens, inc->i_conn points to an rds_connection whose c_net is netns A, but the receiving rs lives in netns B. Once the child process that created netns A exits, cleanup_net() calls rds_loop_exit_net() -> rds_loop_kill_conns() -> rds_conn_destroy(), freeing that connection. If the survivor socket in netns B still holds the inc, any subsequent dereference of inc->i_conn is a use-after-free. There are two dangerous sites in rds_clear_recv_queue(): 1. inc->i_conn->c_lcong (offset 88 of freed rds_connection, size 200) read via rds_recv_rcvbuf_delta() -- confirmed by KASAN. 2. inc->i_conn->c_trans->inc_free(inc) (function pointer at offset 80) called via rds_inc_put() when the inc refcount reaches zero -- same race window, potential call-through-freed-object primitive. The bug is reachable from unprivileged user namespaces (CLONE_NEWUSER + CLONE_NEWNET), available since Linux 3.8. Fix this by rejecting the delivery in rds_recv_incoming() when the socket returned by rds_find_bound() belongs to a different network namespace than the connection that carried the message. Use the existing rds_conn_net() / sock_net() helpers and net_eq() for the comparison. Fixes: c809195f5523 ("rds: clean up loopback rds_connections on netns deletion") Signed-off-by: Aldo Ariel Panzardo Reviewed-by: Allison Henderson Tested-by: Allison Henderson Signed-off-by: Allison Henderson Link: https://patch.msgid.link/20260708024314.601139-1-achender@kernel.org Signed-off-by: Paolo Abeni --- net/rds/recv.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/net/rds/recv.c b/net/rds/recv.c index 4b3f9e4a8bfd..cf3884d87931 100644 --- a/net/rds/recv.c +++ b/net/rds/recv.c @@ -399,6 +399,21 @@ void rds_recv_incoming(struct rds_connection *conn, struct in6_addr *saddr, goto out; } + /* + * rds_find_bound() uses a global (netns-agnostic) hash table. + * An RDS connection created in netns A can match a socket bound + * in the init netns, delivering inc cross-netns with inc->i_conn + * pointing into netns A. When cleanup_net() then frees that conn, + * any subsequent dereference of inc->i_conn is a use-after-free. + * Drop the inc if the receiving socket lives in a different netns. + */ + if (!net_eq(sock_net(rds_rs_to_sk(rs)), rds_conn_net(conn))) { + rds_stats_inc(s_recv_drop_no_sock); + rds_sock_put(rs); + rs = NULL; + goto out; + } + /* Process extension headers */ rds_recv_incoming_exthdrs(inc, rs); From 96e37e2f618e931aa97af95e707dcdfb1ec41264 Mon Sep 17 00:00:00 2001 From: Zhixing Chen Date: Wed, 8 Jul 2026 12:22:44 +0800 Subject: [PATCH 074/234] gtp: parse extension headers before reading inner protocol GTPv1-U packets may carry a chain of extension headers before the inner IP packet. The receive path already parses and skips these extension headers, but it currently reads the inner protocol before doing so. As a result, the first extension header byte is interpreted as the inner IP version. Packets with extension headers are then dropped before PDP lookup. Parse the extension header chain before calling gtp_inner_proto(), so the inner protocol is read from the actual inner IP header. Fixes: c75fc0b9e5be ("gtp: identify tunnel via GTP device + GTP version + TEID + family") Signed-off-by: Zhixing Chen Link: https://patch.msgid.link/20260708042244.120898-1-running910@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/gtp.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/net/gtp.c b/drivers/net/gtp.c index a60ef32b35b8..c0e38878af51 100644 --- a/drivers/net/gtp.c +++ b/drivers/net/gtp.c @@ -826,13 +826,17 @@ static int gtp1u_udp_encap_recv(struct gtp_dev *gtp, struct sk_buff *skb) if (!pskb_may_pull(skb, hdrlen)) return -1; + gtp1 = (struct gtp1_header *)(skb->data + sizeof(struct udphdr)); + + if (gtp1->flags & GTP1_F_EXTHDR && + gtp_parse_exthdrs(skb, &hdrlen) < 0) + return -1; + if (gtp_inner_proto(skb, hdrlen, &inner_proto) < 0) { netdev_dbg(gtp->dev, "GTP packet does not encapsulate an IP packet\n"); return -1; } - gtp1 = (struct gtp1_header *)(skb->data + sizeof(struct udphdr)); - pctx = gtp1_pdp_find(gtp, ntohl(gtp1->tid), gtp_proto_to_family(inner_proto)); if (!pctx) { @@ -840,10 +844,6 @@ static int gtp1u_udp_encap_recv(struct gtp_dev *gtp, struct sk_buff *skb) return 1; } - if (gtp1->flags & GTP1_F_EXTHDR && - gtp_parse_exthdrs(skb, &hdrlen) < 0) - return -1; - return gtp_rx(pctx, skb, hdrlen, gtp->role, inner_proto); } From 745fb794c3e933c023af9dbb5876a5e16ad2dc71 Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Wed, 8 Jul 2026 17:35:34 +0800 Subject: [PATCH 075/234] rxrpc: fix io_thread race in rxrpc_wake_up_io_thread() rxrpc_wake_up_io_thread() checks local->io_thread before waking it, but then reloads the pointer for wake_up_process(). local->io_thread is cleared with WRITE_ONCE() when the I/O thread exits, so the second load can see NULL even if the first load did not. Take a READ_ONCE() snapshot and use it for both the NULL check and the wake_up_process() call, as rxrpc_encap_rcv() already does. Fixes: 5800b1cf3fd8 ("rxrpc: Allow CHALLENGEs to the passed to the app for a RESPONSE") Signed-off-by: Xuanqiang Luo Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260708093534.53486-1-xuanqiang.luo@linux.dev Signed-off-by: Paolo Abeni --- net/rxrpc/ar-internal.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/net/rxrpc/ar-internal.h b/net/rxrpc/ar-internal.h index ce946b0a03e2..865f05fe37ab 100644 --- a/net/rxrpc/ar-internal.h +++ b/net/rxrpc/ar-internal.h @@ -1285,9 +1285,11 @@ int rxrpc_io_thread(void *data); void rxrpc_post_response(struct rxrpc_connection *conn, struct sk_buff *skb); static inline void rxrpc_wake_up_io_thread(struct rxrpc_local *local) { - if (!local->io_thread) + struct task_struct *io_thread = READ_ONCE(local->io_thread); + + if (!io_thread) return; - wake_up_process(READ_ONCE(local->io_thread)); + wake_up_process(io_thread); } static inline bool rxrpc_protocol_error(struct sk_buff *skb, enum rxrpc_abort_reason why) From 2a12c05aef213ff304ecc9e2f351de20731946b8 Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Wed, 8 Jul 2026 12:29:03 +0200 Subject: [PATCH 076/234] vsock/virtio: collapse receive queue under memory pressure When many small packets accumulate in the receive queue, the skb overhead can exceed buf_alloc even while the payload is within bounds. This causes virtio_transport_inc_rx_pkt() to reject packets, leading to connection resets during large transfers under backpressure. The issue was reported by Brien, who has a reproducer, but it is also easily reproducible with iperf-vsock [1] using a small packet size: iperf3 --vsock -c $CID -l 129 which fails immediately without this patch but with commit 059b7dbd20a6 ("vsock/virtio: fix potential unbounded skb queue"). Inspired by TCP's tcp_collapse() which solves a similar problem, add virtio_transport_collapse_rx_queue() that walks the receive queue and re-copies data into compact linear skbs to reduce the overhead. The collapse is triggered proactively from when the number of skb queued is close to exceeding the overhead budget. A pre-scan counts the eligible bytes to size each allocation precisely, avoiding waste for isolated small packets. Partially consumed skbs are kept as-is to preserve buf_used/fwd_cnt accounting, EOM-marked skbs to maintain SEQPACKET message boundaries, and skbs already larger than the collapse target because they already have a good data-to-overhead ratio. Walking a large queue may take a significant amount of time and cache misses, causing traffic burstiness. To limit this, the collapse stops once enough room is freed for this packet and the next one, but may opportunistically free more to fill each collapsed skb to capacity. [1] https://github.com/stefano-garzarella/iperf-vsock Fixes: 059b7dbd20a6 ("vsock/virtio: fix potential unbounded skb queue") Cc: stable@vger.kernel.org Reported-by: Brien Oberstein Closes: https://lore.kernel.org/netdev/618701dd023e$063de350$12b9a9f0$@gmail.com/ Tested-by: Brien Oberstein Signed-off-by: Stefano Garzarella Acked-by: Michael S. Tsirkin Reviewed-by: Bobby Eshleman Link: https://patch.msgid.link/20260708102904.50732-2-sgarzare@redhat.com Signed-off-by: Paolo Abeni --- net/vmw_vsock/virtio_transport_common.c | 165 +++++++++++++++++++++++- 1 file changed, 164 insertions(+), 1 deletion(-) diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c index 09475007165b..8becad81279c 100644 --- a/net/vmw_vsock/virtio_transport_common.c +++ b/net/vmw_vsock/virtio_transport_common.c @@ -26,6 +26,13 @@ /* Threshold for detecting small packets to copy */ #define GOOD_COPY_LEN 128 +/* Max payload that can be collapsed into a single linear skb, using the same + * allocation threshold as virtio_vsock_alloc_skb() to avoid adding pressure + * on the page allocator. + */ +#define MAX_COLLAPSE_LEN \ + SKB_MAX_ORDER(VIRTIO_VSOCK_SKB_HEADROOM, PAGE_ALLOC_COSTLY_ORDER) + static void virtio_transport_cancel_close_work(struct vsock_sock *vsk, bool cancel_timeout); static s64 virtio_transport_has_space(struct virtio_vsock_sock *vvs); @@ -420,6 +427,145 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk, return ret; } +static bool virtio_transport_can_collapse(struct sk_buff *skb) +{ + /* skbs that are partially consumed, mark a SEQPACKET message boundary, + * or are already large enough should not be collapsed: they either + * need special accounting, carry protocol state, or already have a + * good data-to-overhead ratio. + */ + if (VIRTIO_VSOCK_SKB_CB(skb)->offset) + return false; + if (le32_to_cpu(virtio_vsock_hdr(skb)->flags) & VIRTIO_VSOCK_SEQ_EOM) + return false; + if (skb->len >= MAX_COLLAPSE_LEN) + return false; + return true; +} + +/* Iterate through the packets in the queue starting from the current skb to + * count the number of bytes we can collapse. + */ +static unsigned int +virtio_transport_collapse_size(struct sk_buff *skb, struct sk_buff_head *queue) +{ + unsigned int target = skb->len - VIRTIO_VSOCK_SKB_CB(skb)->offset; + + while ((skb = skb_peek_next(skb, queue)) && + virtio_transport_can_collapse(skb)) { + unsigned int len = skb->len - VIRTIO_VSOCK_SKB_CB(skb)->offset; + + if (len > MAX_COLLAPSE_LEN - target) + return target; + + target += len; + } + + return target; +} + +/* Called under lock_sock to compact the receive queue by merging small skbs. + * @min_to_free: minimum number of skbs to eliminate from the queue. May free + * more to fill each collapsed skb to capacity. + */ +static void +virtio_transport_collapse_rx_queue(struct virtio_vsock_sock *vvs, + u32 min_to_free) +{ + struct sk_buff *skb, *next_skb, *new_skb = NULL; + struct sk_buff_head new_queue; + u32 saved = 0; + + __skb_queue_head_init(&new_queue); + + skb_queue_walk_safe(&vvs->rx_queue, skb, next_skb) { + struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb); + u32 src_off = VIRTIO_VSOCK_SKB_CB(skb)->offset; + u32 src_len = skb->len - src_off; + bool keep; + + keep = !virtio_transport_can_collapse(skb); + if (keep) { + /* Finalize pending collapsed skb to preserve packet + * ordering. + */ + if (new_skb) { + __skb_queue_tail(&new_queue, new_skb); + new_skb = NULL; + saved--; + } + goto next; + } + + /* Finalize if this packet won't fit in the remaining tailroom, + * so we can allocate a right-sized new_skb. + */ + if (new_skb && src_len > skb_tailroom(new_skb)) { + __skb_queue_tail(&new_queue, new_skb); + new_skb = NULL; + saved--; + } + + if (!new_skb) { + unsigned int alloc_size; + + /* Check after finalizing to opportunistically fill + * each collapsed skb to capacity, merging more skbs + * than strictly required. + */ + if (saved >= min_to_free) + break; + + alloc_size = virtio_transport_collapse_size(skb, &vvs->rx_queue); + + /* Only this skb's data is eligible, nothing to merge + * with. Keep as-is. + */ + if (alloc_size <= src_len) { + keep = true; + goto next; + } + + new_skb = virtio_vsock_alloc_linear_skb(alloc_size + + VIRTIO_VSOCK_SKB_HEADROOM, GFP_KERNEL); + if (!new_skb) + break; + + memcpy(virtio_vsock_hdr(new_skb), hdr, + sizeof(struct virtio_vsock_hdr)); + virtio_vsock_hdr(new_skb)->len = 0; + } + + /* Cannot fail since src_off/src_len are within bounds, but if + * it does, discard new_skb to avoid queuing corrupted data. + */ + if (WARN_ON_ONCE(skb_copy_bits(skb, src_off, + skb_put(new_skb, src_len), + src_len))) { + kfree_skb(new_skb); + new_skb = NULL; + break; + } + + le32_add_cpu(&virtio_vsock_hdr(new_skb)->len, src_len); + virtio_vsock_hdr(new_skb)->flags |= hdr->flags; + +next: + __skb_unlink(skb, &vvs->rx_queue); + if (keep) { + __skb_queue_tail(&new_queue, skb); + } else { + consume_skb(skb); + saved++; + } + } + + if (new_skb) + __skb_queue_tail(&new_queue, new_skb); + + skb_queue_splice(&new_queue, &vvs->rx_queue); +} + static bool virtio_transport_inc_rx_pkt(struct virtio_vsock_sock *vvs, u32 len) { @@ -1354,12 +1500,29 @@ virtio_transport_recv_enqueue(struct vsock_sock *vsk, { struct virtio_vsock_sock *vvs = vsk->trans; bool can_enqueue, free_pkt = false; + u32 len, queue_max, queue_len; struct virtio_vsock_hdr *hdr; - u32 len; hdr = virtio_vsock_hdr(skb); len = le32_to_cpu(hdr->len); + /* virtio_transport_inc_rx_pkt() rejects packets when the per-skb + * overhead (skb_queue_len * SKB_TRUESIZE(0)) exceeds buf_alloc. + * Proactively collapse the queue before that happens. + * No rx_lock needed: lock_sock is held by caller, preventing + * concurrent enqueue or dequeue. + */ + queue_max = vvs->buf_alloc / SKB_TRUESIZE(0); + queue_len = skb_queue_len(&vvs->rx_queue); + if (queue_len >= queue_max) { + /* Walking a large queue may take a significant amount of time + * and cache misses, causing traffic burstiness. Limit the + * collapse to freeing room for this packet and the next one. + * It may free more to fill each collapsed skb to capacity. + */ + virtio_transport_collapse_rx_queue(vvs, queue_len + 2 - queue_max); + } + spin_lock_bh(&vvs->rx_lock); can_enqueue = virtio_transport_inc_rx_pkt(vvs, len); From 30c82aa0a8b116989bc4d8f75e1936bd83c0134e Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Wed, 8 Jul 2026 12:29:04 +0200 Subject: [PATCH 077/234] vsock/test: add test for small packets under pressure Add a test that sends 2 MB of data using randomly sized small packets (129-512 bytes) over a SOCK_STREAM connection. Packets above GOOD_COPY_LEN (128) bypass the in-place coalescing in recv_enqueue(), forcing each one into its own skb. Without receive queue collapsing, the per-skb overhead eventually exceeds buf_alloc and the connection is reset. The test verifies that all data arrives and that content integrity is preserved. Signed-off-by: Stefano Garzarella Acked-by: Michael S. Tsirkin Reviewed-by: Bobby Eshleman Link: https://patch.msgid.link/20260708102904.50732-3-sgarzare@redhat.com Signed-off-by: Paolo Abeni --- tools/testing/vsock/vsock_test.c | 87 ++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/tools/testing/vsock/vsock_test.c b/tools/testing/vsock/vsock_test.c index 76be0e4a7f0e..b4ff9f946565 100644 --- a/tools/testing/vsock/vsock_test.c +++ b/tools/testing/vsock/vsock_test.c @@ -2347,6 +2347,88 @@ static void test_stream_tx_credit_bounds_server(const struct test_opts *opts) close(fd); } +/* Test that many small packets don't cause a connection reset under pressure + * and that data integrity is preserved. Packet sizes vary randomly between + * 129 and 512 bytes, above GOOD_COPY_LEN (128) to bypass in-place coalescing + * in recv_enqueue, forcing each one into its own skb. Without receive queue + * collapsing, the per-skb overhead eventually exceeds buf_alloc and the + * connection is reset. + */ +#define COLLAPSE_PKT_MIN 129 +#define COLLAPSE_PKT_MAX 512 +#define COLLAPSE_TOTAL (2 * 1024 * 1024) + +static void test_stream_collapse_client(const struct test_opts *opts) +{ + unsigned char *data; + unsigned long hash; + size_t offset = 0; + int i, fd; + + data = malloc(COLLAPSE_TOTAL); + if (!data) { + perror("malloc"); + exit(EXIT_FAILURE); + } + + for (i = 0; i < COLLAPSE_TOTAL; i++) + data[i] = rand() & 0xff; + + fd = vsock_stream_connect(opts->peer_cid, opts->peer_port); + if (fd < 0) { + perror("connect"); + exit(EXIT_FAILURE); + } + + while (offset < COLLAPSE_TOTAL) { + size_t pkt_size = COLLAPSE_PKT_MIN + + rand() % (COLLAPSE_PKT_MAX - COLLAPSE_PKT_MIN + 1); + + pkt_size = min(pkt_size, COLLAPSE_TOTAL - offset); + + send_buf(fd, data + offset, pkt_size, 0, pkt_size); + offset += pkt_size; + } + + hash = hash_djb2(data, COLLAPSE_TOTAL); + control_writeulong(hash); + + free(data); + close(fd); +} + +static void test_stream_collapse_server(const struct test_opts *opts) +{ + unsigned long hash, remote_hash; + unsigned char *data; + int fd; + + data = malloc(COLLAPSE_TOTAL); + if (!data) { + perror("malloc"); + exit(EXIT_FAILURE); + } + + fd = vsock_stream_accept(VMADDR_CID_ANY, opts->peer_port, NULL); + if (fd < 0) { + perror("accept"); + exit(EXIT_FAILURE); + } + + recv_buf(fd, data, COLLAPSE_TOTAL, 0, COLLAPSE_TOTAL); + + hash = hash_djb2(data, COLLAPSE_TOTAL); + remote_hash = control_readulong(); + if (hash != remote_hash) { + fprintf(stderr, "hash mismatch: local %lu remote %lu\n", + hash, remote_hash); + exit(EXIT_FAILURE); + } + + free(data); + close(fd); +} + static struct test_case test_cases[] = { { .name = "SOCK_STREAM connection reset", @@ -2546,6 +2628,11 @@ static struct test_case test_cases[] = { .run_client = test_stream_msg_peek_client, .run_server = test_stream_peek_after_recv_server, }, + { + .name = "SOCK_STREAM small packets backpressure", + .run_client = test_stream_collapse_client, + .run_server = test_stream_collapse_server, + }, {}, }; From 4c1eabbef7a1707635652e956e39db1269c3af2b Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Wed, 8 Jul 2026 19:10:25 +0800 Subject: [PATCH 078/234] dpaa2-switch: put MAC endpoint device on disconnect fsl_mc_get_endpoint() returns the MAC endpoint device with a reference taken through device_find_child(). The switch port connect path stores that device in mac->mc_dev and keeps it for the lifetime of the connected MAC object. However, the disconnect path only closes the MAC and frees the dpaa2_mac object. It does not drop the endpoint device reference stored in mac->mc_dev, so every successful connect leaks that device reference when the MAC is later disconnected. Drop the endpoint device reference before freeing the dpaa2_mac object. Fixes: 84cba72956fd ("dpaa2-switch: integrate the MAC endpoint support") Signed-off-by: Guangshuo Li Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260708111025.749311-1-lgs201920130244@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index 858ba844ac51..dd4f60031d0c 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -1560,6 +1560,7 @@ static void dpaa2_switch_port_disconnect_mac(struct ethsw_port_priv *port_priv) dpaa2_mac_disconnect(mac); dpaa2_mac_close(mac); + put_device(&mac->mc_dev->dev); kfree(mac); } From 2484568a335cd7bda951c75b3a7d95ea36161ae7 Mon Sep 17 00:00:00 2001 From: Wayen Yan Date: Wed, 8 Jul 2026 19:16:16 +0800 Subject: [PATCH 079/234] net: airoha: Fix potential use-after-free in airoha_ppe_deinit() airoha_ppe_deinit() replaces the NPU pointer with NULL via rcu_replace_pointer() but does not wait for existing RCU readers to exit before calling ppe_deinit() and airoha_npu_put(). This can cause a use-after-free if a reader in an RCU read-side critical section still holds a reference to the NPU when it is freed. The init path (airoha_ppe_init) already calls synchronize_rcu() after rcu_assign_pointer(), but the deinit path introduced in commit 6abcf751bc08 ("net: airoha: Fix schedule while atomic in airoha_ppe_deinit()") omitted the matching barrier when switching from rcu_read_lock()/rcu_dereference() to rcu_replace_pointer(). Add synchronize_rcu() before ppe_deinit() to ensure all existing RCU readers have completed before the NPU resources are released. Fixes: 6abcf751bc084804a9e5b3051442e8a2ce67f48a ("net: airoha: Fix schedule while atomic in airoha_ppe_deinit()") Signed-off-by: Wayen Yan Acked-by: Lorenzo Bianconi Link: https://patch.msgid.link/178351022574.97989.6880403520276841703@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/airoha/airoha_ppe.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c index e7c78293002a..f6396925722d 100644 --- a/drivers/net/ethernet/airoha/airoha_ppe.c +++ b/drivers/net/ethernet/airoha/airoha_ppe.c @@ -1659,6 +1659,7 @@ void airoha_ppe_deinit(struct airoha_eth *eth) npu = rcu_replace_pointer(eth->npu, NULL, lockdep_is_held(&flow_offload_mutex)); if (npu) { + synchronize_rcu(); npu->ops.ppe_deinit(npu); airoha_npu_put(npu); } From b4b201cc93ff70150853aba03e14d314d1980ca0 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Wed, 8 Jul 2026 19:17:37 +0800 Subject: [PATCH 080/234] dpaa2-eth: put MAC endpoint device on disconnect fsl_mc_get_endpoint() returns the MAC endpoint device with a reference taken through device_find_child(). The Ethernet connect path stores that device in mac->mc_dev and keeps it for the lifetime of the connected MAC object. However, the disconnect path only disconnects and closes the MAC before freeing the dpaa2_mac object. It does not drop the endpoint device reference stored in mac->mc_dev, so every successful connect leaks that device reference when the MAC is later disconnected. Drop the endpoint device reference after closing the MAC and before freeing the dpaa2_mac object. Fixes: 719479230893 ("dpaa2-eth: add MAC/PHY support through phylink") Signed-off-by: Guangshuo Li Reviewed-by: Ioana Ciornei Reviewed-by: Ioana Ciornei Link: https://patch.msgid.link/20260708111738.750391-1-lgs201920130244@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c index 9335703768a9..764d2a09668f 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c @@ -4732,6 +4732,7 @@ static void dpaa2_eth_disconnect_mac(struct dpaa2_eth_priv *priv) dpaa2_mac_disconnect(mac); dpaa2_mac_close(mac); + put_device(&mac->mc_dev->dev); kfree(mac); } From 6f884eb87a79e0c482baef2ad96c96b81d024235 Mon Sep 17 00:00:00 2001 From: Wayen Yan Date: Wed, 8 Jul 2026 19:35:29 +0800 Subject: [PATCH 081/234] net: airoha: Fix DMA direction for NPU mailbox buffer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit airoha_npu_send_msg() always maps the mailbox buffer with DMA_TO_DEVICE, but some callers expect the NPU to write response data back into the same buffer: - airoha_npu_wlan_msg_get() (NPU_OP_GET): NPU writes response into the buffer, then the caller reads it via memcpy() - airoha_npu_ppe_stats_setup() (NPU_OP_SET): NPU writes back npu_stats_addr field in the response On non-cache-coherent architectures like EN7581 (Cortex-A53 without hardware cache coherency for NPU DMA), DMA_TO_DEVICE unmap is a no-op — it does not invalidate the CPU cache. If the NPU-written cache line is still present in the CPU cache when the caller reads the buffer, the CPU observes stale data instead of the NPU response. This is a timing-sensitive bug: small mailbox buffers (~24 bytes) typically fit in a single cache line and may survive in the cache until the caller reads them, producing silent data corruption rather than a crash. The bug is more likely to trigger when the caller reads the response immediately after dma_unmap_single() without intervening cache-evicting operations. Fix by using DMA_BIDIRECTIONAL for both map and unmap, which ensures dma_unmap_single() invalidates the CPU cache on non-coherent systems. The mailbox buffers are small so there is no performance concern. Fixes: c52918744ee1e49cea86622a2633b9782446428f ("net: airoha: npu: Move memory allocation in airoha_npu_send_msg() caller") Signed-off-by: Wayen Yan Acked-by: Lorenzo Bianconi Link: https://patch.msgid.link/178351055214.98729.11403147818632027428@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/airoha/airoha_npu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/airoha/airoha_npu.c b/drivers/net/ethernet/airoha/airoha_npu.c index 870d61fdd9c6..b679bed952de 100644 --- a/drivers/net/ethernet/airoha/airoha_npu.c +++ b/drivers/net/ethernet/airoha/airoha_npu.c @@ -168,7 +168,7 @@ static int airoha_npu_send_msg(struct airoha_npu *npu, int func_id, dma_addr_t dma_addr; int ret; - dma_addr = dma_map_single(npu->dev, p, size, DMA_TO_DEVICE); + dma_addr = dma_map_single(npu->dev, p, size, DMA_BIDIRECTIONAL); ret = dma_mapping_error(npu->dev, dma_addr); if (ret) return ret; @@ -191,7 +191,7 @@ static int airoha_npu_send_msg(struct airoha_npu *npu, int func_id, spin_unlock_bh(&npu->cores[core].lock); - dma_unmap_single(npu->dev, dma_addr, size, DMA_TO_DEVICE); + dma_unmap_single(npu->dev, dma_addr, size, DMA_BIDIRECTIONAL); return ret; } From 952c02b33f56207a160421bcd61e7ac53c9c59ae Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Sat, 11 Jul 2026 14:03:02 -0700 Subject: [PATCH 082/234] wifi: mac80211: tear down new links on vif update error path When ieee80211_vif_update_links() adds new links it allocates a link container for each and calls ieee80211_link_init() (which registers the per-link debugfs files with file->private_data pointing into the container) and ieee80211_link_setup(). If the subsequent drv_change_vif_links() fails, the error path restores the old pointers and jumps to 'free', which frees the new containers but never removes their debugfs entries or stops the links. The debugfs files survive with file->private_data dangling at the freed container, so a later open()+read() (e.g. link-1/txpower) dereferences freed memory in ieee80211_if_read_link(), a use-after-free. The removal path already dismantles links correctly via ieee80211_tear_down_links(), which removes each link's keys and debugfs entries and calls ieee80211_link_stop(); the add path on the error branch does not. Commit be1ba9ed221f ("wifi: mac80211: avoid weird state in error path") hardened this same error path for the link-removal case (new_links == 0) but left the newly-added links' teardown unaddressed. drv_change_vif_links() can fail at runtime on MLO drivers (internal allocation / queue / firmware command failures). Remove the new links' debugfs entries and stop them before freeing. BUG: KASAN: slab-use-after-free in ieee80211_if_read_link (net/mac80211/debugfs_netdev.c:127) Read of size 8 at addr ffff888011290000 by task exploit/145 Call Trace: ... ieee80211_if_read_link (net/mac80211/debugfs_netdev.c:127) short_proxy_read (fs/debugfs/file.c:373) vfs_read (fs/read_write.c:572) ksys_read (fs/read_write.c:716) do_syscall_64 (arch/x86/entry/syscall_64.c:94) entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121) ... Oops: general protection fault, probably for non-canonical address 0xdffffc000000000a RIP: 0010:ieee80211_if_read_link (net/mac80211/debugfs_netdev.c:127) Kernel panic - not syncing: Fatal exception Fixes: 170cd6a66d9a ("wifi: mac80211: add netdev per-link debugfs data and driver hook") Reported-by: Weiming Shi Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Xiang Mei Link: https://patch.msgid.link/20260711210302.2098404-1-xmei5@asu.edu Signed-off-by: Johannes Berg --- net/mac80211/link.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/mac80211/link.c b/net/mac80211/link.c index d0535268962c..dc68144dc363 100644 --- a/net/mac80211/link.c +++ b/net/mac80211/link.c @@ -399,6 +399,10 @@ static int ieee80211_vif_update_links(struct ieee80211_sub_if_data *sdata, memcpy(sdata->link, old_data, sizeof(old_data)); memcpy(sdata->vif.link_conf, old, sizeof(old)); ieee80211_set_vif_links_bitmaps(sdata, old_links, dormant_links); + for_each_set_bit(link_id, &add, IEEE80211_MLD_MAX_NUM_LINKS) { + ieee80211_link_debugfs_remove(&links[link_id]->data); + ieee80211_link_stop(&links[link_id]->data); + } /* and free (only) the newly allocated links */ memset(to_free, 0, sizeof(links)); goto free; From a61b4db34a753bdf5c9e77a7f3d3dddd41dcfacc Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Wed, 8 Jul 2026 22:34:08 +0800 Subject: [PATCH 083/234] nfp: Check resource mutex allocation nfp_cpp_resource_find() allocates a CPP mutex handle for the matching resource-table entry and then reports success. nfp_resource_try_acquire() immediately passes that handle to nfp_cpp_mutex_trylock(). However, nfp_cpp_mutex_alloc() returns NULL on failure. If that happens for a matching table entry, the resource lookup still returns success and the following trylock dereferences a NULL mutex pointer while opening the resource. nfp_resource_acquire() already treats failure to allocate the table mutex as -ENOMEM. Do the same for the resource mutex and fail the lookup before publishing the rest of the resource handle. This issue was found by a static analysis checker and confirmed by manual source review. Fixes: f01a2161577d ("nfp: add support for resources") Signed-off-by: Ruoyu Wang Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260708143408.3168425-1-ruoyuw560@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/netronome/nfp/nfpcore/nfp_resource.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/netronome/nfp/nfpcore/nfp_resource.c b/drivers/net/ethernet/netronome/nfp/nfpcore/nfp_resource.c index 6d5833479d12..237300b82b91 100644 --- a/drivers/net/ethernet/netronome/nfp/nfpcore/nfp_resource.c +++ b/drivers/net/ethernet/netronome/nfp/nfpcore/nfp_resource.c @@ -96,6 +96,9 @@ static int nfp_cpp_resource_find(struct nfp_cpp *cpp, struct nfp_resource *res) res->mutex = nfp_cpp_mutex_alloc(cpp, NFP_RESOURCE_TBL_TARGET, addr, key); + if (!res->mutex) + return -ENOMEM; + res->cpp_id = NFP_CPP_ID(entry.region.cpp_target, entry.region.cpp_action, entry.region.cpp_token); From 91957b89da995607cb654b1f9a3c126ddbaee10f Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Wed, 8 Jul 2026 22:34:15 +0800 Subject: [PATCH 084/234] wan: wanxl: Only reset hardware after BAR mapping wanxl_pci_init_one() stores the freshly allocated card in driver data before the PLX BAR is mapped. Several early probe failures then unwind through wanxl_pci_remove_one(), including failure to allocate the coherent status area or to restore the DMA mask. wanxl_pci_remove_one() unconditionally calls wanxl_reset(), and wanxl_reset() dereferences card->plx. On those early failures card->plx is still NULL, so the error path can dereference a NULL MMIO pointer. Only issue the hardware reset once the BAR mapping exists. The remaining cleanup in wanxl_pci_remove_one() already checks whether later resources were allocated. This issue was found by a static analysis checker and confirmed by manual source review. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Ruoyu Wang Link: https://patch.msgid.link/20260708143415.3169358-1-ruoyuw560@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/wan/wanxl.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wan/wanxl.c b/drivers/net/wan/wanxl.c index d4da88c77112..065c00c12cc1 100644 --- a/drivers/net/wan/wanxl.c +++ b/drivers/net/wan/wanxl.c @@ -514,7 +514,8 @@ static void wanxl_pci_remove_one(struct pci_dev *pdev) if (card->irq) free_irq(card->irq, card); - wanxl_reset(card); + if (card->plx) + wanxl_reset(card); for (i = 0; i < RX_QUEUE_LENGTH; i++) if (card->rx_skbs[i]) { From 121a96c5a0db8d18e2ba2cb89660cca8a40508fe Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Mon, 13 Jul 2026 01:17:09 +0300 Subject: [PATCH 085/234] wifi: mwifiex: fix freeze for 60 seconds caused by request_firmware Fix regression in rgpower table loading, caused by using request_firmware(): when the requested firmware does not exist, e.g. nxp/rgpower_WW.bin does not exist on OpenWRT builds for WRT3200ACM, request_firmware() falls back to firmware_fallback_sysfs(), which expects the firmware to be provided by user space using SYSFS. No such utility is provided in this configuration, so the entire system locks up for 60 seconds, until the request times out. During this time, no other log messages are observed, and the device does not respond to commands over UART. The request_firmware() call is performed in the following context: current->comm kworker/1:2 in_task 1 irqs_disabled 0 in_atomic 0 Fixed by using request_firmware_direct(). This prevents fallback to SYSFS, and avoids delay. The rgpower table is optional. The driver falls back to the device tree power table if the firmware is not present. The error code is printed for debugging and returned to the caller, which only cares for success or failure, so there are no side effects. Fixes: 7b6f16a25806 ("wifi: mwifiex: add rgpower table loading support") Signed-off-by: Georgi Valkov Reviewed-by: Francesco Dolcini Link: https://patch.msgid.link/20260712221709.7099-1-gvalkov@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/mwifiex/sta_ioctl.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/marvell/mwifiex/sta_ioctl.c b/drivers/net/wireless/marvell/mwifiex/sta_ioctl.c index a6550548d3b4..9460d5352b23 100644 --- a/drivers/net/wireless/marvell/mwifiex/sta_ioctl.c +++ b/drivers/net/wireless/marvell/mwifiex/sta_ioctl.c @@ -196,6 +196,7 @@ static int mwifiex_request_rgpower_table(struct mwifiex_private *priv) struct mwifiex_adapter *adapter = priv->adapter; char rgpower_table_name[30]; char country_code[3]; + int ret; strscpy(country_code, domain_info->country_code, sizeof(country_code)); @@ -214,16 +215,17 @@ static int mwifiex_request_rgpower_table(struct mwifiex_private *priv) adapter->rgpower_data = NULL; } - if ((request_firmware(&adapter->rgpower_data, rgpower_table_name, - adapter->dev))) { + ret = request_firmware_direct(&adapter->rgpower_data, rgpower_table_name, + adapter->dev); + + if (ret) { mwifiex_dbg( adapter, INFO, - "info: %s: failed to request regulatory power table\n", - __func__); - return -EIO; + "info: %s: failed to request regulatory power table: %d\n", + __func__, ret); } - return 0; + return ret; } static int mwifiex_dnld_rgpower_table(struct mwifiex_private *priv) From 4c4c97b60a5e978121d9ee8cb0ab3916e5d6a8de Mon Sep 17 00:00:00 2001 From: Huihui Huang Date: Tue, 14 Jul 2026 17:17:58 +0800 Subject: [PATCH 086/234] wifi: wilc1000: validate assoc response length before subtracting header wilc_parse_assoc_resp_info() computes the trailing IE length as ies_len = buffer_len - sizeof(*res); without first checking that buffer_len is at least sizeof(struct wilc_assoc_resp) (6 bytes). buffer_len is the length reported for a received association response (host_int_parse_assoc_resp_info() passes hif_drv->assoc_resp / assoc_resp_info_len straight in) and must be validated before the driver accesses the fixed header. For a frame shorter than the 6-byte fixed header, the subtraction wraps. For a four-byte response the result is truncated to a u16 ies_len of 65534, so kmemdup() then attempts to copy 65534 bytes starting at buffer + sizeof(*res), beyond the valid association-response data (CWE-125). A response shorter than four bytes can also cause an out-of-bounds read of res->status_code at offsets 2 and 3. Reject frames too short to hold the fixed header before touching the header or computing ies_len. Also set the connection status to a failure on this path: the caller falls through to a "conn_info->status == WLAN_STATUS_SUCCESS" check after the parser returns, so leaving the status untouched could let a malformed short response be treated as a successful association. Fixes: c5c77ba18ea6 ("staging: wilc1000: Add SDIO/SPI 802.11 driver") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Huihui Huang Link: https://patch.msgid.link/20260714091811.3596126-1-hhhuang@smu.edu.sg Signed-off-by: Johannes Berg --- drivers/net/wireless/microchip/wilc1000/hif.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/net/wireless/microchip/wilc1000/hif.c b/drivers/net/wireless/microchip/wilc1000/hif.c index 009c4770a6f9..60fe5f08964f 100644 --- a/drivers/net/wireless/microchip/wilc1000/hif.c +++ b/drivers/net/wireless/microchip/wilc1000/hif.c @@ -600,6 +600,11 @@ static s32 wilc_parse_assoc_resp_info(u8 *buffer, u32 buffer_len, u16 ies_len; struct wilc_assoc_resp *res = (struct wilc_assoc_resp *)buffer; + if (buffer_len < sizeof(*res)) { + ret_conn_info->status = WLAN_STATUS_UNSPECIFIED_FAILURE; + return -EINVAL; + } + ret_conn_info->status = le16_to_cpu(res->status_code); if (ret_conn_info->status == WLAN_STATUS_SUCCESS) { ies = &buffer[sizeof(*res)]; From 3c0d10f233f19153f81fef685b5c6716776a5af3 Mon Sep 17 00:00:00 2001 From: Enrico Zanda Date: Wed, 8 Jul 2026 16:22:42 +0100 Subject: [PATCH 087/234] vhost-net: fix TX stall when vhost owns virtio-net header When vhost owns the virtio-net header, i.e. when VHOST_NET_F_VIRTIO_NET_HDR is negotiated, sock_hlen is 0, meaning that no header will be forwarded to the TAP device. In the current vhost_net_build_xdp() implementation, when sock_hlen == 0, the gso pointer can point at the start of the Ethernet frame instead of a virtio-net header. This results in a wrong interpretation of the destination MAC address bytes as struct virtio_net_hdr fields. This can, for some MAC addresses, trigger -EINVAL and return early before the TX descriptor is completed, which can stall vhost-net TX. Before 97b2409f28e0, the gso pointer was set to the zeroed padding area, using it as a synthetic virtio-net header. Restore that behavior. Fixes: 97b2409f28e0 ("vhost-net: reduce one userspace copy when building XDP buff") Signed-off-by: Enrico Zanda Acked-by: Michael S. Tsirkin Link: https://patch.msgid.link/20260708152242.2268848-1-enrico.zanda@arm.com Signed-off-by: Paolo Abeni --- drivers/vhost/net.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/vhost/net.c b/drivers/vhost/net.c index 77b59f49bddb..3e72b9c6af0c 100644 --- a/drivers/vhost/net.c +++ b/drivers/vhost/net.c @@ -731,10 +731,12 @@ static int vhost_net_build_xdp(struct vhost_net_virtqueue *nvq, goto err; } - gso = buf + pad - sock_hlen; - - if (!sock_hlen) + if (!sock_hlen) { memset(buf, 0, pad); + gso = buf; + } else { + gso = buf + pad - sock_hlen; + } if ((gso->flags & VIRTIO_NET_HDR_F_NEEDS_CSUM) && vhost16_to_cpu(vq, gso->csum_start) + From 0fe2d5be7ab59717adb3f9cfab3832c6c4dd770c Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Tue, 14 Jul 2026 14:10:46 +0300 Subject: [PATCH 088/234] wifi: mac80211: copy aggregation information This information can be considered part of the capabilities and should also be copied to the NAN data station. Fixes: 27e9b326b674 ("wifi: mac80211: support NAN stations") Signed-off-by: Benjamin Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260714141038.15620aa5324b.I049254b854ac91c32e0768eb7c819f32eda34218@changeid Signed-off-by: Johannes Berg --- net/mac80211/cfg.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index b00191e02a63..43f142624d33 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -2611,6 +2611,9 @@ static int sta_apply_parameters(struct ieee80211_local *local, memcpy(&sta->deflink.pub->supp_rates, &nmi_sta->deflink.pub->supp_rates, sizeof(sta->deflink.pub->supp_rates)); + + sta->deflink.pub->agg = nmi_sta->deflink.pub->agg; + __ieee80211_sta_recalc_aggregates(sta, 0); } /* set the STA state after all sta info from usermode has been set */ From f0858bfc7d3cab411a447b88e3ef970e575032c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?HE=20WEI=20=28=E3=82=AE=E3=82=AB=E3=82=AF=29?= Date: Wed, 15 Jul 2026 22:57:11 +0900 Subject: [PATCH 089/234] wifi: mwifiex: bound uAP association event IEs to the event buffer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mwifiex_process_uap_event() handles EVENT_UAP_STA_ASSOC by exposing the (re)association request IEs that the firmware copies into the event: sinfo->assoc_req_ies = &event->data[len]; len = (u8 *)sinfo->assoc_req_ies - (u8 *)&event->frame_control; sinfo->assoc_req_ies_len = le16_to_cpu(event->len) - (u16)len; event->len is supplied by the device firmware and is never validated, and the subtraction is unchecked. assoc_req_ies points into adapter->event_body[MAX_EVENT_SIZE], a fixed-size array embedded in the kmalloc()'d struct mwifiex_adapter. On the ap_11n_enabled path mwifiex_set_sta_ht_cap() walks these IEs with cfg80211_find_ie(), whose for_each_element() loop dereferences each element header. A firmware-reported event->len larger than the bytes actually received makes assoc_req_ies_len describe IEs that extend past event_body, so the walk reads out of the adapter slab object, a slab-out-of-bounds read (KASAN: slab-out-of-bounds in cfg80211_find_ie). An event->len smaller than the header instead makes the int subtraction negative, which wraps to a huge size_t when stored in assoc_req_ies_len. The same length is handed to cfg80211_new_sta(), so a more modest over-claim can also copy stale event_body bytes into the NL80211_CMD_NEW_STATION notification. A malicious or malfunctioning mwifiex device (USB/SDIO/PCIe) can deliver such an event while the interface is in AP/uAP mode. Validate event->len before use: reject a length that underflows the header or that would place the IEs outside the event_body[] buffer the event was copied into. event->len here is struct mwifiex_assoc_event.len, a payload field internal to this event, not the transport frame length, so it is validated in this handler rather than at the generic MWIFIEX_TYPE_EVENT receive path, which only sees the event cause and the transport frame length. The bound is against event_body[MAX_EVENT_SIZE] rather than the actually-received length because the transports store the event differently (USB and SDIO leave the 4-byte event header in event_skb, PCIe strips it via skb_pull), whereas event_body is the single fixed buffer all of them copy the event into. This is the event-path analogue of the receive-path bounds checks added in commit 119585281617 ("wifi: mwifiex: Fix OOB and integer underflow when rx packets"). Fixes: e568634ae7ac ("mwifiex: add AP event handling framework") Signed-off-by: HE WEI (ギカク) Reviewed-by: Francesco Dolcini Link: https://patch.msgid.link/20260715135711.34688-1-skyexpoc@gmail.com Signed-off-by: Johannes Berg --- .../net/wireless/marvell/mwifiex/uap_event.c | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/marvell/mwifiex/uap_event.c b/drivers/net/wireless/marvell/mwifiex/uap_event.c index 679fdae0f001..ba1bdbbff687 100644 --- a/drivers/net/wireless/marvell/mwifiex/uap_event.c +++ b/drivers/net/wireless/marvell/mwifiex/uap_event.c @@ -123,11 +123,31 @@ int mwifiex_process_uap_event(struct mwifiex_private *priv) len = ETH_ALEN; if (len != -1) { + u16 evt_len = le16_to_cpu(event->len); + sinfo->assoc_req_ies = &event->data[len]; len = (u8 *)sinfo->assoc_req_ies - (u8 *)&event->frame_control; - sinfo->assoc_req_ies_len = - le16_to_cpu(event->len) - (u16)len; + + /* + * event->len is reported by the device firmware + * and is not otherwise validated. Reject a + * length that underflows the header, or that + * would place the association request IEs + * outside the fixed-size event_body[] buffer the + * event was copied into; otherwise the IE walk + * in mwifiex_set_sta_ht_cap() reads past + * event_body and out of the adapter slab object. + */ + if (evt_len < len || + (u8 *)&event->frame_control + evt_len > + adapter->event_body + MAX_EVENT_SIZE) { + mwifiex_dbg(adapter, ERROR, + "invalid STA assoc event length\n"); + kfree(sinfo); + return -1; + } + sinfo->assoc_req_ies_len = evt_len - (u16)len; } } cfg80211_new_sta(priv->netdev->ieee80211_ptr, event->sta_addr, From 61a799ffd1e5a4fd3702d547828b7ff3d161468e Mon Sep 17 00:00:00 2001 From: Huihui Huang Date: Wed, 15 Jul 2026 22:08:10 +0800 Subject: [PATCH 090/234] wifi: at76c50x-usb: avoid length underflow in at76_guess_freq() at76_guess_freq() checks only that the received frame is at least a bare 802.11 header (24 bytes) before subtracting the fixed management-body offset: len -= el_off; For both beacon and probe response frames, el_off is 36. If the frame is shorter than el_off, subtracting it causes the calculated IE length to wrap. The length is eventually passed to cfg80211_find_elem_match() as a very large unsigned value, so the element walk runs beyond the RX skb. This path is reached from at76_rx_tasklet() while scanning. If the device delivers a truncated beacon or probe response, the oversized IE length causes an out-of-bounds read during scanning. Skip the IE lookup if the frame does not reach the variable elements, before subtracting el_off. Fixes: 1264b951463a ("at76c50x-usb: add driver") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Huihui Huang Link: https://patch.msgid.link/20260715140815.1242033-1-hhhuang@smu.edu.sg Signed-off-by: Johannes Berg --- drivers/net/wireless/atmel/at76c50x-usb.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/atmel/at76c50x-usb.c b/drivers/net/wireless/atmel/at76c50x-usb.c index 32e3e09e7680..d9c2809be4ba 100644 --- a/drivers/net/wireless/atmel/at76c50x-usb.c +++ b/drivers/net/wireless/atmel/at76c50x-usb.c @@ -1521,13 +1521,16 @@ static inline int at76_guess_freq(struct at76_priv *priv) if (ieee80211_is_probe_resp(hdr->frame_control)) { el_off = offsetof(struct ieee80211_mgmt, u.probe_resp.variable); - el = ((struct ieee80211_mgmt *)hdr)->u.probe_resp.variable; } else if (ieee80211_is_beacon(hdr->frame_control)) { el_off = offsetof(struct ieee80211_mgmt, u.beacon.variable); - el = ((struct ieee80211_mgmt *)hdr)->u.beacon.variable; } else { goto exit; } + + if (len < el_off) + goto exit; + + el = priv->rx_skb->data + el_off; len -= el_off; el = cfg80211_find_ie(WLAN_EID_DS_PARAMS, el, len); From 1cb5845a58d8e1f85d5766c6fbcbfddf96c212a1 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Thu, 16 Jul 2026 03:17:28 +0300 Subject: [PATCH 091/234] wifi: mwifiex: replace one-element arrays with flexible array members Replace deprecated one-element arrays with flexible array members. CONFIG_FORTIFY_SOURCE reports the following warning when one-element arrays are used as variable-length buffers: sta_cmd.c:1033 mwifiex_sta_prepare_cmd memcpy: detected field-spanning write (size 84) of single field "domain->triplet" at .../marvell/mwifiex/sta_cmd.c:1033 (size 3) Convert affected structs to use flexible array members. - Preserve existing wire layouts. - Use DECLARE_FLEX_ARRAY() for structs inside affected unions. Tested-on: WRT3200ACM, OpenWrt Signed-off-by: Georgi Valkov Reviewed-by: Francesco Dolcini Link: https://patch.msgid.link/20260716001728.57799-1-gvalkov@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/mwifiex/fw.h | 18 +++++++++--------- drivers/net/wireless/marvell/mwifiex/join.c | 8 ++++---- drivers/net/wireless/marvell/mwifiex/sta_cmd.c | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/drivers/net/wireless/marvell/mwifiex/fw.h b/drivers/net/wireless/marvell/mwifiex/fw.h index e9e896606912..93561116959a 100644 --- a/drivers/net/wireless/marvell/mwifiex/fw.h +++ b/drivers/net/wireless/marvell/mwifiex/fw.h @@ -823,7 +823,7 @@ struct chan_band_param_set { struct mwifiex_ie_types_chan_band_list_param_set { struct mwifiex_ie_types_header header; - struct chan_band_param_set chan_band_param[1]; + struct chan_band_param_set chan_band_param[]; } __packed; struct mwifiex_ie_types_rates_param_set { @@ -886,7 +886,7 @@ struct mwifiex_ie_types_wildcard_ssid_params { #define TSF_DATA_SIZE 8 struct mwifiex_ie_types_tsf_timestamp { struct mwifiex_ie_types_header header; - u8 tsf_data[1]; + u8 tsf_data[]; } __packed; struct mwifiex_cf_param_set { @@ -903,8 +903,8 @@ struct mwifiex_ibss_param_set { struct mwifiex_ie_types_ss_param_set { struct mwifiex_ie_types_header header; union { - struct mwifiex_cf_param_set cf_param_set[1]; - struct mwifiex_ibss_param_set ibss_param_set[1]; + DECLARE_FLEX_ARRAY(struct mwifiex_cf_param_set, cf_param_set); + DECLARE_FLEX_ARRAY(struct mwifiex_ibss_param_set, ibss_param_set); } cf_ibss; } __packed; @@ -922,8 +922,8 @@ struct mwifiex_ds_param_set { struct mwifiex_ie_types_phy_param_set { struct mwifiex_ie_types_header header; union { - struct mwifiex_fh_param_set fh_param_set[1]; - struct mwifiex_ds_param_set ds_param_set[1]; + DECLARE_FLEX_ARRAY(struct mwifiex_fh_param_set, fh_param_set); + DECLARE_FLEX_ARRAY(struct mwifiex_ds_param_set, ds_param_set); } fh_ds; } __packed; @@ -1383,7 +1383,7 @@ struct host_cmd_ds_802_11_snmp_mib { __le16 query_type; __le16 oid; __le16 buf_size; - u8 value[1]; + u8 value[]; } __packed; struct mwifiex_rate_scope { @@ -1551,7 +1551,7 @@ struct mwifiex_scan_cmd_config { * TLV_TYPE_CHANLIST, mwifiex_ie_types_chan_list_param_set * WLAN_EID_SSID, mwifiex_ie_types_ssid_param_set */ - u8 tlv_buf[1]; /* SSID TLV(s) and ChanList TLVs are stored + u8 tlv_buf[]; /* SSID TLV(s) and ChanList TLVs are stored here */ } __packed; @@ -1683,7 +1683,7 @@ struct host_cmd_ds_802_11_bg_scan_query_rsp { struct mwifiex_ietypes_domain_param_set { struct mwifiex_ie_types_header header; u8 country_code[IEEE80211_COUNTRY_STRING_LEN]; - struct ieee80211_country_ie_triplet triplet[1]; + struct ieee80211_country_ie_triplet triplet[]; } __packed; struct host_cmd_ds_802_11d_domain_info { diff --git a/drivers/net/wireless/marvell/mwifiex/join.c b/drivers/net/wireless/marvell/mwifiex/join.c index b48f7febaf03..259140395d35 100644 --- a/drivers/net/wireless/marvell/mwifiex/join.c +++ b/drivers/net/wireless/marvell/mwifiex/join.c @@ -421,15 +421,15 @@ int mwifiex_cmd_802_11_associate(struct mwifiex_private *priv, phy_tlv = (struct mwifiex_ie_types_phy_param_set *) pos; phy_tlv->header.type = cpu_to_le16(WLAN_EID_DS_PARAMS); - phy_tlv->header.len = cpu_to_le16(sizeof(phy_tlv->fh_ds.ds_param_set)); - memcpy(&phy_tlv->fh_ds.ds_param_set, + phy_tlv->header.len = cpu_to_le16(sizeof(*phy_tlv->fh_ds.ds_param_set)); + memcpy(phy_tlv->fh_ds.ds_param_set, &bss_desc->phy_param_set.ds_param_set.current_chan, - sizeof(phy_tlv->fh_ds.ds_param_set)); + sizeof(*phy_tlv->fh_ds.ds_param_set)); pos += sizeof(phy_tlv->header) + le16_to_cpu(phy_tlv->header.len); ss_tlv = (struct mwifiex_ie_types_ss_param_set *) pos; ss_tlv->header.type = cpu_to_le16(WLAN_EID_CF_PARAMS); - ss_tlv->header.len = cpu_to_le16(sizeof(ss_tlv->cf_ibss.cf_param_set)); + ss_tlv->header.len = cpu_to_le16(sizeof(*ss_tlv->cf_ibss.cf_param_set)); pos += sizeof(ss_tlv->header) + le16_to_cpu(ss_tlv->header.len); /* Get the common rates supported between the driver and the BSS Desc */ diff --git a/drivers/net/wireless/marvell/mwifiex/sta_cmd.c b/drivers/net/wireless/marvell/mwifiex/sta_cmd.c index 623ddde8c8e5..071f7cb305e1 100644 --- a/drivers/net/wireless/marvell/mwifiex/sta_cmd.c +++ b/drivers/net/wireless/marvell/mwifiex/sta_cmd.c @@ -108,7 +108,7 @@ static int mwifiex_cmd_802_11_snmp_mib(struct mwifiex_private *priv, "cmd: SNMP_CMD: cmd_oid = 0x%x\n", cmd_oid); cmd->command = cpu_to_le16(HostCmd_CMD_802_11_SNMP_MIB); cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_802_11_snmp_mib) - - 1 + S_DS_GEN); + + S_DS_GEN); snmp_mib->oid = cpu_to_le16((u16)cmd_oid); if (cmd_action == HostCmd_ACT_GEN_GET) { From c3d68e294cbb6a4090bb219d3dcaca85a011809b Mon Sep 17 00:00:00 2001 From: Doruk Tan Ozturk Date: Thu, 16 Jul 2026 12:30:42 +0200 Subject: [PATCH 092/234] wifi: mwifiex: fix NULL dereference when the AP has HT-cap but no HT-oper mwifiex_tdls_add_ht_oper() gates its follow-the-AP-bandwidth path on bss_desc->bcn_ht_cap being present, but then dereferences a different pointer, bss_desc->bcn_ht_oper: if (ISSUPP_CHANWIDTH40(priv->adapter->hw_dot_11n_dev_cap) && bss_desc->bcn_ht_cap && ISALLOWED_CHANWIDTH40(bss_desc->bcn_ht_oper->ht_param)) bcn_ht_cap and bcn_ht_oper are populated independently while parsing the associated AP's beacon in mwifiex_update_bss_desc_with_ie(): an AP that advertises an HT Capabilities element but no HT Operation element leaves bcn_ht_cap non-NULL and bcn_ht_oper NULL. Setting up a TDLS link to a peer while associated to such an AP then dereferences the NULL bcn_ht_oper and crashes the kernel. Every other bcn_ht_oper user in the driver NULL-checks it first. Guard on the pointer that is actually dereferenced. Found by 0sec automated security-research tooling (https://0sec.ai). Fixes: 396939f94084 ("mwifiex: add HT operation IE in TDLS setup confirm") Cc: stable@vger.kernel.org Assisted-by: 0sec:multi-model Signed-off-by: Doruk Tan Ozturk Reviewed-by: Francesco Dolcini Link: https://patch.msgid.link/20260716103042.88469-1-doruk@0sec.ai Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/mwifiex/tdls.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/marvell/mwifiex/tdls.c b/drivers/net/wireless/marvell/mwifiex/tdls.c index 845f2a22e071..c71ffe8399e4 100644 --- a/drivers/net/wireless/marvell/mwifiex/tdls.c +++ b/drivers/net/wireless/marvell/mwifiex/tdls.c @@ -215,7 +215,7 @@ mwifiex_tdls_add_ht_oper(struct mwifiex_private *priv, const u8 *mac, /* follow AP's channel bandwidth */ if (ISSUPP_CHANWIDTH40(priv->adapter->hw_dot_11n_dev_cap) && - bss_desc->bcn_ht_cap && + bss_desc->bcn_ht_oper && ISALLOWED_CHANWIDTH40(bss_desc->bcn_ht_oper->ht_param)) ht_oper->ht_param = bss_desc->bcn_ht_oper->ht_param; From a007a384c9eb17610f53a53e2f59944c31f1565a Mon Sep 17 00:00:00 2001 From: Andrew Pope Date: Fri, 17 Jul 2026 11:17:51 +1000 Subject: [PATCH 093/234] wifi: mac80211: recalculate TIM when a station enters power save When an AP buffers frames for a station on its per-station TXQs and the station subsequently enters power save, sta_ps_start() records the buffered TIDs in txq_buffered_tids but does not update the TIM. The station's TIM bit is only ever set when a further frame is buffered while the station is already asleep (ieee80211_tx_h_unicast_ps_buf() -> sta_info_recalc_tim()). If no further downlink frame arrives for that station the beacon TIM never advertises the buffered traffic. A station relying on the TIM then remains in doze indefinitely on top of a non-empty queue. Its TXQs were removed from the scheduler's active list at PS entry, nothing pages it, and the flow deadlocks until an unrelated event wakes the station. Recalculate the TIM at the end of sta_ps_start(), so traffic already buffered at PS entry is advertised immediately. sta_info_recalc_tim() already consults txq_buffered_tids, which is updated above, and is safe in this context (it is already called from equivalent paths such as the tx handlers and ieee80211_handle_filtered_frame()). Fixes: ba8c3d6f16a1 ("mac80211: add an intermediate software queue implementation") Signed-off-by: Andrew Pope Link: https://patch.msgid.link/20260717011751.79524-1-andrew.pope@morsemicro.com [add wifi: subject prefix] Signed-off-by: Johannes Berg --- net/mac80211/rx.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index d9ea19be075d..5e26be8e27d8 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -1717,6 +1717,8 @@ static void sta_ps_start(struct sta_info *sta) else clear_bit(tid, &sta->txq_buffered_tids); } + + sta_info_recalc_tim(sta); } static void sta_ps_end(struct sta_info *sta) From 538c51e9d124cf656f2dd0c0394a8545efc7102d Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Sat, 18 Jul 2026 02:43:52 +0000 Subject: [PATCH 094/234] wifi: brcmfmac: make release_scratchbuffers idempotent brcmf_pcie_release_scratchbuffers() frees the shared.scratch and shared.ringupd DMA buffers with dma_free_coherent() but does not clear the pointers afterwards, unlike the sibling release_ringbuffers() which NULLs commonrings/flowrings/idxbuf on release. Both the bus_reset .reset callback (brcmf_pcie_reset) and brcmf_pcie_remove() call release_scratchbuffers. When reset teardown has run before removal, remove's own teardown would call dma_free_coherent() a second time on the already-freed DMA allocation. NULL the pointers after free, matching release_ringbuffers(), so a later release observes that the allocation has already been released. This patch makes repeated sequential release safe; the reset-work lifetime is handled separately by the following patch. This issue was found by an in-house static analysis tool. Fixes: 4684997d9eea ("brcmfmac: reset PCIe bus on a firmware crash") Cc: stable@vger.kernel.org Signed-off-by: Fan Wu Assisted-by: Codex:gpt-5.6 Acked-by: Arend van Spriel Link: https://patch.msgid.link/20260718024353.3147201-2-fanwu01@zju.edu.cn Signed-off-by: Johannes Berg --- drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c index 13662aa4b4ea..9f10b3fff9ff 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c @@ -1383,16 +1383,20 @@ static int brcmf_pcie_init_ringbuffers(struct brcmf_pciedev_info *devinfo) static void brcmf_pcie_release_scratchbuffers(struct brcmf_pciedev_info *devinfo) { - if (devinfo->shared.scratch) + if (devinfo->shared.scratch) { dma_free_coherent(&devinfo->pdev->dev, BRCMF_DMA_D2H_SCRATCH_BUF_LEN, devinfo->shared.scratch, devinfo->shared.scratch_dmahandle); - if (devinfo->shared.ringupd) + devinfo->shared.scratch = NULL; + } + if (devinfo->shared.ringupd) { dma_free_coherent(&devinfo->pdev->dev, BRCMF_DMA_D2H_RINGUPD_BUF_LEN, devinfo->shared.ringupd, devinfo->shared.ringupd_dmahandle); + devinfo->shared.ringupd = NULL; + } } static int brcmf_pcie_init_scratchbuffers(struct brcmf_pciedev_info *devinfo) From 43b25879f004c98defa2776bedc6ca4763c51945 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Sat, 18 Jul 2026 02:43:53 +0000 Subject: [PATCH 095/234] wifi: brcmfmac: drain bus_reset work on device removal brcmf_fw_crashed() and the debugfs "reset" entry both schedule drvr->bus_reset, whose callback recovers drvr through container_of() and dereferences it. The removal path frees drvr (brcmf_free -> wiphy_free) without draining the work, so a bus_reset callback pending or running during removal can outlive drvr. Cancellation cannot live in brcmf_detach() or brcmf_free(): the work callback reaches teardown through the bus .reset op (PCIe brcmf_pcie_reset -> brcmf_detach; SDIO brcmf_sdio_bus_reset -> brcmf_sdiod_remove -> brcmf_free), so cancelling there would wait for the running work and deadlock. Add a per-bus mutex (bus_reset_lock) and route all arming through brcmf_bus_schedule_reset(), which under the lock skips when the bus is marked removing. Each bus remove entry calls brcmf_bus_cancel_reset_work(), which under the same lock sets removing and cancels the work. Holding the mutex across cancel_work_sync() makes the set-removing + drain step atomic. Every producer reaches the arming path from process context -- the PCIe firmware-halt notification runs in the threaded IRQ handler (brcmf_pcie_isr_thread) and the SDIO hostmail path runs from the data workqueue -- so the mutex is taken only in sleepable contexts. Where applicable the remove entry first stops the firmware-crash producer: on PCIe mask the mailbox and synchronize_irq; on SDIO unregister the bus interrupt and cancel the data worker, which also reports firmware halts through brcmf_fw_crashed(). The mutex is initialized at bus allocation. The SDIO suspend power-off path frees drvr through the same brcmf_sdiod_remove() and takes the same lock; resume re-allows the work only on a successful re-probe. Also guard brcmf_fw_crashed() against a NULL bus_if/drvr: it can fire before brcmf_attach() wires up drvr, and it dereferences drvr (bphy_err/brcmf_dev_coredump) before reaching the arming gate. The bus_reset work is shared across buses, so the drain is applied to every remove path: PCIe (the .reset op introduced by the Fixes commit), SDIO (arms the same work through brcmf_fw_crashed()), and USB (via the debugfs "reset" entry). cancel_work_sync() drains a running or pending bus_reset work item before removal frees drvr, and patch 1/2 makes the scratch-buffer release safe when reset teardown has already released those DMA buffers. This patch fixes the lifetime of the bus_reset work item itself. It does not attempt to address the separate, pre-existing lifetime of the asynchronous firmware completion started by the PCIe reset path. That callback needs its own lifetime/ownership protocol and is being tracked separately. This issue was found by an in-house static analysis tool. Fixes: 4684997d9eea ("brcmfmac: reset PCIe bus on a firmware crash") Cc: stable@vger.kernel.org Signed-off-by: Fan Wu Assisted-by: Codex:gpt-5.6 Acked-by: Arend van Spriel Link: https://patch.msgid.link/20260718024353.3147201-3-fanwu01@zju.edu.cn Signed-off-by: Johannes Berg --- .../broadcom/brcm80211/brcmfmac/bcmsdh.c | 13 ++++++ .../broadcom/brcm80211/brcmfmac/bus.h | 6 +++ .../broadcom/brcm80211/brcmfmac/core.c | 46 +++++++++++++++++-- .../broadcom/brcm80211/brcmfmac/pcie.c | 6 +++ .../broadcom/brcm80211/brcmfmac/sdio.c | 6 +++ .../broadcom/brcm80211/brcmfmac/sdio.h | 1 + .../broadcom/brcm80211/brcmfmac/usb.c | 3 ++ 7 files changed, 77 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c index d24b80e492e0..ec487c6f2e38 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c @@ -1069,6 +1069,7 @@ static int brcmf_ops_sdio_probe(struct sdio_func *func, bus_if = kzalloc_obj(*bus_if); if (!bus_if) return -ENOMEM; + mutex_init(&bus_if->bus_reset_lock); sdiodev = kzalloc_obj(*sdiodev); if (!sdiodev) { kfree(bus_if); @@ -1130,6 +1131,14 @@ static void brcmf_ops_sdio_remove(struct sdio_func *func) if (func->num != 1) return; + /* Drain bus_reset before the shared brcmf_sdiod_remove() + * teardown, which the SDIO reset callback also reaches. The + * data worker can arm bus_reset via brcmf_fw_crashed(); cancel + * it first. + */ + brcmf_sdio_cancel_datawork(sdiodev->bus); + brcmf_bus_cancel_reset_work(bus_if); + /* only proceed with rest of cleanup if func 1 */ brcmf_sdiod_remove(sdiodev); @@ -1204,6 +1213,8 @@ static int brcmf_ops_sdio_suspend(struct device *dev) } else { /* power will be cut so remove device, probe again in resume */ brcmf_sdiod_intr_unregister(sdiodev); + brcmf_sdio_cancel_datawork(sdiodev->bus); + brcmf_bus_cancel_reset_work(bus_if); ret = brcmf_sdiod_remove(sdiodev); if (ret) brcmf_err("Failed to remove device on suspend\n"); @@ -1229,6 +1240,8 @@ static int brcmf_ops_sdio_resume(struct device *dev) ret = brcmf_sdiod_probe(sdiodev); if (ret) brcmf_err("Failed to probe device on resume\n"); + else + brcmf_bus_allow_reset_work(bus_if); } else { if (sdiodev->wowl_enabled && sdiodev->settings->bus.sdio.oob_irq_supported) disable_irq_wake(sdiodev->settings->bus.sdio.oob_irq_nr); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h index fe31051a9e11..9371c1489948 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h @@ -9,6 +9,7 @@ #include #include #include +#include #include "debug.h" /* IDs of the 6 default common rings of msgbuf protocol */ @@ -179,6 +180,8 @@ struct brcmf_bus { enum brcmf_fwvendor fwvid; bool always_use_fws_queue; bool wowl_supported; + bool removing; /* device removal in progress; quiesce async work */ + struct mutex bus_reset_lock; const struct brcmf_bus_ops *ops; struct brcmf_bus_msgbuf *msgbuf; @@ -186,6 +189,9 @@ struct brcmf_bus { struct list_head list; }; +void brcmf_bus_cancel_reset_work(struct brcmf_bus *bus_if); +void brcmf_bus_allow_reset_work(struct brcmf_bus *bus_if); + /* * callback wrappers */ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c index ec170647800d..dad6f4563d14 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c @@ -1167,6 +1167,35 @@ static int brcmf_revinfo_read(struct seq_file *s, void *data) return 0; } +/* + * Serialize arming from debugfs reset and brcmf_fw_crashed() against + * teardown. The remove path sets ->removing and drains the work while + * holding bus_reset_lock, so a racing armer is either drained or skips it. + */ +static void brcmf_bus_schedule_reset(struct brcmf_bus *bus_if) +{ + mutex_lock(&bus_if->bus_reset_lock); + if (bus_if->drvr && bus_if->drvr->bus_reset.func && !bus_if->removing) + schedule_work(&bus_if->drvr->bus_reset); + mutex_unlock(&bus_if->bus_reset_lock); +} + +void brcmf_bus_cancel_reset_work(struct brcmf_bus *bus_if) +{ + mutex_lock(&bus_if->bus_reset_lock); + bus_if->removing = true; + if (bus_if->drvr) + cancel_work_sync(&bus_if->drvr->bus_reset); + mutex_unlock(&bus_if->bus_reset_lock); +} + +void brcmf_bus_allow_reset_work(struct brcmf_bus *bus_if) +{ + mutex_lock(&bus_if->bus_reset_lock); + bus_if->removing = false; + mutex_unlock(&bus_if->bus_reset_lock); +} + static void brcmf_core_bus_reset(struct work_struct *work) { struct brcmf_pub *drvr = container_of(work, struct brcmf_pub, @@ -1187,7 +1216,7 @@ static ssize_t bus_reset_write(struct file *file, const char __user *user_buf, if (value != 1) return -EINVAL; - schedule_work(&drvr->bus_reset); + brcmf_bus_schedule_reset(drvr->bus_if); return count; } @@ -1417,14 +1446,23 @@ void brcmf_dev_coredump(struct device *dev) void brcmf_fw_crashed(struct device *dev) { struct brcmf_bus *bus_if = dev_get_drvdata(dev); - struct brcmf_pub *drvr = bus_if->drvr; + struct brcmf_pub *drvr; + + /* May fire before brcmf_attach() wires up drvr, or after removal + * has cleared it; guard the derefs below (and the arming gate in + * brcmf_bus_schedule_reset() already checks drvr/->removing). + */ + if (!bus_if) + return; + drvr = bus_if->drvr; + if (!drvr) + return; bphy_err(drvr, "Firmware has halted or crashed\n"); brcmf_dev_coredump(dev); - if (drvr->bus_reset.func) - schedule_work(&drvr->bus_reset); + brcmf_bus_schedule_reset(bus_if); } void brcmf_detach(struct device *dev) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c index 9f10b3fff9ff..55f4d7b970f2 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c @@ -2503,6 +2503,7 @@ brcmf_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id) ret = -ENOMEM; goto fail; } + mutex_init(&bus->bus_reset_lock); bus->msgbuf = kzalloc_obj(*bus->msgbuf); if (!bus->msgbuf) { ret = -ENOMEM; @@ -2598,6 +2599,11 @@ brcmf_pcie_remove(struct pci_dev *pdev) if (devinfo->ci) brcmf_pcie_intr_disable(devinfo); + if (devinfo->irq_allocated) + synchronize_irq(pdev->irq); + + brcmf_bus_cancel_reset_work(bus); + brcmf_detach(&pdev->dev); brcmf_free(&pdev->dev); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c index b725c64e5b5c..9f7ed1d293a0 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c @@ -4560,6 +4560,12 @@ int brcmf_sdio_probe(struct brcmf_sdio_dev *sdiodev) return ret; } +void brcmf_sdio_cancel_datawork(struct brcmf_sdio *bus) +{ + if (bus) + cancel_work_sync(&bus->datawork); +} + /* Detach and free everything */ void brcmf_sdio_remove(struct brcmf_sdio *bus) { diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.h index 80180d5c6c87..b93d153a8963 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.h @@ -361,6 +361,7 @@ int brcmf_sdiod_remove(struct brcmf_sdio_dev *sdiodev); int brcmf_sdio_probe(struct brcmf_sdio_dev *sdiodev); void brcmf_sdio_remove(struct brcmf_sdio *bus); void brcmf_sdio_isr(struct brcmf_sdio *bus, bool in_isr); +void brcmf_sdio_cancel_datawork(struct brcmf_sdio *bus); void brcmf_sdio_wd_timer(struct brcmf_sdio *bus, bool active); void brcmf_sdio_wowl_config(struct device *dev, bool enabled); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/usb.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/usb.c index 0b52f968b907..b41949a9bdc8 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/usb.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/usb.c @@ -1260,6 +1260,7 @@ static int brcmf_usb_probe_cb(struct brcmf_usbdev_info *devinfo, ret = -ENOMEM; goto fail; } + mutex_init(&bus->bus_reset_lock); bus->dev = dev; bus_pub->bus = bus; @@ -1329,6 +1330,8 @@ brcmf_usb_disconnect_cb(struct brcmf_usbdev_info *devinfo) return; brcmf_dbg(USB, "Enter, bus_pub %p\n", devinfo); + brcmf_bus_cancel_reset_work(devinfo->bus_pub.bus); + brcmf_detach(devinfo->dev); brcmf_free(devinfo->dev); kfree(devinfo->bus_pub.bus); From 3dc723ac78a6e4fa0fd49e27e487ed319da40a9f Mon Sep 17 00:00:00 2001 From: Ibrahim Hashimov Date: Tue, 21 Jul 2026 13:53:46 +0200 Subject: [PATCH 096/234] wifi: mac80211_hwsim: reject undersized HWSIM_ATTR_TX_INFO hwsim_tx_info_frame_received_nl() casts the HWSIM_ATTR_TX_INFO payload to a struct hwsim_tx_rate * and unconditionally reads IEEE80211_TX_MAX_RATES entries (8 bytes) from it. The policy only bounds the attribute from above (NLA_BINARY .len is a maximum) and the op sets GENL_DONT_VALIDATE_STRICT, so a short or zero-length attribute is accepted and the loop reads past the payload. Require the exact length in the policy, so a malformed attribute is rejected before the handler runs. Signed-off-by: Ibrahim Hashimov Assisted-by: AuditCode-AI:2026.07 Link: https://patch.msgid.link/20260721115346.17236-1-security@auditcode.ai Signed-off-by: Johannes Berg --- drivers/net/wireless/virtual/mac80211_hwsim_main.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c index 956ff9b94526..75caa97becc8 100644 --- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c +++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c @@ -870,9 +870,9 @@ static const struct nla_policy hwsim_genl_policy[HWSIM_ATTR_MAX + 1] = { [HWSIM_ATTR_FLAGS] = { .type = NLA_U32 }, [HWSIM_ATTR_RX_RATE] = { .type = NLA_U32 }, [HWSIM_ATTR_SIGNAL] = { .type = NLA_U32 }, - [HWSIM_ATTR_TX_INFO] = { .type = NLA_BINARY, - .len = IEEE80211_TX_MAX_RATES * - sizeof(struct hwsim_tx_rate)}, + [HWSIM_ATTR_TX_INFO] = + NLA_POLICY_EXACT_LEN(IEEE80211_TX_MAX_RATES * + sizeof(struct hwsim_tx_rate)), [HWSIM_ATTR_COOKIE] = { .type = NLA_U64 }, [HWSIM_ATTR_CHANNELS] = { .type = NLA_U32 }, [HWSIM_ATTR_RADIO_ID] = { .type = NLA_U32 }, From b24adfed83ae3fe623d978a30d5c6c636a874821 Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Thu, 9 Jul 2026 03:59:07 +0800 Subject: [PATCH 097/234] wifi: cfg80211: guard optional PMSR nominal time pmsr_parse_ftm() rejects a request that omits NOMINAL_TIME only for non-trigger-based PD ranging. It then reads the attribute unconditionally for every non-trigger-based request: out->ftm.nominal_time = nla_get_u32(tb[NL80211_PMSR_FTM_REQ_ATTR_NOMINAL_TIME]); For the other non-trigger-based request types NOMINAL_TIME is optional, so tb[...] can be NULL and nla_get_u32() dereferences a NULL pointer. Keep the requirement for PD ranging and read the nominal-time value only when the attribute is present. Fixes: 8823a9b0e7af ("wifi: cfg80211: add NTB continuous ranging and FTM request type support") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5 Assisted-by: Claude:opus-4.8 Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260708195911.84365-5-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- net/wireless/pmsr.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/net/wireless/pmsr.c b/net/wireless/pmsr.c index 34c3625f7fd5..d1e2fae5bc0e 100644 --- a/net/wireless/pmsr.c +++ b/net/wireless/pmsr.c @@ -263,8 +263,9 @@ static int pmsr_parse_ftm(struct cfg80211_registered_device *rdev, "FTM: nominal time is required for PD NTB ranging"); return -EINVAL; } - out->ftm.nominal_time = - nla_get_u32(tb[NL80211_PMSR_FTM_REQ_ATTR_NOMINAL_TIME]); + if (tb[NL80211_PMSR_FTM_REQ_ATTR_NOMINAL_TIME]) + out->ftm.nominal_time = + nla_get_u32(tb[NL80211_PMSR_FTM_REQ_ATTR_NOMINAL_TIME]); if (tb[NL80211_PMSR_FTM_REQ_ATTR_MIN_TIME_BETWEEN_MEASUREMENTS]) out->ftm.min_time_between_measurements = From 7be2552e601c247a328a5aba6fc06ac844b94a16 Mon Sep 17 00:00:00 2001 From: "Nikhil P. Rao" Date: Wed, 8 Jul 2026 16:36:49 +0000 Subject: [PATCH 098/234] pds_core: reject component parameter in legacy firmware update The legacy firmware update path does not support per-component updates. If a user specifies a component parameter with devlink flash, reject the request with -EOPNOTSUPP rather than silently ignoring the component parameter and flashing the entire firmware image. Fixes: 49ce92fbee0b ("pds_core: add FW update feature to devlink") Signed-off-by: Nikhil P. Rao Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260708163649.128620-1-nikhil.rao@amd.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/amd/pds_core/devlink.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/ethernet/amd/pds_core/devlink.c b/drivers/net/ethernet/amd/pds_core/devlink.c index 2ea97e1c5939..8adae7b18898 100644 --- a/drivers/net/ethernet/amd/pds_core/devlink.c +++ b/drivers/net/ethernet/amd/pds_core/devlink.c @@ -90,6 +90,12 @@ int pdsc_dl_flash_update(struct devlink *dl, { struct pdsc *pdsc = devlink_priv(dl); + if (params->component) { + NL_SET_ERR_MSG_MOD(extack, + "Component update not supported by this device"); + return -EOPNOTSUPP; + } + return pdsc_firmware_update(pdsc, params->fw, extack); } From e751256486d0ded20f5a9f9863467f1dce65142f Mon Sep 17 00:00:00 2001 From: Shiming Cheng Date: Thu, 9 Jul 2026 09:46:39 +0800 Subject: [PATCH 099/234] net: gro: fix double aggregation of flush-marked skbs Commit 0ab03f353d36 ("net-gro: Fix GRO flush when receiving a GSO packet.") added a flush check to skb_gro_receive(), but skb_gro_receive_list() lacks the same validation. As a result, packets marked with NAPI_GRO_CB(skb)->flush may still be re-aggregated. This allows already-GRO'd packets with existing frag_list to be re-aggregated into a new GRO session, corrupting the frag_list chain structure. When skb_segment() attempts to unpack these malformed packets, it encounters invalid state and triggers a kernel panic. Scenario (Tethering/Device forwarding): 1. Driver: Generated aggregated packet P1 via LRO with frag_list 2. Dev A: Receives aggregated fraglist packet and flush flag set 3. Dev A: Re-enters GRO, skb_gro_receive_list() is called 4. Missing flush check allows re-aggregation despite flush flag 5. Frag_list chain becomes corrupted (loops or dangling refs) 6. Dev B: TX path calls skb_segment(), crashes on corrupted frag_list Root cause in skb_segment(): The check at line ~4891: if (hsize <= 0 && i >= nfrags && skb_headlen(list_skb) && (skb_headlen(list_skb) == len || sg)) { When frag_list is corrupted by double aggregation, when list_skb is a NULL pointer from skb->next, skb_headlen(list_skb) dereference NULL/corrupted pointers occurs. Call Trace: skb_headlen(NULL skb) skb_segment tcp_gso_segment tcp4_gso_segment inet_gso_segment skb_mac_gso_segment __skb_gso_segment skb_gso_segment validate_xmit_skb validate_xmit_skb_list sch_direct_xmit qdisc_restart __qdisc_run qdisc_run net_tx_action Fix: Add NAPI_GRO_CB(skb)->flush validation to the early-return check in skb_gro_receive_list(), matching the defensive programming pattern of skb_gro_receive(). Fixes: 3a1296a38d0c ("net: Support GRO/GSO fraglist chaining.") Cc: stable@vger.kernel.org Signed-off-by: Shiming Cheng Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260709014704.3625-1-shiming.cheng@mediatek.com Signed-off-by: Jakub Kicinski --- net/core/gro.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/core/gro.c b/net/core/gro.c index 35f2f708f010..29b4d02bf519 100644 --- a/net/core/gro.c +++ b/net/core/gro.c @@ -229,7 +229,9 @@ int skb_gro_receive(struct sk_buff *p, struct sk_buff *skb) int skb_gro_receive_list(struct sk_buff *p, struct sk_buff *skb) { - if (unlikely(p->len + skb->len >= 65536)) + /* make sure to check flush flag and to not merge */ + if (unlikely(p->len + skb->len >= 65536 || + NAPI_GRO_CB(skb)->flush)) return -E2BIG; if (!pskb_may_pull(skb, skb_gro_offset(skb))) { From 4bf22afe53a1de4b44b04cf677fd5199089cbdff Mon Sep 17 00:00:00 2001 From: Prashanth Kumar KR Date: Thu, 9 Jul 2026 15:20:06 +0530 Subject: [PATCH 100/234] amd-xgbe: fix MAC_AUTO_SW handling in CL37 AN MAC_AUTO_SW (VR_MII_DIG_CTRL1 bit 9) enables automatic XPCS speed mode switching after CL37 auto-negotiation and is only meaningful in SGMII MAC mode. The original code unconditionally set this bit on every call to xgbe_an37_set(), including when called from xgbe_an37_disable() with enable=false. This left MAC_AUTO_SW=1 after AN was disabled, causing the XPCS to autonomously switch speed from stale AN state during subsequent mode changes, breaking SGMII speed negotiation on 1G copper SFP modules. Patrick: This was breaking negotiation for all 1G SFP modules, not just copper modules. Fixes: 42fd432fe6d3 ("amd-xgbe: align CL37 AN sequence as per databook") Reported-by: Patrick Oppenlander Link: https://lore.kernel.org/netdev/CAEg67GmFS0Q4oSZkz8zWdOzckSth9_vBPiOy6a7-d697C2w2Xg@mail.gmail.com Signed-off-by: Prashanth Kumar KR Tested-by: Patrick Oppenlander Link: https://patch.msgid.link/20260709095006.3683940-1-prashanthkumar.k.r@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/amd/xgbe/xgbe-mdio.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/amd/xgbe/xgbe-mdio.c b/drivers/net/ethernet/amd/xgbe/xgbe-mdio.c index fa0df6181207..12770af031eb 100644 --- a/drivers/net/ethernet/amd/xgbe/xgbe-mdio.c +++ b/drivers/net/ethernet/amd/xgbe/xgbe-mdio.c @@ -267,9 +267,14 @@ static void xgbe_an37_set(struct xgbe_prv_data *pdata, bool enable, XMDIO_WRITE(pdata, MDIO_MMD_VEND2, MDIO_CTRL1, reg); - reg = XMDIO_READ(pdata, MDIO_MMD_VEND2, MDIO_PCS_DIG_CTRL); - reg |= XGBE_VEND2_MAC_AUTO_SW; - XMDIO_WRITE(pdata, MDIO_MMD_VEND2, MDIO_PCS_DIG_CTRL, reg); + if (pdata->an_mode == XGBE_AN_MODE_CL37_SGMII) { + reg = XMDIO_READ(pdata, MDIO_MMD_VEND2, MDIO_PCS_DIG_CTRL); + if (enable) + reg |= XGBE_VEND2_MAC_AUTO_SW; + else + reg &= ~XGBE_VEND2_MAC_AUTO_SW; + XMDIO_WRITE(pdata, MDIO_MMD_VEND2, MDIO_PCS_DIG_CTRL, reg); + } } static void xgbe_an37_restart(struct xgbe_prv_data *pdata) From 98da8ce87dd561f08fbe44f75865edc5d9b2ba5f Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Thu, 9 Jul 2026 18:31:39 +0000 Subject: [PATCH 101/234] soreuseport: Clear sk_reuseport_cb before failure in sk_clone(). When sk_clone() fails, sk_destruct() is called for the new socket. If the parent socket has sk->sk_reuseport_cb, the child will call reuseport_detach_sock() for the reuseport group. Let's clear sk->sk_reuseport_cb before any failure path in sk_clone(). Note that this was not a problem before the cited commit because reuseport_detach_sock() did nothing if the socket was not found in the reuseport array. Fixes: 5dc4c4b7d4e8 ("bpf: Introduce BPF_MAP_TYPE_REUSEPORT_SOCKARRAY") Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260709032007.9E4D61F000E9@smtp.kernel.org/ Signed-off-by: Kuniyuki Iwashima Reviewed-by: Willem de Bruijn Reviewed-by: Jason Xing Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260709183315.965751-2-kuniyu@google.com Signed-off-by: Jakub Kicinski --- net/core/sock.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/core/sock.c b/net/core/sock.c index 8a59bfaa8096..fc3ff0552d68 100644 --- a/net/core/sock.c +++ b/net/core/sock.c @@ -2544,6 +2544,8 @@ struct sock *sk_clone(const struct sock *sk, const gfp_t priority, cgroup_sk_clone(&newsk->sk_cgrp_data); + RCU_INIT_POINTER(newsk->sk_reuseport_cb, NULL); + rcu_read_lock(); filter = rcu_dereference(sk->sk_filter); if (filter != NULL) @@ -2566,8 +2568,6 @@ struct sock *sk_clone(const struct sock *sk, const gfp_t priority, goto free; } - RCU_INIT_POINTER(newsk->sk_reuseport_cb, NULL); - if (bpf_sk_storage_clone(sk, newsk)) goto free; From d50557779257a00162411e3048d82971ff1f644c Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Thu, 9 Jul 2026 18:31:40 +0000 Subject: [PATCH 102/234] net: Call net_enable_timestamp() before failure in sk_clone(). When sk_clone() fails, sk_destruct() is called for the new socket. If the parent socket has SK_FLAGS_TIMESTAMP in sk->sk_flags, net_disable_timestamp() is called for the child socket even though net_enable_timestamp() is not called for it. Let's call net_enable_timestamp() before any failure path in sk_clone(). Fixes: 704da560c0a0 ("tcp: update the netstamp_needed counter when cloning sockets") Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260709032007.9E4D61F000E9@smtp.kernel.org/ Signed-off-by: Kuniyuki Iwashima Reviewed-by: Willem de Bruijn Reviewed-by: Jason Xing Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260709183315.965751-3-kuniyu@google.com Signed-off-by: Jakub Kicinski --- net/core/sock.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/net/core/sock.c b/net/core/sock.c index fc3ff0552d68..504d82a3aacd 100644 --- a/net/core/sock.c +++ b/net/core/sock.c @@ -2546,6 +2546,9 @@ struct sock *sk_clone(const struct sock *sk, const gfp_t priority, RCU_INIT_POINTER(newsk->sk_reuseport_cb, NULL); + if (sock_needs_netstamp(sk) && newsk->sk_flags & SK_FLAGS_TIMESTAMP) + net_enable_timestamp(); + rcu_read_lock(); filter = rcu_dereference(sk->sk_filter); if (filter != NULL) @@ -2595,9 +2598,6 @@ struct sock *sk_clone(const struct sock *sk, const gfp_t priority, if (newsk->sk_prot->sockets_allocated) sk_sockets_allocated_inc(newsk); - - if (sock_needs_netstamp(sk) && newsk->sk_flags & SK_FLAGS_TIMESTAMP) - net_enable_timestamp(); out: return newsk; free: From 29ab31f3f27157648f2f7e6d5e1fd9792fdf0614 Mon Sep 17 00:00:00 2001 From: LiangCheng Wang Date: Wed, 15 Jul 2026 14:49:38 +0800 Subject: [PATCH 103/234] wifi: brcmfmac: set F2 blocksize to 256 for BCM43752 The BCM43752 is not reliable with the default 512-byte SDIO function 2 block size: on an i.MX8MP board with an AMPAK AP6275S module at SDR104 / 200 MHz, an iperf TX stress test kills WLAN within seconds: mmc_submit_one: CMD53 sg block write failed -84 brcmf_sdio_dpc: failed backplane access over SDIO, halting operation Commit d2587c57ffd8 ("brcmfmac: add 43752 SDIO ids and initialization") set up the 43752 like the 4373 for the F2 watermark but missed the F2 block size, which the 4373 limits to 256 bytes. The vendor driver (bcmdhd) also programs a 256-byte F2 block size for this chip and runs the same hardware without errors. Group the 43752 with the 4373, matching the F2 watermark handling. With this change a 10-minute bidirectional iperf3 soak completes with zero SDIO errors at ~270 Mbit/s in each direction. Backporting note: kernels before v6.18 name this id SDIO_DEVICE_ID_BROADCOM_CYPRESS_43752, so on those trees the case label added by this patch must be adjusted to that name. Cherry-picking the rename commit 74e2ef72bd4b ("wifi: brcmfmac: fix 43752 SDIO FWVID incorrectly labelled as Cypress (CYW)") first is not a clean alternative: on trees before v6.17 its context collides with the 43751 additions, and trees before v6.2 lack the FWVID framework it touches. Fixes: d2587c57ffd8 ("brcmfmac: add 43752 SDIO ids and initialization") Cc: stable@vger.kernel.org # see patch description, needs adjustments for <= 6.17 Signed-off-by: LiangCheng Wang Acked-by: Arend van Spriel Link: https://patch.msgid.link/20260715-b43752-f2-blksz-v2-1-f9be49856050@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c index ec487c6f2e38..869c4872d399 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c @@ -911,6 +911,7 @@ int brcmf_sdiod_probe(struct brcmf_sdio_dev *sdiodev) return ret; } switch (sdiodev->func2->device) { + case SDIO_DEVICE_ID_BROADCOM_43752: case SDIO_DEVICE_ID_BROADCOM_CYPRESS_4373: f2_blksz = SDIO_4373_FUNC2_BLOCKSIZE; break; From 751bfa982b4a6de8275a552804f6971adcf08473 Mon Sep 17 00:00:00 2001 From: Antoine Tenart Date: Thu, 9 Jul 2026 14:48:00 +0200 Subject: [PATCH 104/234] geneve: fix hint header definition wrt endianness Bitfields are packed differently depending on the endianness, take it into account in the GRO hint header definition. Fixes: e0a12cbf262b ("geneve: add GRO hint output path") Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260529144713.780938-1-atenart%40kernel.org Signed-off-by: Antoine Tenart Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260709124801.140632-1-atenart@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/geneve.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/net/geneve.c b/drivers/net/geneve.c index 396e1a113cd4..bed1c9713e0a 100644 --- a/drivers/net/geneve.c +++ b/drivers/net/geneve.c @@ -43,8 +43,17 @@ MODULE_PARM_DESC(log_ecn_error, "Log packets received with corrupted ECN"); #define GENEVE_OPT_GRO_HINT_LEN 1 struct geneve_opt_gro_hint { +#if defined(__LITTLE_ENDIAN_BITFIELD) u8 inner_proto_id:2, - nested_is_v6:1; + nested_is_v6:1, + rsvd:5; +#elif defined(__BIG_ENDIAN_BITFIELD) + u8 rsvd:5, + nested_is_v6:1, + inner_proto_id:2; +#else +#error "Please fix " +#endif u8 nested_nh_offset; u8 nested_tp_offset; u8 nested_hdr_len; From 447ec540233c60d6af4d68a164a5bc8ce7e975c1 Mon Sep 17 00:00:00 2001 From: Antoine Tenart Date: Thu, 9 Jul 2026 14:50:00 +0200 Subject: [PATCH 105/234] geneve: ensure the skb is writable before fixing its headers Make sure the IPv4/6 and UDP headers are writable before fixing them up in geneve_post_decap_hint. As skb_ensure_writable can reallocate the skb linear area, reload the GRO hint header pointer and only set the IPv4/6 header ones after the call. Fixes: fd0dd796576e ("geneve: use GRO hint option in the RX path") Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260529144713.780938-1-atenart%40kernel.org Signed-off-by: Antoine Tenart Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260709125000.141092-1-atenart@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/geneve.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/net/geneve.c b/drivers/net/geneve.c index bed1c9713e0a..bb275b30d3e5 100644 --- a/drivers/net/geneve.c +++ b/drivers/net/geneve.c @@ -586,6 +586,7 @@ static int geneve_post_decap_hint(const struct sock *sk, struct sk_buff *skb, struct iphdr *iph; struct udphdr *uh; __be16 p; + int err; hint_off = geneve_sk_gro_hint_off(sk, *geneveh, &p, &len); if (!hint_off) @@ -610,12 +611,20 @@ static int geneve_post_decap_hint(const struct sock *sk, struct sk_buff *skb, !geneve_opt_gro_hint_validate(skb->data, gro_hint))) return -EINVAL; - ipv6h = (void *)skb->data + gro_hint->nested_nh_offset; - iph = (struct iphdr *)ipv6h; total_len = skb->len - gro_hint->nested_nh_offset; if (total_len >= GRO_LEGACY_MAX_SIZE) return -E2BIG; + err = skb_ensure_writable(skb, gro_hint->nested_tp_offset + sizeof(*uh)); + if (unlikely(err)) + return err; + + *geneveh = geneve_hdr(skb); + gro_hint = geneve_opt_gro_hint(*geneveh, hint_off); + + ipv6h = (void *)skb->data + gro_hint->nested_nh_offset; + iph = (struct iphdr *)ipv6h; + /* * After stripping the outer encap, the packet still carries a * tunnel encapsulation: the nested one. From b2ff91b752b0d85e8815e7f44fd85205c4268094 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Thu, 9 Jul 2026 15:19:25 -0400 Subject: [PATCH 106/234] mptcp: only set DATA_FIN when a mapping is present mptcp_get_options() clears only the status group of struct mptcp_options_received; data_seq, subflow_seq and data_len are filled in by mptcp_parse_option() exclusively inside the DSS mapping block, which runs only when the DSS M (mapping present) bit is set. A peer can send a DSS option with the DATA_FIN flag set but the mapping bit clear. The parser then records mp_opt->data_fin while leaving data_len and data_seq uninitialized. For a zero-length segment mptcp_incoming_options() evaluates if (mp_opt.data_fin && mp_opt.data_len == 1 && mptcp_update_rcv_data_fin(msk, mp_opt.data_seq, mp_opt.dsn64)) which reads the uninitialized data_len and data_seq; KMSAN reports an uninit-value in mptcp_incoming_options(). The stale data_seq can also be fed into the receive-side DATA_FIN sequence tracking. Record the DATA_FIN flag only when the DSS option carries a mapping, so data_fin is never set without data_seq and data_len also being present. data_fin is part of the status group that mptcp_get_options() clears up front, so on the no-map path it stays zero and the zero-length DATA_FIN branch is simply skipped. A DATA_FIN is always transmitted together with a mapping (mptcp_write_data_fin() sets use_map along with data_seq and data_len), so legitimate DATA_FIN handling is unaffected. Move the pr_debug() that logs the parsed DSS flags below the mapping block, so it reports the final data_fin value instead of the stale one it would otherwise print before the assignment. Fixes: 43b54c6ee382 ("mptcp: Use full MPTCP-level disconnect state machine") Suggested-by: Paolo Abeni Cc: stable@vger.kernel.org Signed-off-by: Michael Bommarito Reviewed-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260709191925.2811195-1-michael.bommarito@gmail.com Signed-off-by: Jakub Kicinski --- net/mptcp/options.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/net/mptcp/options.c b/net/mptcp/options.c index dff3fd5d3b55..1b74ca5b6a59 100644 --- a/net/mptcp/options.c +++ b/net/mptcp/options.c @@ -157,17 +157,11 @@ static void mptcp_parse_option(const struct sk_buff *skb, ptr++; flags = (*ptr++) & MPTCP_DSS_FLAG_MASK; - mp_opt->data_fin = (flags & MPTCP_DSS_DATA_FIN) != 0; mp_opt->dsn64 = (flags & MPTCP_DSS_DSN64) != 0; mp_opt->use_map = (flags & MPTCP_DSS_HAS_MAP) != 0; mp_opt->ack64 = (flags & MPTCP_DSS_ACK64) != 0; mp_opt->use_ack = (flags & MPTCP_DSS_HAS_ACK); - pr_debug("data_fin=%d dsn64=%d use_map=%d ack64=%d use_ack=%d\n", - mp_opt->data_fin, mp_opt->dsn64, - mp_opt->use_map, mp_opt->ack64, - mp_opt->use_ack); - expected_opsize = TCPOLEN_MPTCP_DSS_BASE; if (mp_opt->use_ack) { @@ -178,12 +172,18 @@ static void mptcp_parse_option(const struct sk_buff *skb, } if (mp_opt->use_map) { + mp_opt->data_fin = (flags & MPTCP_DSS_DATA_FIN) != 0; if (mp_opt->dsn64) expected_opsize += TCPOLEN_MPTCP_DSS_MAP64; else expected_opsize += TCPOLEN_MPTCP_DSS_MAP32; } + pr_debug("data_fin=%d dsn64=%d use_map=%d ack64=%d use_ack=%d\n", + mp_opt->data_fin, mp_opt->dsn64, + mp_opt->use_map, mp_opt->ack64, + mp_opt->use_ack); + /* Always parse any csum presence combination, we will enforce * RFC 8684 Section 3.3.0 checks later in subflow_data_ready */ From 1c50efa1faf3a1a96e100b07ec7a2f3164d90bee Mon Sep 17 00:00:00 2001 From: Justin Lai Date: Thu, 9 Jul 2026 18:34:56 +0800 Subject: [PATCH 107/234] rtase: Workaround for TX hang caused by hardware packet parsing The hardware performs packet parsing before packet transmission. Parsing incomplete IPv4, IPv6, TCP, or UDP headers may trigger a TX hang because the hardware parser expects additional protocol header data that is not present in the packet. The hardware performs additional PTP parsing on UDP packets identified by destination ports 319/320 at the expected UDP destination port offset. If such a packet has transport data smaller than RTASE_MIN_PAD_LEN, the hardware parser expects additional packet data and may trigger a TX hang. To avoid these hardware issues, the driver applies the following workarounds. Drop malformed packets that may trigger this hardware issue before transmission. For IPv4 non-initial fragments, the hardware does not check the fragment offset before parsing the expected transport header location. As a result, these packets are still subject to transport header parsing even though they do not contain a transport header. If the transport data is shorter than the minimum transport header required by the hardware parser, pad the transport data to the minimum transport header length required by the hardware parser. Packets that also match the hardware PTP parsing conditions continue to follow the corresponding workaround. For IPv6 fragmented packets, neither of the above hardware issues occurs because the hardware only continues packet parsing when the IPv6 Base Header Next Header field directly indicates UDP. Packets carrying a Fragment Header do not continue through the subsequent packet parsing stages. For packets identified for hardware PTP parsing, pad the transport data so it reaches RTASE_MIN_PAD_LEN before transmission. Fixes: d6e882b89fdf ("rtase: Implement .ndo_start_xmit function") Cc: stable@vger.kernel.org Signed-off-by: Justin Lai Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260709103456.83789-1-justinlai0215@realtek.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/realtek/rtase/rtase.h | 8 + .../net/ethernet/realtek/rtase/rtase_main.c | 197 ++++++++++++++++++ 2 files changed, 205 insertions(+) diff --git a/drivers/net/ethernet/realtek/rtase/rtase.h b/drivers/net/ethernet/realtek/rtase/rtase.h index 9bd6872474c1..03b12d83f6e9 100644 --- a/drivers/net/ethernet/realtek/rtase/rtase.h +++ b/drivers/net/ethernet/realtek/rtase/rtase.h @@ -192,6 +192,12 @@ enum rtase_sw_flag_content { RTASE_SWF_MSIX_ENABLED = BIT(2), }; +enum rtase_parse_result { + RTASE_PARSE_OK, + RTASE_PARSE_SKIP, + RTASE_PARSE_DROP, +}; + #define RSVD_MASK 0x3FFFC000 struct rtase_tx_desc { @@ -363,4 +369,6 @@ struct rtase_private { #define RTASE_MSS_MASK GENMASK(28, 18) +#define RTASE_MIN_PAD_LEN 47 + #endif /* RTASE_H */ diff --git a/drivers/net/ethernet/realtek/rtase/rtase_main.c b/drivers/net/ethernet/realtek/rtase/rtase_main.c index 255667775f0e..4168ad9e48ea 100644 --- a/drivers/net/ethernet/realtek/rtase/rtase_main.c +++ b/drivers/net/ethernet/realtek/rtase/rtase_main.c @@ -61,6 +61,7 @@ #include #include #include +#include #include #include #include @@ -1252,6 +1253,199 @@ static u32 rtase_tx_csum(struct sk_buff *skb, const struct net_device *dev) return csum_cmd; } +static enum rtase_parse_result rtase_get_l3_proto(struct sk_buff *skb, + __be16 *proto, + u32 *network_offset) +{ + struct vlan_hdr *vh, _vh; + struct ethhdr *eh, _eh; + u32 offset = ETH_HLEN; + + eh = skb_header_pointer(skb, 0, sizeof(_eh), &_eh); + if (!eh) + return RTASE_PARSE_DROP; + + *proto = eh->h_proto; + + while (eth_type_vlan(*proto)) { + vh = skb_header_pointer(skb, offset, sizeof(_vh), &_vh); + if (!vh) + return RTASE_PARSE_DROP; + + *proto = vh->h_vlan_encapsulated_proto; + offset += VLAN_HLEN; + } + + *network_offset = offset; + + return RTASE_PARSE_OK; +} + +static bool rtase_pad_to_transport_len(struct sk_buff *skb, + u32 transport_offset, + u32 pad_to_len) +{ + u32 trans_data_len; + u32 pad_len; + + trans_data_len = skb->len - transport_offset; + if (trans_data_len >= pad_to_len) + return true; + + if (skb_is_nonlinear(skb)) { + if (skb_linearize(skb)) + return false; + } + + pad_len = pad_to_len - trans_data_len; + if (__skb_put_padto(skb, skb->len + pad_len, false)) + return false; + + return true; +} + +static enum rtase_parse_result rtase_get_transport_offset(struct sk_buff *skb, + u32 *transport_offset, + u8 *transport_proto, + u32 *pad_to_len) +{ + enum rtase_parse_result ret; + struct ipv6hdr *i6h, _i6h; + struct iphdr *ih, _ih; + bool non_first_frag; + __be16 proto; + u32 offset; + u32 no; + + ret = rtase_get_l3_proto(skb, &proto, &no); + if (ret != RTASE_PARSE_OK) + return ret; + + switch (proto) { + case htons(ETH_P_IP): + ih = skb_header_pointer(skb, no, sizeof(_ih), &_ih); + if (!ih) + return RTASE_PARSE_DROP; + + if (ih->ihl < 5) + return RTASE_PARSE_DROP; + + offset = no + ih->ihl * 4; + if (offset > skb->len) + return RTASE_PARSE_DROP; + + non_first_frag = ntohs(ih->frag_off) & IP_OFFSET; + + if (ih->protocol == IPPROTO_TCP) { + if (skb->len - offset < sizeof(struct tcphdr)) { + if (non_first_frag) { + *transport_offset = offset; + *transport_proto = IPPROTO_TCP; + *pad_to_len = sizeof(struct tcphdr); + + return RTASE_PARSE_OK; + } + + return RTASE_PARSE_DROP; + } + + return RTASE_PARSE_SKIP; + } + + if (ih->protocol != IPPROTO_UDP) + return RTASE_PARSE_SKIP; + + *transport_offset = offset; + *transport_proto = IPPROTO_UDP; + + if (skb->len - offset < sizeof(struct udphdr)) { + if (non_first_frag) { + *pad_to_len = sizeof(struct udphdr); + + return RTASE_PARSE_OK; + } + + return RTASE_PARSE_DROP; + } + + return RTASE_PARSE_OK; + + case htons(ETH_P_IPV6): + i6h = skb_header_pointer(skb, no, sizeof(_i6h), &_i6h); + if (!i6h) + return RTASE_PARSE_DROP; + + offset = no + sizeof(*i6h); + + if (i6h->nexthdr == IPPROTO_TCP) { + if (skb->len - offset < sizeof(struct tcphdr)) + return RTASE_PARSE_DROP; + + return RTASE_PARSE_SKIP; + } + + if (i6h->nexthdr != IPPROTO_UDP) + return RTASE_PARSE_SKIP; + + if (skb->len - offset < sizeof(struct udphdr)) + return RTASE_PARSE_DROP; + + *transport_offset = offset; + *transport_proto = IPPROTO_UDP; + + return RTASE_PARSE_OK; + + default: + return RTASE_PARSE_SKIP; + } +} + +static bool rtase_skb_pad(struct sk_buff *skb) +{ + enum rtase_parse_result ret; + u32 transport_offset; + __be16 *dest, _dest; + u32 trans_data_len; + u32 pad_to_len = 0; + u8 transport_proto; + u16 dest_port; + + ret = rtase_get_transport_offset(skb, &transport_offset, + &transport_proto, &pad_to_len); + if (ret == RTASE_PARSE_SKIP) { + return true; + } else if (ret == RTASE_PARSE_DROP) { + netdev_dbg(skb->dev, "drop malformed packet\n"); + return false; + } + + if (pad_to_len && + !rtase_pad_to_transport_len(skb, transport_offset, pad_to_len)) + return false; + + if (transport_proto != IPPROTO_UDP) + return true; + + trans_data_len = skb->len - transport_offset; + if (trans_data_len < offsetof(struct udphdr, len) || + trans_data_len >= RTASE_MIN_PAD_LEN) + return true; + + dest = skb_header_pointer(skb, + transport_offset + + offsetof(struct udphdr, dest), + sizeof(_dest), &_dest); + if (!dest) + return true; + + dest_port = ntohs(*dest); + if (dest_port != PTP_EV_PORT && dest_port != PTP_GEN_PORT) + return true; + + return rtase_pad_to_transport_len(skb, transport_offset, + RTASE_MIN_PAD_LEN); +} + static int rtase_xmit_frags(struct rtase_ring *ring, struct sk_buff *skb, u32 opts1, u32 opts2) { @@ -1365,6 +1559,9 @@ static netdev_tx_t rtase_start_xmit(struct sk_buff *skb, opts2 |= rtase_tx_csum(skb, dev); } + if (!rtase_skb_pad(skb)) + goto err_dma_0; + frags = rtase_xmit_frags(ring, skb, opts1, opts2); if (unlikely(frags < 0)) goto err_dma_0; From 350e592ff4e30e48ffb55e142d11a73e63f4869c Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Wed, 15 Jul 2026 19:52:04 -0700 Subject: [PATCH 108/234] tipc: serialize udp bearer replicast list updates tipc_udp_rcast_add() and cleanup_bearer() both update ub->rcast.list with list_add_rcu() / list_del_rcu(), but nothing serializes them. The add runs from the encap receive softirq (via tipc_udp_rcast_disc()) without rtnl_lock(), so it can race the cleanup delete and corrupt the list: list_del corruption. prev->next should be ffff8880298d7ab8, but was ffff88802449ad38. (prev=ffff888027e3ec98) kernel BUG at lib/list_debug.c:62! RIP: __list_del_entry_valid_or_report+0x17a/0x200 Workqueue: events cleanup_bearer Call Trace: cleanup_bearer (net/tipc/udp_media.c:811) process_one_work (kernel/workqueue.c:3302) worker_thread (kernel/workqueue.c:3466) The bearer can be enabled from an unprivileged user namespace, as the TIPCv2 generic-netlink ops carry no GENL_ADMIN_PERM. Add a spinlock to struct udp_bearer and take it around the list_add_rcu() in tipc_udp_rcast_add() and the list_del_rcu() loop in cleanup_bearer() so the two writers can no longer corrupt the list. Reject a duplicate peer under the same lock before allocating, and remove tipc_udp_is_known_peer(). The old lockless pre-check in tipc_udp_rcast_disc() was racy: two softirqs discovering the same peer could both find it absent and add it twice. cleanup_bearer() runs from a workqueue after tipc_udp_disable() clears the bearer's up bit, so an encap softirq can still reach tipc_udp_rcast_add() and add a peer after cleanup_bearer() has already emptied the list, leaking that entry when the bearer is freed. Mark the bearer disabled under rcast_lock once the list is emptied and refuse further additions. Fixes: ef20cd4dd163 ("tipc: introduce UDP replicast") Reported-by: Xiang Mei Suggested-by: Tung Nguyen Signed-off-by: Weiming Shi Reviewed-by: Tung Nguyen Link: https://patch.msgid.link/20260716025203.9332-2-bestswngs@gmail.com Signed-off-by: Jakub Kicinski --- net/tipc/udp_media.c | 56 +++++++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/net/tipc/udp_media.c b/net/tipc/udp_media.c index 62ae7f5b5840..230645cc01c9 100644 --- a/net/tipc/udp_media.c +++ b/net/tipc/udp_media.c @@ -94,6 +94,8 @@ struct udp_replicast { * @ifindex: local address scope * @work: used to schedule deferred work on a bearer * @rcast: associated udp_replicast container + * @rcast_lock: serialize updates to @rcast.list against concurrent updaters + * @disabled: bearer is being torn down; reject further @rcast.list additions */ struct udp_bearer { struct tipc_bearer __rcu *bearer; @@ -101,6 +103,8 @@ struct udp_bearer { u32 ifindex; struct work_struct work; struct udp_replicast rcast; + spinlock_t rcast_lock; + bool disabled; }; static int tipc_udp_is_mcast_addr(struct udp_media_addr *addr) @@ -278,26 +282,6 @@ static int tipc_udp_send_msg(struct net *net, struct sk_buff *skb, return err; } -static bool tipc_udp_is_known_peer(struct tipc_bearer *b, - struct udp_media_addr *addr) -{ - struct udp_replicast *rcast, *tmp; - struct udp_bearer *ub; - - ub = rcu_dereference_rtnl(b->media_ptr); - if (!ub) { - pr_err_ratelimited("UDP bearer instance not found\n"); - return false; - } - - list_for_each_entry_safe(rcast, tmp, &ub->rcast.list, list) { - if (!memcmp(&rcast->addr, addr, sizeof(struct udp_media_addr))) - return true; - } - - return false; -} - static int tipc_udp_rcast_add(struct tipc_bearer *b, struct udp_media_addr *addr) { @@ -308,16 +292,34 @@ static int tipc_udp_rcast_add(struct tipc_bearer *b, if (!ub) return -ENODEV; + spin_lock_bh(&ub->rcast_lock); + if (ub->disabled) { + spin_unlock_bh(&ub->rcast_lock); + return 0; + } + list_for_each_entry(rcast, &ub->rcast.list, list) { + if (!memcmp(&rcast->addr, addr, sizeof(*addr))) { + spin_unlock_bh(&ub->rcast_lock); + return 0; + } + } + rcast = kmalloc_obj(*rcast, GFP_ATOMIC); - if (!rcast) + if (!rcast) { + spin_unlock_bh(&ub->rcast_lock); return -ENOMEM; + } if (dst_cache_init(&rcast->dst_cache, GFP_ATOMIC)) { + spin_unlock_bh(&ub->rcast_lock); kfree(rcast); return -ENOMEM; } memcpy(&rcast->addr, addr, sizeof(struct udp_media_addr)); + list_add_rcu(&rcast->list, &ub->rcast.list); + b->bcast_addr.broadcast = TIPC_REPLICAST_SUPPORT; + spin_unlock_bh(&ub->rcast_lock); if (ntohs(addr->proto) == ETH_P_IP) pr_info("New replicast peer: %pI4\n", &rcast->addr.ipv4); @@ -325,8 +327,6 @@ static int tipc_udp_rcast_add(struct tipc_bearer *b, else if (ntohs(addr->proto) == ETH_P_IPV6) pr_info("New replicast peer: %pI6\n", &rcast->addr.ipv6); #endif - b->bcast_addr.broadcast = TIPC_REPLICAST_SUPPORT; - list_add_rcu(&rcast->list, &ub->rcast.list); return 0; } @@ -361,9 +361,6 @@ static int tipc_udp_rcast_disc(struct tipc_bearer *b, struct sk_buff *skb) return 0; } - if (likely(tipc_udp_is_known_peer(b, &src))) - return 0; - return tipc_udp_rcast_add(b, &src); } @@ -644,9 +641,6 @@ int tipc_udp_nl_bearer_add(struct tipc_bearer *b, struct nlattr *attr) return -EINVAL; } - if (tipc_udp_is_known_peer(b, &addr)) - return 0; - return tipc_udp_rcast_add(b, &addr); } @@ -679,6 +673,7 @@ static int tipc_udp_enable(struct net *net, struct tipc_bearer *b, return -ENOMEM; INIT_LIST_HEAD(&ub->rcast.list); + spin_lock_init(&ub->rcast_lock); if (!attrs[TIPC_NLA_BEARER_UDP_OPTS]) goto err; @@ -819,10 +814,13 @@ static void cleanup_bearer(struct work_struct *work) struct udp_replicast *rcast, *tmp; struct tipc_net *tn; + spin_lock_bh(&ub->rcast_lock); list_for_each_entry_safe(rcast, tmp, &ub->rcast.list, list) { list_del_rcu(&rcast->list); call_rcu_hurry(&rcast->rcu, rcast_free_rcu); } + ub->disabled = true; + spin_unlock_bh(&ub->rcast_lock); tn = tipc_net(sock_net(ub->sk)); From 043c1f6d84f6cd5d23ddd508ce5209cf0a3a3f41 Mon Sep 17 00:00:00 2001 From: Alice Mikityanska Date: Sat, 11 Jul 2026 02:43:19 +0300 Subject: [PATCH 109/234] mailmap: update entry for Alice Mikityanska Map all my corporate and old emails and update my name. Signed-off-by: Alice Mikityanska Link: https://patch.msgid.link/20260710234319.328687-1-alice.kernel@fastmail.im Signed-off-by: Jakub Kicinski --- .mailmap | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.mailmap b/.mailmap index 735470e21075..bc28ffdc9a77 100644 --- a/.mailmap +++ b/.mailmap @@ -66,6 +66,11 @@ Alex Hung Alex Shi Alex Shi Alex Shi +Alice Mikityanska +Alice Mikityanska +Alice Mikityanska +Alice Mikityanska +Alice Mikityanska Aloka Dixit Al Viro Al Viro @@ -585,8 +590,6 @@ Mauro Carvalho Chehab Mauro Carvalho Chehab Mauro Carvalho Chehab Mauro Carvalho Chehab -Maxim Mikityanskiy -Maxim Mikityanskiy Maxime Ripard Maxime Ripard Maxime Ripard From 6a905a71fd43ce8b45f05044b11491337f232c9d Mon Sep 17 00:00:00 2001 From: Chenguang Zhao Date: Mon, 13 Jul 2026 16:51:11 +0800 Subject: [PATCH 110/234] net: txgbe: fix heap overflow when reading module EEPROM txgbe_read_eeprom_hostif() always copies round_up(length, 4) bytes into the caller buffer, which ethtool allocates with exactly 'length' bytes. A non-4-aligned length therefore causes an out-of-bounds write. Copy only the remaining bytes on the final dword instead. Signed-off-by: Chenguang Zhao Reviewed-by: Jiawen Wu Reviewed-by: Jacob Keller Fixes: 9b97b6b5635b ("net: txgbe: support getting module EEPROM by page") Link: https://patch.msgid.link/20260713085111.1481884-1-chenguang.zhao@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/wangxun/txgbe/txgbe_aml.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_aml.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_aml.c index affea1a364ef..26d0cfc58ee2 100644 --- a/drivers/net/ethernet/wangxun/txgbe/txgbe_aml.c +++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_aml.c @@ -96,11 +96,13 @@ int txgbe_read_eeprom_hostif(struct wx *wx, dword_len = round_up(length, 4) >> 2; for (i = 0; i < dword_len; i++) { + u32 copy_len = min_t(u32, 4, length - i * 4); + value = rd32a(wx, WX_FW2SW_MBOX, i + offset); le32_to_cpus(&value); - memcpy(data, &value, 4); - data += 4; + memcpy(data, &value, copy_len); + data += copy_len; } return 0; From 9c805e592a29be9e4e61ff1bd567da04aa8fd6f9 Mon Sep 17 00:00:00 2001 From: Ilia Gavrilov Date: Thu, 9 Jul 2026 16:27:54 +0000 Subject: [PATCH 111/234] rds: Fix inet6_addr_lst NULL dereference when IPv6 is disabled When booting with the 'ipv6.disable=1' parameter, inet6_addr_lst is never initialized because inet6_init() exits before addrconf_init() is called to initialize it. An attempt to bind an RDS socket to an ipv6 address results in a crash in __ipv6_chk_addr_and_flags() KASAN: null-ptr-deref in range [0x0000000000000008-0x000000000000000f] RIP: 0010:__ipv6_chk_addr_and_flags+0x1df/0x7e0 Call Trace: ipv6_chk_addr+0x3b/0x50 rds_tcp_laddr_check+0x155/0x3b0 [rds_tcp] rds_trans_get_preferred+0x15d/0x2d0 [rds] ? trace_hardirqs_on+0x2d/0x110 rds_bind+0x1433/0x1d60 [rds] ? rds_remove_bound+0xd50/0xd50 [rds] ? aa_af_perm+0x250/0x250 ? __might_fault+0xde/0x190 ? __sys_bind+0x1dc/0x210 __sys_bind+0x1dc/0x210 ? __ia32_sys_socketpair+0x100/0x100 ? restore_fpregs_from_fpstate+0x53/0x100 __x64_sys_bind+0x73/0xb0 ? syscall_enter_from_user_mode+0x1c/0x50 do_syscall_64+0x34/0x80 entry_SYSCALL_64_after_hwframe+0x6e/0xd8 RIP: 0033:0x7f47f8269ea9 The following code reproduces the issue: struct sockaddr_in6 addr; s = socket(PF_RDS, SOCK_SEQPACKET, 0); memset(&addr, 0, sizeof(addr)); inet_pton(AF_INET6, ADDRESS, &addr.sin6_addr); addr.sin6_family = AF_INET6; addr.sin6_port = htons(PORT); bind(s, &addr, sizeof(addr)); Found by InfoTeCS on behalf of Linux Verification Center (linuxtesting.org) with Syzkaller. Fixes: eee2fa6ab322 ("rds: Changing IP address internal representation to struct in6_addr") Fixes: 1e2b44e78eea ("rds: Enable RDS IPv6 support") Signed-off-by: Ilia Gavrilov Reviewed-by: Allison Henderson Link: https://patch.msgid.link/20260709162723.367523-1-Ilia.Gavrilov@infotecs.ru Signed-off-by: Jakub Kicinski --- net/rds/ib.c | 4 ++++ net/rds/ib_cm.c | 4 ++++ net/rds/tcp.c | 8 +++++--- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/net/rds/ib.c b/net/rds/ib.c index 39f87272e071..8f9cf491984f 100644 --- a/net/rds/ib.c +++ b/net/rds/ib.c @@ -429,6 +429,10 @@ static int rds_ib_laddr_check_cm(struct net *net, const struct in6_addr *addr, sa = (struct sockaddr *)&sin; } else { #if IS_ENABLED(CONFIG_IPV6) + if (!ipv6_mod_enabled()) { + ret = -EADDRNOTAVAIL; + goto out; + } memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6; sin6.sin6_addr = *addr; diff --git a/net/rds/ib_cm.c b/net/rds/ib_cm.c index 5667f0173b47..d46146887ba4 100644 --- a/net/rds/ib_cm.c +++ b/net/rds/ib_cm.c @@ -810,6 +810,10 @@ int rds_ib_cm_handle_connect(struct rdma_cm_id *cm_id, dp = event->param.conn.private_data; if (isv6) { #if IS_ENABLED(CONFIG_IPV6) + if (!ipv6_mod_enabled()) { + err = -EOPNOTSUPP; + goto out; + } dp_cmn = &dp->ricp_v6.dp_cmn; saddr6 = &dp->ricp_v6.dp_saddr; daddr6 = &dp->ricp_v6.dp_daddr; diff --git a/net/rds/tcp.c b/net/rds/tcp.c index a1de114d5e2e..955d92277d5a 100644 --- a/net/rds/tcp.c +++ b/net/rds/tcp.c @@ -366,9 +366,11 @@ int rds_tcp_laddr_check(struct net *net, const struct in6_addr *addr, rcu_read_unlock(); } #if IS_ENABLED(CONFIG_IPV6) - ret = ipv6_chk_addr(net, addr, dev, 0); - if (ret) - return 0; + if (ipv6_mod_enabled()) { + ret = ipv6_chk_addr(net, addr, dev, 0); + if (ret) + return 0; + } #endif return -EADDRNOTAVAIL; } From ecaa37826340520664a4e5522f803ff48fc3f564 Mon Sep 17 00:00:00 2001 From: Chenguang Zhao Date: Mon, 13 Jul 2026 17:19:11 +0800 Subject: [PATCH 112/234] net: txgbe: fix FDIR filter leak on remove Perfect FDIR filters can be added while the interface is down and are kept on the software list for later restore. unregister_netdev() only calls ndo_stop when the device is up, so txgbe_fdir_filter_exit() in txgbe_close() is skipped in that case and the filters are leaked on driver remove. Free the filter list from txgbe_remove() as well. Fixes: 4bdb441105dc ("net: txgbe: support Flow Director perfect filters") Signed-off-by: Chenguang Zhao Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260713091911.1614795-1-chenguang.zhao@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/wangxun/txgbe/txgbe_main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c index 20c5a295c6c2..c277863baf67 100644 --- a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c +++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c @@ -945,6 +945,7 @@ static void txgbe_remove(struct pci_dev *pdev) netdev = wx->netdev; wx_disable_sriov(wx); unregister_netdev(netdev); + txgbe_fdir_filter_exit(wx); timer_shutdown_sync(&wx->service_timer); cancel_work_sync(&wx->service_task); From ff04b26794a16a8a879eb4fd2c02c2d6b03850e9 Mon Sep 17 00:00:00 2001 From: HanQuan Date: Mon, 13 Jul 2026 03:20:21 +0000 Subject: [PATCH 113/234] sctp: fix auth_chunk_list capacity check in sctp_auth_ep_add_chunkid sctp_auth_ep_add_chunkid() uses SCTP_NUM_CHUNK_TYPES (20) as the capacity limit for ep->auth_chunk_list, allowing it to hold up to 20 chunk entries (param_hdr.length up to 24). However, the copy destination asoc->c.auth_chunks in struct sctp_cookie is only SCTP_AUTH_MAX_CHUNKS (16) entries (20 bytes). When more than 16 chunks are added, sctp_association_init() memcpy overflows the destination by up to 4 bytes. Fix by using SCTP_AUTH_MAX_CHUNKS as the capacity limit, matching the destination capacity. Fixes: 1f485649f529 ("[SCTP]: Implement SCTP-AUTH internals") Signed-off-by: HanQuan Acked-by: Xin Long Link: https://patch.msgid.link/20260713032021.3491702-1-zhoujian.zja@antgroup.com Signed-off-by: Jakub Kicinski --- net/sctp/auth.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/sctp/auth.c b/net/sctp/auth.c index be9782760f50..c901d373af80 100644 --- a/net/sctp/auth.c +++ b/net/sctp/auth.c @@ -672,7 +672,7 @@ int sctp_auth_ep_add_chunkid(struct sctp_endpoint *ep, __u8 chunk_id) /* Check if we can add this chunk to the array */ param_len = ntohs(p->param_hdr.length); nchunks = param_len - sizeof(struct sctp_paramhdr); - if (nchunks == SCTP_NUM_CHUNK_TYPES) + if (nchunks == SCTP_AUTH_MAX_CHUNKS) return -EINVAL; p->chunks[nchunks] = chunk_id; From defd52c1eab2926d874fa95408187dac6f890ff1 Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Mon, 13 Jul 2026 00:51:03 +0200 Subject: [PATCH 114/234] mailmap: fix wrong canonical name for mgr@kernel.org After picking up some pending patches for the kernel to work on, I realized my name in the mailmap file somehow got mixed up. When switching to my kernel.org Address some time ago, I had never the intention to use a scrambled variant of Polish and German used for my first name to be found in this file. However, so here we are. Lets fix it for good. Signed-off-by: Michael Grzeschik Link: https://patch.msgid.link/20260713-mailmap-v1-1-cb40979cb190@kernel.org Signed-off-by: Jakub Kicinski --- .mailmap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.mailmap b/.mailmap index bc28ffdc9a77..45b133c19eb5 100644 --- a/.mailmap +++ b/.mailmap @@ -598,8 +598,8 @@ Mayuresh Janorkar Md Sadre Alam Miaoqing Pan Michael Buesch -Michal Grzeschik -Michal Grzeschik +Michael Grzeschik +Michael Grzeschik Michael Riesch Michal Simek Michel Dänzer From ab0eec0ff0a421737a37f510ceab5c6ea59cd05a Mon Sep 17 00:00:00 2001 From: "Nikhil P. Rao" Date: Tue, 14 Jul 2026 18:02:22 +0000 Subject: [PATCH 115/234] pds_core: fix deadlock between reset thread and remove pci_reset_function() acquires device_lock before performing the reset. pdsc_remove() is called by the PCI core with device_lock already held. If pdsc_pci_reset_thread() is running when pdsc_remove() is called, destroy_workqueue() will block waiting for the work to complete, while the work is blocked waiting for device_lock - deadlock. Use pci_try_reset_function() which uses pci_dev_trylock() internally. This acquires both the device lock and the PCI config access lock without blocking - if either lock is contended, it returns -EAGAIN immediately. This avoids the deadlock while also ensuring proper config space access serialization during the reset. The pci_dev_get/put calls are also removed as they were unnecessary - the driver-owned workqueue is destroyed in pdsc_remove(), guaranteeing the work completes before remove returns. The PCI core holds its reference to pci_dev throughout the entire unbind sequence. Fixes: 81665adf25d2 ("pds_core: Fix pdsc_check_pci_health function to use work thread") Reported-by: sashiko-bot Closes: https://patchwork.kernel.org/comment/27002369/ Signed-off-by: Nikhil P. Rao Reviewed-by: Harshitha Ramamurthy Link: https://patch.msgid.link/20260714180223.1642792-2-nikhil.rao@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/amd/pds_core/core.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/amd/pds_core/core.c b/drivers/net/ethernet/amd/pds_core/core.c index 38a2446571af..1074a022a52f 100644 --- a/drivers/net/ethernet/amd/pds_core/core.c +++ b/drivers/net/ethernet/amd/pds_core/core.c @@ -606,9 +606,10 @@ void pdsc_pci_reset_thread(struct work_struct *work) struct pdsc *pdsc = container_of(work, struct pdsc, pci_reset_work); struct pci_dev *pdev = pdsc->pdev; - pci_dev_get(pdev); - pci_reset_function(pdev); - pci_dev_put(pdev); + /* Use try variant to avoid deadlock with pdsc_remove(). + * If lock is contended, the watchdog timer will retry. + */ + pci_try_reset_function(pdev); } static void pdsc_check_pci_health(struct pdsc *pdsc) From 0ad134881508c36b65c1a8864f8bec53adbd3327 Mon Sep 17 00:00:00 2001 From: "Nikhil P. Rao" Date: Tue, 14 Jul 2026 18:02:23 +0000 Subject: [PATCH 116/234] pds_core: fix use-after-free on workqueue during remove In pdsc_remove(), the workqueue is destroyed before pdsc_teardown() is called. This ordering allows two paths to queue work on the destroyed workqueue: 1. If pdsc_teardown() -> pdsc_devcmd_reset() times out, the error path in pdsc_devcmd_locked() queues health_work. 2. A NotifyQ event can trigger the ISR and queue work before free_irq() is called in pdsc_teardown(). Fix by moving destroy_workqueue() after pdsc_teardown() so the workqueue outlives every queuer; destroy_workqueue() then flushes any work still pending. Draining the queued work also requires ordering the teardown so the resources that work touches are freed last: - In pdsc_qcq_free(), after freeing the interrupt, cancel_work_sync() the queue's work and only then clear qcq->intx, so pdsc_process_adminq()'s read of qcq->intx for interrupt-credit return cannot race with the clear. - Free adminqcq before notifyqcq: the shared adminq ISR is released when adminqcq is freed, and the adminq work accesses notifyqcq, so both must be stopped before notifyqcq is freed. Fixes: 01ba61b55b20 ("pds_core: Add adminq processing and commands") Reported-by: sashiko-bot Closes: https://patchwork.kernel.org/comment/27002369/ Signed-off-by: Nikhil P. Rao Link: https://patch.msgid.link/20260714180223.1642792-3-nikhil.rao@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/amd/pds_core/core.c | 14 ++++++++++---- drivers/net/ethernet/amd/pds_core/main.c | 5 +++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/amd/pds_core/core.c b/drivers/net/ethernet/amd/pds_core/core.c index 1074a022a52f..e39b2c9beb20 100644 --- a/drivers/net/ethernet/amd/pds_core/core.c +++ b/drivers/net/ethernet/amd/pds_core/core.c @@ -110,7 +110,6 @@ static void pdsc_qcq_intr_free(struct pdsc *pdsc, struct pdsc_qcq *qcq) return; pdsc_intr_free(pdsc, qcq->intx); - qcq->intx = PDS_CORE_INTR_INDEX_NOT_ASSIGNED; } static int pdsc_qcq_intr_alloc(struct pdsc *pdsc, struct pdsc_qcq *qcq) @@ -145,6 +144,12 @@ void pdsc_qcq_free(struct pdsc *pdsc, struct pdsc_qcq *qcq) pdsc_qcq_intr_free(pdsc, qcq); + /* Drain any work queued by ISR before it was freed above */ + if (qcq->work.func) + cancel_work_sync(&qcq->work); + + qcq->intx = PDS_CORE_INTR_INDEX_NOT_ASSIGNED; + if (qcq->q_base) dma_free_coherent(dev, qcq->q_size, qcq->q_base, qcq->q_base_pa); @@ -304,8 +309,11 @@ int pdsc_qcq_alloc(struct pdsc *pdsc, unsigned int type, unsigned int index, static void pdsc_core_uninit(struct pdsc *pdsc) { - pdsc_qcq_free(pdsc, &pdsc->notifyqcq); + /* Free adminqcq first: its work accesses notifyqcq, so we must + * disable its IRQ and drain its work before freeing notifyqcq. + */ pdsc_qcq_free(pdsc, &pdsc->adminqcq); + pdsc_qcq_free(pdsc, &pdsc->notifyqcq); if (pdsc->kern_dbpage) { iounmap(pdsc->kern_dbpage); @@ -479,8 +487,6 @@ void pdsc_teardown(struct pdsc *pdsc, bool removing) { if (!pdsc->pdev->is_virtfn) pdsc_devcmd_reset(pdsc); - if (pdsc->adminqcq.work.func) - cancel_work_sync(&pdsc->adminqcq.work); pci_clear_master(pdsc->pdev); diff --git a/drivers/net/ethernet/amd/pds_core/main.c b/drivers/net/ethernet/amd/pds_core/main.c index 22db78343eb0..638b9c7a509d 100644 --- a/drivers/net/ethernet/amd/pds_core/main.c +++ b/drivers/net/ethernet/amd/pds_core/main.c @@ -435,8 +435,6 @@ static void pdsc_remove(struct pci_dev *pdev) pdsc_auxbus_dev_del(pdsc, pdsc, &pdsc->padev); timer_shutdown_sync(&pdsc->wdtimer); - if (pdsc->wq) - destroy_workqueue(pdsc->wq); mutex_lock(&pdsc->config_lock); set_bit(PDSC_S_STOPPING_DRIVER, &pdsc->state); @@ -444,6 +442,9 @@ static void pdsc_remove(struct pci_dev *pdev) pdsc_stop(pdsc); pdsc_teardown(pdsc, PDSC_TEARDOWN_REMOVING); mutex_unlock(&pdsc->config_lock); + + if (pdsc->wq) + destroy_workqueue(pdsc->wq); mutex_destroy(&pdsc->config_lock); mutex_destroy(&pdsc->devcmd_lock); From a11f0b8a204296fe7db9eaec53441012222cb004 Mon Sep 17 00:00:00 2001 From: "Nikhil P. Rao" Date: Tue, 14 Jul 2026 20:14:56 +0000 Subject: [PATCH 117/234] pds_core: yield the CPU while waiting for the adminq to drain pdsc_adminq_wait_and_dec_once_unused() busy-waits for adminq_refcnt to drop to one: while (!refcount_dec_if_one(&pdsc->adminq_refcnt)) cpu_relax(); The refcount is held by pdsc_adminq_post() for the duration of an in-flight command, which can wait up to devcmd_timeout seconds (PDS_CORE_DEVCMD_TIMEOUT is 5) for the hardware to complete. cpu_relax() is not a reschedule point, so on a non-preemptible kernel this loop can spin on the CPU for several seconds, starving other tasks on that core. Add cond_resched() to the loop so the waiter yields to other runnable tasks while it polls, keeping cpu_relax() as the busy-wait hint between checks. Fixes: 7e82a8745b95 ("pds_core: Prevent race issues involving the adminq") Reported-by: sashiko-bot Closes: https://sashiko.dev/#/patchset/20260629200358.2626129-1-nikhil.rao%40amd.com?part=2 Signed-off-by: Nikhil P. Rao Reviewed-by: Eric Joyner Reviewed-by: Pavan Chebbi Link: https://patch.msgid.link/20260714201456.1776153-1-nikhil.rao@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/amd/pds_core/core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/amd/pds_core/core.c b/drivers/net/ethernet/amd/pds_core/core.c index e39b2c9beb20..04ec2569c61c 100644 --- a/drivers/net/ethernet/amd/pds_core/core.c +++ b/drivers/net/ethernet/amd/pds_core/core.c @@ -539,6 +539,7 @@ static void pdsc_adminq_wait_and_dec_once_unused(struct pdsc *pdsc) dev_dbg_ratelimited(pdsc->dev, "%s: adminq in use\n", __func__); cpu_relax(); + cond_resched(); } } From dd6b1cc748cd28147c113f9daa76393916ad9494 Mon Sep 17 00:00:00 2001 From: "Nikhil P. Rao" Date: Tue, 14 Jul 2026 20:41:45 +0000 Subject: [PATCH 118/234] pds_core: order completion reads after the ownership check pdsc_process_adminq() and pdsc_process_notifyq() decide a completion is valid from its ownership field - the color bit for the adminq, the event id for the notifyq - then read the rest of the descriptor, with no barrier in between. On a weakly ordered architecture the CPU may read the payload first. Add dma_rmb() between the ownership read and the payload reads. Fixes: 7e82a8745b95 ("pds_core: Prevent race issues involving the adminq") Reported-by: sashiko-bot Closes: https://sashiko.dev/#/patchset/20260629200358.2626129-1-nikhil.rao%40amd.com?part=2 Signed-off-by: Nikhil P. Rao Reviewed-by: Eric Joyner Link: https://patch.msgid.link/20260714204145.1782390-1-nikhil.rao@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/amd/pds_core/adminq.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/amd/pds_core/adminq.c b/drivers/net/ethernet/amd/pds_core/adminq.c index 097bb092bdb8..eadb4b604fbe 100644 --- a/drivers/net/ethernet/amd/pds_core/adminq.c +++ b/drivers/net/ethernet/amd/pds_core/adminq.c @@ -18,7 +18,13 @@ static int pdsc_process_notifyq(struct pdsc_qcq *qcq) comp = cq_info->comp; eid = le64_to_cpu(comp->event.eid); while (eid > pdsc->last_eid) { - u16 ecode = le16_to_cpu(comp->event.ecode); + u16 ecode; + + /* Order the payload read after the event id, the field the + * driver uses to detect a new completion. + */ + dma_rmb(); + ecode = le16_to_cpu(comp->event.ecode); switch (ecode) { case PDS_EVENT_LINK_CHANGE: @@ -101,6 +107,10 @@ void pdsc_process_adminq(struct pdsc_qcq *qcq) spin_lock_irqsave(&pdsc->adminq_lock, irqflags); comp = cq->info[cq->tail_idx].comp; while (pdsc_color_match(comp->color, cq->done_color)) { + /* Order the payload reads after the color bit, the field the + * driver uses to detect a new completion. + */ + dma_rmb(); q_info = &q->info[q->tail_idx]; q->tail_idx = (q->tail_idx + 1) & (q->num_descs - 1); From bfa33cd513c7ceb93c5a4c30e5662acd73c0a916 Mon Sep 17 00:00:00 2001 From: "Nikhil P. Rao" Date: Tue, 14 Jul 2026 21:07:45 +0000 Subject: [PATCH 119/234] pds_core: fix auxiliary device add/del races Two paths add or delete the same slot (pf->vfs[vf_id].padev): a VF's pdsc_reset_done() and the PF's devlink enable_vnet/disable_vnet handler. They serialize on config_lock, but neither guards the slot under it correctly. add() registers and stores a new auxiliary device without first checking the slot, so a second add of an already-populated slot leaks the first device. del() makes that check outside config_lock, so two concurrent dels can both pass it; the first clears the slot, and the second dereferences a NULL pointer. Check and update the slot under config_lock in both paths. Fixes: b699bdc720c0 ("pds_core: specify auxiliary_device to be created") Reported-by: sashiko-bot@kernel.org # Running on a local machine Signed-off-by: Nikhil P. Rao Reviewed-by: Brett Creeley Reviewed-by: Pavan Chebbi Link: https://patch.msgid.link/20260714210745.1785625-1-nikhil.rao@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/amd/pds_core/auxbus.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/amd/pds_core/auxbus.c b/drivers/net/ethernet/amd/pds_core/auxbus.c index 73b3481220b1..3acafe10a6d5 100644 --- a/drivers/net/ethernet/amd/pds_core/auxbus.c +++ b/drivers/net/ethernet/amd/pds_core/auxbus.c @@ -177,17 +177,21 @@ void pdsc_auxbus_dev_del(struct pdsc *cf, struct pdsc *pf, { struct pds_auxiliary_dev *padev; - if (!*pd_ptr) - return; - mutex_lock(&pf->config_lock); + /* A concurrent del may have already torn this device down and + * cleared it. + */ padev = *pd_ptr; + if (!padev) + goto out_unlock; + pds_client_unregister(pf, padev->client_id); auxiliary_device_delete(&padev->aux_dev); auxiliary_device_uninit(&padev->aux_dev); *pd_ptr = NULL; +out_unlock: mutex_unlock(&pf->config_lock); } @@ -210,6 +214,13 @@ int pdsc_auxbus_dev_add(struct pdsc *cf, struct pdsc *pf, mutex_lock(&pf->config_lock); + /* Nothing to do if the aux device is already present. This also + * guards against a second add overwriting *pd_ptr and leaking the + * first, symmetric with the check in pdsc_auxbus_dev_del(). + */ + if (*pd_ptr) + goto out_unlock; + mask = BIT_ULL(PDSC_S_FW_DEAD) | BIT_ULL(PDSC_S_STOPPING_DRIVER); if (cf->state & mask) { From 3a660ca49e2c3807bffe0519db3cff677a5906e0 Mon Sep 17 00:00:00 2001 From: "Nikhil P. Rao" Date: Tue, 14 Jul 2026 21:27:13 +0000 Subject: [PATCH 120/234] pds_core: check for workqueue allocation failure pdsc_init_pf() does not check whether create_singlethread_workqueue() succeeded. Fail probe on failure. The workqueue is set up before the timer and mutexes, so its failure path must unwind only the earlier setup. Fixes: c2dbb0904310 ("pds_core: health timer and workqueue") Reported-by: sashiko-bot Closes: https://sashiko.dev/#/patchset/20260629200358.2626129-1-nikhil.rao%40amd.com?part=2 Signed-off-by: Nikhil P. Rao Reviewed-by: Brett Creeley Reviewed-by: Pavan Chebbi Link: https://patch.msgid.link/20260714212713.1788438-1-nikhil.rao@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/amd/pds_core/main.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/amd/pds_core/main.c b/drivers/net/ethernet/amd/pds_core/main.c index 638b9c7a509d..8d94a4d70395 100644 --- a/drivers/net/ethernet/amd/pds_core/main.c +++ b/drivers/net/ethernet/amd/pds_core/main.c @@ -238,6 +238,10 @@ static int pdsc_init_pf(struct pdsc *pdsc) /* General workqueue and timer, but don't start timer yet */ snprintf(wq_name, sizeof(wq_name), "%s.%d", PDS_CORE_DRV_NAME, pdsc->uid); pdsc->wq = create_singlethread_workqueue(wq_name); + if (!pdsc->wq) { + err = -ENOMEM; + goto err_out_unmap_bars; + } INIT_WORK(&pdsc->health_work, pdsc_health_thread); INIT_WORK(&pdsc->pci_reset_work, pdsc_pci_reset_thread); timer_setup(&pdsc->wdtimer, pdsc_wdtimer_cb, 0); @@ -253,7 +257,7 @@ static int pdsc_init_pf(struct pdsc *pdsc) err = pdsc_setup(pdsc, PDSC_SETUP_INIT); if (err) { mutex_unlock(&pdsc->config_lock); - goto err_out_unmap_bars; + goto err_out_shutdown_timer; } err = pdsc_start(pdsc); @@ -305,13 +309,14 @@ static int pdsc_init_pf(struct pdsc *pdsc) pdsc_stop(pdsc); err_out_teardown: pdsc_teardown(pdsc, PDSC_TEARDOWN_REMOVING); -err_out_unmap_bars: +err_out_shutdown_timer: timer_shutdown_sync(&pdsc->wdtimer); if (pdsc->wq) destroy_workqueue(pdsc->wq); mutex_destroy(&pdsc->config_lock); mutex_destroy(&pdsc->devcmd_lock); pci_free_irq_vectors(pdsc->pdev); +err_out_unmap_bars: pdsc_unmap_bars(pdsc); err_out_release_regions: pci_release_regions(pdsc->pdev); From 47a5116e56a6b6fe1e909f244e39cd0fc26ceee4 Mon Sep 17 00:00:00 2001 From: Hidayath Khan Date: Thu, 9 Jul 2026 21:17:32 +0200 Subject: [PATCH 121/234] net/af_iucv: fix NULL deref in afiucv_hs_callback_syn() afiucv_hs_callback_syn() allocates the child socket with GFP_ATOMIC. If the allocation fails, nsk is NULL. The connection-refused path is entered when the listen state check fails, the accept backlog is full, or nsk is NULL. The code unconditionally calls iucv_sock_kill(nsk) in that path. iucv_sock_kill() does not accept a NULL socket pointer and immediately dereferences sk via sock_flag(sk, SOCK_ZAPPED). When nsk is NULL, calling iucv_sock_kill(nsk) results in a NULL pointer dereference. Only call iucv_sock_kill() when a child socket was successfully allocated. Fixes: 3881ac441f64 ("af_iucv: add HiperSockets transport") Cc: stable@vger.kernel.org Reviewed-by: Alexandra Winter Signed-off-by: Hidayath Khan Link: https://patch.msgid.link/20260709191732.124092-1-hidayath@linux.ibm.com Signed-off-by: Jakub Kicinski --- net/iucv/af_iucv.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/iucv/af_iucv.c b/net/iucv/af_iucv.c index e3e71d168c47..ea047bab65e7 100644 --- a/net/iucv/af_iucv.c +++ b/net/iucv/af_iucv.c @@ -1886,7 +1886,8 @@ static int afiucv_hs_callback_syn(struct sock *sk, struct sk_buff *skb) afiucv_swap_src_dest(skb); trans_hdr->flags = AF_IUCV_FLAG_SYN | AF_IUCV_FLAG_FIN; err = dev_queue_xmit(skb); - iucv_sock_kill(nsk); + if (nsk) + iucv_sock_kill(nsk); bh_unlock_sock(sk); goto out; } From 18ae07691d43183d270de8be9dc8e027906015d9 Mon Sep 17 00:00:00 2001 From: "Cen Zhang (Microsoft)" Date: Thu, 9 Jul 2026 21:07:18 -0400 Subject: [PATCH 122/234] sctp: validate stream count in sctp_process_strreset_inreq() When processing a RESET_IN_REQUEST from a peer, sctp_process_strreset_inreq() derives the stream count from the parameter length but does not check whether the resulting RESET_OUT_REQUEST would exceed SCTP_MAX_CHUNK_LEN. The OUT request header (sctp_strreset_outreq, 16 bytes) is 8 bytes larger than the IN request header (sctp_strreset_inreq, 8 bytes). Generally, the IP payload is bounded to 65535 bytes, so the stream list cannot be large enough to trigger the overflow. However, on interfaces with MTU > 65535 (e.g., loopback with IPv6 jumbograms), a stream list that fits within the incoming IN parameter can cause a __u16 overflow in sctp_make_strreset_req() when computing the OUT request size, leading to an undersized skb allocation and a kernel BUG: net/core/skbuff.c:207 skb_panic net/core/skbuff.c:2625 skb_put net/sctp/sm_make_chunk.c:1535 sctp_addto_chunk net/sctp/sm_make_chunk.c:3695 sctp_make_strreset_req net/sctp/stream.c:655 sctp_process_strreset_inreq The local setsockopt path validates the generated reset request size. However, for an incoming-only reset, it accounts for the smaller IN request even though the peer must generate an OUT request with the same stream list. Such a request cannot be completed successfully by the peer. Reject peer IN requests whose corresponding OUT request would exceed SCTP_MAX_CHUNK_LEN. Also tighten the local check so it does not send an IN request that would require an oversized OUT request from the peer. Fixes: 7f9d68ac944e ("sctp: implement sender-side procedures for SSN Reset Request Parameter") Reported-by: AutonomousCodeSecurity@microsoft.com Closes: https://lore.kernel.org/all/20260707203215.2752-1-blbllhy@gmail.com/ Suggested-by: Xin Long Signed-off-by: Cen Zhang (Microsoft) Acked-by: Xin Long Link: https://patch.msgid.link/20260710010718.20318-1-blbllhy@gmail.com Signed-off-by: Jakub Kicinski --- net/sctp/stream.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/sctp/stream.c b/net/sctp/stream.c index 5c2fdedea088..34ffe6c945a4 100644 --- a/net/sctp/stream.c +++ b/net/sctp/stream.c @@ -308,7 +308,8 @@ int sctp_send_reset_streams(struct sctp_association *asoc, goto out; param_len += str_nums * sizeof(__u16) + - sizeof(struct sctp_strreset_inreq); + (out ? sizeof(struct sctp_strreset_inreq) + : sizeof(struct sctp_strreset_outreq)); } if (param_len > SCTP_MAX_CHUNK_LEN - @@ -639,6 +640,9 @@ struct sctp_chunk *sctp_process_strreset_inreq( nums = (ntohs(param.p->length) - sizeof(*inreq)) / sizeof(__u16); str_p = inreq->list_of_streams; + if (nums * sizeof(__u16) + sizeof(struct sctp_strreset_outreq) > + SCTP_MAX_CHUNK_LEN - sizeof(struct sctp_reconf_chunk)) + goto out; for (i = 0; i < nums; i++) { if (ntohs(str_p[i]) >= stream->outcnt) { result = SCTP_STRRESET_ERR_WRONG_SSN; From f8d5e7846025f4ab15a461235f8ebae9094a361a Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Wed, 15 Jul 2026 09:50:10 +0800 Subject: [PATCH 123/234] sctp: avoid auth_enable sysctl UAF during netns teardown proc_sctp_do_auth() updates the SCTP control socket after changing net.sctp.auth_enable. The handler gets the per-net SCTP state from ctl->data, so an already opened sysctl file can still target a network namespace while that namespace is being torn down. SCTP previously registered its per-net sysctls from sctp_defaults_init(), while the control socket is created later from sctp_ctrlsock_init(). This exposed a window during initialization where auth_enable was writable before net->sctp.ctl_sock existed, and a teardown window where auth_enable stayed writable after inet_ctl_sock_destroy() had released the control socket. Move the per-net SCTP sysctl registration into sctp_ctrlsock_init() after sctp_ctl_sock_init() succeeds, and unregister the sysctl table before destroying the control socket in sctp_ctrlsock_exit(). If sysctl registration fails after the control socket was created, destroy the control socket in the same init path. Make sctp_sysctl_net_unregister() tolerate a missing header and clear the saved pointer so init-error and exit paths can safely share the unregister helper. Fixes: 15649fd5415e ("sctp: sysctl: auth_enable: avoid using current->nsproxy") Cc: stable@vger.kernel.org Reported-by: Yuan Tan Reported-by: Yifan Wu Reported-by: Juefei Pu Reported-by: Xin Liu Co-developed-by: Qi Tang Signed-off-by: Qi Tang Signed-off-by: Zhiling Zou Signed-off-by: Ren Wei Acked-by: Xin Long Link: https://patch.msgid.link/390cd5e91ed60eea27b0b64d0468301a9e73b808.1784033357.git.roxy520tt@gmail.com Signed-off-by: Jakub Kicinski --- net/sctp/protocol.c | 20 ++++++++++++-------- net/sctp/sysctl.c | 9 +++++++-- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/net/sctp/protocol.c b/net/sctp/protocol.c index cf335494bffe..49d9740b1e0f 100644 --- a/net/sctp/protocol.c +++ b/net/sctp/protocol.c @@ -1383,10 +1383,6 @@ static int __net_init sctp_defaults_init(struct net *net) net->sctp.l3mdev_accept = 1; #endif - status = sctp_sysctl_net_register(net); - if (status) - goto err_sysctl_register; - /* Allocate and initialise sctp mibs. */ status = init_sctp_mibs(net); if (status) @@ -1420,8 +1416,6 @@ static int __net_init sctp_defaults_init(struct net *net) cleanup_sctp_mibs(net); #endif err_init_mibs: - sctp_sysctl_net_unregister(net); -err_sysctl_register: return status; } @@ -1436,7 +1430,6 @@ static void __net_exit sctp_defaults_exit(struct net *net) net->sctp.proc_net_sctp = NULL; #endif cleanup_sctp_mibs(net); - sctp_sysctl_net_unregister(net); } static struct pernet_operations sctp_defaults_ops = { @@ -1450,16 +1443,27 @@ static int __net_init sctp_ctrlsock_init(struct net *net) /* Initialize the control inode/socket for handling OOTB packets. */ status = sctp_ctl_sock_init(net); - if (status) + if (status) { pr_err("Failed to initialize the SCTP control sock\n"); + return status; + } + + status = sctp_sysctl_net_register(net); + if (status) { + inet_ctl_sock_destroy(net->sctp.ctl_sock); + net->sctp.ctl_sock = NULL; + } return status; } static void __net_exit sctp_ctrlsock_exit(struct net *net) { + sctp_sysctl_net_unregister(net); + /* Free the control endpoint. */ inet_ctl_sock_destroy(net->sctp.ctl_sock); + net->sctp.ctl_sock = NULL; } static struct pernet_operations sctp_ctrlsock_ops = { diff --git a/net/sctp/sysctl.c b/net/sctp/sysctl.c index 15e7db9a3ab2..fca840484ebf 100644 --- a/net/sctp/sysctl.c +++ b/net/sctp/sysctl.c @@ -615,11 +615,16 @@ int sctp_sysctl_net_register(struct net *net) void sctp_sysctl_net_unregister(struct net *net) { + struct ctl_table_header *header = net->sctp.sysctl_header; const struct ctl_table *table; - table = net->sctp.sysctl_header->ctl_table_arg; - unregister_net_sysctl_table(net->sctp.sysctl_header); + if (!header) + return; + + table = header->ctl_table_arg; + unregister_net_sysctl_table(header); kfree(table); + net->sctp.sysctl_header = NULL; } static struct ctl_table_header *sctp_sysctl_header; From ffb2bd7ade36ec4da32c46a6eddbf4515316d08c Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Wed, 15 Jul 2026 09:50:11 +0800 Subject: [PATCH 124/234] sctp: close UDP tunnel sockets during netns teardown proc_sctp_do_udp_port() starts per-net SCTP UDP tunneling sockets when net.sctp.udp_port is set, and stops/restarts them when the sysctl value changes. The netns exit path does not stop these sockets, so a namespace can be torn down while its SCTP UDP tunnel sockets are still installed. Close the UDP tunnel sockets from sctp_ctrlsock_exit() after unregistering the per-net sysctl table. This prevents new sysctl writes from racing in while the sockets are being released, and closes the sockets before the control socket is destroyed. Fixes: 046c052b475e ("sctp: enable udp tunneling socks") Cc: stable@vger.kernel.org Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/b9f1f02b0780ad6a719e2413f5f0bb8eb7702d94.1782585631.git.roxy520tt%40gmail.com Signed-off-by: Zhiling Zou Signed-off-by: Ren Wei Acked-by: Xin Long Link: https://patch.msgid.link/6dab75f22855cb219e2e30a5497cab03b970ab91.1784033357.git.roxy520tt@gmail.com Signed-off-by: Jakub Kicinski --- net/sctp/protocol.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/sctp/protocol.c b/net/sctp/protocol.c index 49d9740b1e0f..27c26e12f95d 100644 --- a/net/sctp/protocol.c +++ b/net/sctp/protocol.c @@ -1460,6 +1460,7 @@ static int __net_init sctp_ctrlsock_init(struct net *net) static void __net_exit sctp_ctrlsock_exit(struct net *net) { sctp_sysctl_net_unregister(net); + sctp_udp_sock_stop(net); /* Free the control endpoint. */ inet_ctl_sock_destroy(net->sctp.ctl_sock); From 03d1057305ef17ac3f5936ac1580bc9a1a826e14 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Wed, 15 Jul 2026 16:25:17 +0900 Subject: [PATCH 125/234] net: mctp i3c: clean up notifier and buses if driver register fails mctp_i3c_mod_init() registers the I3C bus notifier and then walks the existing buses with i3c_for_each_bus_locked(mctp_i3c_bus_add_new, NULL) before registering the I3C device driver. If i3c_driver_register() fails, the function returns the error directly, leaving the notifier registered and every mctp_i3c_bus object created for the existing buses allocated. The notifier is left pointing into the module that failed to load and the bus list is leaked. Mirror the module exit path on this failure: unregister the notifier and tear down the buses that were added before returning the error. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: c8755b29b58e ("mctp i3c: MCTP I3C driver") Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Acked-by: Jeremy Kerr Link: https://patch.msgid.link/20260715072517.13216-1-mhun512@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/mctp/mctp-i3c.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/net/mctp/mctp-i3c.c b/drivers/net/mctp/mctp-i3c.c index 6d2bbae7477b..88d9e36cd4a2 100644 --- a/drivers/net/mctp/mctp-i3c.c +++ b/drivers/net/mctp/mctp-i3c.c @@ -731,18 +731,21 @@ static __init int mctp_i3c_mod_init(void) int rc; rc = i3c_register_notifier(&mctp_i3c_notifier); - if (rc < 0) { - i3c_driver_unregister(&mctp_i3c_driver); + if (rc < 0) return rc; - } i3c_for_each_bus_locked(mctp_i3c_bus_add_new, NULL); rc = i3c_driver_register(&mctp_i3c_driver); if (rc < 0) - return rc; + goto err_unregister_notifier; return 0; + +err_unregister_notifier: + i3c_unregister_notifier(&mctp_i3c_notifier); + mctp_i3c_bus_remove_all(); + return rc; } static __exit void mctp_i3c_mod_exit(void) From b65352a1bac64442ad95e64f385b40ccb9f1b0db Mon Sep 17 00:00:00 2001 From: Eddie Phillips Date: Thu, 9 Jul 2026 21:19:06 +0000 Subject: [PATCH 126/234] gve: fix Rx queue stall on alloc failure When the system is under extreme memory pressure, page allocations can fail during the Rx buffer refill loop. If the number of buffers posted to hardware falls below a critical low threshold and the refill loop exits due to allocation failures, the queue can stall: 1. The device drops incoming packets because there are no descriptors. 2. Since no packets are processed, no Rx completions are generated. 3. Because no completions occur, NAPI is never scheduled, preventing the refill loop from running again even after memory is freed. This results in a permanent queue stall. Resolve this by introducing a starvation recovery timer for each Rx queue. If the number of buffers posted to hardware falls below a critical low threshold, start a timer to periodically reschedule NAPI. Once NAPI runs and successfully refills the queue above the threshold, the timer is not rescheduled. The threshold is set to 32 because a single maximum-sized Receive Segment Coalescing (RSC) packet can consume up to 19 descriptors in the Rx path. Lower thresholds (such as 8 or 16) would be insufficient to process a complete maximum-sized RSC packet, risking packet drops or unexpected hardware behavior under memory pressure. Setting the threshold to 32 guarantees a safe margin to handle at least one full RSC packet. Cc: stable@vger.kernel.org Fixes: 9b8dd5e5ea48 ("gve: DQO: Add RX path") Reviewed-by: Jordan Rhee Signed-off-by: Eddie Phillips Signed-off-by: Harshitha Ramamurthy Reviewed-by: Przemek Kitszel Link: https://patch.msgid.link/20260709211906.3322883-1-hramamurthy@google.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/google/gve/gve.h | 3 ++ drivers/net/ethernet/google/gve/gve_rx_dqo.c | 34 ++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/drivers/net/ethernet/google/gve/gve.h b/drivers/net/ethernet/google/gve/gve.h index 1d66d3834f7e..c280ff35ee77 100644 --- a/drivers/net/ethernet/google/gve/gve.h +++ b/drivers/net/ethernet/google/gve/gve.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -41,6 +42,7 @@ /* Interval to schedule a stats report update, 20000ms. */ #define GVE_STATS_REPORT_TIMER_PERIOD 20000 +#define GVE_RX_NAPI_RESCHED_MS 20 /* msecs */ /* Numbers of NIC tx/rx stats in stats report. */ #define NIC_TX_STATS_REPORT_NUM 0 @@ -341,6 +343,7 @@ struct gve_rx_ring { struct xdp_rxq_info xdp_rxq; struct xsk_buff_pool *xsk_pool; struct page_frag_cache page_cache; /* Page cache to allocate XDP frames */ + struct timer_list starvation_timer; /* for queue starvation recovery */ }; /* A TX desc ring entry */ diff --git a/drivers/net/ethernet/google/gve/gve_rx_dqo.c b/drivers/net/ethernet/google/gve/gve_rx_dqo.c index 02cba280d81a..8271f731a91f 100644 --- a/drivers/net/ethernet/google/gve/gve_rx_dqo.c +++ b/drivers/net/ethernet/google/gve/gve_rx_dqo.c @@ -18,6 +18,16 @@ #include #include +static void gve_rx_starvation_timer(struct timer_list *t) +{ + struct gve_rx_ring *rx = timer_container_of(rx, t, starvation_timer); + struct gve_priv *priv = rx->gve; + struct gve_notify_block *block; + + block = &priv->ntfy_blocks[rx->ntfy_id]; + napi_schedule(&block->napi); +} + static void gve_rx_free_hdr_bufs(struct gve_priv *priv, struct gve_rx_ring *rx) { struct device *hdev = &priv->pdev->dev; @@ -120,6 +130,7 @@ void gve_rx_stop_ring_dqo(struct gve_priv *priv, int idx) if (rx->dqo.page_pool) page_pool_disable_direct_recycling(rx->dqo.page_pool); + timer_shutdown_sync(&rx->starvation_timer); gve_remove_napi(priv, ntfy_idx); gve_rx_remove_from_block(priv, idx); gve_rx_reset_ring_dqo(priv, idx); @@ -208,8 +219,10 @@ static int gve_rx_alloc_hdr_bufs(struct gve_priv *priv, struct gve_rx_ring *rx, void gve_rx_start_ring_dqo(struct gve_priv *priv, int idx) { int ntfy_idx = gve_rx_idx_to_ntfy(priv, idx); + struct gve_rx_ring *rx = &priv->rx[idx]; gve_rx_add_to_block(priv, idx); + timer_setup(&rx->starvation_timer, gve_rx_starvation_timer, 0); gve_add_napi(priv, ntfy_idx, gve_napi_poll_dqo); } @@ -365,6 +378,7 @@ void gve_rx_post_buffers_dqo(struct gve_rx_ring *rx) struct gve_rx_compl_queue_dqo *complq = &rx->dqo.complq; struct gve_rx_buf_queue_dqo *bufq = &rx->dqo.bufq; struct gve_priv *priv = rx->gve; + u32 num_bufs_avail_to_hw; u32 num_avail_slots; u32 num_full_slots; u32 num_posted = 0; @@ -400,6 +414,26 @@ void gve_rx_post_buffers_dqo(struct gve_rx_ring *rx) } rx->fill_cnt += num_posted; + + /* If the queue has fewer than GVE_RX_BUF_THRESH_DQO descriptors + * visible to the hardware, the hardware is in danger of starving + * and cannot trigger interrupts. + * + * We use a threshold of 32 because a single maximum-sized RSC + * packet can consume up to 19 descriptors in the Rx path. Lower + * thresholds (e.g., 8 or 16) would be unsafe as they could cause + * the device to drop/stall on a maximum-sized RSC packet. + * + * Start the timer to periodically reschedule NAPI and recover. + */ + num_bufs_avail_to_hw = + ((bufq->tail & ~(GVE_RX_BUF_THRESH_DQO - 1)) - + bufq->head) & bufq->mask; + + if (num_bufs_avail_to_hw < GVE_RX_BUF_THRESH_DQO) { + mod_timer(&rx->starvation_timer, + jiffies + msecs_to_jiffies(GVE_RX_NAPI_RESCHED_MS)); + } } static void gve_rx_skb_csum(struct sk_buff *skb, From eaa39f9f8ac8c1d032cd26b9cd572804e9d7683f Mon Sep 17 00:00:00 2001 From: Rishikesh Jethwani Date: Thu, 9 Jul 2026 16:44:36 -0600 Subject: [PATCH 127/234] tls: device: push pending open record on splice EOF On kTLS device-offload sockets, sendfile() with count > EOF can reach ->splice_eof() with a fully assembled but still-open TLS record left pending. tls_device_splice_eof() only flushes partially sent records, so an abrupt close() can drop the final record and the peer receives a short file. Fix tls_device_splice_eof() to also push pending open records. This matches the software path, where splice EOF already flushes pending open records. Fixes: d4c1e80b0d1b ("tls/device: Use splice_eof() to flush") Link: https://lore.kernel.org/netdev/CAMPsyauZ+jzG9AysO0FWv6ZY0kvCUpjX_U7o=oOjCuOQ87BCgg@mail.gmail.com/ Reported-by: Nils Juenemann Signed-off-by: Rishikesh Jethwani Tested-by: Nils Juenemann Link: https://patch.msgid.link/20260709224436.1608993-2-rjethwani@purestorage.com Signed-off-by: Jakub Kicinski --- net/tls/tls_device.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/net/tls/tls_device.c b/net/tls/tls_device.c index 741aef09bfd3..37bb06a8e8f5 100644 --- a/net/tls/tls_device.c +++ b/net/tls/tls_device.c @@ -595,13 +595,15 @@ void tls_device_splice_eof(struct socket *sock) struct tls_context *tls_ctx = tls_get_ctx(sk); struct iov_iter iter = {}; - if (!tls_is_partially_sent_record(tls_ctx)) + if (!tls_is_partially_sent_record(tls_ctx) && + !tls_is_pending_open_record(tls_ctx)) return; mutex_lock(&tls_ctx->tx_lock); lock_sock(sk); - if (tls_is_partially_sent_record(tls_ctx)) { + if (tls_is_partially_sent_record(tls_ctx) || + tls_is_pending_open_record(tls_ctx)) { iov_iter_bvec(&iter, ITER_SOURCE, NULL, 0, 0); tls_push_data(sk, &iter, 0, 0, TLS_RECORD_TYPE_DATA); } From f8b1abed736111f914b2c567d9a3db1f71e788e8 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Fri, 10 Jul 2026 20:04:41 +0200 Subject: [PATCH 128/234] selftests: af_unix: add USER_NS config This is required to use unshare(CLONE_NEWUSER). This has not been seen on NIPA before, because the 'af_unix' tests are executed with the 'net' ones, merging their config files. USER_NS is present in tools/testing/selftests/net/config. This issue is visible when only the af_unix config is used on top of the default one. This is the recommended way to execute selftest targets. Fixes: ac011361bd4f ("af_unix: Add test for sock_diag and UDIAG_SHOW_UID.") Signed-off-by: Matthieu Baerts (NGI0) Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260710-net-sft-fix-containers-v1-1-a2915c294ef5@kernel.org Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/af_unix/config | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/testing/selftests/net/af_unix/config b/tools/testing/selftests/net/af_unix/config index b5429c15a53c..41dbb03c747e 100644 --- a/tools/testing/selftests/net/af_unix/config +++ b/tools/testing/selftests/net/af_unix/config @@ -1,3 +1,4 @@ CONFIG_AF_UNIX_OOB=y CONFIG_UNIX=y CONFIG_UNIX_DIAG=m +CONFIG_USER_NS=y From 441a820ccef9af80a9ac5a4c85b9c396e595967c Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Fri, 10 Jul 2026 20:04:42 +0200 Subject: [PATCH 129/234] selftests: openvswitch: add config file The kselftests doc mentions that a config file should be present "if a test needs specific kernel config options enabled". This selftest requires some kernel config, but no config file was provided. We could say that a sub-target could use the parent's config file, but the kselftests doc doesn't mention anything about that. Plus the net/openvswitch target is the only net target without a config file. Here is a new config file, which is a trimmed version of the net one, with hopefully the minimal required kconfig on top of 'make defconfig'. The Fixes tag points to the introduction of the net/openvswitch target, just to help validating this target on stable kernels. Fixes: 25f16c873fb1 ("selftests: add openvswitch selftest suite") Signed-off-by: Matthieu Baerts (NGI0) Reviewed-by: Eelco Chaudron Link: https://patch.msgid.link/20260710-net-sft-fix-containers-v1-2-a2915c294ef5@kernel.org Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/openvswitch/config | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 tools/testing/selftests/net/openvswitch/config diff --git a/tools/testing/selftests/net/openvswitch/config b/tools/testing/selftests/net/openvswitch/config new file mode 100644 index 000000000000..c659749cd086 --- /dev/null +++ b/tools/testing/selftests/net/openvswitch/config @@ -0,0 +1,16 @@ +CONFIG_GENEVE=m +CONFIG_INET_DIAG=y +CONFIG_IPV6=y +CONFIG_NETFILTER=y +CONFIG_NET_IPGRE=m +CONFIG_NET_IPGRE_DEMUX=m +CONFIG_NF_CONNTRACK=m +CONFIG_NF_CONNTRACK_OVS=y +CONFIG_OPENVSWITCH=m +CONFIG_OPENVSWITCH_GENEVE=m +CONFIG_OPENVSWITCH_GRE=m +CONFIG_OPENVSWITCH_VXLAN=m +CONFIG_PSAMPLE=m +CONFIG_VETH=y +CONFIG_VLAN_8021Q=y +CONFIG_VXLAN=m From 90c792681a3732caaf7bf5bc435877736baf591a Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Fri, 10 Jul 2026 20:04:43 +0200 Subject: [PATCH 130/234] selftests: ovpn: add IPV6 and VETH configs They are required to run the selftests: - Tests are executed in v4 and v6. - Virtual Ethernet are used between the different netns. This has not been seen on NIPA before, because the 'ovpn' tests are executed with the 'tcp_ao' ones, merging their config files. These two kernel config are present in tools/testing/selftests/net/tcp_ao/config. This issue is visible when only the ovpn config is used on top of the default one. This is the recommended way to execute selftest targets. Fixes: 959bc330a439 ("testing/selftests: add test tool and scripts for ovpn module") Signed-off-by: Matthieu Baerts (NGI0) Acked-by: Antonio Quartulli Link: https://patch.msgid.link/20260710-net-sft-fix-containers-v1-3-a2915c294ef5@kernel.org Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/ovpn/config | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/testing/selftests/net/ovpn/config b/tools/testing/selftests/net/ovpn/config index d6cf033d555e..6b424762e46e 100644 --- a/tools/testing/selftests/net/ovpn/config +++ b/tools/testing/selftests/net/ovpn/config @@ -4,6 +4,7 @@ CONFIG_CRYPTO_CHACHA20POLY1305=y CONFIG_CRYPTO_GCM=y CONFIG_DST_CACHE=y CONFIG_INET=y +CONFIG_IPV6=y CONFIG_NET=y CONFIG_NETFILTER=y CONFIG_NET_UDP_TUNNEL=y @@ -11,3 +12,4 @@ CONFIG_NF_TABLES=m CONFIG_NF_TABLES_INET=y CONFIG_OVPN=m CONFIG_STREAM_PARSER=y +CONFIG_VETH=y From 61ac7049aaa86ae044e8a5b94d852218163d5bf8 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Fri, 10 Jul 2026 20:04:44 +0200 Subject: [PATCH 131/234] selftests: ovpn: increase timeout The default timeout is 45 seconds, that's too low for a few ovpn tests. Indeed, these tests can take up to 50 seconds with some debug kernel config on NIPA. Set a timeout to 90 seconds, just to be on the safe side. Note that the Fixes tag here points to the introduction of the ovpn tests because I don't know when they started to take more than 45 seconds. That's OK because a timeout of 1.5 minutes is not exaggerated. Fixes: 959bc330a439 ("testing/selftests: add test tool and scripts for ovpn module") Signed-off-by: Matthieu Baerts (NGI0) Acked-by: Antonio Quartulli Link: https://patch.msgid.link/20260710-net-sft-fix-containers-v1-4-a2915c294ef5@kernel.org Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/ovpn/settings | 1 + 1 file changed, 1 insertion(+) create mode 100644 tools/testing/selftests/net/ovpn/settings diff --git a/tools/testing/selftests/net/ovpn/settings b/tools/testing/selftests/net/ovpn/settings new file mode 100644 index 000000000000..ba4d85f74cd6 --- /dev/null +++ b/tools/testing/selftests/net/ovpn/settings @@ -0,0 +1 @@ +timeout=90 From 3529d75d67411497341cd804a045185d6035dff2 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Fri, 10 Jul 2026 20:04:45 +0200 Subject: [PATCH 132/234] selftests: drv-net: increase timeout The default timeout is 45 seconds, that's too low for the xdp.py test. Indeed, this test can take up to 3 minutes with some debug kernel config on NIPA. Set a timeout to 6 minutes, just to be on the safe side. Note that the Fixes tag here points to the introduction of the xdp.py test because I don't know when this test started to take more than 45 seconds. That's OK because a timeout of 6 minutes is not exaggerated. Fixes: 1cbcb1b28b26 ("selftests: drv-net: Test XDP_PASS/DROP support") Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260710-net-sft-fix-containers-v1-5-a2915c294ef5@kernel.org Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/settings | 1 + 1 file changed, 1 insertion(+) create mode 100644 tools/testing/selftests/drivers/net/settings diff --git a/tools/testing/selftests/drivers/net/settings b/tools/testing/selftests/drivers/net/settings new file mode 100644 index 000000000000..eef533824a3c --- /dev/null +++ b/tools/testing/selftests/drivers/net/settings @@ -0,0 +1 @@ +timeout=360 From c25dd7439f84cf607e13d6de8cc1c79cd51f56ff Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Fri, 10 Jul 2026 20:04:46 +0200 Subject: [PATCH 133/234] selftests: drv-net: add missing kconfig for psp.py This psp.py selftest was failing on my side when only using the drivers/net config file on top of the default one -- the recommended way to execute selftest targets. It looks like some kernel config are needed to execute the new tc commands. Note that this was not visible on NIPA, because these tests are executed with the drivers/net/hw ones, combining the two config files, and the hw one contains the missing ones. Fixes: 3f74d5bb807e ("selftests/net: Add env for container based tests") Signed-off-by: Matthieu Baerts (NGI0) Reviewed-by: Wei Wang Link: https://patch.msgid.link/20260710-net-sft-fix-containers-v1-6-a2915c294ef5@kernel.org Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/config | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/testing/selftests/drivers/net/config b/tools/testing/selftests/drivers/net/config index 91d4fd410914..2070e890e064 100644 --- a/tools/testing/selftests/drivers/net/config +++ b/tools/testing/selftests/drivers/net/config @@ -4,6 +4,8 @@ CONFIG_DEBUG_INFO_BTF_MODULES=n CONFIG_INET_PSP=y CONFIG_IPV6=y CONFIG_MACSEC=m +CONFIG_NET_CLS_ACT=y +CONFIG_NET_CLS_BPF=y CONFIG_NETCONSOLE=m CONFIG_NETCONSOLE_DYNAMIC=y CONFIG_NETCONSOLE_EXTENDED_LOG=y @@ -11,6 +13,7 @@ CONFIG_NETDEVSIM=m CONFIG_NETKIT=y CONFIG_NET_SCH_ETF=m CONFIG_NET_SCH_FQ=m +CONFIG_NET_SCH_INGRESS=y CONFIG_PPP=y CONFIG_PPPOE=y CONFIG_VLAN_8021Q=m From e6ad44a5b1d55f5396b69d9575b2711dfeecba12 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Fri, 10 Jul 2026 20:04:47 +0200 Subject: [PATCH 134/234] selftests: netconsole: ignore busywait errors In these netconsole tests, bash is used with errexit (set -e). It means that if the busywait timeout, the tests finish without printing an error message. It is fine to ignore these errors, because the following validate_xxx helpers will check the content of the output file, and exit with an appropriated error message, e.g. FAIL: File was not generated. Signed-off-by: Matthieu Baerts (NGI0) Reviewed-by: Breno Leitao Link: https://patch.msgid.link/20260710-net-sft-fix-containers-v1-7-a2915c294ef5@kernel.org Signed-off-by: Jakub Kicinski --- .../selftests/drivers/net/netconsole/netcons_cmdline.sh | 2 +- .../drivers/net/netconsole/netcons_fragmented_msg.sh | 4 ++-- .../selftests/drivers/net/netconsole/netcons_resume.sh | 2 +- .../selftests/drivers/net/netconsole/netcons_sysdata.sh | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/testing/selftests/drivers/net/netconsole/netcons_cmdline.sh b/tools/testing/selftests/drivers/net/netconsole/netcons_cmdline.sh index 96d704b8d9d9..4436567abc94 100755 --- a/tools/testing/selftests/drivers/net/netconsole/netcons_cmdline.sh +++ b/tools/testing/selftests/drivers/net/netconsole/netcons_cmdline.sh @@ -50,7 +50,7 @@ do # Send the message echo "${MSG}: ${TARGET}" > /dev/kmsg # Wait until socat saves the file to disk - busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" + busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" || true # Make sure the message was received in the dst part # and exit validate_msg "${OUTPUT_FILE}" diff --git a/tools/testing/selftests/drivers/net/netconsole/netcons_fragmented_msg.sh b/tools/testing/selftests/drivers/net/netconsole/netcons_fragmented_msg.sh index 0dc7280c3080..fc3db40c1df5 100755 --- a/tools/testing/selftests/drivers/net/netconsole/netcons_fragmented_msg.sh +++ b/tools/testing/selftests/drivers/net/netconsole/netcons_fragmented_msg.sh @@ -104,7 +104,7 @@ wait_local_port_listen "${NAMESPACE}" "${PORT}" udp # Send the message echo "${MSG}: ${TARGET}" > /dev/kmsg # Wait until socat saves the file to disk -busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" +busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" || true # Check if the message was not corrupted validate_fragmented_result "${OUTPUT_FILE}" @@ -117,6 +117,6 @@ disable_release_append listen_port_and_save_to "${OUTPUT_FILE}" & wait_local_port_listen "${NAMESPACE}" "${PORT}" udp echo "${MSG}: ${TARGET}" > /dev/kmsg -busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" +busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" || true validate_fragmented_result "${OUTPUT_FILE}" exit "${ksft_pass}" diff --git a/tools/testing/selftests/drivers/net/netconsole/netcons_resume.sh b/tools/testing/selftests/drivers/net/netconsole/netcons_resume.sh index d9111f2102bc..b379dff9087e 100755 --- a/tools/testing/selftests/drivers/net/netconsole/netcons_resume.sh +++ b/tools/testing/selftests/drivers/net/netconsole/netcons_resume.sh @@ -108,7 +108,7 @@ do # Send the message echo "${MSG}: ${TARGET}" > /dev/kmsg # Wait until socat saves the file to disk - busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" + busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" || true # Make sure the message was received in the dst part # and exit validate_msg "${OUTPUT_FILE}" diff --git a/tools/testing/selftests/drivers/net/netconsole/netcons_sysdata.sh b/tools/testing/selftests/drivers/net/netconsole/netcons_sysdata.sh index 3fb8c4afe3d2..7089f7bd1e34 100755 --- a/tools/testing/selftests/drivers/net/netconsole/netcons_sysdata.sh +++ b/tools/testing/selftests/drivers/net/netconsole/netcons_sysdata.sh @@ -197,7 +197,7 @@ function runtest { # Send the message taskset -c "${CPU}" echo "${MSG}: ${TARGET}" > /dev/kmsg # Wait until socat saves the file to disk - busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" + busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" || true } # ========== # From cd170f051dba9ac146fabcd1b91726487c0cb9fa Mon Sep 17 00:00:00 2001 From: "Xiang Mei (Microsoft)" Date: Fri, 10 Jul 2026 23:07:24 +0000 Subject: [PATCH 135/234] gtp: check skb_pull_data() return in gtp1u_send_echo_resp() gtp1u_send_echo_resp() ignores skb_pull_data()'s return value. Its caller gtp1u_udp_encap_recv() only guarantees 16 bytes (udphdr + gtp1_header), but the pull requests 20 (gtp1_header_long + udphdr). For a 16-19 byte echo request the pull fails and returns NULL without advancing skb->data; execution continues, and the following skb_push() plus the IP header pushed by iptunnel_xmit() move skb->data below skb->head, tripping skb_under_panic(). Fix it by dropping the packet when skb_pull_data() fails. skbuff: skb_under_panic: ... kernel BUG at net/core/skbuff.c:214! Call Trace: skb_push (net/core/skbuff.c:2648) iptunnel_xmit (net/ipv4/ip_tunnel_core.c:82) gtp_encap_recv (drivers/net/gtp.c:701 drivers/net/gtp.c:808 drivers/net/gtp.c:920) udp_queue_rcv_one_skb (net/ipv4/udp.c:2388) ... Kernel panic - not syncing: Fatal exception in interrupt Fixes: 9af41cc33471 ("gtp: Implement GTP echo response") Reported-by: AutonomousCodeSecurity@microsoft.com Signed-off-by: Xiang Mei (Microsoft) Link: https://patch.msgid.link/20260710230724.942574-1-xmei5@asu.edu Signed-off-by: Jakub Kicinski --- drivers/net/gtp.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/gtp.c b/drivers/net/gtp.c index c0e38878af51..9a12cc53da00 100644 --- a/drivers/net/gtp.c +++ b/drivers/net/gtp.c @@ -669,8 +669,9 @@ static int gtp1u_send_echo_resp(struct gtp_dev *gtp, struct sk_buff *skb) return -1; /* pull GTP and UDP headers */ - skb_pull_data(skb, - sizeof(struct gtp1_header_long) + sizeof(struct udphdr)); + if (!skb_pull_data(skb, sizeof(struct gtp1_header_long) + + sizeof(struct udphdr))) + return -1; gtp_pkt = skb_push(skb, sizeof(struct gtp1u_packet)); memset(gtp_pkt, 0, sizeof(struct gtp1u_packet)); From 6347c5314cee49f364aaf2e40ff15415a57a116e Mon Sep 17 00:00:00 2001 From: "Xiang Mei (Microsoft)" Date: Mon, 13 Jul 2026 22:15:51 +0000 Subject: [PATCH 136/234] nexthop: initialize extack in nh_res_bucket_migrate() nh_res_bucket_migrate() passes an uninitialized netlink_ext_ack to call_nexthop_res_bucket_notifiers(). When nh_notifier_res_bucket_info_init() fails (e.g. the kzalloc returns -ENOMEM), the error is propagated back before any notifier sets extack._msg, and the error path formats the stale pointer with pr_err_ratelimited("%s\n", extack._msg). With CONFIG_INIT_STACK_NONE this dereferences uninitialized stack memory: Oops: general protection fault, probably for non-canonical address ... KASAN: maybe wild-memory-access in range [...] RIP: 0010:string (lib/vsprintf.c:730) vsnprintf (lib/vsprintf.c:2945) _printk (kernel/printk/printk.c:2504) nh_res_bucket_migrate (net/ipv4/nexthop.c:1816) nh_res_table_upkeep (net/ipv4/nexthop.c:1866) rtm_new_nexthop (net/ipv4/nexthop.c:3323) rtnetlink_rcv_msg (net/core/rtnetlink.c:7076) netlink_sendmsg (net/netlink/af_netlink.c:1900) Kernel panic - not syncing: Fatal exception Zero-initialize extack so _msg is NULL on error paths that never set it. Fixes: 7c37c7e00411 ("nexthop: Implement notifiers for resilient nexthop groups") Reported-by: AutonomousCodeSecurity@microsoft.com Signed-off-by: Xiang Mei (Microsoft) Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260713221551.3344650-1-xmei5@asu.edu Signed-off-by: Jakub Kicinski --- net/ipv4/nexthop.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ipv4/nexthop.c b/net/ipv4/nexthop.c index 6205bd57aa85..44fe75004cac 100644 --- a/net/ipv4/nexthop.c +++ b/net/ipv4/nexthop.c @@ -1788,8 +1788,8 @@ static bool nh_res_bucket_migrate(struct nh_res_table *res_table, bool notify_nl, bool force) { struct nh_res_bucket *bucket = &res_table->nh_buckets[bucket_index]; + struct netlink_ext_ack extack = {}; struct nh_grp_entry *new_nhge; - struct netlink_ext_ack extack; int err; new_nhge = list_first_entry_or_null(&res_table->uw_nh_entries, From 22f8aa35964e8f2ab026578f45befc9605fd1b28 Mon Sep 17 00:00:00 2001 From: Helen Koike Date: Mon, 13 Jul 2026 17:49:35 -0300 Subject: [PATCH 137/234] tipc: fix infinite loop in __tipc_nl_compat_dumpit cmd->dumpit callback can return a negative errno, causing an infinite loop due to the while(len) condition. As the loop never terminates, genl_mutex is never released, and other tasks waiting on it starve in D state. Check dumpit's return value, propagate it and jump to err_out on error. Reported-by: syzbot+85d0bec020d805014a3a@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=85d0bec020d805014a3a Fixes: d0796d1ef63d ("tipc: convert legacy nl bearer dump to nl compat") Signed-off-by: Helen Koike Reviewed-by: Tung Nguyen Link: https://patch.msgid.link/20260713204940.647668-1-koike@igalia.com Signed-off-by: Jakub Kicinski --- net/tipc/netlink_compat.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/tipc/netlink_compat.c b/net/tipc/netlink_compat.c index 2a786c56c8c5..d9a4f94ea2d4 100644 --- a/net/tipc/netlink_compat.c +++ b/net/tipc/netlink_compat.c @@ -221,6 +221,10 @@ static int __tipc_nl_compat_dumpit(struct tipc_nl_compat_cmd_dump *cmd, int rem; len = (*cmd->dumpit)(buf, &cb); + if (len < 0) { + err = len; + goto err_out; + } nlmsg_for_each_msg(nlmsg, nlmsg_hdr(buf), len, rem) { err = nlmsg_parse_deprecated(nlmsg, GENL_HDRLEN, From ff194cffd586cbd4cc49eccb002c65f2a902a277 Mon Sep 17 00:00:00 2001 From: Youssef Samir Date: Mon, 13 Jul 2026 16:59:01 +0200 Subject: [PATCH 138/234] net: qrtr: ns: Raise node count limit to 512 The current node limit of 64 breaks the functionality for a number of AI200 deployments that have up to 384 nodes. Raise the limit to 512. Fixes: 27d5e84e810b ("net: qrtr: ns: Limit the total number of nodes") Cc: stable@vger.kernel.org Signed-off-by: Youssef Samir Link: https://patch.msgid.link/20260713145901.212396-1-youssef.abdulrahman@oss.qualcomm.com Signed-off-by: Jakub Kicinski --- net/qrtr/ns.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/qrtr/ns.c b/net/qrtr/ns.c index b3f9bbcf9ab9..e5b2adb161d9 100644 --- a/net/qrtr/ns.c +++ b/net/qrtr/ns.c @@ -76,11 +76,11 @@ struct qrtr_node { * requirements. If the requirement changes in the future, these values can be * increased. */ -#define QRTR_NS_MAX_NODES 64 +#define QRTR_NS_MAX_NODES 512 #define QRTR_NS_MAX_SERVERS 256 #define QRTR_NS_MAX_LOOKUPS 64 -static u8 node_count; +static u16 node_count; static struct qrtr_node *node_get(unsigned int node_id) { From e1a9d3cc11829c5414a75eb39c704f461936eb24 Mon Sep 17 00:00:00 2001 From: Yizhou Zhao Date: Mon, 13 Jul 2026 18:56:30 +0800 Subject: [PATCH 139/234] tcp: initialize standalone TCP-AO response padding tcp_v4_send_ack() and tcp_v6_send_response() construct standalone TCP responses with TCP-AO options. The option length carries the actual MAC length, but the TCP header length includes the option rounded up to a four-byte boundary. tcp_ao_hash_hdr() writes the MAC only. Thus, when the MAC length is not four-byte aligned, the one to three bytes after the MAC are left uninitialized and may be transmitted. For the normal TCP-AO hashing mode, those bytes also have to be initialized before computing the MAC. Initialize only the alignment padding in the TCP-AO branches, before hashing the header. Use TCPOPT_NOP, as in the normal TCP-AO output path. This avoids adding work to non-AO TCP responses while preserving a valid authenticated header. Fixes: decde2586b34 ("net/tcp: Add TCP-AO sign to twsk") Fixes: da7dfaa6d6f7 ("net/tcp: Consistently align TCP-AO option in the header") Cc: stable@vger.kernel.org Reported-by: Yizhou Zhao Reported-by: Yuxiang Yang Reported-by: Ao Wang Reported-by: Xuewei Feng Reported-by: Qi Li Reported-by: Ke Xu Suggested-by: Eric Dumazet Signed-off-by: Yizhou Zhao Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260713105631.8616-1-zhaoyz24@mails.tsinghua.edu.cn Signed-off-by: Jakub Kicinski --- net/ipv4/tcp_ipv4.c | 3 +++ net/ipv6/tcp_ipv6.c | 2 ++ 2 files changed, 5 insertions(+) diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c index 4a46da375043..aada52769057 100644 --- a/net/ipv4/tcp_ipv4.c +++ b/net/ipv4/tcp_ipv4.c @@ -971,6 +971,9 @@ static void tcp_v4_send_ack(const struct sock *sk, key->rcv_next); arg.iov[0].iov_len += tcp_ao_len_aligned(key->ao_key); rep.th.doff = arg.iov[0].iov_len / 4; + memset((u8 *)&rep.opt[offset] + tcp_ao_maclen(key->ao_key), + TCPOPT_NOP, tcp_ao_len_aligned(key->ao_key) - + tcp_ao_len(key->ao_key)); tcp_ao_hash_hdr(AF_INET, (char *)&rep.opt[offset], key->ao_key, key->traffic_key, diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c index 522ba45ce9b7..9e9155b1b3aa 100644 --- a/net/ipv6/tcp_ipv6.c +++ b/net/ipv6/tcp_ipv6.c @@ -923,6 +923,8 @@ static void tcp_v6_send_response(const struct sock *sk, struct sk_buff *skb, u32 (tcp_ao_len(key->ao_key) << 16) | (key->ao_key->sndid << 8) | (key->rcv_next)); + memset((u8 *)topt + tcp_ao_maclen(key->ao_key), TCPOPT_NOP, + tcp_ao_len_aligned(key->ao_key) - tcp_ao_len(key->ao_key)); tcp_ao_hash_hdr(AF_INET6, (char *)topt, key->ao_key, key->traffic_key, From 0ab78ead2481adb52f9eb5b403865c529f6f2348 Mon Sep 17 00:00:00 2001 From: Markus Breitenberger Date: Mon, 13 Jul 2026 19:16:19 +0200 Subject: [PATCH 140/234] net: stmmac: intel: skip SerDes reconfig when rate is unchanged intel_mac_finish() is registered as the phylink mac_finish() callback for the Elkhart Lake SGMII ports. phylink calls it at the end of every major link reconfiguration, including the initial one during probe. The callback selects the PMC ModPHY LCPLL programming for the requested MAC-side interface and then power-cycles the SerDes. On Elkhart Lake that ModPHY is also used by the on-die AHCI SATA PHY. Reapplying the programming during the initial boot-time link-up disturbs the shared analog block while it is still driving SATA, so the SATA link fails to train: ata1: SATA link down (SStatus 1 SControl 300) The disk carrying the root filesystem is never detected and the system hangs at rootwait. Ethernet itself comes up normally, which makes the failure look unrelated to the network driver. Before mac_finish() runs, the legacy SerDes power-up path has already programmed SERDES_GCR0 for the current interface. The 1G and 2.5G ModPHY tables selected by mac_finish() correspond to the SerDes lane rate, so read that rate back from SERDES_GCR0 and skip the PMC reprogramming and SerDes power-cycle when it already matches the selected interface. This keeps the disruptive reprogramming out of the boot path when the SerDes is configured correctly, while preserving the previous behavior when a real SGMII/1000BASE-X to 2500BASE-X rate change is needed. If the register read fails, reconfigure as before. Fixes: a42f6b3f1cc1 ("net: stmmac: configure SerDes according to the interface mode") Cc: stable@vger.kernel.org Signed-off-by: Markus Breitenberger Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260713171619.192452-1-bre@breiti.cc Signed-off-by: Jakub Kicinski --- .../net/ethernet/stmicro/stmmac/dwmac-intel.c | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c index b8d467ba6d72..4d207f41a43b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c @@ -525,6 +525,32 @@ static int intel_set_reg_access(const struct pmc_serdes_regs *regs, int max_regs return ret; } +/* + * Return true if the SerDes lane rate must change to serve @interface. + * If the current rate cannot be determined, reconfigure as before. + */ +static bool intel_serdes_needs_reconfig(struct stmmac_priv *priv, + struct intel_priv_data *intel_priv, + phy_interface_t interface) +{ + u32 cur_rate, want_rate; + int data; + + if (!intel_priv->mdio_adhoc_addr) + return true; + + data = mdiobus_read(priv->mii, intel_priv->mdio_adhoc_addr, + SERDES_GCR0); + if (data < 0) + return true; + + cur_rate = (data & SERDES_RATE_MASK) >> SERDES_RATE_PCIE_SHIFT; + want_rate = interface == PHY_INTERFACE_MODE_2500BASEX ? + SERDES_RATE_PCIE_GEN2 : SERDES_RATE_PCIE_GEN1; + + return cur_rate != want_rate; +} + static int intel_mac_finish(struct net_device *ndev, void *intel_data, unsigned int mode, @@ -536,6 +562,11 @@ static int intel_mac_finish(struct net_device *ndev, int max_regs = 0; int ret = 0; + if (!intel_serdes_needs_reconfig(priv, intel_priv, interface)) { + priv->plat->phy_interface = interface; + return 0; + } + ret = intel_tsn_lane_is_available(ndev, intel_priv); if (ret < 0) { netdev_info(priv->dev, "No TSN lane available to set the registers.\n"); From ba712ecfd942b68b21a4b0a5daaf72f6616cc66d Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 15 Jul 2026 05:55:41 +0000 Subject: [PATCH 141/234] ppp: annotate concurrent dev->stats accesses dev->stats fields can be updated concurrently from multiple CPUs without synchronization. Use DEV_STATS_INC() for stats increments and DEV_STATS_READ() when reading dev->stats in ppp_get_stats64() and ppp_get_stats() to avoid data races. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Eric Dumazet Reviewed-by: Qingfang Deng Link: https://patch.msgid.link/20260715055541.1147542-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- drivers/net/ppp/ppp_generic.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/drivers/net/ppp/ppp_generic.c b/drivers/net/ppp/ppp_generic.c index 717c1d3aa953..ef54e0a0462a 100644 --- a/drivers/net/ppp/ppp_generic.c +++ b/drivers/net/ppp/ppp_generic.c @@ -1482,7 +1482,7 @@ ppp_start_xmit(struct sk_buff *skb, struct net_device *dev) outf: kfree_skb(skb); - ++dev->stats.tx_dropped; + DEV_STATS_INC(dev, tx_dropped); return NETDEV_TX_OK; } @@ -1532,11 +1532,11 @@ ppp_net_siocdevprivate(struct net_device *dev, struct ifreq *ifr, static void ppp_get_stats64(struct net_device *dev, struct rtnl_link_stats64 *stats64) { - stats64->rx_errors = dev->stats.rx_errors; - stats64->tx_errors = dev->stats.tx_errors; - stats64->rx_dropped = dev->stats.rx_dropped; - stats64->tx_dropped = dev->stats.tx_dropped; - stats64->rx_length_errors = dev->stats.rx_length_errors; + stats64->rx_errors = DEV_STATS_READ(dev, rx_errors); + stats64->tx_errors = DEV_STATS_READ(dev, tx_errors); + stats64->rx_dropped = DEV_STATS_READ(dev, rx_dropped); + stats64->tx_dropped = DEV_STATS_READ(dev, tx_dropped); + stats64->rx_length_errors = DEV_STATS_READ(dev, rx_length_errors); dev_fetch_sw_netstats(stats64, dev->tstats); } @@ -1889,7 +1889,7 @@ ppp_prepare_tx_skb(struct ppp *ppp, struct sk_buff **pskb) drop: kfree_skb(skb); - ++ppp->dev->stats.tx_errors; + DEV_STATS_INC(ppp->dev, tx_errors); return 1; } @@ -2156,7 +2156,7 @@ static int ppp_mp_explode(struct ppp *ppp, struct sk_buff *skb) err_linearize: if (ppp->debug & 1) netdev_err(ppp->dev, "PPP: no memory (fragment)\n"); - ++ppp->dev->stats.tx_errors; + DEV_STATS_INC(ppp->dev, tx_errors); ++ppp->nxseq; return 1; /* abandon the frame */ } @@ -2329,7 +2329,7 @@ ppp_input(struct ppp_channel *chan, struct sk_buff *skb) if (!ppp_decompress_proto(skb)) { kfree_skb(skb); if (ppp) { - ++ppp->dev->stats.rx_length_errors; + DEV_STATS_INC(ppp->dev, rx_length_errors); ppp_receive_error(ppp); } goto done; @@ -2391,7 +2391,7 @@ ppp_receive_frame(struct ppp *ppp, struct sk_buff *skb, struct channel *pch) static void ppp_receive_error(struct ppp *ppp) { - ++ppp->dev->stats.rx_errors; + DEV_STATS_INC(ppp->dev, rx_errors); if (ppp->vj) slhc_toss(ppp->vj); } @@ -2658,7 +2658,7 @@ ppp_receive_mp_frame(struct ppp *ppp, struct sk_buff *skb, struct channel *pch) */ if (seq_before(seq, ppp->nextseq)) { kfree_skb(skb); - ++ppp->dev->stats.rx_dropped; + DEV_STATS_INC(ppp->dev, rx_dropped); ppp_receive_error(ppp); return; } @@ -2694,7 +2694,7 @@ ppp_receive_mp_frame(struct ppp *ppp, struct sk_buff *skb, struct channel *pch) if (pskb_may_pull(skb, 2)) ppp_receive_nonmp_frame(ppp, skb); else { - ++ppp->dev->stats.rx_length_errors; + DEV_STATS_INC(ppp->dev, rx_length_errors); kfree_skb(skb); ppp_receive_error(ppp); } @@ -2800,7 +2800,7 @@ ppp_mp_reconstruct(struct ppp *ppp) if (lost == 0 && (PPP_MP_CB(p)->BEbits & E) && (PPP_MP_CB(head)->BEbits & B)) { if (len > ppp->mrru + 2) { - ++ppp->dev->stats.rx_length_errors; + DEV_STATS_INC(ppp->dev, rx_length_errors); netdev_printk(KERN_DEBUG, ppp->dev, "PPP: reconstructed packet" " is too long (%d)\n", len); @@ -2855,7 +2855,7 @@ ppp_mp_reconstruct(struct ppp *ppp) " missed pkts %u..%u\n", ppp->nextseq, PPP_MP_CB(head)->sequence-1); - ++ppp->dev->stats.rx_dropped; + DEV_STATS_INC(ppp->dev, rx_dropped); ppp_receive_error(ppp); } @@ -3322,8 +3322,8 @@ ppp_get_stats(struct ppp *ppp, struct ppp_stats *st) st->p.ppp_opackets += tx_packets; st->p.ppp_obytes += tx_bytes; } - st->p.ppp_ierrors = ppp->dev->stats.rx_errors; - st->p.ppp_oerrors = ppp->dev->stats.tx_errors; + st->p.ppp_ierrors = DEV_STATS_READ(ppp->dev, rx_errors); + st->p.ppp_oerrors = DEV_STATS_READ(ppp->dev, tx_errors); if (!vj) return; st->vj.vjs_packets = vj->sls_o_compressed + vj->sls_o_uncompressed; From da4082e91acabc1498611ed8ccc53f0610baefc6 Mon Sep 17 00:00:00 2001 From: Devin Wittmayer Date: Sat, 27 Jun 2026 12:13:34 -0700 Subject: [PATCH 142/234] wifi: mt76: mt7921: drop TXRX_NOTIFY on non-mmio buses PKT_TYPE_TXRX_NOTIFY is an mmio-only event, but mt7921_rx_check() and mt7921_queue_rx_skb() dispatch it to mt7921_mac_tx_free() on every bus. mt7921_mac_tx_free() cleans the DMA tx queues with mt76_queue_tx_cleanup(), which calls queue_ops->tx_cleanup(). Only the mmio queue ops implement that callback; on USB and SDIO it is NULL, so a TXRX_NOTIFY there calls a NULL pointer in the RX worker: BUG: kernel NULL pointer dereference, address: 0000000000000000 RIP: 0010:0x0 Call Trace: mt7921_mac_tx_free+0x64/0x310 [mt7921_common] mt7921_rx_check+0x5f/0xf0 [mt7921_common] mt76u_rx_worker+0x1b9/0x620 [mt76_usb] Drop the event on non-mmio buses via mt76_is_mmio(), as in commit 5683e1488aa9 ("wifi: mt76: connac: do not check WED status for non-mmio devices"). Fixes: 48fab5bbef40 ("mt76: mt7921: introduce mt7921s support") Cc: stable@vger.kernel.org Signed-off-by: Devin Wittmayer Link: https://patch.msgid.link/20260627191336.20223-2-lucid_duck@justthetip.ca Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7921/mac.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mac.c b/drivers/net/wireless/mediatek/mt76/mt7921/mac.c index 1c2377d0a53d..f7d54472da1b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/mac.c @@ -570,8 +570,9 @@ bool mt7921_rx_check(struct mt76_dev *mdev, void *data, int len) switch (type) { case PKT_TYPE_TXRX_NOTIFY: - /* PKT_TYPE_TXRX_NOTIFY can be received only by mmio devices */ - mt7921_mac_tx_free(dev, data, len); /* mmio */ + if (!mt76_is_mmio(mdev)) + return false; + mt7921_mac_tx_free(dev, data, len); return false; case PKT_TYPE_TXS: for (rxd += 2; rxd + 8 <= end; rxd += 8) @@ -600,7 +601,10 @@ void mt7921_queue_rx_skb(struct mt76_dev *mdev, enum mt76_rxq_id q, switch (type) { case PKT_TYPE_TXRX_NOTIFY: - /* PKT_TYPE_TXRX_NOTIFY can be received only by mmio devices */ + if (!mt76_is_mmio(mdev)) { + napi_consume_skb(skb, 1); + break; + } mt7921_mac_tx_free(dev, skb->data, skb->len); napi_consume_skb(skb, 1); break; From feeff151c83e7f0ffcdedcad5343852d23d1f6e1 Mon Sep 17 00:00:00 2001 From: Devin Wittmayer Date: Sat, 27 Jun 2026 12:13:35 -0700 Subject: [PATCH 143/234] wifi: mt76: mt7925: drop TXRX_NOTIFY on non-mmio buses PKT_TYPE_TXRX_NOTIFY is an mmio-only event, but mt7925_rx_check() and mt7925_queue_rx_skb() dispatch it to mt7925_mac_tx_free() on every bus. mt7925_mac_tx_free() cleans the DMA tx queues with mt76_queue_tx_cleanup(), which calls queue_ops->tx_cleanup(). Only the mmio queue ops implement that callback; on USB it is NULL, so a TXRX_NOTIFY there calls a NULL pointer in the RX worker: BUG: kernel NULL pointer dereference, address: 0000000000000000 RIP: 0010:0x0 Call Trace: mt7925_mac_tx_free+0x58/0x350 [mt7925_common] mt7925_rx_check+0xe2/0x130 [mt7925_common] mt76u_rx_worker+0x1b9/0x620 [mt76_usb] Drop the event on non-mmio buses via mt76_is_mmio(), as in commit 5683e1488aa9 ("wifi: mt76: connac: do not check WED status for non-mmio devices"). Fixes: c948b5da6bbe ("wifi: mt76: mt7925: add Mediatek Wi-Fi7 driver for mt7925 chips") Cc: stable@vger.kernel.org Signed-off-by: Devin Wittmayer Link: https://patch.msgid.link/20260627191336.20223-3-lucid_duck@justthetip.ca Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/mac.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c index 0641a7131d7c..2f9871792ea1 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c @@ -1203,8 +1203,9 @@ bool mt7925_rx_check(struct mt76_dev *mdev, void *data, int len) switch (type) { case PKT_TYPE_TXRX_NOTIFY: - /* PKT_TYPE_TXRX_NOTIFY can be received only by mmio devices */ - mt7925_mac_tx_free(dev, data, len); /* mmio */ + if (!mt76_is_mmio(mdev)) + return false; + mt7925_mac_tx_free(dev, data, len); return false; case PKT_TYPE_TXS: for (rxd += 4; rxd + 12 <= end; rxd += 12) @@ -1240,7 +1241,10 @@ void mt7925_queue_rx_skb(struct mt76_dev *mdev, enum mt76_rxq_id q, switch (type) { case PKT_TYPE_TXRX_NOTIFY: - /* PKT_TYPE_TXRX_NOTIFY can be received only by mmio devices */ + if (!mt76_is_mmio(mdev)) { + napi_consume_skb(skb, 1); + break; + } mt7925_mac_tx_free(dev, skb->data, skb->len); napi_consume_skb(skb, 1); break; From 39afc46c0243d10b7795e6e6cf4ae91f41732120 Mon Sep 17 00:00:00 2001 From: Devin Wittmayer Date: Sat, 27 Jun 2026 12:13:36 -0700 Subject: [PATCH 144/234] wifi: mt76: mt7615: drop TXRX_NOTIFY on non-mmio buses PKT_TYPE_TXRX_NOTIFY is an mmio-only event, but mt7615_rx_check() and mt7615_queue_rx_skb() dispatch it to mt7615_mac_tx_free() on every bus. mt7615_mac_tx_free() cleans the DMA tx queues with mt76_queue_tx_cleanup(), which calls queue_ops->tx_cleanup(). Only the mmio queue ops implement that callback; on the mt7663 USB and SDIO buses it is NULL, so a TXRX_NOTIFY there calls a NULL pointer in the RX worker. Same defect as the mt7921 and mt7925 patches in this series. Drop the event on non-mmio buses via mt76_is_mmio(), as in commit 5683e1488aa9 ("wifi: mt76: connac: do not check WED status for non-mmio devices"). Fixes: eb99cc95c3b6 ("mt76: mt7615: introduce mt7663u support") Cc: stable@vger.kernel.org Signed-off-by: Devin Wittmayer Link: https://patch.msgid.link/20260627191336.20223-4-lucid_duck@justthetip.ca Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7615/mac.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/mac.c b/drivers/net/wireless/mediatek/mt76/mt7615/mac.c index ce0051468501..aad232c5a6fa 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7615/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7615/mac.c @@ -1601,6 +1601,8 @@ bool mt7615_rx_check(struct mt76_dev *mdev, void *data, int len) switch (type) { case PKT_TYPE_TXRX_NOTIFY: + if (!mt76_is_mmio(mdev)) + return false; mt7615_mac_tx_free(dev, data, len); return false; case PKT_TYPE_TXS: @@ -1634,6 +1636,10 @@ void mt7615_queue_rx_skb(struct mt76_dev *mdev, enum mt76_rxq_id q, dev_kfree_skb(skb); break; case PKT_TYPE_TXRX_NOTIFY: + if (!mt76_is_mmio(mdev)) { + dev_kfree_skb(skb); + break; + } mt7615_mac_tx_free(dev, skb->data, skb->len); dev_kfree_skb(skb); break; From 13b7e6a96a005c656d38f3da51581deaf9866375 Mon Sep 17 00:00:00 2001 From: Nicolas Cavallari Date: Wed, 8 Jul 2026 16:43:47 +0200 Subject: [PATCH 145/234] wifi: mt76: Disable napi when removing device Unloading the mt7915e module with a MT7916 triggers multiples WARN in __netif_napi_del_locked() and in page_pool_disable_direct_recycling() because the driver does not disable the napi before destroying it. This is troublesome since on MT7916 it is required to unload the module and reinsert it with a different enable_6ghz parameter to change the frequency. The system generally becomes unstable after reinserting the module. Fix it by disabling napi before deleting it. Also, do not delete napi on WED queues since napi is neither used nor initialized on them. Fixes: 17f1de56df05 ("mt76: add common code shared between multiple chipsets") Signed-off-by: Nicolas Cavallari Link: https://patch.msgid.link/20260708144615.24092-1-nicolas.cavallari@green-communications.fr Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/dma.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/dma.c b/drivers/net/wireless/mediatek/mt76/dma.c index f8c2fe5f2f58..322041859217 100644 --- a/drivers/net/wireless/mediatek/mt76/dma.c +++ b/drivers/net/wireless/mediatek/mt76/dma.c @@ -1189,7 +1189,10 @@ void mt76_dma_cleanup(struct mt76_dev *dev) mt76_for_each_q_rx(dev, i) { struct mt76_queue *q = &dev->q_rx[i]; - netif_napi_del(&dev->napi[i]); + if (!mt76_queue_is_wed_rro(q)) { + napi_disable(&dev->napi[i]); + netif_napi_del(&dev->napi[i]); + } mt76_dma_rx_cleanup(dev, q); page_pool_destroy(q->page_pool); From 96ea44f2269f30364cffa054ee3a87e595bef0d4 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Wed, 8 Jul 2026 15:55:39 +0800 Subject: [PATCH 146/234] wifi: mt76: mt7925: guard link STA in decap offload mt7925_sta_set_decap_offload() iterates over the vif valid_links mask when updating decap offload state for an MLO station. The station may not have a link STA for every valid link of the vif, so mt792x_sta_to_link() can return NULL for a link that belongs to the vif but not to the station. The function currently dereferences mlink before checking whether the link WCID is ready. If mlink is NULL, setting or clearing MT_WCID_FLAG_HDR_TRANS dereferences a NULL pointer. Skip links without a station link before touching mlink->wcid. Fixes: b859ad65309a ("wifi: mt76: mt7925: add link handling in mt7925_sta_set_decap_offload") Signed-off-by: Guangshuo Li Link: https://patch.msgid.link/20260708075539.726200-1-lgs201920130244@gmail.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/main.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index a9059866b701..2b6cc8e253c0 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -1713,6 +1713,9 @@ static void mt7925_sta_set_decap_offload(struct ieee80211_hw *hw, mconf = mt792x_vif_to_link(mvif, i); mlink = mt792x_sta_to_link(msta, i); + if (!mlink) + continue; + if (enabled) set_bit(MT_WCID_FLAG_HDR_TRANS, &mlink->wcid.flags); else From 8e9db062654a388d0fa587acbeeae68dd33eba41 Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Sat, 20 Jun 2026 23:53:32 +0800 Subject: [PATCH 147/234] wifi: mt76: mt7915: guard HE capability lookups mt7915_mcu_bss_he_tlv() and mt7915_mcu_sta_bfer_tlv() both run after checking HE support, then dereference the HE PHY capability returned by mt76_connac_get_he_phy_cap(). That helper can return NULL when no capability entry matches the vif type. Fetch the capability before appending the TLV and skip the HE-specific setup when no matching capability is available. Fixes: e6d557a78b60 ("mt76: mt7915: rely on mt76_connac_get_phy utilities") Signed-off-by: Ruoyu Wang Acked-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260620155332.81120-1-ruoyuw560@gmail.com Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7915/mcu.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c index 4a381d351e61..e8fe86f93309 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c @@ -595,6 +595,8 @@ mt7915_mcu_bss_he_tlv(struct sk_buff *skb, struct ieee80211_vif *vif, struct tlv *tlv; cap = mt76_connac_get_he_phy_cap(phy->mt76, vif); + if (!cap) + return; tlv = mt76_connac_mcu_add_tlv(skb, BSS_INFO_HE_BASIC, sizeof(*he)); @@ -1177,13 +1179,12 @@ mt7915_mcu_sta_bfer_vht(struct ieee80211_sta *sta, struct mt7915_phy *phy, } static void -mt7915_mcu_sta_bfer_he(struct ieee80211_sta *sta, struct ieee80211_vif *vif, - struct mt7915_phy *phy, struct sta_rec_bf *bf) +mt7915_mcu_sta_bfer_he(struct ieee80211_sta *sta, + const struct ieee80211_sta_he_cap *vc, + struct sta_rec_bf *bf) { struct ieee80211_sta_he_cap *pc = &sta->deflink.he_cap; struct ieee80211_he_cap_elem *pe = &pc->he_cap_elem; - const struct ieee80211_sta_he_cap *vc = - mt76_connac_get_he_phy_cap(phy->mt76, vif); const struct ieee80211_he_cap_elem *ve = &vc->he_cap_elem; u16 mcs_map = le16_to_cpu(pc->he_mcs_nss_supp.rx_mcs_80); u8 nss_mcs = mt7915_mcu_get_sta_nss(mcs_map); @@ -1242,6 +1243,7 @@ mt7915_mcu_sta_bfer_tlv(struct mt7915_dev *dev, struct sk_buff *skb, { struct mt7915_vif *mvif = (struct mt7915_vif *)vif->drv_priv; struct mt7915_phy *phy = mvif->phy; + const struct ieee80211_sta_he_cap *vc = NULL; int tx_ant = hweight8(phy->mt76->chainmask) - 1; struct sta_rec_bf *bf; struct tlv *tlv; @@ -1260,6 +1262,12 @@ mt7915_mcu_sta_bfer_tlv(struct mt7915_dev *dev, struct sk_buff *skb, if (!ebf && !dev->ibf) return; + if (sta->deflink.he_cap.has_he && ebf) { + vc = mt76_connac_get_he_phy_cap(phy->mt76, vif); + if (!vc) + return; + } + tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_BF, sizeof(*bf)); bf = (struct sta_rec_bf *)tlv; @@ -1268,7 +1276,7 @@ mt7915_mcu_sta_bfer_tlv(struct mt7915_dev *dev, struct sk_buff *skb, * ht: iBF only, since mac80211 lacks of eBF support */ if (sta->deflink.he_cap.has_he && ebf) - mt7915_mcu_sta_bfer_he(sta, vif, phy, bf); + mt7915_mcu_sta_bfer_he(sta, vc, bf); else if (sta->deflink.vht_cap.vht_supported) mt7915_mcu_sta_bfer_vht(sta, phy, bf, ebf); else if (sta->deflink.ht_cap.ht_supported) From 2c1fb2335f5e3afb34f91bc07ecb63517c328090 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Sun, 21 Jun 2026 15:24:59 +0200 Subject: [PATCH 148/234] wifi: mt76: connac: fix possible NULL-pointer deref in mt76_connac_mcu_uni_bss_he_tlv() mt76_connac_get_he_phy_cap routine can theoretically return NULL so check cap pointer before dereferencing it. Fixes: d0e274af2f2e4 ("mt76: mt76_connac: create mcu library") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260621-mt76_connac_get_he_phy_cap-fix-v1-1-ed4ccf7a0363@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c index 6596c9e198f4..58b0b15e4fd6 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c @@ -1458,6 +1458,8 @@ mt76_connac_mcu_uni_bss_he_tlv(struct mt76_phy *phy, struct ieee80211_vif *vif, struct bss_info_uni_he *he; cap = mt76_connac_get_he_phy_cap(phy, vif); + if (!cap) + return; he = (struct bss_info_uni_he *)tlv; he->he_pe_duration = vif->bss_conf.htc_trig_based_pkt_ext; From 8d1b6738c1ab48c086b17e7994034aca94258931 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Sun, 21 Jun 2026 15:25:00 +0200 Subject: [PATCH 149/234] wifi: mt76: mt7925: fix possible NULL-pointer deref in mt7925_mcu_bss_he_tlv() mt76_connac_get_he_phy_cap routine can theoretically return NULL so check cap pointer before dereferencing it. Fixes: c948b5da6bbec ("wifi: mt76: mt7925: add Mediatek Wi-Fi7 driver for mt7925 chips") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260621-mt76_connac_get_he_phy_cap-fix-v1-2-ed4ccf7a0363@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/mcu.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index e94fa544ff20..cb265a6fc7ad 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -2773,6 +2773,8 @@ mt7925_mcu_bss_he_tlv(struct sk_buff *skb, struct ieee80211_bss_conf *link_conf, struct tlv *tlv; cap = mt76_connac_get_he_phy_cap(phy->mt76, link_conf->vif); + if (!cap) + return; tlv = mt76_connac_mcu_add_tlv(skb, UNI_BSS_INFO_HE_BASIC, sizeof(*he)); From e858cf6bf99880343348ff1e8c942aaff1d9d592 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Sun, 21 Jun 2026 15:25:01 +0200 Subject: [PATCH 150/234] wifi: mt76: mt7996: check pointer returned by mt76_connac_get_he_phy_cap() mt76_connac_get_he_phy_cap routine can theoretically return NULL so check cap pointer before dereferencing it. Fixes: 98686cd21624c ("wifi: mt76: mt7996: add driver for MediaTek Wi-Fi 7 (802.11be) devices") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260621-mt76_connac_get_he_phy_cap-fix-v1-3-ed4ccf7a0363@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index f119f023bcd5..c868b1356894 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -935,6 +935,8 @@ mt7996_mcu_bss_he_tlv(struct sk_buff *skb, struct ieee80211_vif *vif, struct tlv *tlv; cap = mt76_connac_get_he_phy_cap(phy->mt76, vif); + if (!cap) + return; tlv = mt7996_mcu_add_uni_tlv(skb, UNI_BSS_INFO_HE_BASIC, sizeof(*he)); @@ -1855,17 +1857,18 @@ mt7996_mcu_sta_bfer_he(struct ieee80211_link_sta *link_sta, { struct ieee80211_sta_he_cap *pc = &link_sta->he_cap; struct ieee80211_he_cap_elem *pe = &pc->he_cap_elem; - const struct ieee80211_sta_he_cap *vc = - mt76_connac_get_he_phy_cap(phy->mt76, vif); - const struct ieee80211_he_cap_elem *ve = &vc->he_cap_elem; u16 mcs_map = le16_to_cpu(pc->he_mcs_nss_supp.rx_mcs_80); u8 nss_mcs = mt7996_mcu_get_sta_nss(mcs_map); + const struct ieee80211_he_cap_elem *ve; + const struct ieee80211_sta_he_cap *vc; u8 snd_dim, sts; + vc = mt76_connac_get_he_phy_cap(phy->mt76, vif); if (!vc) return; bf->tx_mode = MT_PHY_TYPE_HE_SU; + ve = &vc->he_cap_elem; mt7996_mcu_sta_sounding_rate(bf, phy); From 7fd35e8c0548e97258f64f47c98a891173b8e35e Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Mon, 6 Jul 2026 16:28:57 -0700 Subject: [PATCH 151/234] wifi: mt76: fix MAC address for non OF pcie cards If seems the check for err is wrong as the proper macaddr gets written to from the EEPROM itself. Meaning checking err from of_get_mac_address is wrong as the proper macaddr has been written by this point. Closes: https://lore.kernel.org/linux-wireless/30a90714-02d8-45f2-a7f1-4cfe0627d50b@skade.local/ Reported-by: Klara Modin Closes: https://lore.kernel.org/all/ajRmlyx_AEGybykL@soda.int.kasm.eu/ Reported-by: Tobias Klausmann Fixes: 31ee1582717e ("wifi: mt76: fix of_get_mac_address error handling") Signed-off-by: Rosen Penev Tested-by: Tobias Klausmann Tested-by: Klara Modin Tested-by: John Rowley Link: https://patch.msgid.link/20260706232857.807044-1-rosenp@gmail.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/eeprom.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/eeprom.c b/drivers/net/wireless/mediatek/mt76/eeprom.c index b99d7452800f..afdb73661866 100644 --- a/drivers/net/wireless/mediatek/mt76/eeprom.c +++ b/drivers/net/wireless/mediatek/mt76/eeprom.c @@ -181,7 +181,7 @@ mt76_eeprom_override(struct mt76_phy *phy) if (err == -EPROBE_DEFER) return err; - if (err) { + if (!is_valid_ether_addr(phy->macaddr)) { eth_random_addr(phy->macaddr); dev_info(dev->dev, "Invalid MAC address, using random address %pM\n", From 7981aca2bd28a1f7ad7eeab89715442a95b1f72e Mon Sep 17 00:00:00 2001 From: Devin Wittmayer Date: Mon, 20 Jul 2026 16:27:36 -0700 Subject: [PATCH 152/234] wifi: mt76: restrict NPU/PPE active checks to MMIO devices mt76_npu_device_active() and mt76_ppe_device_active() read dev->mmio.npu and dev->mmio.ppe_dev. The mmio, usb and sdio bus structs share a union in struct mt76_dev, so on USB and SDIO these read unrelated data from the usb/sdio struct, which is non-NULL in practice. mt76_npu_device_active() then returns true on USB, and mt76_rx_poll_complete() takes the offload path and skips mt76_rx_aggr_reorder(). RX A-MPDU subframes are delivered out of order and the peer's TCP stack treats that as loss: heavy retransmissions and reduced throughput in AP mode. Seen on mt7921u, mt7925u, mt76x2u and mt76x0u. Gate both helpers on mt76_is_mmio() so they only run for the bus type that owns the mmio union member. Fixes: 7fb554b1b623 ("wifi: mt76: Introduce the NPU generic layer") Cc: stable@vger.kernel.org Tested-by: Nick Morrow Signed-off-by: Devin Wittmayer Link: https://patch.msgid.link/20260720232640.41293-1-lucid_duck@justthetip.ca Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt76.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index 07955555f84d..a32ba7c23f92 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -1736,12 +1736,12 @@ static inline int mt76_npu_send_txrx_addr(struct mt76_dev *dev, int ifindex, static inline bool mt76_npu_device_active(struct mt76_dev *dev) { - return !!rcu_access_pointer(dev->mmio.npu); + return mt76_is_mmio(dev) && !!rcu_access_pointer(dev->mmio.npu); } static inline bool mt76_ppe_device_active(struct mt76_dev *dev) { - return !!rcu_access_pointer(dev->mmio.ppe_dev); + return mt76_is_mmio(dev) && !!rcu_access_pointer(dev->mmio.ppe_dev); } static inline int mt76_npu_send_msg(struct airoha_npu *npu, int ifindex, From 7cd57ff6c6263519e6e463cbc2e0898828a70c42 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 12 Jun 2026 22:13:19 +0200 Subject: [PATCH 153/234] wifi: mt76: fix airoha_npu dependency tracking There is a new build failure with MT7996E=m MT76_CORE=y and NET_AIROHA_NPU=m: ld.lld: error: undefined symbol: airoha_npu_get ld.lld: error: undefined symbol: airoha_npu_put >>> referenced by npu.c >>> drivers/net/wireless/mediatek/mt76/npu.o:(mt76_npu_init) in archive vmlinux.a Fix this by reworking the dependency for the MT7996_NPU to only allow enabling that when mt76_core can link against the npu driver. To make sure this gets caught more easily in the future when additional mt76 variants need the same dependency, also turn CONFIG_MT76_NPU into a tristate symbol that has the same dependency. Fixes: 7fb554b1b623 ("wifi: mt76: Introduce the NPU generic layer") Acked-by: Lorenzo Bianconi Signed-off-by: Arnd Bergmann Link: https://patch.msgid.link/20260612201519.4054683-1-arnd@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/Kconfig | 4 ++-- drivers/net/wireless/mediatek/mt76/Makefile | 6 +++++- drivers/net/wireless/mediatek/mt76/mt76.h | 2 +- drivers/net/wireless/mediatek/mt76/mt7996/Kconfig | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/Kconfig b/drivers/net/wireless/mediatek/mt76/Kconfig index 502303622a53..2ca96e0527c0 100644 --- a/drivers/net/wireless/mediatek/mt76/Kconfig +++ b/drivers/net/wireless/mediatek/mt76/Kconfig @@ -38,8 +38,8 @@ config MT792x_USB select MT76_USB config MT76_NPU - bool - depends on MT76_CORE + tristate + depends on NET_AIROHA_NPU=y || MT76_CORE=NET_AIROHA_NPU source "drivers/net/wireless/mediatek/mt76/mt76x0/Kconfig" source "drivers/net/wireless/mediatek/mt76/mt76x2/Kconfig" diff --git a/drivers/net/wireless/mediatek/mt76/Makefile b/drivers/net/wireless/mediatek/mt76/Makefile index 1d42adfe8030..cacdd2b13d05 100644 --- a/drivers/net/wireless/mediatek/mt76/Makefile +++ b/drivers/net/wireless/mediatek/mt76/Makefile @@ -12,7 +12,11 @@ mt76-y := \ mmio.o util.o trace.o dma.o mac80211.o debugfs.o eeprom.o \ tx.o agg-rx.o mcu.o wed.o scan.o channel.o -mt76-$(CONFIG_MT76_NPU) += npu.o +ifdef CONFIG_MT76_NPU +# CONFIG_MT76_NPU is tristate to simplify dependency tracking, +# but it behaves as a bool symbol here. +mt76-y += npu.o +endif mt76-$(CONFIG_PCI) += pci.o mt76-$(CONFIG_NL80211_TESTMODE) += testmode.o diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index a32ba7c23f92..3822eb8fd88f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -1647,7 +1647,7 @@ int mt76_testmode_dump(struct ieee80211_hw *hw, struct sk_buff *skb, int mt76_testmode_set_state(struct mt76_phy *phy, enum mt76_testmode_state state); int mt76_testmode_alloc_skb(struct mt76_phy *phy, u32 len); -#ifdef CONFIG_MT76_NPU +#if IS_ENABLED(CONFIG_MT76_NPU) void mt76_npu_check_ppe(struct mt76_dev *dev, struct sk_buff *skb, u32 info); int mt76_npu_dma_add_buf(struct mt76_phy *phy, struct mt76_queue *q, diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/Kconfig b/drivers/net/wireless/mediatek/mt76/mt7996/Kconfig index 5503d03bf62c..5742bce12fbb 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/Kconfig +++ b/drivers/net/wireless/mediatek/mt76/mt7996/Kconfig @@ -16,6 +16,6 @@ config MT7996E config MT7996_NPU bool "MT7996 (PCIe) NPU support" depends on MT7996E - depends on NET_AIROHA_NPU=y || MT7996E=NET_AIROHA_NPU + depends on NET_AIROHA_NPU=y || MT76_CORE=NET_AIROHA_NPU select MT76_NPU default n From bd8b2ec838184236c3fcbf738a926328836adf12 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Tue, 16 Jun 2026 11:10:16 -0500 Subject: [PATCH 154/234] wifi: mt76: mt7925: fix crash in reset link replay During reset recovery, mt7925_vif_connect_iter() replays firmware state for links tracked in mvif->valid_links. After MLO link changes or MCU timeout recovery, the driver bitmap can temporarily contain a link whose mac80211 bss_conf has already gone away. This can pass a NULL bss_conf to mt76_connac_mcu_uni_add_dev(), matching the crash where x1, the second argument, is NULL: pc : mt76_connac_mcu_uni_add_dev+0x8c/0x1f8 [mt76_connac_lib] lr : mt7925_vif_connect_iter+0x9c/0x168 [mt7925_common] x2 : ffffff80a77f6018 x1 : 0000000000000000 x0 : ffffff8099402080 Call trace: mt76_connac_mcu_uni_add_dev+0x8c/0x1f8 [mt76_connac_lib] mt7925_vif_connect_iter+0x9c/0x168 [mt7925_common] mt7925_mac_reset_work+0x264/0x2f8 [mt7925_common] Skip missing bss_conf entries before replaying the link. Non-MLO AP/STA reset replay is unchanged because the helper still returns &vif->bss_conf for the legacy link. Fixes: 14061994184d ("wifi: mt76: mt7925: add link handling in mt7925_vif_connect_iter") Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260616161016.19346-1-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/mac.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c index 2f9871792ea1..6b0cd1996ecb 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c @@ -1288,6 +1288,9 @@ mt7925_vif_connect_iter(void *priv, u8 *mac, for_each_set_bit(i, &valid, IEEE80211_MLD_MAX_NUM_LINKS) { bss_conf = mt792x_vif_to_bss_conf(vif, i); + if (!bss_conf) + continue; + mconf = mt792x_vif_to_link(mvif, i); mt76_connac_mcu_uni_add_dev(&dev->mphy, bss_conf, &mconf->mt76, From 2fffc472bec490c8357defcee9c075ca74467352 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Sun, 21 Jun 2026 15:25:02 +0200 Subject: [PATCH 155/234] wifi: mt76: mt7996: fix possible NULL-pointer deref in mt7996_mcu_sta_bfer_eht() mt76_connac_get_eht_phy_cap routine can theoretically return NULL so check cap pointer before dereferencing it. Fixes: ba01944adee9f ("wifi: mt76: mt7996: add EHT beamforming support") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260621-mt76_connac_get_he_phy_cap-fix-v1-4-ed4ccf7a0363@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index c868b1356894..2e83f4b79c87 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -1924,14 +1924,18 @@ mt7996_mcu_sta_bfer_eht(struct ieee80211_link_sta *link_sta, struct ieee80211_sta_eht_cap *pc = &link_sta->eht_cap; struct ieee80211_eht_cap_elem_fixed *pe = &pc->eht_cap_elem; struct ieee80211_eht_mcs_nss_supp *eht_nss = &pc->eht_mcs_nss_supp; - const struct ieee80211_sta_eht_cap *vc = - mt76_connac_get_eht_phy_cap(phy->mt76, vif); - const struct ieee80211_eht_cap_elem_fixed *ve = &vc->eht_cap_elem; u8 nss_mcs = u8_get_bits(eht_nss->bw._80.rx_tx_mcs9_max_nss, IEEE80211_EHT_MCS_NSS_RX) - 1; + const struct ieee80211_eht_cap_elem_fixed *ve; + const struct ieee80211_sta_eht_cap *vc; u8 snd_dim, sts; + vc = mt76_connac_get_eht_phy_cap(phy->mt76, vif); + if (!vc) + return; + bf->tx_mode = MT_PHY_TYPE_EHT_MU; + ve = &vc->eht_cap_elem; mt7996_mcu_sta_sounding_rate(bf, phy); From 7cb34f6c4fe8a68af621d870abe63bfca2275dd6 Mon Sep 17 00:00:00 2001 From: Shelley Yang Date: Mon, 25 May 2026 16:38:59 +0800 Subject: [PATCH 156/234] wifi: brcmfmac: fix 802.1X-SHA256 call trace warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Based on wpa_auth as 1x_256 mode, need to set up "use_fwsup" with BRCMF_PROFILE_FWSUP_1X. Or it will happen trace warning when call brcmf_cfg80211_set_pmk(). [ 4481.831101] ------------[ cut here ]------------ [ 4481.831102] WARNING: CPU: 1 PID: 2997 at drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c:7242 brcmf_cfg80211_set_pmk+0x77/0xd0 [brcmfmac] [...] [ 4481.831202] Call Trace: [ 4481.831204]   [ 4481.831205]  nl80211_set_pmk+0x183/0x250 [cfg80211] [ 4481.831233]  genl_family_rcv_msg_doit+0xea/0x150 [ 4481.831237]  genl_rcv_msg+0x104/0x240 [ 4481.831239]  ? cfg80211_probe_status+0x2c0/0x2c0 [cfg80211] [ 4481.831257]  ? genl_family_rcv_msg_doit+0x150/0x150 [ 4481.831259]  netlink_rcv_skb+0x4e/0x100 [ 4481.831261]  genl_rcv+0x24/0x40 [ 4481.831262]  netlink_unicast+0x236/0x380 [ 4481.831264]  netlink_sendmsg+0x250/0x4b0 [ 4481.831266]  sock_sendmsg+0x5c/0x70 [ 4481.831269]  ____sys_sendmsg+0x236/0x2b0 [ 4481.831271]  ? copy_msghdr_from_user+0x6d/0xa0 [ 4481.831272]  ___sys_sendmsg+0x86/0xd0 [ 4481.831274]  ? avc_has_perm+0x8c/0x1a0 [ 4481.831276]  ? preempt_count_add+0x6a/0xa0 [ 4481.831279]  ? sock_has_perm+0x82/0xa0 [ 4481.831280]  __sys_sendmsg+0x57/0xa0 [ 4481.831282]  do_syscall_64+0x38/0x90 [ 4481.831284]  entry_SYSCALL_64_after_hwframe+0x63/0xcd [ 4481.831286] RIP: 0033:0x7fd270d369b4 Fixes: 2526ff21aa77 ("brcmfmac: support 4-way handshake offloading for 802.1X") Signed-off-by: Shelley Yang Acked-by: Arend van Spriel Link: https://patch.msgid.link/20260525083859.581246-1-shelley.yang@infineon.com Signed-off-by: Johannes Berg --- drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index 0b55d445895f..89f61710a210 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -2146,7 +2146,7 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme) sme->crypto.akm_suites[0]); return -EINVAL; } - } else if (val & (WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED)) { + } else if (val & (WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED | WPA2_AUTH_1X_SHA256)) { switch (sme->crypto.akm_suites[0]) { case WLAN_AKM_SUITE_8021X: val = WPA2_AUTH_UNSPECIFIED; From 3656a79f94c471827a08f2cacce5f94ad5e52c24 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Sat, 11 Jul 2026 11:19:33 -0400 Subject: [PATCH 157/234] amt: re-read skb header pointers after every pull Several AMT receive and transmit paths cache a pointer into the skb head (ip_hdr(), ipv6_hdr(), eth_hdr() or the AMT message header) and then call a helper that can reallocate that head before the cached pointer is used again. pskb_may_pull(), ip_mc_may_pull(), ipv6_mc_may_pull(), iptunnel_pull_header(), ip_mc_check_igmp() and ipv6_mc_check_mld() can all free the old head and move the data, so a pointer taken before the call dangles afterwards and the later access is a use-after-free of the freed head. The affected sites are: amt_rcv() caches ip_hdr() before amt_parse_type() pulls, then reads iph->saddr. amt_dev_xmit() caches ip_hdr()/ipv6_hdr() before ip_mc_check_igmp()/ ipv6_mc_check_mld() and pskb_may_pull(), then reads the group address. amt_multicast_data_handler() caches eth_hdr() before pskb_may_pull(), then writes the L2 header. amt_membership_query_handler() caches the AMT header, the outer and inner eth_hdr() and ip_hdr() before iptunnel_pull_header() and several pulls, then reads and writes them. amt_igmpv3_report_handler() and amt_mldv2_report_handler() cache ip_hdr()/ipv6_hdr() and the current group record and read the record count from the report header inside the record loop, across the *_mc_may_pull() calls. amt_update_handler() caches ip_hdr() and the AMT membership-update header before pskb_may_pull(), iptunnel_pull_header(), ip_mc_check_igmp() and the report handler, then reads iph->daddr and amtmu->nonce / amtmu->response_mac. Fix each site by either snapshotting the scalar that is used after the pull before the first pull runs, or re-deriving the header pointer from the skb after the last pull that can move the head. Values that are stable across the pull (source and group address, the response MAC and nonce, the record count, the outer source MAC) are snapshotted; pointers that are written through or read repeatedly are re-derived. Fixes: cbc21dc1cfe9 ("amt: add data plane of amt interface") Signed-off-by: Michael Bommarito Reviewed-by: Simon Horman Reviewed-by: Taehee Yoo Link: https://patch.msgid.link/20260711151934.2955226-2-michael.bommarito@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/amt.c | 79 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 55 insertions(+), 24 deletions(-) diff --git a/drivers/net/amt.c b/drivers/net/amt.c index 951dd10e192b..35e77af76bd9 100644 --- a/drivers/net/amt.c +++ b/drivers/net/amt.c @@ -1211,7 +1211,7 @@ static netdev_tx_t amt_dev_xmit(struct sk_buff *skb, struct net_device *dev) data = true; } v6 = false; - group.ip4 = iph->daddr; + group.ip4 = ip_hdr(skb)->daddr; #if IS_ENABLED(CONFIG_IPV6) } else if (iph->version == 6) { ip6h = ipv6_hdr(skb); @@ -1235,7 +1235,7 @@ static netdev_tx_t amt_dev_xmit(struct sk_buff *skb, struct net_device *dev) data = true; } v6 = true; - group.ip6 = ip6h->daddr; + group.ip6 = ipv6_hdr(skb)->daddr; #endif } else { dev->stats.tx_errors++; @@ -1278,12 +1278,12 @@ static netdev_tx_t amt_dev_xmit(struct sk_buff *skb, struct net_device *dev) hlist_for_each_entry_rcu(gnode, &tunnel->groups[hash], node) { if (!v6) { - if (gnode->group_addr.ip4 == iph->daddr) + if (gnode->group_addr.ip4 == group.ip4) goto found; #if IS_ENABLED(CONFIG_IPV6) } else { if (ipv6_addr_equal(&gnode->group_addr.ip6, - &ip6h->daddr)) + &group.ip6)) goto found; #endif } @@ -2000,14 +2000,18 @@ static void amt_igmpv3_report_handler(struct amt_dev *amt, struct sk_buff *skb, struct igmpv3_report *ihrv3 = igmpv3_report_hdr(skb); int len = skb_transport_offset(skb) + sizeof(*ihrv3); void *zero_grec = (void *)&igmpv3_zero_grec; - struct iphdr *iph = ip_hdr(skb); struct amt_group_node *gnode; union amt_addr group, host; struct igmpv3_grec *grec; + __be32 saddr; u16 nsrcs; + u16 ngrec; int i; - for (i = 0; i < ntohs(ihrv3->ngrec); i++) { + saddr = ip_hdr(skb)->saddr; + ngrec = ntohs(ihrv3->ngrec); + + for (i = 0; i < ngrec; i++) { len += sizeof(*grec); if (!ip_mc_may_pull(skb, len)) break; @@ -2019,10 +2023,13 @@ static void amt_igmpv3_report_handler(struct amt_dev *amt, struct sk_buff *skb, if (!ip_mc_may_pull(skb, len)) break; + grec = (void *)(skb->data + len - sizeof(*grec) - + nsrcs * sizeof(__be32)); + memset(&group, 0, sizeof(union amt_addr)); group.ip4 = grec->grec_mca; memset(&host, 0, sizeof(union amt_addr)); - host.ip4 = iph->saddr; + host.ip4 = saddr; gnode = amt_lookup_group(tunnel, &group, &host, false); if (!gnode) { gnode = amt_add_group(amt, tunnel, &group, &host, @@ -2162,14 +2169,18 @@ static void amt_mldv2_report_handler(struct amt_dev *amt, struct sk_buff *skb, struct mld2_report *mld2r = (struct mld2_report *)icmp6_hdr(skb); int len = skb_transport_offset(skb) + sizeof(*mld2r); void *zero_grec = (void *)&mldv2_zero_grec; - struct ipv6hdr *ip6h = ipv6_hdr(skb); struct amt_group_node *gnode; union amt_addr group, host; struct mld2_grec *grec; + struct in6_addr saddr; u16 nsrcs; + u16 ngrec; int i; - for (i = 0; i < ntohs(mld2r->mld2r_ngrec); i++) { + saddr = ipv6_hdr(skb)->saddr; + ngrec = ntohs(mld2r->mld2r_ngrec); + + for (i = 0; i < ngrec; i++) { len += sizeof(*grec); if (!ipv6_mc_may_pull(skb, len)) break; @@ -2181,10 +2192,13 @@ static void amt_mldv2_report_handler(struct amt_dev *amt, struct sk_buff *skb, if (!ipv6_mc_may_pull(skb, len)) break; + grec = (void *)(skb->data + len - sizeof(*grec) - + nsrcs * sizeof(struct in6_addr)); + memset(&group, 0, sizeof(union amt_addr)); group.ip6 = grec->grec_mca; memset(&host, 0, sizeof(union amt_addr)); - host.ip6 = ip6h->saddr; + host.ip6 = saddr; gnode = amt_lookup_group(tunnel, &group, &host, true); if (!gnode) { gnode = amt_add_group(amt, tunnel, &group, &host, @@ -2305,7 +2319,6 @@ static bool amt_multicast_data_handler(struct amt_dev *amt, struct sk_buff *skb) skb_push(skb, sizeof(*eth)); skb_reset_mac_header(skb); skb_pull(skb, sizeof(*eth)); - eth = eth_hdr(skb); if (!pskb_may_pull(skb, sizeof(*iph))) return true; @@ -2315,6 +2328,7 @@ static bool amt_multicast_data_handler(struct amt_dev *amt, struct sk_buff *skb) if (!ipv4_is_multicast(iph->daddr)) return true; skb->protocol = htons(ETH_P_IP); + eth = eth_hdr(skb); eth->h_proto = htons(ETH_P_IP); ip_eth_mc_map(iph->daddr, eth->h_dest); #if IS_ENABLED(CONFIG_IPV6) @@ -2328,6 +2342,7 @@ static bool amt_multicast_data_handler(struct amt_dev *amt, struct sk_buff *skb) if (!ipv6_addr_is_multicast(&ip6h->daddr)) return true; skb->protocol = htons(ETH_P_IPV6); + eth = eth_hdr(skb); eth->h_proto = htons(ETH_P_IPV6); ipv6_eth_mc_map(&ip6h->daddr, eth->h_dest); #endif @@ -2351,10 +2366,12 @@ static bool amt_membership_query_handler(struct amt_dev *amt, struct sk_buff *skb) { struct amt_header_membership_query *amtmq; - struct igmpv3_query *ihv3; struct ethhdr *eth, *oeth; + struct igmpv3_query *ihv3; + u8 h_source[ETH_ALEN]; struct iphdr *iph; int hdr_size, len; + u64 response_mac; hdr_size = sizeof(*amtmq) + sizeof(struct udphdr); if (!pskb_may_pull(skb, hdr_size)) @@ -2367,6 +2384,8 @@ static bool amt_membership_query_handler(struct amt_dev *amt, if (amtmq->nonce != amt->nonce) return true; + response_mac = amtmq->response_mac; + hdr_size -= sizeof(*eth); if (iptunnel_pull_header(skb, hdr_size, htons(ETH_P_TEB), false)) return true; @@ -2376,6 +2395,7 @@ static bool amt_membership_query_handler(struct amt_dev *amt, skb_pull(skb, sizeof(*eth)); skb_reset_network_header(skb); eth = eth_hdr(skb); + ether_addr_copy(h_source, oeth->h_source); if (!pskb_may_pull(skb, sizeof(*iph))) return true; @@ -2388,6 +2408,7 @@ static bool amt_membership_query_handler(struct amt_dev *amt, sizeof(*ihv3))) return true; + iph = ip_hdr(skb); if (!ipv4_is_multicast(iph->daddr)) return true; @@ -2395,10 +2416,11 @@ static bool amt_membership_query_handler(struct amt_dev *amt, skb_reset_transport_header(skb); skb_push(skb, sizeof(*iph) + AMT_IPHDR_OPTS); WRITE_ONCE(amt->ready4, true); - amt->mac = amtmq->response_mac; + amt->mac = response_mac; amt->req_cnt = 0; amt->qi = ihv3->qqic; skb->protocol = htons(ETH_P_IP); + eth = eth_hdr(skb); eth->h_proto = htons(ETH_P_IP); ip_eth_mc_map(iph->daddr, eth->h_dest); #if IS_ENABLED(CONFIG_IPV6) @@ -2421,10 +2443,11 @@ static bool amt_membership_query_handler(struct amt_dev *amt, skb_reset_transport_header(skb); skb_push(skb, sizeof(*ip6h) + AMT_IP6HDR_OPTS); WRITE_ONCE(amt->ready6, true); - amt->mac = amtmq->response_mac; + amt->mac = response_mac; amt->req_cnt = 0; amt->qi = mld2q->mld2q_qqic; skb->protocol = htons(ETH_P_IPV6); + eth = eth_hdr(skb); eth->h_proto = htons(ETH_P_IPV6); ipv6_eth_mc_map(&ip6h->daddr, eth->h_dest); #endif @@ -2432,7 +2455,7 @@ static bool amt_membership_query_handler(struct amt_dev *amt, return true; } - ether_addr_copy(eth->h_source, oeth->h_source); + ether_addr_copy(eth->h_source, h_source); skb->pkt_type = PACKET_MULTICAST; skb->ip_summed = CHECKSUM_NONE; len = skb->len; @@ -2455,8 +2478,11 @@ static bool amt_update_handler(struct amt_dev *amt, struct sk_buff *skb) struct ethhdr *eth; struct iphdr *iph; int len, hdr_size; + u64 response_mac; + __be32 saddr; + __be32 nonce; - iph = ip_hdr(skb); + saddr = ip_hdr(skb)->saddr; hdr_size = sizeof(*amtmu) + sizeof(struct udphdr); if (!pskb_may_pull(skb, hdr_size)) @@ -2466,15 +2492,18 @@ static bool amt_update_handler(struct amt_dev *amt, struct sk_buff *skb) if (amtmu->reserved || amtmu->version) return true; + nonce = amtmu->nonce; + response_mac = amtmu->response_mac; + if (iptunnel_pull_header(skb, hdr_size, skb->protocol, false)) return true; skb_reset_network_header(skb); list_for_each_entry_rcu(tunnel, &amt->tunnel_list, list) { - if (tunnel->ip4 == iph->saddr) { - if ((amtmu->nonce == tunnel->nonce && - amtmu->response_mac == tunnel->mac)) { + if (tunnel->ip4 == saddr) { + if ((nonce == tunnel->nonce && + response_mac == tunnel->mac)) { mod_delayed_work(amt_wq, &tunnel->gc_wq, msecs_to_jiffies(amt_gmi(amt)) * 3); @@ -2508,6 +2537,7 @@ static bool amt_update_handler(struct amt_dev *amt, struct sk_buff *skb) eth = eth_hdr(skb); skb->protocol = htons(ETH_P_IP); eth->h_proto = htons(ETH_P_IP); + iph = ip_hdr(skb); ip_eth_mc_map(iph->daddr, eth->h_dest); #if IS_ENABLED(CONFIG_IPV6) } else if (iph->version == 6) { @@ -2527,6 +2557,7 @@ static bool amt_update_handler(struct amt_dev *amt, struct sk_buff *skb) eth = eth_hdr(skb); skb->protocol = htons(ETH_P_IPV6); eth->h_proto = htons(ETH_P_IPV6); + ip6h = ipv6_hdr(skb); ipv6_eth_mc_map(&ip6h->daddr, eth->h_dest); #endif } else { @@ -2772,7 +2803,7 @@ static void amt_gw_rcv(struct amt_dev *amt, struct sk_buff *skb) static int amt_rcv(struct sock *sk, struct sk_buff *skb) { struct amt_dev *amt; - struct iphdr *iph; + __be32 saddr; int type; bool err; @@ -2785,7 +2816,7 @@ static int amt_rcv(struct sock *sk, struct sk_buff *skb) } skb->dev = amt->dev; - iph = ip_hdr(skb); + saddr = ip_hdr(skb)->saddr; type = amt_parse_type(skb); if (type == -1) { err = true; @@ -2795,7 +2826,7 @@ static int amt_rcv(struct sock *sk, struct sk_buff *skb) if (amt->mode == AMT_MODE_GATEWAY) { switch (type) { case AMT_MSG_ADVERTISEMENT: - if (iph->saddr != amt->discovery_ip) { + if (saddr != amt->discovery_ip) { netdev_dbg(amt->dev, "Invalid Relay IP\n"); err = true; goto drop; @@ -2807,7 +2838,7 @@ static int amt_rcv(struct sock *sk, struct sk_buff *skb) } goto out; case AMT_MSG_MULTICAST_DATA: - if (iph->saddr != amt->remote_ip) { + if (saddr != amt->remote_ip) { netdev_dbg(amt->dev, "Invalid Relay IP\n"); err = true; goto drop; @@ -2818,7 +2849,7 @@ static int amt_rcv(struct sock *sk, struct sk_buff *skb) else goto out; case AMT_MSG_MEMBERSHIP_QUERY: - if (iph->saddr != amt->remote_ip) { + if (saddr != amt->remote_ip) { netdev_dbg(amt->dev, "Invalid Relay IP\n"); err = true; goto drop; From 53969d704fa5b7c1751e277fac96bfc22b435eac Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Sat, 11 Jul 2026 11:19:34 -0400 Subject: [PATCH 158/234] amt: make the head writable before rewriting the L2 header amt_multicast_data_handler(), amt_membership_query_handler() and amt_update_handler() rewrite the ethernet header of the decapsulated skb in place (eth->h_proto, eth->h_dest and, for the query, also eth->h_source) before handing it up the stack. The skb head may be shared, for example when a packet tap has cloned it on the underlay interface, so writing through it corrupts the other reader's copy. Call skb_cow_head() before the rewrite so the head is private. It is placed before the pointers into the head are (re-)derived, so a reallocation caused by the copy is picked up by those derivations. Fixes: cbc21dc1cfe9 ("amt: add data plane of amt interface") Signed-off-by: Michael Bommarito Reviewed-by: Simon Horman Reviewed-by: Taehee Yoo Link: https://patch.msgid.link/20260711151934.2955226-3-michael.bommarito@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/amt.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/net/amt.c b/drivers/net/amt.c index 35e77af76bd9..b733309b866f 100644 --- a/drivers/net/amt.c +++ b/drivers/net/amt.c @@ -2320,6 +2320,9 @@ static bool amt_multicast_data_handler(struct amt_dev *amt, struct sk_buff *skb) skb_reset_mac_header(skb); skb_pull(skb, sizeof(*eth)); + if (skb_cow_head(skb, 0)) + return true; + if (!pskb_may_pull(skb, sizeof(*iph))) return true; iph = ip_hdr(skb); @@ -2396,6 +2399,8 @@ static bool amt_membership_query_handler(struct amt_dev *amt, skb_reset_network_header(skb); eth = eth_hdr(skb); ether_addr_copy(h_source, oeth->h_source); + if (skb_cow_head(skb, 0)) + return true; if (!pskb_may_pull(skb, sizeof(*iph))) return true; @@ -2521,6 +2526,9 @@ static bool amt_update_handler(struct amt_dev *amt, struct sk_buff *skb) if (!pskb_may_pull(skb, sizeof(*iph))) return true; + if (skb_cow_head(skb, 0)) + return true; + iph = ip_hdr(skb); if (iph->version == 4) { if (ip_mc_check_igmp(skb)) { From 14fa65d10f5696b063a7d8d26e8291ea84a2c6ed Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Sun, 12 Jul 2026 14:27:29 +0000 Subject: [PATCH 159/234] net: hip04: fix RX buffer leak on build_skb failure When build_skb() fails in hip04_rx_poll(), the driver jumps to the refill path without releasing the current RX buffer and its DMA mapping. Installing a replacement buffer then overwrites the slot references and leaks both resources. Keep the current slot intact and return budget so NAPI retries the same buffer. Also free a newly allocated RX fragment when dma_map_single() fails. This issue was found by an in-house static analysis tool. Fixes: 701a0fd52318 ("hip04_eth: fix missing error handle for build_skb failed") Cc: stable@vger.kernel.org Signed-off-by: Fan Wu Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260712142729.2057636-1-fanwu01@zju.edu.cn Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/hisilicon/hip04_eth.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/hisilicon/hip04_eth.c b/drivers/net/ethernet/hisilicon/hip04_eth.c index 18376bcc718a..fc2c47dcfaab 100644 --- a/drivers/net/ethernet/hisilicon/hip04_eth.c +++ b/drivers/net/ethernet/hisilicon/hip04_eth.c @@ -594,7 +594,11 @@ static int hip04_rx_poll(struct napi_struct *napi, int budget) skb = build_skb(buf, priv->rx_buf_size); if (unlikely(!skb)) { net_dbg_ratelimited("build_skb failed\n"); - goto refill; + /* Retain the slot; return budget so NAPI retries this + * buffer. Refill would overwrite rx_buf[]/rx_phys[] + * and leak them. + */ + return budget; } dma_unmap_single(priv->dev, priv->rx_phys[priv->rx_head], @@ -622,14 +626,15 @@ static int hip04_rx_poll(struct napi_struct *napi, int budget) rx++; } -refill: buf = netdev_alloc_frag(priv->rx_buf_size); if (!buf) goto done; phys = dma_map_single(priv->dev, buf, RX_BUF_SIZE, DMA_FROM_DEVICE); - if (dma_mapping_error(priv->dev, phys)) + if (dma_mapping_error(priv->dev, phys)) { + skb_free_frag(buf); goto done; + } priv->rx_buf[priv->rx_head] = buf; priv->rx_phys[priv->rx_head] = phys; hip04_set_recv_desc(priv, phys); From 9545fef46d363cdcd63da63259b6d48ebb586024 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Tue, 21 Jul 2026 13:55:55 -0700 Subject: [PATCH 160/234] MAINTAINERS: add nci tests to nfc NCI is part of NFC, so include its selftests under the NFC entry. Reviewed-by: David Heidelberg Link: https://patch.msgid.link/20260721205555.1020513-1-kuba@kernel.org Signed-off-by: Jakub Kicinski --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index a674e36529f7..6fd196545966 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19108,6 +19108,7 @@ F: drivers/nfc/ F: include/net/nfc/ F: include/uapi/linux/nfc.h F: net/nfc/ +F: tools/testing/selftests/nci/ NFC VIRTUAL NCI DEVICE DRIVER M: Bongsu Jeon From 43171c97e4714bf601b468401b37732244639c21 Mon Sep 17 00:00:00 2001 From: Nikolay Aleksandrov Date: Tue, 21 Jul 2026 17:09:21 +0300 Subject: [PATCH 161/234] net: bridge: vlan: fix vlan range dumps starting with pvid There is a bug in all range dumps that rely on br_vlan_can_enter_range() when the PVID is a range starting VLAN, all following VLANs that match its flags can enter the range, but when the range is filled in only the PVID VLAN is dumped and the rest of the range is discarded because br_vlan_fill_vids() checks for the PVID flag. Since the PVID VLAN can be only one, we need to break ranges around it, the best way to do that consistently for all is to alter br_vlan_can_enter_range() to take into account the PVID and return false to break the range when it's matched. Before the fix: $ ip l add br0 type bridge vlan_filtering 1 $ ip l add dumdum type dummy $ ip l set dumdum master br0 $ ip l set br0 up $ ip l set dumdum up $ bridge vlan add dev dumdum vid 1 pvid untagged master $ bridge vlan add dev dumdum vid 2 untagged master $ bridge vlan show dev dumdum # use legacy dump to show all vlans port vlan-id dumdum 1 PVID Egress Untagged 2 Egress Untagged $ bridge -d vlan show dev dumdum # use the new dump (RTM_GETVLAN) port vlan-id dumdum 1 PVID Egress Untagged state forwarding mcast_router 1 VLAN 2 is missing, and if there are more matching VLANs afterwards they'd be missing too. After the fix: [ same setup steps ] $ bridge vlan show dev dumdum port vlan-id dumdum 1 PVID Egress Untagged 2 Egress Untagged $ bridge -d vlan show dev dumdum # use the new dump (RTM_GETVLAN) port vlan-id dumdum 1 PVID Egress Untagged state forwarding mcast_router 1 2 Egress Untagged state forwarding mcast_router 1 Fixes: 0ab558795184 ("net: bridge: vlan: add rtm range support") Signed-off-by: Nikolay Aleksandrov Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260721140922.682265-2-razor@blackwall.org Signed-off-by: Jakub Kicinski --- net/bridge/br_netlink_tunnel.c | 3 ++- net/bridge/br_private.h | 6 ++++-- net/bridge/br_vlan.c | 10 ++++++---- net/bridge/br_vlan_options.c | 3 +-- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/net/bridge/br_netlink_tunnel.c b/net/bridge/br_netlink_tunnel.c index 71a12da30004..a713668ea34f 100644 --- a/net/bridge/br_netlink_tunnel.c +++ b/net/bridge/br_netlink_tunnel.c @@ -271,7 +271,8 @@ static void __vlan_tunnel_handle_range(const struct net_bridge_port *p, if (!*v_start) goto out_init; - if (v && curr_change && br_vlan_can_enter_range(v, *v_end)) { + if (v && curr_change && + br_vlan_can_enter_range(v, *v_end, br_get_pvid(vg))) { *v_end = v; return; } diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h index d55ea9516e3e..d3880f31edc4 100644 --- a/net/bridge/br_private.h +++ b/net/bridge/br_private.h @@ -1627,7 +1627,8 @@ void br_vlan_notify(const struct net_bridge *br, u16 vid, u16 vid_range, int cmd); bool br_vlan_can_enter_range(const struct net_bridge_vlan *v_curr, - const struct net_bridge_vlan *range_end); + const struct net_bridge_vlan *range_end, + u16 pvid); void br_vlan_fill_forward_path_pvid(struct net_bridge *br, struct net_device_path_ctx *ctx, @@ -1874,7 +1875,8 @@ static inline void br_vlan_notify(const struct net_bridge *br, } static inline bool br_vlan_can_enter_range(const struct net_bridge_vlan *v_curr, - const struct net_bridge_vlan *range_end) + const struct net_bridge_vlan *range_end, + u16 pvid) { return true; } diff --git a/net/bridge/br_vlan.c b/net/bridge/br_vlan.c index 5560afcaaca3..31c1b2cf75d9 100644 --- a/net/bridge/br_vlan.c +++ b/net/bridge/br_vlan.c @@ -1982,9 +1982,11 @@ void br_vlan_notify(const struct net_bridge *br, /* check if v_curr can enter a range ending in range_end */ bool br_vlan_can_enter_range(const struct net_bridge_vlan *v_curr, - const struct net_bridge_vlan *range_end) + const struct net_bridge_vlan *range_end, + u16 pvid) { - return v_curr->vid - range_end->vid == 1 && + return v_curr->vid != pvid && range_end->vid != pvid && + v_curr->vid - range_end->vid == 1 && range_end->flags == v_curr->flags && br_vlan_opts_eq_range(v_curr, range_end); } @@ -2066,8 +2068,8 @@ static int br_vlan_dump_dev(const struct net_device *dev, idx += range_end->vid - range_start->vid + 1; range_start = v; - } else if (dump_stats || v->vid == pvid || - !br_vlan_can_enter_range(v, range_end)) { + } else if (dump_stats || + !br_vlan_can_enter_range(v, range_end, pvid)) { u16 vlan_flags = br_vlan_flags(range_start, pvid); if (!br_vlan_fill_vids(skb, range_start->vid, diff --git a/net/bridge/br_vlan_options.c b/net/bridge/br_vlan_options.c index fcc200c3e3da..cb0f556ff40d 100644 --- a/net/bridge/br_vlan_options.c +++ b/net/bridge/br_vlan_options.c @@ -350,8 +350,7 @@ int br_vlan_process_options(const struct net_bridge *br, continue; } - if (v->vid == pvid || - !br_vlan_can_enter_range(v, curr_end)) { + if (!br_vlan_can_enter_range(v, curr_end, pvid)) { br_vlan_notify(br, p, curr_start->vid, curr_end->vid, RTM_NEWVLAN); curr_start = v; From 679eb1e32d2cd1707de4984ca1b6e68f3d8da1ac Mon Sep 17 00:00:00 2001 From: Nikolay Aleksandrov Date: Tue, 21 Jul 2026 17:09:22 +0300 Subject: [PATCH 162/234] selftests: net: bridge: test ranges with PVID VLAN Add a test with PVID VLAN that matches the flags of the VLAN following it and check if the range is properly dumped. PVID VLAN should be on its own and all VLANs should be present in the dump. Signed-off-by: Nikolay Aleksandrov Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260721140922.682265-3-razor@blackwall.org Signed-off-by: Jakub Kicinski --- .../testing/selftests/net/bridge_vlan_dump.sh | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tools/testing/selftests/net/bridge_vlan_dump.sh b/tools/testing/selftests/net/bridge_vlan_dump.sh index ad66731d2a6f..90e18e2104e3 100755 --- a/tools/testing/selftests/net/bridge_vlan_dump.sh +++ b/tools/testing/selftests/net/bridge_vlan_dump.sh @@ -13,6 +13,7 @@ ALL_TESTS=" vlan_range_mcast_max_groups vlan_range_mcast_n_groups vlan_range_mcast_enabled + vlan_range_pvid " setup_prepare() @@ -191,6 +192,28 @@ vlan_range_mcast_enabled() log_test "VLAN range grouping with mcast_enabled" } +vlan_range_pvid() +{ + RET=0 + + ip -n "$NS" link set dev br0 type bridge vlan_default_pvid 1 + check_err $? "Failed to configure default PVID" + defer ip -n "$NS" link set dev br0 type bridge vlan_default_pvid 0 + + bridge -n "$NS" vlan add vid 2 dev dummy0 untagged + check_err $? "Failed to add VLAN 2" + defer bridge -n "$NS" vlan del vid 2 dev dummy0 + + bridge -n "$NS" -d vlan show dev dummy0 | + grep -Eq '(^|[[:space:]])2([[:space:]]|$)' + check_err $? "VLAN following PVID is missing from detailed dump" + + bridge -n "$NS" -d vlan show dev dummy0 | grep -q "1-2" + check_fail $? "PVID was incorrectly included in a VLAN range" + + log_test "PVID is isolated from VLAN dump ranges" +} + # Verify the newest tested option is supported if ! bridge vlan help 2>&1 | grep -q "neigh_suppress"; then echo "SKIP: iproute2 too old, missing per-VLAN neighbor suppression support" From dcf15eaf5641812f1cfc5e96537380132a7da89d Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Tue, 21 Jul 2026 10:12:40 +0000 Subject: [PATCH 163/234] net: hsr: fix memory leak on slave unregistration by removing synced VLANs When an HSR master device is brought UP, it auto-adds VLAN 0 via vlan_vid0_add(), which propagates VID 0 to its slave devices (slave A and B). If a slave device is later unregistered while HSR is active (e.g., during netns cleanup or interface destruction), hsr_del_port() is called to detach the slave port from the HSR master. However, hsr_del_port() currently does not delete the VLAN IDs that were synced to the slave device by HSR. As a result, the slave device retains a refcount on VID 0 (and any other synced VLANs). When the slave device is destroyed, its vlan_info / vlan_vid_info structure remains allocated, leading to a memory leak. Fix this by calling vlan_vids_del_by_dev(port->dev, master->dev) in hsr_del_port() before unlinking slave A or slave B ports, matching the propagation logic in hsr_ndo_vlan_rx_add_vid() / hsr_ndo_vlan_rx_kill_vid() and the cleanup behavior in bonding and team drivers. Fixes: 1a8a63a5305e ("net: hsr: Add VLAN CTAG filter support") Reported-by: syzbot+456957213f32970c0762@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6a4cb6ca.57639fcc.86d58.000b.GAE@google.com/T/#u Signed-off-by: Eric Dumazet Reviewed-by: Fernando Fernandez Mancera Reviewed-by: Felix Maurer Link: https://patch.msgid.link/20260721101240.995597-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/hsr/hsr_slave.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/hsr/hsr_slave.c b/net/hsr/hsr_slave.c index d9af9e65f72f..01c73b4b50dd 100644 --- a/net/hsr/hsr_slave.c +++ b/net/hsr/hsr_slave.c @@ -242,6 +242,8 @@ void hsr_del_port(struct hsr_port *port) netdev_rx_handler_unregister(port->dev); if (!port->hsr->fwd_offloaded) dev_set_promiscuity(port->dev, -1); + if (port->type == HSR_PT_SLAVE_A || port->type == HSR_PT_SLAVE_B) + vlan_vids_del_by_dev(port->dev, master->dev); netdev_upper_dev_unlink(port->dev, master->dev); if (hsr->prot_version == PRP_V1 && port->type == HSR_PT_SLAVE_B) { From da2c6bcc5e30b1496ac587785dcacf6e849eb6ef Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Fri, 17 Jul 2026 15:20:29 +0200 Subject: [PATCH 164/234] net: dpaa: fix mode setting Before converting to the phylink interface, the init function would have set a non-reserved I/F mode in the maccfg2 register. After converting to phylink, 0 is written as mode, which is a reserved value (although it's the hardware default). Without a valid mode, a SGMII link is never established between the MAC and the PHY and thus .link_up() is never called which could set the correct mode according to the actual speed. Fix it by setting the maximum speed of the phy_interface_t in use in .mac_config() - just like the driver did before the phylink conversion. Fixes: 5d93cfcf7360 ("net: dpaa: Convert to phylink") Suggested-by: Sean Anderson Signed-off-by: Michael Walle Reviewed-by: Sean Anderson Reviewed-by: Sean Anderson Link: https://patch.msgid.link/20260717132401.2653252-1-mwalle@kernel.org Signed-off-by: Jakub Kicinski --- .../net/ethernet/freescale/fman/fman_dtsec.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/freescale/fman/fman_dtsec.c b/drivers/net/ethernet/freescale/fman/fman_dtsec.c index fe35703c509e..b8d70c0ecb6c 100644 --- a/drivers/net/ethernet/freescale/fman/fman_dtsec.c +++ b/drivers/net/ethernet/freescale/fman/fman_dtsec.c @@ -900,22 +900,28 @@ static void dtsec_mac_config(struct phylink_config *config, unsigned int mode, { struct mac_device *mac_dev = fman_config_to_mac(config); struct dtsec_regs __iomem *regs = mac_dev->fman_mac->regs; - u32 tmp; + u32 ecntrl, maccfg2; + + maccfg2 = ioread32be(®s->maccfg2); + maccfg2 &= ~(MACCFG2_NIBBLE_MODE | MACCFG2_BYTE_MODE); switch (state->interface) { case PHY_INTERFACE_MODE_RMII: - tmp = DTSEC_ECNTRL_RMM; + ecntrl = DTSEC_ECNTRL_RMM; + maccfg2 |= MACCFG2_NIBBLE_MODE; break; case PHY_INTERFACE_MODE_RGMII: case PHY_INTERFACE_MODE_RGMII_ID: case PHY_INTERFACE_MODE_RGMII_RXID: case PHY_INTERFACE_MODE_RGMII_TXID: - tmp = DTSEC_ECNTRL_GMIIM | DTSEC_ECNTRL_RPM; + ecntrl = DTSEC_ECNTRL_GMIIM | DTSEC_ECNTRL_RPM; + maccfg2 |= MACCFG2_BYTE_MODE; break; case PHY_INTERFACE_MODE_SGMII: case PHY_INTERFACE_MODE_1000BASEX: case PHY_INTERFACE_MODE_2500BASEX: - tmp = DTSEC_ECNTRL_TBIM | DTSEC_ECNTRL_SGMIIM; + ecntrl = DTSEC_ECNTRL_TBIM | DTSEC_ECNTRL_SGMIIM; + maccfg2 |= MACCFG2_BYTE_MODE; break; default: dev_warn(mac_dev->dev, "cannot configure dTSEC for %s\n", @@ -923,7 +929,8 @@ static void dtsec_mac_config(struct phylink_config *config, unsigned int mode, return; } - iowrite32be(tmp, ®s->ecntrl); + iowrite32be(ecntrl, ®s->ecntrl); + iowrite32be(maccfg2, ®s->maccfg2); } static void dtsec_link_up(struct phylink_config *config, struct phy_device *phy, From 59a57128ae5231f9aa9d544fa9d3e38986f0efaa Mon Sep 17 00:00:00 2001 From: Luis Lang Date: Mon, 20 Jul 2026 13:15:29 +0200 Subject: [PATCH 165/234] net: stmmac: dwmac4: mask interrupts when stopping DMA in suspend Since commit 1b9707e6f1a9 ("net: stmmac: enable RPS and RBU interrupts"), suspending causes an interrupt storm from the RPS interrupt. Fix this by adding a deinit_chan() op to stmmac_dma_ops, which masks all default dma channel interrupts. This is called from stmmac_stop_all_dma(), so interrupts don't trigger while suspending. Fixes: 1b9707e6f1a9 ("net: stmmac: enable RPS and RBU interrupts") Suggested-by: Andrew Lunn Suggested-by: Maxime Chevallier Signed-off-by: Luis Lang Reviewed-by: Andrew Lunn Tested-by: Maxime Chevallier Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260720111534.163416-1-luis.la@mail.de Signed-off-by: Jakub Kicinski --- .../net/ethernet/stmicro/stmmac/dwmac4_dma.c | 24 +++++++++++++++++++ drivers/net/ethernet/stmicro/stmmac/hwif.h | 4 ++++ .../net/ethernet/stmicro/stmmac/stmmac_main.c | 4 ++++ 3 files changed, 32 insertions(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c index 829a23bdad01..23ffe1adcd0d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c @@ -106,6 +106,17 @@ static void dwmac4_dma_init_channel(struct stmmac_priv *priv, ioaddr + DMA_CHAN_INTR_ENA(dwmac4_addrs, chan)); } +static void dwmac4_dma_deinit_channel(struct stmmac_priv *priv, + void __iomem *ioaddr, u32 chan) +{ + const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs; + u32 value; + + value = readl(ioaddr + DMA_CHAN_INTR_ENA(dwmac4_addrs, chan)); + value &= ~DMA_CHAN_INTR_DEFAULT_MASK; + writel(value, ioaddr + DMA_CHAN_INTR_ENA(dwmac4_addrs, chan)); +} + static void dwmac410_dma_init_channel(struct stmmac_priv *priv, void __iomem *ioaddr, struct stmmac_dma_cfg *dma_cfg, u32 chan) @@ -125,6 +136,17 @@ static void dwmac410_dma_init_channel(struct stmmac_priv *priv, ioaddr + DMA_CHAN_INTR_ENA(dwmac4_addrs, chan)); } +static void dwmac410_dma_deinit_channel(struct stmmac_priv *priv, + void __iomem *ioaddr, u32 chan) +{ + const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs; + u32 value; + + value = readl(ioaddr + DMA_CHAN_INTR_ENA(dwmac4_addrs, chan)); + value &= ~DMA_CHAN_INTR_DEFAULT_MASK_4_10; + writel(value, ioaddr + DMA_CHAN_INTR_ENA(dwmac4_addrs, chan)); +} + static void dwmac4_dma_init(void __iomem *ioaddr, struct stmmac_dma_cfg *dma_cfg) { @@ -548,6 +570,7 @@ const struct stmmac_dma_ops dwmac4_dma_ops = { .reset = dwmac4_dma_reset, .init = dwmac4_dma_init, .init_chan = dwmac4_dma_init_channel, + .deinit_chan = dwmac4_dma_deinit_channel, .init_rx_chan = dwmac4_dma_init_rx_chan, .init_tx_chan = dwmac4_dma_init_tx_chan, .axi = dwmac4_dma_axi, @@ -577,6 +600,7 @@ const struct stmmac_dma_ops dwmac410_dma_ops = { .reset = dwmac4_dma_reset, .init = dwmac4_dma_init, .init_chan = dwmac410_dma_init_channel, + .deinit_chan = dwmac410_dma_deinit_channel, .init_rx_chan = dwmac4_dma_init_rx_chan, .init_tx_chan = dwmac4_dma_init_tx_chan, .axi = dwmac4_dma_axi, diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index e6317b94fff7..04dafec021b4 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -170,6 +170,8 @@ struct stmmac_dma_ops { void (*init)(void __iomem *ioaddr, struct stmmac_dma_cfg *dma_cfg); void (*init_chan)(struct stmmac_priv *priv, void __iomem *ioaddr, struct stmmac_dma_cfg *dma_cfg, u32 chan); + void (*deinit_chan)(struct stmmac_priv *priv, void __iomem *ioaddr, + u32 chan); void (*init_rx_chan)(struct stmmac_priv *priv, void __iomem *ioaddr, struct stmmac_dma_cfg *dma_cfg, dma_addr_t phy, u32 chan); @@ -235,6 +237,8 @@ struct stmmac_dma_ops { stmmac_do_void_callback(__priv, dma, init, __args) #define stmmac_init_chan(__priv, __args...) \ stmmac_do_void_callback(__priv, dma, init_chan, __priv, __args) +#define stmmac_deinit_chan(__priv, __args...) \ + stmmac_do_void_callback(__priv, dma, deinit_chan, __priv, __args) #define stmmac_init_rx_chan(__priv, __args...) \ stmmac_do_void_callback(__priv, dma, init_rx_chan, __priv, __args) #define stmmac_init_tx_chan(__priv, __args...) \ diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 2a0d7eff88d3..af29a50ddb89 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -2560,6 +2560,7 @@ static void stmmac_stop_all_dma(struct stmmac_priv *priv) { u8 rx_channels_count = priv->plat->rx_queues_to_use; u8 tx_channels_count = priv->plat->tx_queues_to_use; + u8 dma_csr_ch = max(rx_channels_count, tx_channels_count); u8 chan; for (chan = 0; chan < rx_channels_count; chan++) @@ -2567,6 +2568,9 @@ static void stmmac_stop_all_dma(struct stmmac_priv *priv) for (chan = 0; chan < tx_channels_count; chan++) stmmac_stop_tx_dma(priv, chan); + + for (chan = 0; chan < dma_csr_ch; chan++) + stmmac_deinit_chan(priv, priv->ioaddr, chan); } /** From dcd9b465965422b9654f6026e8a2fa8984f74c3c Mon Sep 17 00:00:00 2001 From: James Raphael Tiovalen Date: Tue, 21 Jul 2026 00:04:24 +0800 Subject: [PATCH 166/234] vxlan: mdb: Fix source list corruption on a failed replace When replacing the source list of an MDB remote entry, all existing sources are first marked for deletion and vxlan_mdb_remote_srcs_add() is then called to add the new source list. Sources present in the new list have their deletion mark cleared, and any sources left marked afterwards are removed. If vxlan_mdb_remote_srcs_add() fails partway through, its error path deletes all entries on the remote's source list. That rollback is only correct for its other caller, vxlan_mdb_remote_add(), where the remote was just allocated and the list contains solely entries added during the call. On the replace path the list also holds pre-existing sources, so a failed replace tears them down together with their (S, G) forwarding entries instead of leaving the entry unchanged. This is reachable from an existing (*, G) remote. An EXCLUDE filter that loses sources starts forwarding traffic that should be blocked, while an INCLUDE filter that loses sources drops traffic that should be forwarded. Mark entries created during the current pass with a new VXLAN_SGRP_F_NEW flag. On failure, delete only those entries and clear the deletion mark on the pre-existing ones, so a failed replace leaves the source list untouched. Retain the flag until the whole operation succeeds and then clear it. Also stop vxlan_mdb_remote_src_add() from deleting a pre-existing entry it only looked up when adding that entry's forwarding entry fails. Fixes: a3a48de5eade ("vxlan: mdb: Add MDB control path support") Cc: stable@vger.kernel.org Signed-off-by: James Raphael Tiovalen Reviewed-by: Ido Schimmel Reviewed-by: Antoine Tenart Reviewed-by: Nikolay Aleksandrov Link: https://patch.msgid.link/20260720160428.249356-1-jamestiotio@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/vxlan/vxlan_mdb.c | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/drivers/net/vxlan/vxlan_mdb.c b/drivers/net/vxlan/vxlan_mdb.c index 055a4969f593..af7a0d7f95a5 100644 --- a/drivers/net/vxlan/vxlan_mdb.c +++ b/drivers/net/vxlan/vxlan_mdb.c @@ -42,6 +42,7 @@ struct vxlan_mdb_remote { }; #define VXLAN_SGRP_F_DELETE BIT(0) +#define VXLAN_SGRP_F_NEW BIT(1) struct vxlan_mdb_src_entry { struct hlist_node node; @@ -844,6 +845,7 @@ vxlan_mdb_remote_src_add(const struct vxlan_mdb_config *cfg, ent = vxlan_mdb_remote_src_entry_add(remote, &src->addr); if (!ent) return -ENOMEM; + ent->flags |= VXLAN_SGRP_F_NEW; } else if (!(cfg->nlflags & NLM_F_REPLACE)) { NL_SET_ERR_MSG_MOD(extack, "Source entry already exists"); return -EEXIST; @@ -853,15 +855,16 @@ vxlan_mdb_remote_src_add(const struct vxlan_mdb_config *cfg, if (err) goto err_src_del; - /* Clear flags in case source entry was marked for deletion as part of - * replace flow. + /* Clear the deletion mark so the entry survives the replace sweep. + * The new mark is retained until the whole operation succeeds. */ - ent->flags = 0; + ent->flags &= ~VXLAN_SGRP_F_DELETE; return 0; err_src_del: - vxlan_mdb_remote_src_entry_del(ent); + if (ent->flags & VXLAN_SGRP_F_NEW) + vxlan_mdb_remote_src_entry_del(ent); return err; } @@ -889,11 +892,19 @@ static int vxlan_mdb_remote_srcs_add(const struct vxlan_mdb_config *cfg, goto err_src_del; } + hlist_for_each_entry(ent, &remote->src_list, node) + ent->flags &= ~VXLAN_SGRP_F_NEW; + return 0; err_src_del: - hlist_for_each_entry_safe(ent, tmp, &remote->src_list, node) - vxlan_mdb_remote_src_del(cfg->vxlan, &cfg->group, remote, ent); + hlist_for_each_entry_safe(ent, tmp, &remote->src_list, node) { + if (ent->flags & VXLAN_SGRP_F_NEW) + vxlan_mdb_remote_src_del(cfg->vxlan, &cfg->group, remote, + ent); + else + ent->flags &= ~VXLAN_SGRP_F_DELETE; + } return err; } @@ -1069,7 +1080,7 @@ vxlan_mdb_remote_srcs_replace(const struct vxlan_mdb_config *cfg, err = vxlan_mdb_remote_srcs_add(cfg, remote, extack); if (err) - goto err_clear_delete; + return err; hlist_for_each_entry_safe(ent, tmp, &remote->src_list, node) { if (ent->flags & VXLAN_SGRP_F_DELETE) @@ -1078,11 +1089,6 @@ vxlan_mdb_remote_srcs_replace(const struct vxlan_mdb_config *cfg, } return 0; - -err_clear_delete: - hlist_for_each_entry(ent, &remote->src_list, node) - ent->flags &= ~VXLAN_SGRP_F_DELETE; - return err; } static int vxlan_mdb_remote_replace(const struct vxlan_mdb_config *cfg, From 8e04823c120b376ef7dab14b60ebf6823aa16c14 Mon Sep 17 00:00:00 2001 From: Qing Luo Date: Tue, 21 Jul 2026 09:55:32 +0800 Subject: [PATCH 167/234] sctp: auth: verify auth requirement when auth_chunk is NULL sctp_auth_chunk_verify() returns true unconditionally when chunk->auth_chunk is NULL, silently skipping authentication. This is incorrect when: 1. skb_clone() failed in the BH receive path, leaving auth_chunk NULL. In sctp_endpoint_bh_rcv() asoc is NULL for new connections, so the early sctp_auth_recv_cid() check cannot catch this. 2. No AUTH chunk precedes COOKIE-ECHO, so skb_clone() is never called and auth_chunk remains NULL. Fix by checking sctp_auth_recv_cid() when auth_chunk is NULL: if authentication is required, return false to drop the chunk; otherwise continue normally. Fixes: bbd0d59809f9 ("[SCTP]: Implement the receive and verification of AUTH chunk") Signed-off-by: Qing Luo Acked-by: Xin Long Link: https://patch.msgid.link/20260721015532.120157-2-l1138897701@163.com Signed-off-by: Jakub Kicinski --- net/sctp/sm_statefuns.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/sctp/sm_statefuns.c b/net/sctp/sm_statefuns.c index 3893b44448b3..708fa07d5fff 100644 --- a/net/sctp/sm_statefuns.c +++ b/net/sctp/sm_statefuns.c @@ -643,7 +643,7 @@ static bool sctp_auth_chunk_verify(struct net *net, struct sctp_chunk *chunk, struct sctp_chunk auth; if (!chunk->auth_chunk) - return true; + return !sctp_auth_recv_cid(chunk->chunk_hdr->type, asoc); /* SCTP-AUTH: auth_chunk pointer is only set when the cookie-echo * is supposed to be authenticated and we have to do delayed From 34a71f5361fc3adb5b7138da78750b0d535a8252 Mon Sep 17 00:00:00 2001 From: Harshaka Narayana Date: Mon, 13 Jul 2026 07:09:15 -0700 Subject: [PATCH 168/234] vmxnet3: fix BUG_ON in vmxnet3_get_hdr_len() for Geneve packets vmxnet3_get_hdr_len() assumes gdesc->rcd.v4/v6/tcp always describe the outer header, but for a Geneve-encapsulated packet the device can set them based on the inner header instead, signalled by the VMXNET3_RCD_HDR_INNER_SHIFT bit in the completion descriptor. Since the function never skips the outer encapsulation, this mismatch triggers: - BUG_ON(hdr.ipv4->protocol != IPPROTO_TCP), because the outer protocol is UDP (Geneve), not TCP. - BUG_ON(hdr.eth->h_proto != ...), when the tunnel's outer and inner IP versions differ (e.g. outer IPv6/inner IPv4 or vice versa). Check VMXNET3_RCD_HDR_INNER_SHIFT up front and bail out, since the function cannot locate the inner header it would need to parse. Also convert the remaining BUG_ON()s in this function to return 0 defensively. Fixes: 45dac1d6ea04 ("vmxnet3: Changes for vmxnet3 adapter version 2 (fwd)") Signed-off-by: Harshaka Narayana Reviewed-by: Ronak Doshi Reviewed-by: Sankararaman Jayaraman Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260713140915.3381715-1-harshaka.narayana@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/vmxnet3/vmxnet3_drv.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/drivers/net/vmxnet3/vmxnet3_drv.c b/drivers/net/vmxnet3/vmxnet3_drv.c index 40522afc0532..f8df83f9965d 100644 --- a/drivers/net/vmxnet3/vmxnet3_drv.c +++ b/drivers/net/vmxnet3/vmxnet3_drv.c @@ -1530,7 +1530,11 @@ vmxnet3_get_hdr_len(struct vmxnet3_adapter *adapter, struct sk_buff *skb, struct ipv6hdr *ipv6; struct tcphdr *tcp; } hdr; - BUG_ON(gdesc->rcd.tcp == 0); + + /* v4/v6/tcp then describe the inner header, which we can't locate. */ + if ((le32_to_cpu(gdesc->dword[0]) & (1UL << VMXNET3_RCD_HDR_INNER_SHIFT)) || + gdesc->rcd.tcp == 0) + return 0; maplen = skb_headlen(skb); if (unlikely(sizeof(struct iphdr) + sizeof(struct tcphdr) > maplen)) @@ -1544,15 +1548,21 @@ vmxnet3_get_hdr_len(struct vmxnet3_adapter *adapter, struct sk_buff *skb, hdr.eth = eth_hdr(skb); if (gdesc->rcd.v4) { - BUG_ON(hdr.eth->h_proto != htons(ETH_P_IP) && - hdr.veth->h_vlan_encapsulated_proto != htons(ETH_P_IP)); + if (hdr.eth->h_proto != htons(ETH_P_IP) && + hdr.veth->h_vlan_encapsulated_proto != htons(ETH_P_IP)) + return 0; + hdr.ptr += hlen; - BUG_ON(hdr.ipv4->protocol != IPPROTO_TCP); + if (hdr.ipv4->protocol != IPPROTO_TCP) + return 0; + hlen = hdr.ipv4->ihl << 2; hdr.ptr += hdr.ipv4->ihl << 2; } else if (gdesc->rcd.v6) { - BUG_ON(hdr.eth->h_proto != htons(ETH_P_IPV6) && - hdr.veth->h_vlan_encapsulated_proto != htons(ETH_P_IPV6)); + if (hdr.eth->h_proto != htons(ETH_P_IPV6) && + hdr.veth->h_vlan_encapsulated_proto != htons(ETH_P_IPV6)) + return 0; + hdr.ptr += hlen; /* Use an estimated value, since we also need to handle * TSO case. From 92d3817649df2b0b6a008a686c8275c88d7ef594 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 14 Jul 2026 07:49:03 -0400 Subject: [PATCH 169/234] ila: reload IPv6 header after pskb_may_pull in checksum adjust ila_csum_adjust_transport() caches ip6h = ipv6_hdr(skb) before calling pskb_may_pull(). On a non-linear skb whose transport header sits in a page fragment, pskb_may_pull() can call __pskb_pull_tail() / pskb_expand_head() and free the old skb head, leaving ip6h dangling; the following get_csum_diff(ip6h, p) then reads freed memory. ila_update_ipv6_locator() uses ip6h (and the iaddr derived from it) again after the csum-adjust call and additionally writes the new locator through that pointer. Impact: a remote IPv6 packet routed through a configured ILA csum-adjust-transport route or receive-side mapping triggers a slab-use-after-free in ila_update_ipv6_locator() (KASAN). The route or mapping requires CAP_NET_ADMIN to configure, but trigger packets are unauthenticated once it exists. Reload ip6h after each pskb_may_pull() in ila_csum_adjust_transport() before the csum-diff read. In ila_update_ipv6_locator() only the ILA_CSUM_ADJUST_TRANSPORT case pulls the skb, so reload ip6h and iaddr in that case alone before the destination-address write; the neutral-map modes never pull and keep their cached pointers. Fixes: 33f11d16142b ("ila: Create net/ipv6/ila directory") Cc: stable@vger.kernel.org Signed-off-by: Michael Bommarito Reviewed-by: Simon Horman Reviewed-by: Antoine Tenart Link: https://patch.msgid.link/20260714114903.3763420-1-michael.bommarito@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv6/ila/ila_common.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/net/ipv6/ila/ila_common.c b/net/ipv6/ila/ila_common.c index e71571455c8a..b78179bfc4c7 100644 --- a/net/ipv6/ila/ila_common.c +++ b/net/ipv6/ila/ila_common.c @@ -85,6 +85,7 @@ static void ila_csum_adjust_transport(struct sk_buff *skb, struct tcphdr *th = (struct tcphdr *) (skb_network_header(skb) + nhoff); + ip6h = ipv6_hdr(skb); diff = get_csum_diff(ip6h, p); inet_proto_csum_replace_by_diff(&th->check, skb, diff, true, true); @@ -96,6 +97,7 @@ static void ila_csum_adjust_transport(struct sk_buff *skb, (skb_network_header(skb) + nhoff); if (uh->check || skb->ip_summed == CHECKSUM_PARTIAL) { + ip6h = ipv6_hdr(skb); diff = get_csum_diff(ip6h, p); inet_proto_csum_replace_by_diff(&uh->check, skb, diff, true, true); @@ -110,6 +112,7 @@ static void ila_csum_adjust_transport(struct sk_buff *skb, struct icmp6hdr *ih = (struct icmp6hdr *) (skb_network_header(skb) + nhoff); + ip6h = ipv6_hdr(skb); diff = get_csum_diff(ip6h, p); inet_proto_csum_replace_by_diff(&ih->icmp6_cksum, skb, diff, true, true); @@ -127,6 +130,15 @@ void ila_update_ipv6_locator(struct sk_buff *skb, struct ila_params *p, switch (p->csum_mode) { case ILA_CSUM_ADJUST_TRANSPORT: ila_csum_adjust_transport(skb, p); + /* + * ila_csum_adjust_transport() calls pskb_may_pull(), which can + * reallocate the skb head and leave ip6h (and the iaddr derived + * from it) dangling; reload both before the write below. The + * other csum modes do not pull, so their cached pointers stay + * valid. + */ + ip6h = ipv6_hdr(skb); + iaddr = ila_a2i(&ip6h->daddr); break; case ILA_CSUM_NEUTRAL_MAP: if (sir2ila) { From f43ee0c0730d6191629b5ee1ceae27b1ebfdc047 Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Wed, 15 Jul 2026 08:41:14 -0300 Subject: [PATCH 170/234] net/sched: serialize qdisc_rtab_list against concurrent get/put qdisc_get_rtab() and qdisc_put_rtab() mutate the process-global singly linked list qdisc_rtab_list and a plain non-atomic 'int refcnt' with no lock. This was only safe because every caller historically held the RTNL mutex, which serialized all rate-table lookups, inserts and frees. That invariant no longer holds. cls_flower sets TCF_PROTO_OPS_DOIT_UNLOCKED, so tc_new_tfilter() keeps rtnl_held == false for it and sets TCA_ACT_FLAGS_NO_RTNL. That flag propagates through tcf_exts_validate_ex() -> tcf_action_init() -> tcf_action_init_1() -> tcf_police_init(), which calls qdisc_get_rtab()/qdisc_put_rtab() with the RTNL mutex NOT held. Two RTM_NEWTFILTER requests on different CPUs, each adding a flower filter with a police action carrying the same rate, then race on qdisc_rtab_list and on the non-atomic refcnt, leading to a use-after-free / double-free of the kmalloc-2k struct qdisc_rate_table. qdisc_rtab_list is a single global (not per-netns), so the corrupted object is shared system-wide. BUG: KASAN: slab-use-after-free in qdisc_put_rtab+0x12f/0x160 qdisc_put_rtab+0x12f/0x160 tcf_police_init+0xda9/0x1590 tcf_action_init_1+0x460/0x6b0 tcf_action_init+0x439/0xa40 tcf_exts_validate_ex+0x42d/0x550 fl_change+0xddd/0x7da0 tc_new_tfilter+0xaa7/0x2420 rtnetlink_rcv_msg+0x95e/0xe90 which belongs to the cache kmalloc-2k of size 2048 Protect qdisc_rtab_list and the refcount with a dedicated spinlock. The (sleeping, GFP_KERNEL) allocation in qdisc_get_rtab() is performed before taking the lock; if a concurrent inserter added an identical table in the meantime the freshly allocated one is freed under the lock, so no duplicate is leaked. qdisc_put_rtab() now decrements the refcount and unlinks under the same lock. Fixes: 470502de5bdb ("net: sched: unlock rules update API") Suggested-by: Eric Dumazet Signed-off-by: Aldo Ariel Panzardo Cc: stable@vger.kernel.org Acked-by: Jamal Hadi Salim Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260715114114.446841-1-qwe.aldo@gmail.com Signed-off-by: Jakub Kicinski --- net/sched/sch_api.c | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/net/sched/sch_api.c b/net/sched/sch_api.c index 8a3236456db4..668bcd60d183 100644 --- a/net/sched/sch_api.c +++ b/net/sched/sch_api.c @@ -415,12 +415,13 @@ static __u8 __detect_linklayer(struct tc_ratespec *r, __u32 *rtab) } static struct qdisc_rate_table *qdisc_rtab_list; +static DEFINE_SPINLOCK(qdisc_rtab_lock); struct qdisc_rate_table *qdisc_get_rtab(struct tc_ratespec *r, struct nlattr *tab, struct netlink_ext_ack *extack) { - struct qdisc_rate_table *rtab; + struct qdisc_rate_table *rtab, *new_rtab; if (tab == NULL || r->rate == 0 || r->cell_log == 0 || r->cell_log >= 32 || @@ -429,15 +430,20 @@ struct qdisc_rate_table *qdisc_get_rtab(struct tc_ratespec *r, return NULL; } + new_rtab = kmalloc_obj(*new_rtab); + + spin_lock(&qdisc_rtab_lock); for (rtab = qdisc_rtab_list; rtab; rtab = rtab->next) { if (!memcmp(&rtab->rate, r, sizeof(struct tc_ratespec)) && !memcmp(&rtab->data, nla_data(tab), TC_RTAB_SIZE)) { rtab->refcnt++; + spin_unlock(&qdisc_rtab_lock); + kfree(new_rtab); return rtab; } } - rtab = kmalloc_obj(*rtab); + rtab = new_rtab; if (rtab) { rtab->rate = *r; rtab->refcnt = 1; @@ -449,6 +455,7 @@ struct qdisc_rate_table *qdisc_get_rtab(struct tc_ratespec *r, } else { NL_SET_ERR_MSG(extack, "Failed to allocate new qdisc rate table"); } + spin_unlock(&qdisc_rtab_lock); return rtab; } EXPORT_SYMBOL(qdisc_get_rtab); @@ -457,18 +464,25 @@ void qdisc_put_rtab(struct qdisc_rate_table *tab) { struct qdisc_rate_table *rtab, **rtabp; - if (!tab || --tab->refcnt) + if (!tab) return; + spin_lock(&qdisc_rtab_lock); + if (--tab->refcnt) { + spin_unlock(&qdisc_rtab_lock); + return; + } + for (rtabp = &qdisc_rtab_list; (rtab = *rtabp) != NULL; rtabp = &rtab->next) { if (rtab == tab) { *rtabp = rtab->next; - kfree(rtab); - return; + break; } } + spin_unlock(&qdisc_rtab_lock); + kfree(tab); } EXPORT_SYMBOL(qdisc_put_rtab); From 9f29cd8a8e7901a2617c8064ce9f50fc67b97cb8 Mon Sep 17 00:00:00 2001 From: "Cen Zhang (Microsoft)" Date: Tue, 14 Jul 2026 00:15:41 -0400 Subject: [PATCH 171/234] tipc: fix u16 MTU truncation in media and bearer MTU validation Both TIPC_NL_MEDIA_SET and TIPC_NL_BEARER_SET accept user-supplied MTU values but only enforce a minimum bound, not a maximum. When a user sets the MTU to a value exceeding U16_MAX (65535), it passes validation but is silently truncated when assigned to u16 fields l->mtu and l->advertised_mtu in tipc_link_create(). Values like 65536 (0x10000) truncate to 0, causing a division by zero in tipc_link_set_queue_limits() which computes TIPC_MAX_PUBL / (l->mtu / ITEM_SIZE). Other overflowing values (e.g. 65537-131071) produce small incorrect MTU values, resulting in link malfunction behaviors. Crash stack (triggered as unprivileged user via user namespace): tipc_link_set_queue_limits net/tipc/link.c:2531 tipc_link_create net/tipc/link.c:520 tipc_node_check_dest net/tipc/node.c:1279 tipc_disc_rcv net/tipc/discover.c:252 tipc_rcv net/tipc/node.c:2129 tipc_udp_recv net/tipc/udp_media.c:392 Two independent paths lack the upper bound check: 1. tipc_udp_mtu_bad() -- called from __tipc_nl_media_set() (MEDIA_SET) 2. inline check in __tipc_nl_bearer_set() at bearer.c:1160 (BEARER_SET) Fix both by rejecting MTU values above U16_MAX. Fixes: 901271e0403a ("tipc: implement configuration of UDP media MTU") Reported-by: AutonomousCodeSecurity@microsoft.com Closes: https://lore.kernel.org/all/CAB8m9WgETt0AjmFwE=F-CKjGXsK6_WDv0=kbYRcC8-noo+amnA@mail.gmail.com Reviewed-by: Vadim Fedorenko Signed-off-by: Cen Zhang (Microsoft) Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260714041541.307702-1-blbllhy@gmail.com Signed-off-by: Paolo Abeni --- net/tipc/netlink.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/tipc/netlink.c b/net/tipc/netlink.c index 8336a9664703..1307dd1a9613 100644 --- a/net/tipc/netlink.c +++ b/net/tipc/netlink.c @@ -113,12 +113,16 @@ const struct nla_policy tipc_nl_node_policy[TIPC_NLA_NODE_MAX + 1] = { }; /* Properties valid for media, bearer and link */ +static const struct netlink_range_validation tipc_nl_mtu_range = { + .max = U16_MAX, +}; + const struct nla_policy tipc_nl_prop_policy[TIPC_NLA_PROP_MAX + 1] = { [TIPC_NLA_PROP_UNSPEC] = { .type = NLA_UNSPEC }, [TIPC_NLA_PROP_PRIO] = { .type = NLA_U32 }, [TIPC_NLA_PROP_TOL] = { .type = NLA_U32 }, [TIPC_NLA_PROP_WIN] = { .type = NLA_U32 }, - [TIPC_NLA_PROP_MTU] = { .type = NLA_U32 }, + [TIPC_NLA_PROP_MTU] = NLA_POLICY_FULL_RANGE(NLA_U32, &tipc_nl_mtu_range), [TIPC_NLA_PROP_BROADCAST] = { .type = NLA_U32 }, [TIPC_NLA_PROP_BROADCAST_RATIO] = { .type = NLA_U32 } }; From 5499e0602d2faafd42c580d25f615903c3fbe11b Mon Sep 17 00:00:00 2001 From: David Lee Date: Mon, 13 Jul 2026 10:47:50 +0000 Subject: [PATCH 172/234] net/x25: fix use-after-free in x25_kill_by_neigh() x25_kill_by_neigh() walks the global X.25 socket list looking for sockets attached to a terminating neighbour. x25_list_lock protects list membership while the lookup is in progress, but it does not pin a socket's lifetime after the lock is dropped. The function currently drops x25_list_lock before calling lock_sock(s). A concurrent close can run x25_release(), remove the same socket from x25_list, and drop the last socket reference in that window. The neighbour teardown path can then lock or inspect a freed struct sock/struct x25_sock. Take sock_hold(s) while x25_list_lock still proves that the list entry is live, then drop the temporary reference after the socket has been locked, rechecked, and released. Recheck x25_sk(s)->neighbour after lock_sock(), because another path may have disconnected the socket before this path acquired the socket lock. Restart the list walk after each disconnect because the list lock was dropped and the previous iterator state may no longer be valid. A QEMU/KASAN run against origin/master reproduced a slab-use-after-free in x25_kill_by_neigh(). Fixes: 7781607938c8 ("net/x25: Fix null-ptr-deref caused by x25_disconnect") Cc: stable@vger.kernel.org Signed-off-by: David Lee Assisted-by: Codex:gpt-5.5 Acked-by: Martin Schiller Link: https://patch.msgid.link/20260713104752.241175-1-david.lee@trailofbits.com Signed-off-by: Paolo Abeni --- net/x25/af_x25.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/net/x25/af_x25.c b/net/x25/af_x25.c index c31d2af5dd22..8aae9273b7c1 100644 --- a/net/x25/af_x25.c +++ b/net/x25/af_x25.c @@ -1768,15 +1768,19 @@ void x25_kill_by_neigh(struct x25_neigh *nb) { struct sock *s; +again: write_lock_bh(&x25_list_lock); sk_for_each(s, &x25_list) { if (x25_sk(s)->neighbour == nb) { + sock_hold(s); write_unlock_bh(&x25_list_lock); lock_sock(s); - x25_disconnect(s, ENETUNREACH, 0, 0); + if (x25_sk(s)->neighbour == nb) + x25_disconnect(s, ENETUNREACH, 0, 0); release_sock(s); - write_lock_bh(&x25_list_lock); + sock_put(s); + goto again; } } write_unlock_bh(&x25_list_lock); From 980a813452754f8001704744e92f7aa697c53dd3 Mon Sep 17 00:00:00 2001 From: "Xiang Mei (Microsoft)" Date: Mon, 13 Jul 2026 23:32:30 +0000 Subject: [PATCH 173/234] bpf: tcp: fix double sock release on batch realloc bpf_iter_tcp_batch() releases the current batch via bpf_iter_tcp_put_batch(), which drops the socket refs and rewrites each slot with the socket cookie, then grows the batch. cur_sk/end_sk are kept for bpf_iter_tcp_resume(), but on realloc failure the function returns ERR_PTR() before resume runs, leaving cur_sk < end_sk over slots that now hold cookies rather than sock pointers. bpf_iter_tcp_seq_stop() then calls bpf_iter_tcp_put_batch() again and dereferences a cookie as a struct sock. Empty the batch on the failure path so stop() does not release it again. The sockets were already freed by the first bpf_iter_tcp_put_batch(), so nothing leaks, and a later read() rescans the bucket from the start instead of skipping it. The sibling GFP_NOWAIT failure path still holds real socket references and is left for stop() to release. BUG: KASAN: null-ptr-deref in __sock_gen_cookie Read of size 8 at addr 0000000000000059 by task exploit ... __sock_gen_cookie (net/core/sock_diag.c:28) bpf_iter_tcp_put_batch (net/ipv4/tcp_ipv4.c:2918) bpf_iter_tcp_seq_stop (net/ipv4/tcp_ipv4.c:3270) bpf_seq_read (kernel/bpf/bpf_iter.c:205) vfs_read (fs/read_write.c:572) ksys_read (fs/read_write.c:716) do_syscall_64 entry_SYSCALL_64_after_hwframe Kernel panic - not syncing: Fatal exception Fixes: cdec67a489d4 ("bpf: tcp: Make sure iter->batch always contains a full bucket snapshot") Reported-by: AutonomousCodeSecurity@microsoft.com Signed-off-by: Xiang Mei (Microsoft) Reviewed-by: Eric Dumazet Reviewed-by: Jordan Rife Link: https://patch.msgid.link/20260713233230.3553593-1-xmei5@asu.edu Signed-off-by: Paolo Abeni --- net/ipv4/tcp_ipv4.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c index aada52769057..b8887cdd66c5 100644 --- a/net/ipv4/tcp_ipv4.c +++ b/net/ipv4/tcp_ipv4.c @@ -3146,8 +3146,11 @@ static struct sock *bpf_iter_tcp_batch(struct seq_file *seq) bpf_iter_tcp_put_batch(iter); err = bpf_iter_tcp_realloc_batch(iter, expected * 3 / 2, GFP_USER); - if (err) + if (err) { + iter->cur_sk = 0; + iter->end_sk = 0; return ERR_PTR(err); + } sk = bpf_iter_tcp_resume(seq); if (!sk) From 9fcf274d93af17396f20cccb63f1d4c17492a000 Mon Sep 17 00:00:00 2001 From: Nazim Amirul Date: Mon, 13 Jul 2026 19:37:14 -0700 Subject: [PATCH 174/234] net: stmmac: xgmac: fix l4 filter port overwrite on register update The XGMAC_L4_ADDR register holds both source and destination port match values. The current implementation overwrites the entire register when configuring either port, so setting one silently erases the other. Fix this by reading the register first, then masking and updating only the relevant field before writing back. Fixes: 425eabddaf0f ("net: stmmac: Implement L3/L4 Filters using TC Flower") Signed-off-by: Rohan G Thomas Signed-off-by: Nazim Amirul Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260714023716.29865-3-muhammad.nazim.amirul.nazle.asmade@altera.com Reviewed-by: Jakub Raczynski Signed-off-by: Paolo Abeni --- .../ethernet/stmicro/stmmac/dwxgmac2_core.c | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c index f02b434bbd50..52054f31376d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c @@ -1370,36 +1370,40 @@ static int dwxgmac2_config_l4_filter(struct mac_device_info *hw, u32 filter_no, value &= ~XGMAC_L4PEN0; } - value &= ~(XGMAC_L4SPM0 | XGMAC_L4SPIM0); - value &= ~(XGMAC_L4DPM0 | XGMAC_L4DPIM0); if (sa) { value |= XGMAC_L4SPM0; if (inv) value |= XGMAC_L4SPIM0; + else + value &= ~XGMAC_L4SPIM0; } else { value |= XGMAC_L4DPM0; if (inv) value |= XGMAC_L4DPIM0; + else + value &= ~XGMAC_L4DPIM0; } ret = dwxgmac2_filter_write(hw, filter_no, XGMAC_L3L4_CTRL, value); if (ret) return ret; + ret = dwxgmac2_filter_read(hw, filter_no, XGMAC_L4_ADDR, &value); + if (ret) + return ret; + if (sa) { - value = FIELD_PREP(XGMAC_L4SP0, match); - - ret = dwxgmac2_filter_write(hw, filter_no, XGMAC_L4_ADDR, value); - if (ret) - return ret; + value &= ~XGMAC_L4SP0; + value |= FIELD_PREP(XGMAC_L4SP0, match); } else { - value = FIELD_PREP(XGMAC_L4DP0, match); - - ret = dwxgmac2_filter_write(hw, filter_no, XGMAC_L4_ADDR, value); - if (ret) - return ret; + value &= ~XGMAC_L4DP0; + value |= FIELD_PREP(XGMAC_L4DP0, match); } + ret = dwxgmac2_filter_write(hw, filter_no, XGMAC_L4_ADDR, value); + if (ret) + return ret; + if (!en) return dwxgmac2_filter_write(hw, filter_no, XGMAC_L3L4_CTRL, 0); From 5536d7c843637e9430279b94935fcf7df98babb3 Mon Sep 17 00:00:00 2001 From: Nazim Amirul Date: Mon, 13 Jul 2026 19:37:15 -0700 Subject: [PATCH 175/234] net: stmmac: fix l3l4 filter rejecting unsupported offload requests The basic flow parser in tc_add_basic_flow() does not validate match keys before proceeding. Unsupported offload configurations such as partial protocol masks, non-IPv4 network proto, or non-TCP/UDP transport proto are silently accepted instead of returning -EOPNOTSUPP. Add validation to return -EOPNOTSUPP early for: - No network or transport proto present in the key - Partial protocol mask (only full mask supported) - Network proto is not IPv4 - Transport proto is not TCP or UDP Each rejection includes an extack message so the user knows which part of the match is unsupported. Also propagate -EOPNOTSUPP from tc_add_basic_flow() in tc_add_flow() by returning it directly rather than using break. The break was silently discarding the error for FLOW_CLS_REPLACE operations where entry->in_use is already true, causing tc_add_flow() to return 0 (success) for unsupported replace requests. Fixes: 425eabddaf0f ("net: stmmac: Implement L3/L4 Filters using TC Flower") Signed-off-by: Rohan G Thomas Signed-off-by: Nazim Amirul Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260714023716.29865-4-muhammad.nazim.amirul.nazle.asmade@altera.com Reviewed-by: Jakub Raczynski Signed-off-by: Paolo Abeni --- .../net/ethernet/stmicro/stmmac/stmmac_tc.c | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c index d78652718599..1f8c9f47306b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c @@ -446,6 +446,7 @@ static int tc_parse_flow_actions(struct stmmac_priv *priv, } #define ETHER_TYPE_FULL_MASK cpu_to_be16(~0) +#define IP_PROTO_FULL_MASK 0xFF static int tc_add_basic_flow(struct stmmac_priv *priv, struct flow_cls_offload *cls, @@ -461,6 +462,37 @@ static int tc_add_basic_flow(struct stmmac_priv *priv, flow_rule_match_basic(rule, &match); + /* Both network proto and transport proto not present in the key */ + if (!match.mask || !(match.mask->n_proto || match.mask->ip_proto)) { + NL_SET_ERR_MSG_MOD(cls->common.extack, + "filter must specify network or transport protocol"); + return -EOPNOTSUPP; + } + + /* If the proto is present in the key and is not full mask */ + if ((match.mask->n_proto && match.mask->n_proto != ETHER_TYPE_FULL_MASK) || + (match.mask->ip_proto && match.mask->ip_proto != IP_PROTO_FULL_MASK)) { + NL_SET_ERR_MSG_MOD(cls->common.extack, + "only full protocol mask is supported"); + return -EOPNOTSUPP; + } + + /* Network proto is present in the key and is not IPv4 */ + if (match.mask->n_proto && match.key->n_proto != cpu_to_be16(ETH_P_IP)) { + NL_SET_ERR_MSG_MOD(cls->common.extack, + "only IPv4 network protocol is supported"); + return -EOPNOTSUPP; + } + + /* Transport proto is present in the key and is not TCP or UDP */ + if (match.mask->ip_proto && + match.key->ip_proto != IPPROTO_TCP && + match.key->ip_proto != IPPROTO_UDP) { + NL_SET_ERR_MSG_MOD(cls->common.extack, + "only TCP and UDP transport protocols are supported"); + return -EOPNOTSUPP; + } + entry->ip_proto = match.key->ip_proto; return 0; } @@ -598,6 +630,8 @@ static int tc_add_flow(struct stmmac_priv *priv, ret = tc_flow_parsers[i].fn(priv, cls, entry); if (!ret) entry->in_use = true; + else if (ret == -EOPNOTSUPP) + return ret; } if (!entry->in_use) From a448f821289934b961dd9d8d0beb006cc8937ba2 Mon Sep 17 00:00:00 2001 From: Nazim Amirul Date: Mon, 13 Jul 2026 19:37:16 -0700 Subject: [PATCH 176/234] net: stmmac: reset residual action in L3L4 filters on delete When deleting an L3/L4 flower filter entry, the action field is not reset. If a filter was previously configured with a drop action, that action may persist and affect subsequent filter configurations unintentionally. Clear the action field when the filter entry is deleted. Fixes: 425eabddaf0f ("net: stmmac: Implement L3/L4 Filters using TC Flower") Signed-off-by: Rohan G Thomas Signed-off-by: Nazim Amirul Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260714023716.29865-5-muhammad.nazim.amirul.nazle.asmade@altera.com Reviewed-by: Jakub Raczynski Signed-off-by: Paolo Abeni --- drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c index 1f8c9f47306b..14cabe76e53e 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c @@ -661,6 +661,7 @@ static int tc_del_flow(struct stmmac_priv *priv, entry->in_use = false; entry->cookie = 0; entry->is_l4 = false; + entry->action = 0; return ret; } From 9c99db3a2080b8c2cbbb1100369586a9bea43321 Mon Sep 17 00:00:00 2001 From: vadik likholetov Date: Mon, 13 Jul 2026 10:49:11 +0300 Subject: [PATCH 177/234] net: stmmac: enable the MAC on link up for all supported speeds stmmac_mac_link_down() clears the MAC's transmit and receive enable bits. stmmac_mac_link_up() is expected to set them again through stmmac_mac_set(..., true), but it first switches on the negotiated speed and returns early for a speed the switch does not list. The MAC is then left gated off. The speed selection is split into three switches, keyed on the interface. The generic branch -- taken for everything that is neither USXGMII nor XLGMII, so including PHY_INTERFACE_MODE_10GBASER -- lists only SPEED_2500, SPEED_1000, SPEED_100 and SPEED_10. MGBE on Tegra234 runs 10GBASE-R into an Aquantia AQR113C. That PHY does rate matching, so phylink_link_up() replaces the media speed with the MAC-side interface speed before calling into the MAC: case RATE_MATCH_PAUSE: speed = phylink_interface_max_speed(link_state.interface); duplex = DUPLEX_FULL; The driver is therefore called as stmmac_mac_link_up(interface=10GBASER, speed=10000, duplex=1) which falls through to "default: return;". The interface stops passing traffic after the first link flap. The failure is easy to misread. The link still comes up, because the PHY is polled over MDIO and needs no MAC, so the interface reports carrier 1 at the media speed. The DMA is untouched, so its start bits stay set and descriptors are still consumed. Only the MAC itself is gated off: the receiver counts nothing (mmc_rx_framecount_gb stops advancing, RE is 0) and nothing reaches the wire (TE is 0). The interface survives boot only because stmmac_hw_setup(), called from ndo_open, enables the MAC unconditionally -- so the problem appears only once the cable has been unplugged and plugged back in, and "ip link set dev down && ip link set dev up" appears to fix it. The interface is not what the speed bits depend on: with the single exception of 2.5G, which is selected through the XGMII block on USXGMII and through the regular speed bits otherwise, each speed maps to one field of struct mac_link. The per-interface switches are speed validation, and phylink already validates the speed against priv->hw->link.caps. So collapse the three switches into one keyed on the speed alone, keeping the interface test only for the 2.5G case. This covers 10G on 10GBASE-R, and equally 5G, and 1G/100/10 on USXGMII, all of which hit "default: return;" today. A core that does not support a speed leaves the corresponding mac_link field at 0, and phylink will not offer it that speed in the first place. For dwxgmac2 at 10G, link.xgmii.speed10000 is XGMAC_CONFIG_SS_10000, which is 0 and is the correct speed selection for a 10GBASE-R MAC: ctrl then equals old_ctrl, the register write is skipped, and execution reaches stmmac_mac_set(..., true). Log an error in the default case, since a speed with no entry here leaves the MAC disabled and the symptom does not point at the cause. Fixes: d8ca113724e7 ("net: stmmac: tegra: Add MGBE support") Suggested-by: Maxime Chevallier Signed-off-by: vadik likholetov Reviewed-by: Jacob Keller Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260713074911.30090-1-vadikas@gmail.com Signed-off-by: Paolo Abeni --- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 92 ++++++++----------- 1 file changed, 37 insertions(+), 55 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index af29a50ddb89..151c77713025 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -1083,63 +1083,45 @@ static void stmmac_mac_link_up(struct phylink_config *config, old_ctrl = readl(priv->ioaddr + MAC_CTRL_REG); ctrl = old_ctrl & ~priv->hw->link.speed_mask; - if (interface == PHY_INTERFACE_MODE_USXGMII) { - switch (speed) { - case SPEED_10000: - ctrl |= priv->hw->link.xgmii.speed10000; - break; - case SPEED_5000: - ctrl |= priv->hw->link.xgmii.speed5000; - break; - case SPEED_2500: + switch (speed) { + case SPEED_100000: + ctrl |= priv->hw->link.xlgmii.speed100000; + break; + case SPEED_50000: + ctrl |= priv->hw->link.xlgmii.speed50000; + break; + case SPEED_40000: + ctrl |= priv->hw->link.xlgmii.speed40000; + break; + case SPEED_25000: + ctrl |= priv->hw->link.xlgmii.speed25000; + break; + case SPEED_10000: + ctrl |= priv->hw->link.xgmii.speed10000; + break; + case SPEED_5000: + ctrl |= priv->hw->link.xgmii.speed5000; + break; + case SPEED_2500: + if (interface == PHY_INTERFACE_MODE_USXGMII) ctrl |= priv->hw->link.xgmii.speed2500; - break; - default: - return; - } - } else if (interface == PHY_INTERFACE_MODE_XLGMII) { - switch (speed) { - case SPEED_100000: - ctrl |= priv->hw->link.xlgmii.speed100000; - break; - case SPEED_50000: - ctrl |= priv->hw->link.xlgmii.speed50000; - break; - case SPEED_40000: - ctrl |= priv->hw->link.xlgmii.speed40000; - break; - case SPEED_25000: - ctrl |= priv->hw->link.xlgmii.speed25000; - break; - case SPEED_10000: - ctrl |= priv->hw->link.xgmii.speed10000; - break; - case SPEED_2500: + else ctrl |= priv->hw->link.speed2500; - break; - case SPEED_1000: - ctrl |= priv->hw->link.speed1000; - break; - default: - return; - } - } else { - switch (speed) { - case SPEED_2500: - ctrl |= priv->hw->link.speed2500; - break; - case SPEED_1000: - ctrl |= priv->hw->link.speed1000; - break; - case SPEED_100: - ctrl |= priv->hw->link.speed100; - break; - case SPEED_10: - ctrl |= priv->hw->link.speed10; - break; - default: - return; - } + break; + case SPEED_1000: + ctrl |= priv->hw->link.speed1000; + break; + case SPEED_100: + ctrl |= priv->hw->link.speed100; + break; + case SPEED_10: + ctrl |= priv->hw->link.speed10; + break; + default: + netdev_err(priv->dev, + "unsupported speed %s on %s, leaving the MAC disabled\n", + phy_speed_to_str(speed), phy_modes(interface)); + return; } if (priv->plat->fix_mac_speed) From ba0533fc163f905fe817cfabdf8ed4058da44800 Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Tue, 14 Jul 2026 22:19:39 +0900 Subject: [PATCH 178/234] tipc: clear sock->sk on the failed-insert path in tipc_sk_create() When tipc_sk_create() fails to insert the new socket (tipc_sk_insert() returns non-zero), its error path frees the sk with sk_free() but leaves sock->sk pointing at the freed object: if (tipc_sk_insert(tsk)) { sk_free(sk); pr_warn("Socket create failed; port number exhausted\n"); return -EINVAL; } This is harmless for plain socket(): the syscall layer clears sock->ops before releasing, so tipc_release() is never called. It is not harmless on the accept() path. tipc_accept() creates the pre-allocated child socket with tipc_sk_create(net, new_sock, 0, kern); on failure it leaves new_sock->sk dangling and new_sock->ops non-NULL, and do_accept() then fput()s the new file, so __sock_release() -> tipc_release() runs lock_sock(new_sock->sk) on the freed sk -- a use-after-free write of the sk_lock spinlock. tipc_release() already guards this exact "failed accept() releases a pre-allocated child" case with "if (sk == NULL) return 0;", but the guard is bypassed because tipc_sk_create() left sock->sk non-NULL (dangling) rather than NULL. Clear sock->sk on the failed-insert path so the existing tipc_release() NULL check fires and the use-after-free is avoided. The tipc_sk_insert() failure is reached when the per-netns socket rhashtable hits its max_size (tsk_rht_params.max_size = 1048576, ~2M elements) -- i.e. once a netns holds ~2M TIPC sockets every insert returns -E2BIG. BUG: KASAN: slab-use-after-free in lock_sock_nested (net/core/sock.c:3839) Write of size 8 at addr ffff8880047cdc38 by task init/1 lock_sock_nested (net/core/sock.c:3839) tipc_release (net/tipc/socket.c:638) __sock_release (net/socket.c:710) sock_close (net/socket.c:1501) __fput (fs/file_table.c:512) Allocated by task 1: sk_alloc (net/core/sock.c:2308) tipc_sk_create (net/tipc/socket.c:487) tipc_accept (net/tipc/socket.c:2744) do_accept (net/socket.c:2034) Freed by task 1: __sk_destruct (net/core/sock.c:2391) tipc_sk_create (net/tipc/socket.c:504) tipc_accept (net/tipc/socket.c:2744) do_accept (net/socket.c:2034) Fixes: 00aff3590fc0 ("net: tipc: fix possible refcount leak in tipc_sk_create()") Cc: stable@vger.kernel.org Reviewed-by: Tung Nguyen Reviewed-by: Breno Leitao Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260714131939.1255974-1-4ncienth@gmail.com Signed-off-by: Paolo Abeni --- net/tipc/socket.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/tipc/socket.c b/net/tipc/socket.c index e564341e0216..55e695748332 100644 --- a/net/tipc/socket.c +++ b/net/tipc/socket.c @@ -502,6 +502,7 @@ static int tipc_sk_create(struct net *net, struct socket *sock, tipc_set_sk_state(sk, TIPC_OPEN); if (tipc_sk_insert(tsk)) { sk_free(sk); + sock->sk = NULL; pr_warn("Socket create failed; port number exhausted\n"); return -EINVAL; } From 675ed582c1aa4d919dd535490de08c015005c653 Mon Sep 17 00:00:00 2001 From: Yun Zhou Date: Mon, 13 Jul 2026 23:09:45 +0800 Subject: [PATCH 179/234] net: gre: fix lltx regression for GRE tunnels with SEQ/CSUM Before commit 00d066a4d4ed ("netdev_features: convert NETIF_F_LLTX to dev->lltx"), NETIF_F_LLTX was set unconditionally in both __gre_tunnel_init() and ip6gre_tnl_init_features() alongside GRE_FEATURES: dev->features |= GRE_FEATURES | NETIF_F_LLTX; When that commit converted NETIF_F_LLTX to the dev->lltx flag, it placed 'dev->lltx = true' after the SEQ/CSUM early returns instead of before them. This causes GRE/GRETAP/ip6gre tunnels with SEQ or CSUM+encap to lose lockless TX, reintroducing _xmit_lock acquisition around their ndo_start_xmit. Since GRE xmit re-enters the stack via ip_tunnel_xmit(), holding _xmit_lock risks ABBA deadlock with the underlay device. CPU0 CPU1 ---- ---- lock(&qdisc_xmit_lock_key#6); lock(&qdisc_xmit_lock_key#3); lock(&qdisc_xmit_lock_key#6); lock(&qdisc_xmit_lock_key#3); Fix by moving dev->lltx = true before the early returns in both functions, restoring the original unconditional behavior. Fixes: 00d066a4d4ed ("netdev_features: convert NETIF_F_LLTX to dev->lltx") Signed-off-by: Yun Zhou Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260713150945.1779628-1-yun.zhou@windriver.com Signed-off-by: Paolo Abeni --- net/ipv4/ip_gre.c | 4 ++-- net/ipv6/ip6_gre.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c index 3efdfb4ffa21..0ba1e94e9012 100644 --- a/net/ipv4/ip_gre.c +++ b/net/ipv4/ip_gre.c @@ -1018,6 +1018,8 @@ static void __gre_tunnel_init(struct net_device *dev) dev->features |= GRE_FEATURES; dev->hw_features |= GRE_FEATURES; + dev->lltx = true; + /* TCP offload with GRE SEQ is not supported, nor can we support 2 * levels of outer headers requiring an update. */ @@ -1029,8 +1031,6 @@ static void __gre_tunnel_init(struct net_device *dev) dev->features |= NETIF_F_GSO_SOFTWARE; dev->hw_features |= NETIF_F_GSO_SOFTWARE; - - dev->lltx = true; } static int ipgre_tunnel_init(struct net_device *dev) diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c index 7c09a269b352..b843116e9b70 100644 --- a/net/ipv6/ip6_gre.c +++ b/net/ipv6/ip6_gre.c @@ -1455,6 +1455,8 @@ static void ip6gre_tnl_init_features(struct net_device *dev) dev->features |= GRE6_FEATURES; dev->hw_features |= GRE6_FEATURES; + dev->lltx = true; + /* TCP offload with GRE SEQ is not supported, nor can we support 2 * levels of outer headers requiring an update. */ @@ -1466,8 +1468,6 @@ static void ip6gre_tnl_init_features(struct net_device *dev) dev->features |= NETIF_F_GSO_SOFTWARE; dev->hw_features |= NETIF_F_GSO_SOFTWARE; - - dev->lltx = true; } static int ip6gre_tunnel_init_common(struct net_device *dev) From 3671f0419d90b98a02f313830595ab958c8b2025 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Thu, 16 Jul 2026 17:06:07 +0000 Subject: [PATCH 180/234] mpls: Set rt->rt_nhn just before returning from mpls_nh_build_multi(). Commit f0914b8436c5 ("mpls: Hold dev refcnt for mpls_nh.") added change_nexthops() loop to call netdev_put() for the nexthop devices before freeing mpls_route. Then, mpls_nh_build_multi() was also changed to avoid iterating uninitialised nexthops in mpls_rt_free_rcu(). However, setting rt->rt_nhn to 0 at the entry of mpls_nh_build_multi() makes the following change_nexthops() no-op. Let's set rt->rt_nhn just before returning from mpls_nh_build_multi(). Fixes: f0914b8436c5 ("mpls: Hold dev refcnt for mpls_nh.") Reported-by: Anthony Doeraene Closes: https://lore.kernel.org/netdev/036a0c95-f5d4-46ab-88e7-1eab567d7a84@uclouvain.be/ Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260716170609.804629-1-kuniyu@google.com Signed-off-by: Paolo Abeni --- net/mpls/af_mpls.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/net/mpls/af_mpls.c b/net/mpls/af_mpls.c index 318cb7e2ac5f..4406c304b639 100644 --- a/net/mpls/af_mpls.c +++ b/net/mpls/af_mpls.c @@ -922,8 +922,7 @@ static int mpls_nh_build_multi(struct mpls_route_config *cfg, struct nlattr *nla_via, *nla_newdst; int remaining = cfg->rc_mp_len; int err = 0; - - rt->rt_nhn = 0; + u8 nhs = 0; change_nexthops(rt) { int attrlen; @@ -959,12 +958,15 @@ static int mpls_nh_build_multi(struct mpls_route_config *cfg, rt->rt_nhn_alive--; rtnh = rtnh_next(rtnh, &remaining); - rt->rt_nhn++; + nhs++; } endfor_nexthops(rt); + rt->rt_nhn = nhs; + return 0; errout: + rt->rt_nhn = nhs; return err; } From 0d4d31e3cc5dd6204fa1495c4107f5075acce5ed Mon Sep 17 00:00:00 2001 From: Suman Ghosh Date: Wed, 15 Jul 2026 10:50:07 +0530 Subject: [PATCH 181/234] octeontx2-vf: set TC flower flag on MCAM entry allocation When MCAM entries are allocated for a VF netdev via the devlink mcam_count parameter, only OTX2_FLAG_NTUPLE_SUPPORT was set. That enabled ethtool ntuple filters but not tc flower offload. Also set OTX2_FLAG_TC_FLOWER_SUPPORT when entries are successfully allocated. Fixes: 2da489432747 ("octeontx2-pf: devlink params support to set mcam entry count") Signed-off-by: Suman Ghosh Signed-off-by: Ratheesh Kannoth Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260715052007.2099851-1-rkannoth@marvell.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/marvell/octeontx2/nic/otx2_flows.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_flows.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_flows.c index 5dd0591fed99..99d78fc5a2c4 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_flows.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_flows.c @@ -272,6 +272,7 @@ int otx2_alloc_mcam_entries(struct otx2_nic *pfvf, u16 count) if (allocated) { pfvf->flags |= OTX2_FLAG_MCAM_ENTRIES_ALLOC; pfvf->flags |= OTX2_FLAG_NTUPLE_SUPPORT; + pfvf->flags |= OTX2_FLAG_TC_FLOWER_SUPPORT; } if (allocated != count) From 793b9b729f1e8de57be8c8daf1a9838be96cabed Mon Sep 17 00:00:00 2001 From: Doruk Tan Ozturk Date: Wed, 15 Jul 2026 10:20:21 +0200 Subject: [PATCH 182/234] mctp: serial: handle zero-length frames to prevent rx buffer overflow The MCTP serial receive state machine reads a frame length byte in mctp_serial_push_header() case 2 and validates it upper-bound-only: if (c > MCTP_SERIAL_FRAME_MTU) { dev->rxstate = STATE_ERR; } else { dev->rxlen = c; dev->rxpos = 0; dev->rxstate = STATE_DATA; ... } A length of zero passes this check, so rxlen is set to 0 and the state machine advances to STATE_DATA. In mctp_serial_push() STATE_DATA, the incoming byte is stored and rxpos incremented before the terminator is tested: dev->rxbuf[dev->rxpos] = c; dev->rxpos++; dev->rxstate = STATE_DATA; if (dev->rxpos == dev->rxlen) { dev->rxpos = 0; dev->rxstate = STATE_TRAILER; } With rxlen == 0 the "rxpos == rxlen" terminator can never fire (rxpos is already 1 on the first data byte), so subsequent bytes are written past the end of the fixed 74-byte rxbuf, which is the last member of the netdev private area. Every following data byte is an attacker-controlled 1-byte out-of-bounds heap write, and the overflow continues until a frame (0x7e) or escape byte resets the parser -- effectively unbounded. Reaching this requires CAP_NET_ADMIN to attach the N_MCTP line discipline and bring the resulting mctpserialN netdev up, after which the bytes arrive via the tty receive path. Route a zero-length frame straight to STATE_TRAILER instead of STATE_DATA. The trailer/framing bytes are still consumed, and the frame resolves to a zero-length skb that the MCTP core rejects; the parser never enters STATE_DATA with rxlen == 0, so the out-of-bounds write can no longer occur. KASAN, on a frame of 0x7e 0x01 0x00 followed by data bytes (before this change): UBSAN: array-index-out-of-bounds in drivers/net/mctp/mctp-serial.c:370 index 74 is out of range for type 'u8 [74]' BUG: KASAN: slab-out-of-bounds in mctp_serial_tty_receive_buf Write of size 1 at addr ... by task kworker/u16:0 mctp_serial_tty_receive_buf tty_ldisc_receive_buf flush_to_ldisc Allocated by task 152: alloc_netdev_mqs mctp_serial_open v2: route zero-length frames to STATE_TRAILER instead of STATE_ERR so the trailer/framing bytes are still consumed (Jeremy Kerr). Found by 0sec automated security-research tooling (https://0sec.ai). Fixes: a0c2ccd9b5ad ("mctp: Add MCTP-over-serial transport binding") Cc: stable@vger.kernel.org Suggested-by: Jeremy Kerr Assisted-by: 0sec:multi-model Signed-off-by: Doruk Tan Ozturk Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260715082021.46315-1-doruk@0sec.ai Signed-off-by: Paolo Abeni --- drivers/net/mctp/mctp-serial.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/mctp/mctp-serial.c b/drivers/net/mctp/mctp-serial.c index 26c9a33fd636..a5070ffa9a95 100644 --- a/drivers/net/mctp/mctp-serial.c +++ b/drivers/net/mctp/mctp-serial.c @@ -318,7 +318,7 @@ static void mctp_serial_push_header(struct mctp_serial *dev, u8 c) } else { dev->rxlen = c; dev->rxpos = 0; - dev->rxstate = STATE_DATA; + dev->rxstate = c > 0 ? STATE_DATA : STATE_TRAILER; dev->rxfcs = crc_ccitt_byte(dev->rxfcs, c); } break; From 249447ff83967980ed6751660665cc682ff84e0c Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Wed, 22 Jul 2026 15:41:43 +0200 Subject: [PATCH 183/234] MAINTAINERS: remove Rengarajan Sundararajan from LAN78XX Rengarajan has left Microchip and mails to his address bounce. Remove him from the USB LAN78XX entry. Link: https://lore.kernel.org/netdev/DSWPR11MB971547088066C2CA91638F3BECF42@DSWPR11MB9715.namprd11.prod.outlook.com/ Signed-off-by: Nicolai Buchwitz Reviewed-by: Thangaraj Samynathan Link: https://patch.msgid.link/20260722134143.4141579-1-nb@tipi-net.de Signed-off-by: Jakub Kicinski --- MAINTAINERS | 1 - 1 file changed, 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 6fd196545966..b8b3b3b7e183 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -27930,7 +27930,6 @@ F: drivers/usb/isp1760/* USB LAN78XX ETHERNET DRIVER M: Thangaraj Samynathan -M: Rengarajan Sundararajan M: UNGLinuxDriver@microchip.com L: netdev@vger.kernel.org S: Maintained From ea20c44935d6142daecfa9b39d635033a7553e1b Mon Sep 17 00:00:00 2001 From: Shihuang Liu Date: Wed, 22 Jul 2026 19:39:19 +0800 Subject: [PATCH 184/234] amt: fix use-after-free in AMT delayed works When an AMT device is removed, pending delayed works can still access the freed amt_dev structure, which may result in kernel crashes or memory corruption. amt_dev_stop() cancels req_wq and discovery_wq with cancel_delayed_work_sync(), but these works can be scheduled again from event_wq after the cancellation. This allows delayed works to access the freed amt_dev structure after the netdev has been released. The following is a simple race scenario: CPU0 CPU1 amt_dev_stop() cancel_delayed_work_sync() amt_event_work() mod_delayed_work(req_wq) free netdev req_wq accesses freed amt_dev Use disable_delayed_work_sync() in amt_dev_stop() to prevent req_wq and discovery_wq from being queued again and wait for running work items to complete. The delayed works are disabled after initialization in amt_newlink() and enabled only when the device is successfully opened. This keeps the delayed work lifecycle synchronized with the lifetime of the AMT device. Fixes: cbc21dc1cfe9 ("amt: add data plane of amt interface") Cc: stable@vger.kernel.org Signed-off-by: Shihuang Liu Reviewed-by: Simon Horman Reviewed-by: Taehee Yoo Link: https://patch.msgid.link/20260722113919.7723-1-shlomojune6@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/amt.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/drivers/net/amt.c b/drivers/net/amt.c index b733309b866f..182a41d59a75 100644 --- a/drivers/net/amt.c +++ b/drivers/net/amt.c @@ -3034,9 +3034,15 @@ static int amt_dev_open(struct net_device *dev) amt->event_idx = 0; amt->nr_events = 0; + enable_delayed_work(&amt->discovery_wq); + enable_delayed_work(&amt->req_wq); + err = amt_socket_create(amt); - if (err) + if (err) { + disable_delayed_work(&amt->req_wq); + disable_delayed_work(&amt->discovery_wq); return err; + } amt->req_cnt = 0; amt->remote_ip = 0; @@ -3062,8 +3068,8 @@ static int amt_dev_stop(struct net_device *dev) struct sock *sk; int i; - cancel_delayed_work_sync(&amt->req_wq); - cancel_delayed_work_sync(&amt->discovery_wq); + disable_delayed_work_sync(&amt->req_wq); + disable_delayed_work_sync(&amt->discovery_wq); cancel_delayed_work_sync(&amt->secret_wq); /* shutdown */ @@ -3317,6 +3323,8 @@ static int amt_newlink(struct net_device *dev, INIT_DELAYED_WORK(&amt->req_wq, amt_req_work); INIT_DELAYED_WORK(&amt->secret_wq, amt_secret_work); INIT_WORK(&amt->event_wq, amt_event_work); + disable_delayed_work(&amt->req_wq); + disable_delayed_work(&amt->discovery_wq); INIT_LIST_HEAD(&amt->tunnel_list); return 0; err: From 853e164c2b321f0711361bc23505aaeb7dc432c3 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 22 Jul 2026 10:42:36 +0000 Subject: [PATCH 185/234] ipv4: icmp: fill flow parameters in icmp_route_lookup decoy lookup When Linux forwards a packet and needs to generate an ICMP error, icmp_route_lookup() performs a reverse-path relookup. For non-local destinations, it performs a decoy lookup to find the expected egress interface (rt2->dst.dev) before validating the path with ip_route_input(). Currently, the decoy flow structure (fl4_2) only sets .daddr = fl4_dec.saddr, leaving .saddr, .flowi4_dscp, .flowi4_proto, .flowi4_mark, .flowi4_oif, .fl4_sport, .fl4_dport, and .flowi4_uid zeroed out. When policy routing rules (such as ip rule add from $SRC lookup 100, or dscp/fwmark/ipproto/port rules, or VRF bindings) are configured: 1. The decoy lookup fails to match the policy rule because saddr and other key flow selectors are missing in fl4_2. 2. It resolves a route using the default table instead, returning an incorrect egress netdev. 3. Passing the wrong netdev to ip_route_input() causes strict reverse-path filtering (rp_filter=1) to fail, logging false-positive "martian source" warnings and causing the relookup to fail. Fix this by initializing fl4_2 from fl4_dec and: - Swapping source/destination IP addresses. - Swapping L4 ports for transport protocols with ports (TCP, UDP, SCTP, DCCP) so port-based policy routing matches correctly. Non-port protocols (such as ICMP or GRE) leave the flowi_uli union fields intact to prevent corruption. - Setting .flowi4_oif = l3mdev_master_ifindex(route_lookup_dev) to ensure VRF routing tables are respected. - Setting .flowi4_flags |= FLOWI_FLAG_ANYSRC to allow output route lookups for non-local source IP addresses. - Using __ip_route_output_key() instead of ip_route_output_key() for fl4_2 so that raw FIB routing is used without triggering spurious XFRM policy lookups on the decoy flow (the actual XFRM lookup is performed later using fl4_dec). Fixes: 415b3334a21a ("icmp: Fix regression in nexthop resolution during replies.") Reported-by: Muhammad Ziad Closes: https://lore.kernel.org/netdev/CAOAwikA60AYKdFr_UDLyja3oU4hqyAE7uFZWqum5uRdaQsgRYg@mail.gmail.com/ Signed-off-by: Eric Dumazet Reviewed-by: David Ahern Link: https://patch.msgid.link/20260722104236.2938082-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/ipv4/icmp.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c index 23e921d313b3..0caedfc7ca92 100644 --- a/net/ipv4/icmp.c +++ b/net/ipv4/icmp.c @@ -548,11 +548,23 @@ static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4, if (IS_ERR(rt2)) err = PTR_ERR(rt2); } else { - struct flowi4 fl4_2 = {}; + struct flowi4 fl4_2 = fl4_dec; unsigned long orefdst; - fl4_2.daddr = fl4_dec.saddr; - rt2 = ip_route_output_key(net, &fl4_2); + swap(fl4_2.daddr, fl4_2.saddr); + switch (fl4_2.flowi4_proto) { + case IPPROTO_TCP: + case IPPROTO_UDP: + case IPPROTO_SCTP: + case IPPROTO_DCCP: + swap(fl4_2.fl4_sport, fl4_2.fl4_dport); + break; + } + + fl4_2.flowi4_oif = l3mdev_master_ifindex(route_lookup_dev); + fl4_2.flowi4_flags |= FLOWI_FLAG_ANYSRC; + + rt2 = __ip_route_output_key(net, &fl4_2); if (IS_ERR(rt2)) { err = PTR_ERR(rt2); goto relookup_failed; From 543adf072165aaf2e3b635c0476204f9658ed3bf Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 22 Jul 2026 10:16:05 +0000 Subject: [PATCH 186/234] ppp: annotate data races in ppp_generic Several fields in struct ppp can be read or updated concurrently from multiple CPUs without synchronization, causing data races: 1. ppp->mru is read concurrently in ppp_receive_nonmp_frame() while being updated via PPPIOCSMRU ioctl. Protect ppp->mru updates in PPPIOCSMRU with ppp_recv_lock(ppp). 2. PPPIOCGFLAGS reads ppp->flags, ppp->xstate, and ppp->rstate unlocked. Wrap the read in ppp_lock(ppp) to get a consistent snapshot. 3. ppp->debug is updated via PPPIOCSDEBUG and read concurrently on fast paths. Annotate reads with READ_ONCE() and writes with WRITE_ONCE(). 4. ppp->last_xmit and ppp->last_recv are updated on TX/RX data paths and read via PPPIOCGIDLE32 / PPPIOCGIDLE64 ioctls. Annotate with WRITE_ONCE() / READ_ONCE() and use max() to handle jiffies subtraction. 5. ppp->npmode[] is updated via PPPIOCSNPMODE and read on TX/RX paths. Annotate with WRITE_ONCE() / READ_ONCE(). Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Eric Dumazet Reviewed-by: Qingfang Deng Link: https://patch.msgid.link/20260722101605.2868548-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- drivers/net/ppp/ppp_generic.c | 50 ++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/drivers/net/ppp/ppp_generic.c b/drivers/net/ppp/ppp_generic.c index ef54e0a0462a..cacc4c3a37d2 100644 --- a/drivers/net/ppp/ppp_generic.c +++ b/drivers/net/ppp/ppp_generic.c @@ -810,7 +810,9 @@ static long ppp_ioctl(struct file *file, unsigned int cmd, unsigned long arg) case PPPIOCSMRU: if (get_user(val, p)) break; + ppp_recv_lock(ppp); ppp->mru = val; + ppp_recv_unlock(ppp); err = 0; break; @@ -831,7 +833,9 @@ static long ppp_ioctl(struct file *file, unsigned int cmd, unsigned long arg) break; case PPPIOCGFLAGS: + ppp_lock(ppp); val = ppp->flags | ppp->xstate | ppp->rstate; + ppp_unlock(ppp); if (put_user(val, p)) break; err = 0; @@ -855,7 +859,7 @@ static long ppp_ioctl(struct file *file, unsigned int cmd, unsigned long arg) case PPPIOCSDEBUG: if (get_user(val, p)) break; - ppp->debug = val; + WRITE_ONCE(ppp->debug, val); err = 0; break; @@ -866,16 +870,16 @@ static long ppp_ioctl(struct file *file, unsigned int cmd, unsigned long arg) break; case PPPIOCGIDLE32: - idle32.xmit_idle = (jiffies - ppp->last_xmit) / HZ; - idle32.recv_idle = (jiffies - ppp->last_recv) / HZ; - if (copy_to_user(argp, &idle32, sizeof(idle32))) + idle32.xmit_idle = max(0L, (long)(jiffies - READ_ONCE(ppp->last_xmit))) / HZ; + idle32.recv_idle = max(0L, (long)(jiffies - READ_ONCE(ppp->last_recv))) / HZ; + if (copy_to_user(argp, &idle32, sizeof(idle32))) break; err = 0; break; case PPPIOCGIDLE64: - idle64.xmit_idle = (jiffies - ppp->last_xmit) / HZ; - idle64.recv_idle = (jiffies - ppp->last_recv) / HZ; + idle64.xmit_idle = max(0L, (long)(jiffies - READ_ONCE(ppp->last_xmit))) / HZ; + idle64.recv_idle = max(0L, (long)(jiffies - READ_ONCE(ppp->last_recv))) / HZ; if (copy_to_user(argp, &idle64, sizeof(idle64))) break; err = 0; @@ -916,7 +920,7 @@ static long ppp_ioctl(struct file *file, unsigned int cmd, unsigned long arg) if (copy_to_user(argp, &npi, sizeof(npi))) break; } else { - ppp->npmode[i] = npi.mode; + WRITE_ONCE(ppp->npmode[i], npi.mode); /* we may be able to transmit more packets now (??) */ netif_wake_queue(ppp->dev); } @@ -1454,7 +1458,7 @@ ppp_start_xmit(struct sk_buff *skb, struct net_device *dev) goto outf; /* Drop, accept or reject the packet */ - switch (ppp->npmode[npi]) { + switch (READ_ONCE(ppp->npmode[npi])) { case NPMODE_PASS: break; case NPMODE_QUEUE: @@ -1790,7 +1794,7 @@ ppp_prepare_tx_skb(struct ppp *ppp, struct sk_buff **pskb) *(__be16 *)skb_push(skb, 2) = htons(PPP_FILTER_OUTBOUND_TAG); if (ppp->pass_filter && bpf_prog_run(ppp->pass_filter, skb) == 0) { - if (ppp->debug & 1) + if (READ_ONCE(ppp->debug) & 1) netdev_printk(KERN_DEBUG, ppp->dev, "PPP: outbound frame " "not passed\n"); @@ -1800,11 +1804,11 @@ ppp_prepare_tx_skb(struct ppp *ppp, struct sk_buff **pskb) /* if this packet passes the active filter, record the time */ if (!(ppp->active_filter && bpf_prog_run(ppp->active_filter, skb) == 0)) - ppp->last_xmit = jiffies; + WRITE_ONCE(ppp->last_xmit, jiffies); skb_pull(skb, 2); #else /* for data packets, record the time */ - ppp->last_xmit = jiffies; + WRITE_ONCE(ppp->last_xmit, jiffies); #endif /* CONFIG_PPP_FILTER */ } @@ -2154,7 +2158,7 @@ static int ppp_mp_explode(struct ppp *ppp, struct sk_buff *skb) noskb: spin_unlock(&pch->downl); err_linearize: - if (ppp->debug & 1) + if (READ_ONCE(ppp->debug) & 1) netdev_err(ppp->dev, "PPP: no memory (fragment)\n"); DEV_STATS_INC(ppp->dev, tx_errors); ++ppp->nxseq; @@ -2502,7 +2506,7 @@ ppp_receive_nonmp_frame(struct ppp *ppp, struct sk_buff *skb) *(__be16 *)skb_push(skb, 2) = htons(PPP_FILTER_INBOUND_TAG); if (ppp->pass_filter && bpf_prog_run(ppp->pass_filter, skb) == 0) { - if (ppp->debug & 1) + if (READ_ONCE(ppp->debug) & 1) netdev_printk(KERN_DEBUG, ppp->dev, "PPP: inbound frame " "not passed\n"); @@ -2511,14 +2515,14 @@ ppp_receive_nonmp_frame(struct ppp *ppp, struct sk_buff *skb) } if (!(ppp->active_filter && bpf_prog_run(ppp->active_filter, skb) == 0)) - ppp->last_recv = jiffies; + WRITE_ONCE(ppp->last_recv, jiffies); __skb_pull(skb, 2); } else #endif /* CONFIG_PPP_FILTER */ - ppp->last_recv = jiffies; + WRITE_ONCE(ppp->last_recv, jiffies); if ((ppp->dev->flags & IFF_UP) == 0 || - ppp->npmode[npi] != NPMODE_PASS) { + READ_ONCE(ppp->npmode[npi]) != NPMODE_PASS) { kfree_skb(skb); } else { /* chop off protocol */ @@ -2771,7 +2775,7 @@ ppp_mp_reconstruct(struct ppp *ppp) seq = seq_before(minseq, PPP_MP_CB(p)->sequence)? minseq + 1: PPP_MP_CB(p)->sequence; - if (ppp->debug & 1) + if (READ_ONCE(ppp->debug) & 1) netdev_printk(KERN_DEBUG, ppp->dev, "lost frag %u..%u\n", oldseq, seq-1); @@ -2820,7 +2824,7 @@ ppp_mp_reconstruct(struct ppp *ppp) struct sk_buff *tmp2; skb_queue_reverse_walk_from_safe(list, p, tmp2) { - if (ppp->debug & 1) + if (READ_ONCE(ppp->debug) & 1) netdev_printk(KERN_DEBUG, ppp->dev, "discarding frag %u\n", PPP_MP_CB(p)->sequence); @@ -2842,7 +2846,7 @@ ppp_mp_reconstruct(struct ppp *ppp) skb_queue_walk_safe(list, p, tmp) { if (p == head) break; - if (ppp->debug & 1) + if (READ_ONCE(ppp->debug) & 1) netdev_printk(KERN_DEBUG, ppp->dev, "discarding frag %u\n", PPP_MP_CB(p)->sequence); @@ -2850,7 +2854,7 @@ ppp_mp_reconstruct(struct ppp *ppp) kfree_skb(p); } - if (ppp->debug & 1) + if (READ_ONCE(ppp->debug) & 1) netdev_printk(KERN_DEBUG, ppp->dev, " missed pkts %u..%u\n", ppp->nextseq, @@ -3161,7 +3165,8 @@ ppp_ccp_peek(struct ppp *ppp, struct sk_buff *skb, int inbound) if (!ppp->rc_state) break; if (ppp->rcomp->decomp_init(ppp->rc_state, dp, len, - ppp->file.index, 0, ppp->mru, ppp->debug)) { + ppp->file.index, 0, ppp->mru, + READ_ONCE(ppp->debug))) { ppp->rstate |= SC_DECOMP_RUN; ppp->rstate &= ~(SC_DC_ERROR | SC_DC_FERROR); } @@ -3170,7 +3175,8 @@ ppp_ccp_peek(struct ppp *ppp, struct sk_buff *skb, int inbound) if (!ppp->xc_state) break; if (ppp->xcomp->comp_init(ppp->xc_state, dp, len, - ppp->file.index, 0, ppp->debug)) + ppp->file.index, 0, + READ_ONCE(ppp->debug))) ppp->xstate |= SC_COMP_RUN; } break; From e9c238f6fe42fb1b4dba3a578277de32cb487937 Mon Sep 17 00:00:00 2001 From: Asim Viladi Oglu Manizada Date: Wed, 22 Jul 2026 09:38:43 +0000 Subject: [PATCH 187/234] pppoe: reload header pointer after dev_hard_header() pppoe_sendmsg() saves a pointer to the PPPoE header before calling dev_hard_header(). Device header callbacks are allowed to reallocate the skb head, invalidating pointers into it. This can happen when a send is blocked in copy_from_user() while the first non-Ethernet port is added to an empty team device. The team's delegated GRE header callback then expands the skb head. PPPoE subsequently writes six bytes through the stale pointer into the freed head. Reload the PPPoE header through the skb's network-header offset after device header creation. pskb_expand_head() updates that offset when it relocates the head. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Asim Viladi Oglu Manizada Reviewed-by: Vadim Fedorenko Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260722093814.3017176-1-manizada@pm.me Signed-off-by: Jakub Kicinski --- drivers/net/ppp/pppoe.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ppp/pppoe.c b/drivers/net/ppp/pppoe.c index 4a018acb5262..6874a1a8edaf 100644 --- a/drivers/net/ppp/pppoe.c +++ b/drivers/net/ppp/pppoe.c @@ -825,6 +825,7 @@ static int pppoe_sendmsg(struct socket *sock, struct msghdr *m, dev_hard_header(skb, dev, ETH_P_PPP_SES, po->pppoe_pa.remote, NULL, total_len); + ph = pppoe_hdr(skb); memcpy(ph, &hdr, sizeof(struct pppoe_hdr)); ph->length = htons(total_len); From 4a05269bb723073a9299a5209876d4d40c51a030 Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Wed, 22 Jul 2026 16:21:24 +0200 Subject: [PATCH 188/234] MAINTAINERS: Add myself for stmmac ethernet driver maintainance The stmmac driver based on Synopsys' dwmac IP is used in a very wide variety of SoCs and is currently very actively used and contributed to. It has been orphaned in January 2025 after the previous maintainers became inactive, but Russell King was providing very valuable reviews and fixes for the driver at that point. Now we're seeing more and more activity on the driver, but are lacking people to test and review contributions to both glue drivers as well as core stmmac code. I have access to some variety of stmmac-based platforms such as socfpga CycloneV, imx8mp, some Allwinner SoCs and stm32mp1xx boards that I can run regression tests on, and I'm offering to step-up as a maintainer for driver, for the time being at least. Let's hope other people will eventually join this effort. Signed-off-by: Maxime Chevallier Link: https://patch.msgid.link/20260722142125.1767689-1-maxime.chevallier@bootlin.com Signed-off-by: Jakub Kicinski --- MAINTAINERS | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index b8b3b3b7e183..fdc42ef971e2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25973,8 +25973,9 @@ F: Documentation/devicetree/bindings/phy/st,stm32mp25-combophy.yaml F: drivers/phy/st/phy-stm32-combophy.c STMMAC ETHERNET DRIVER +M: Maxime Chevallier L: netdev@vger.kernel.org -S: Orphan +S: Maintained F: Documentation/networking/device_drivers/ethernet/stmicro/ F: drivers/net/ethernet/stmicro/stmmac/ From 9de445d8296a7f2b011ebb5834fdc94dcda5c778 Mon Sep 17 00:00:00 2001 From: Sven Schnelle Date: Tue, 14 Jul 2026 15:03:41 +0200 Subject: [PATCH 189/234] s390/ptff: Export ptff_function_mask[] Export the ptff_function_mask to make ptff_query() usable in modules. Signed-off-by: Sven Schnelle Acked-by: Heiko Carstens Link: https://patch.msgid.link/20260714130342.1971700-2-svens@linux.ibm.com Signed-off-by: Jakub Kicinski --- arch/s390/kernel/time.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/s390/kernel/time.c b/arch/s390/kernel/time.c index bd0df61d1907..2b989bebd220 100644 --- a/arch/s390/kernel/time.c +++ b/arch/s390/kernel/time.c @@ -65,6 +65,7 @@ ATOMIC_NOTIFIER_HEAD(s390_epoch_delta_notifier); EXPORT_SYMBOL(s390_epoch_delta_notifier); unsigned char ptff_function_mask[16]; +EXPORT_SYMBOL(ptff_function_mask); static unsigned long lpar_offset; static unsigned long initial_leap_seconds; From e78f1ac37afcb16cb6fef8a2c92591eab6558956 Mon Sep 17 00:00:00 2001 From: Sven Schnelle Date: Tue, 14 Jul 2026 15:03:42 +0200 Subject: [PATCH 190/234] ptp: ptp_s390: Add missing facility check Only register the physical clock when facility 28 is installed and PTFF QAF returns that PTFF QPT is available. Fixes: 2d7de7a3010d ("s390/time: Add PtP driver") Signed-off-by: Sven Schnelle Cc: stable@kernel.org Reviewed-by: Heiko Carstens Link: https://patch.msgid.link/20260714130342.1971700-3-svens@linux.ibm.com Signed-off-by: Jakub Kicinski --- drivers/ptp/ptp_s390.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/ptp/ptp_s390.c b/drivers/ptp/ptp_s390.c index 29618eb9bf44..02d624d89a0a 100644 --- a/drivers/ptp/ptp_s390.c +++ b/drivers/ptp/ptp_s390.c @@ -107,6 +107,9 @@ static __init int ptp_s390_init(void) if (IS_ERR(ptp_stcke_clock)) return PTR_ERR(ptp_stcke_clock); + if (!test_facility(28) || !ptff_query(PTFF_QPT)) + return 0; + ptp_qpt_clock = ptp_clock_register(&ptp_s390_qpt_info, NULL); if (IS_ERR(ptp_qpt_clock)) { ptp_clock_unregister(ptp_stcke_clock); @@ -117,7 +120,8 @@ static __init int ptp_s390_init(void) static __exit void ptp_s390_exit(void) { - ptp_clock_unregister(ptp_qpt_clock); + if (ptp_qpt_clock) + ptp_clock_unregister(ptp_qpt_clock); ptp_clock_unregister(ptp_stcke_clock); } From fe0c002928c6749b7f4a726f6f600f6dd70280ea Mon Sep 17 00:00:00 2001 From: Chenguang Zhao Date: Wed, 22 Jul 2026 10:53:53 +0800 Subject: [PATCH 191/234] hinic: remove unused ethtool RSS user configuration buffers rss_indir_user and rss_hkey_user are allocated and filled in __set_rss_rxfh() when the user configures RSS via ethtool, but nothing ever reads them. hinic_get_rxfh() fetches the state from the device, and the hardware is programmed from the original indir/key arguments. These buffers only leaked on driver unload. Drop the unused allocations, memcpys, and struct fields. Fixes: 4fdc51bb4e92 ("hinic: add support for rss parameters with ethtool") Signed-off-by: Chenguang Zhao Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260722025353.328179-1-chenguang.zhao@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/huawei/hinic/hinic_dev.h | 2 -- .../net/ethernet/huawei/hinic/hinic_ethtool.c | 21 ------------------- 2 files changed, 23 deletions(-) diff --git a/drivers/net/ethernet/huawei/hinic/hinic_dev.h b/drivers/net/ethernet/huawei/hinic/hinic_dev.h index 52ea97c818b8..d9ab94910a2a 100644 --- a/drivers/net/ethernet/huawei/hinic/hinic_dev.h +++ b/drivers/net/ethernet/huawei/hinic/hinic_dev.h @@ -104,8 +104,6 @@ struct hinic_dev { u16 num_rss; u16 rss_limit; struct hinic_rss_type rss_type; - u8 *rss_hkey_user; - s32 *rss_indir_user; struct hinic_intr_coal_info *rx_intr_coalesce; struct hinic_intr_coal_info *tx_intr_coalesce; struct hinic_sriov_info sriov_info; diff --git a/drivers/net/ethernet/huawei/hinic/hinic_ethtool.c b/drivers/net/ethernet/huawei/hinic/hinic_ethtool.c index a8b129ce1b7e..f75e8563f23a 100644 --- a/drivers/net/ethernet/huawei/hinic/hinic_ethtool.c +++ b/drivers/net/ethernet/huawei/hinic/hinic_ethtool.c @@ -1064,17 +1064,6 @@ static int __set_rss_rxfh(struct net_device *netdev, int err; if (indir) { - if (!nic_dev->rss_indir_user) { - nic_dev->rss_indir_user = - kzalloc(sizeof(u32) * HINIC_RSS_INDIR_SIZE, - GFP_KERNEL); - if (!nic_dev->rss_indir_user) - return -ENOMEM; - } - - memcpy(nic_dev->rss_indir_user, indir, - sizeof(u32) * HINIC_RSS_INDIR_SIZE); - err = hinic_rss_set_indir_tbl(nic_dev, nic_dev->rss_tmpl_idx, indir); if (err) @@ -1082,16 +1071,6 @@ static int __set_rss_rxfh(struct net_device *netdev, } if (key) { - if (!nic_dev->rss_hkey_user) { - nic_dev->rss_hkey_user = - kzalloc(HINIC_RSS_KEY_SIZE * 2, GFP_KERNEL); - - if (!nic_dev->rss_hkey_user) - return -ENOMEM; - } - - memcpy(nic_dev->rss_hkey_user, key, HINIC_RSS_KEY_SIZE); - err = hinic_rss_set_template_tbl(nic_dev, nic_dev->rss_tmpl_idx, key); if (err) From 3b536db8fb32da9e9c62f2bb45e2e319331f0426 Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Thu, 16 Jul 2026 12:43:19 -0300 Subject: [PATCH 192/234] net: qrtr: restrict socket creation to the initial network namespace QRTR keeps its entire port and node state in module-global variables that are not partitioned per network namespace: qrtr_local_nid is a single global node id (always 1) and qrtr_ports is a single global xarray. qrtr_port_lookup() and qrtr_local_enqueue() operate on that global state with no network-namespace check, and qrtr_create() places no restriction on the namespace a socket is created in. As a result an unprivileged process that creates an AF_QIPCRTR socket in a separate network namespace, e.g. via unshare(CLONE_NEWUSER | CLONE_NEWNET), can send QRTR datagrams - including control-plane messages such as QRTR_TYPE_NEW_SERVER - to QRTR sockets owned by another namespace, and vice versa. The receiving socket sees such a message as coming from node id 1, indistinguishable from a legitimate local client, breaking the isolation that network namespaces are expected to provide. QRTR is a transport to global hardware endpoints (the modem and other remote processors) and has no per-namespace semantics; its in-kernel name service already creates its socket in init_net only. Confine the socket family to the initial network namespace, as other non-namespace-aware socket families do (see llc_ui_create() and the ieee802154 socket code). Fixes: bdabad3e363d ("net: Add Qualcomm IPC router") Signed-off-by: Aldo Ariel Panzardo Link: https://patch.msgid.link/20260716154319.3297699-1-qwe.aldo@gmail.com Signed-off-by: Jakub Kicinski --- net/qrtr/af_qrtr.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/qrtr/af_qrtr.c b/net/qrtr/af_qrtr.c index d02ef9a74c3c..a30fa56e6aa3 100644 --- a/net/qrtr/af_qrtr.c +++ b/net/qrtr/af_qrtr.c @@ -1263,6 +1263,14 @@ static int qrtr_create(struct net *net, struct socket *sock, if (sock->type != SOCK_DGRAM) return -EPROTOTYPE; + /* QRTR keeps its port and node state in module-global variables that + * are not partitioned per network namespace, and the in-kernel name + * service only operates in init_net. Confine the family to init_net so + * a socket in another namespace cannot reach the global control plane. + */ + if (!net_eq(net, &init_net)) + return -EAFNOSUPPORT; + sk = sk_alloc(net, AF_QIPCRTR, GFP_KERNEL, &qrtr_proto, kern); if (!sk) return -ENOMEM; From 18f116931f52e3c3303ad4b15ff41eb89b0e4239 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Thu, 16 Jul 2026 22:29:58 +0800 Subject: [PATCH 193/234] raw: annotate lockless match fields in raw_v4_match() raw_v4_match() is a lockless match helper under sk_for_each_rcu(). It still reads inet->inet_daddr, inet->inet_rcv_saddr and sk->sk_bound_dev_if with plain loads while bind, connect and bind-to-device paths can update the same match fields concurrently. Annotate only those mutable match fields in raw_v4_match(), and do so at the point of use instead of hoisting the bound-device read before the earlier short-circuit tests. Also annotate the raw bind writer and the shared IPv4 datagram connect writer used by raw sockets, so the address fields updated on bind and connect match explicit WRITE_ONCE() updates. This version intentionally leaves the shared disconnect-side IPv4 writers to follow-up cleanup and limits the writer changes here to the raw bind path and the datagram connect path directly exercised by raw sockets. Fixes: 0daf07e52709 ("raw: convert raw sockets to RCU") Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20260716142958.3064224-1-runyu.xiao@seu.edu.cn Signed-off-by: Jakub Kicinski --- net/ipv4/datagram.c | 4 ++-- net/ipv4/raw.c | 23 ++++++++++++++++------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/net/ipv4/datagram.c b/net/ipv4/datagram.c index 1614593b6d72..7d25519a6cdd 100644 --- a/net/ipv4/datagram.c +++ b/net/ipv4/datagram.c @@ -63,12 +63,12 @@ int __ip4_datagram_connect(struct sock *sk, struct sockaddr_unsized *uaddr, int } /* Update addresses before rehashing */ - inet->inet_daddr = fl4->daddr; + WRITE_ONCE(inet->inet_daddr, fl4->daddr); inet->inet_dport = usin->sin_port; if (!inet->inet_saddr) inet->inet_saddr = fl4->saddr; if (!inet->inet_rcv_saddr) { - inet->inet_rcv_saddr = fl4->saddr; + WRITE_ONCE(inet->inet_rcv_saddr, fl4->saddr); if (sk->sk_prot->rehash) sk->sk_prot->rehash(sk); } diff --git a/net/ipv4/raw.c b/net/ipv4/raw.c index e9fbab6ad914..7f74d8b95a37 100644 --- a/net/ipv4/raw.c +++ b/net/ipv4/raw.c @@ -118,13 +118,21 @@ bool raw_v4_match(struct net *net, const struct sock *sk, unsigned short num, __be32 raddr, __be32 laddr, int dif, int sdif) { const struct inet_sock *inet = inet_sk(sk); + __be32 daddr, rcv_saddr; - if (net_eq(sock_net(sk), net) && inet->inet_num == num && - !(inet->inet_daddr && inet->inet_daddr != raddr) && - !(inet->inet_rcv_saddr && inet->inet_rcv_saddr != laddr) && - raw_sk_bound_dev_eq(net, sk->sk_bound_dev_if, dif, sdif)) - return true; - return false; + if (!net_eq(sock_net(sk), net) || inet->inet_num != num) + return false; + + daddr = READ_ONCE(inet->inet_daddr); + if (daddr && daddr != raddr) + return false; + + rcv_saddr = READ_ONCE(inet->inet_rcv_saddr); + if (rcv_saddr && rcv_saddr != laddr) + return false; + + return raw_sk_bound_dev_eq(net, READ_ONCE(sk->sk_bound_dev_if), + dif, sdif); } EXPORT_SYMBOL_GPL(raw_v4_match); @@ -722,7 +730,8 @@ static int raw_bind(struct sock *sk, struct sockaddr_unsized *uaddr, chk_addr_ret)) goto out; - inet->inet_rcv_saddr = inet->inet_saddr = addr->sin_addr.s_addr; + inet->inet_saddr = addr->sin_addr.s_addr; + WRITE_ONCE(inet->inet_rcv_saddr, addr->sin_addr.s_addr); if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST) inet->inet_saddr = 0; /* Use device */ sk_dst_reset(sk); From fd3a3f28ed60c6af4b2a39933b151d6b27842c3b Mon Sep 17 00:00:00 2001 From: Doruk Tan Ozturk Date: Thu, 16 Jul 2026 21:34:23 +0200 Subject: [PATCH 194/234] mac802154: llsec: reject frames shorter than the authentication tag llsec_do_decrypt_auth() computes the associated-data length for the AEAD request as assoclen += datalen - authlen; where datalen is the number of bytes after the MAC header and authlen (4, 8 or 16) is the length of the authentication tag. Nothing verifies that the frame actually carries at least authlen payload bytes. A secured frame whose payload is shorter than the tag makes datalen - authlen negative; assoclen is then passed to aead_request_set_ad() as an unsigned value close to 4 GiB, so crypto_aead_decrypt() walks far off the end of the scatterlist that only spans the real frame. The frame is fully attacker-controlled and reaches this path from any IEEE 802.15.4 peer in radio range. Reject frames whose payload is shorter than the authentication tag before the subtraction. Dynamically reproduced on a KASAN kernel as a general-protection-fault in the AEAD scatterwalk, and the fix confirmed. Fixes: 4c14a2fb5d14 ("mac802154: add llsec decryption method") Cc: stable@vger.kernel.org Reviewed-by: Simon Horman Signed-off-by: Doruk Tan Ozturk Link: https://patch.msgid.link/20260716193423.32498-1-doruk@0sec.ai Signed-off-by: Jakub Kicinski --- net/mac802154/llsec.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/mac802154/llsec.c b/net/mac802154/llsec.c index 5e7cc11fab3a..85452ef9a58c 100644 --- a/net/mac802154/llsec.c +++ b/net/mac802154/llsec.c @@ -891,6 +891,11 @@ llsec_do_decrypt_auth(struct sk_buff *skb, const struct mac802154_llsec *sec, data = skb_mac_header(skb) + skb->mac_len; datalen = skb_tail_pointer(skb) - data; + if (datalen < authlen) { + kfree_sensitive(req); + return -EBADMSG; + } + sg_init_one(&sg, skb_mac_header(skb), assoclen + datalen); if (!(hdr->sec.level & IEEE802154_SCF_SECLEVEL_ENC)) { From 3a61bd9637f3d929aa846e4eb3d98b48c26fcb0e Mon Sep 17 00:00:00 2001 From: Doruk Tan Ozturk Date: Thu, 16 Jul 2026 22:34:59 +0200 Subject: [PATCH 195/234] vxlan: require CAP_NET_ADMIN in the device netns for changelink A tunnel changelink() operates on at most two netns, dev_net(dev) and the sticky underlay netns vxlan->net. They differ once the device is created in or moved to a netns other than the one the request runs in. The rtnl changelink path checks CAP_NET_ADMIN only against dev_net(dev), so a caller privileged there but not in vxlan->net can rewrite a vxlan device whose underlay lives in vxlan->net. vxlan_changelink() validates and applies the new configuration against vxlan->net (vxlan_config_validate(vxlan->net, ...)) and can reopen the underlay socket in that netns, so the same reasoning as the tunnel changelink series applies here. Gate vxlan_changelink() with rtnl_dev_link_net_capable(), at the top of the op before any attribute is parsed, matching ipgre_changelink() and the rest of the "require CAP_NET_ADMIN in the device netns for changelink" series. Found by 0sec automated security-research tooling (https://0sec.ai). Fixes: 8bcdc4f3a20b ("vxlan: add changelink support") Cc: stable@vger.kernel.org Signed-off-by: Doruk Tan Ozturk Reviewed-by: Fernando Fernandez Mancera Link: https://patch.msgid.link/20260716203500.70573-2-doruk@0sec.ai Signed-off-by: Jakub Kicinski --- drivers/net/vxlan/vxlan_core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index 67c367cc5662..d834a4865aec 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -4421,6 +4421,9 @@ static int vxlan_changelink(struct net_device *dev, struct nlattr *tb[], struct vxlan_rdst *dst; int err; + if (!rtnl_dev_link_net_capable(dev, vxlan->net)) + return -EPERM; + dst = &vxlan->default_dst; err = vxlan_nl2conf(tb, data, dev, &conf, true, extack); if (err) From 8efb8f8bbb353b8f2fdf4f37534c6d96c9f69e01 Mon Sep 17 00:00:00 2001 From: Doruk Tan Ozturk Date: Thu, 16 Jul 2026 22:35:00 +0200 Subject: [PATCH 196/234] geneve: require CAP_NET_ADMIN in the device netns for changelink A tunnel changelink() operates on at most two netns, dev_net(dev) and the sticky underlay netns geneve->net. They differ once the device is created in or moved to a netns other than the one the request runs in. The rtnl changelink path checks CAP_NET_ADMIN only against dev_net(dev), so a caller privileged there but not in geneve->net can rewrite a geneve device whose underlay lives in geneve->net. geneve_changelink() applies the new configuration against geneve->net: geneve_link_config() and the geneve_quiesce()/geneve_unquiesce() pair reopen the underlay sockets in that netns (geneve_sock_add() uses geneve->net), so the same reasoning as the tunnel changelink series applies here. Gate geneve_changelink() with rtnl_dev_link_net_capable(), at the top of the op before any attribute is parsed, matching ipgre_changelink() and the rest of the "require CAP_NET_ADMIN in the device netns for changelink" series. Found by 0sec automated security-research tooling (https://0sec.ai). Fixes: 5b861f6baa3a ("geneve: add rtnl changelink support") Cc: stable@vger.kernel.org Signed-off-by: Doruk Tan Ozturk Reviewed-by: Fernando Fernandez Mancera Link: https://patch.msgid.link/20260716203500.70573-3-doruk@0sec.ai Signed-off-by: Jakub Kicinski --- drivers/net/geneve.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/geneve.c b/drivers/net/geneve.c index bb275b30d3e5..72023ebd0e1b 100644 --- a/drivers/net/geneve.c +++ b/drivers/net/geneve.c @@ -2394,6 +2394,9 @@ static int geneve_changelink(struct net_device *dev, struct nlattr *tb[], struct geneve_config cfg; int err; + if (!rtnl_dev_link_net_capable(dev, geneve->net)) + return -EPERM; + /* If the geneve device is configured for metadata (or externally * controlled, for example, OVS), then nothing can be changed. */ From 11c057d23465c7a5817a7284c896d19d54c0b616 Mon Sep 17 00:00:00 2001 From: Gal Pressman Date: Fri, 17 Jul 2026 10:23:38 +0300 Subject: [PATCH 197/234] net/mlx5: Fix MCIA register buffer overflow on 32 dword reads The MCIA register can return up to 32 dwords (128 bytes) when the device advertises the mcia_32dwords capability, but struct mlx5_ifc_mcia_reg_bits only defines dword_0..11, leaving room for just 12 dwords (48 bytes) of data. mlx5_query_mcia() clamps the read size to mlx5_mcia_max_bytes() and then memcpy()s that many bytes out of the register, potentially reading past the end of the 'out' buffer. On kernels built with FORTIFY_SOURCE this is caught as a buffer overflow while reading the module EEPROM via ethtool: detected buffer overflow in memcpy kernel BUG at lib/string_helpers.c:1048! RIP: 0010:fortify_panic+0x13/0x20 Call Trace: mlx5_query_mcia.isra.0+0x200/0x210 [mlx5_core] mlx5_query_module_eeprom_by_page+0x4a/0xa0 [mlx5_core] mlx5e_get_module_eeprom_by_page+0xbb/0x120 [mlx5_core] eeprom_prepare_data+0xf3/0x170 ethnl_default_doit+0xf1/0x3b0 Extend the mcia_reg layout to 32 dwords. Fixes: 271907ee2f29 ("net/mlx5: Query the maximum MCIA register read size from firmware") Signed-off-by: Gal Pressman Reviewed-by: Alex Lazar Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260717072338.1240582-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/port.c | 4 ++-- include/linux/mlx5/mlx5_ifc.h | 13 +------------ 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/port.c b/drivers/net/ethernet/mellanox/mlx5/core/port.c index ddbe9ca8971d..9f682f6bdb50 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/port.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/port.c @@ -314,7 +314,7 @@ static int mlx5_query_module_id(struct mlx5_core_dev *dev, int module_num, return -EIO; } - ptr = MLX5_ADDR_OF(mcia_reg, out, dword_0); + ptr = MLX5_ADDR_OF(mcia_reg, out, dwords); *module_id = ptr[0]; @@ -399,7 +399,7 @@ static int mlx5_query_mcia(struct mlx5_core_dev *dev, return -EIO; } - ptr = MLX5_ADDR_OF(mcia_reg, out, dword_0); + ptr = MLX5_ADDR_OF(mcia_reg, out, dwords); memcpy(data, ptr, size); return size; diff --git a/include/linux/mlx5/mlx5_ifc.h b/include/linux/mlx5/mlx5_ifc.h index 695c86ee6d7a..8f18a508320d 100644 --- a/include/linux/mlx5/mlx5_ifc.h +++ b/include/linux/mlx5/mlx5_ifc.h @@ -12215,18 +12215,7 @@ struct mlx5_ifc_mcia_reg_bits { u8 reserved_at_60[0x20]; - u8 dword_0[0x20]; - u8 dword_1[0x20]; - u8 dword_2[0x20]; - u8 dword_3[0x20]; - u8 dword_4[0x20]; - u8 dword_5[0x20]; - u8 dword_6[0x20]; - u8 dword_7[0x20]; - u8 dword_8[0x20]; - u8 dword_9[0x20]; - u8 dword_10[0x20]; - u8 dword_11[0x20]; + u8 dwords[0x400]; }; struct mlx5_ifc_dcbx_param_bits { From d12956d083eb70f2c6d72711aebaf8c2ce21e170 Mon Sep 17 00:00:00 2001 From: Yael Chemla Date: Fri, 17 Jul 2026 10:33:06 +0300 Subject: [PATCH 198/234] net/mlx5: E-Switch, fix zero num_dest in prio_tag egress vlan rule esw_egress_acl_vlan_create() hardcodes num_dest=0 in its mlx5_add_flow_rules() call. When invoked from the non-bond path fwd_dest is NULL and num_dest=0 is correct. When invoked from esw_acl_egress_ofld_rules_create() during a bond event, fwd_dest is non-NULL and flow_act.action carries MLX5_FLOW_CONTEXT_ACTION_FWD_DEST, but _mlx5_add_flow_rules() rejects a non-NULL dest pointer paired with dest_num<=0 and returns -EINVAL. The error propagates as "configure slave vport egress fwd, err(-22)". The passive vport's egress ACL table ends up with its flow groups allocated but no FTEs, so prio-tagged packets are not popped and bond failover is broken on prio_tag_required devices. Fix by passing fwd_dest ? 1 : 0 as num_dest to match the actual number of destinations supplied. Fixes: bf773dc0e6d5 ("net/mlx5: E-Switch, Introduce APIs to enable egress acl forward-to-vport rule") Signed-off-by: Yael Chemla Reviewed-by: Cosmin Ratiu Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260717073306.1242399-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/esw/acl/helper.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/acl/helper.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/acl/helper.c index ba5cce706ea2..9693c74e9b16 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/acl/helper.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/acl/helper.c @@ -71,7 +71,7 @@ int esw_egress_acl_vlan_create(struct mlx5_eswitch *esw, flow_act.action = flow_action; vport->egress.allowed_vlan = mlx5_add_flow_rules(vport->egress.acl, spec, - &flow_act, fwd_dest, 0); + &flow_act, fwd_dest, fwd_dest ? 1 : 0); if (IS_ERR(vport->egress.allowed_vlan)) { err = PTR_ERR(vport->egress.allowed_vlan); esw_warn(esw->dev, From def9a4745e105145133e442dd8a1c126caf0f553 Mon Sep 17 00:00:00 2001 From: Coia Prant Date: Fri, 17 Jul 2026 15:43:25 +0800 Subject: [PATCH 199/234] net: pcs: xpcs: fix SGMII state reading Commit 2a22b7ae2fa3 ("net: pcs: xpcs: adapt Wangxun NICs for SGMII mode") added a path in xpcs_get_state_c37_sgmii() that reads speed/duplex from BMCR after AN completes. However, BMCR does not reflect the negotiated result on the hardware where this has been tested: - On RK3568 (MAC side SGMII), BMCR returns a fixed hardware reset value - Wangxun engineer Jiawen Wu confirmed that on their side, "BMCR looks like it only wants to be return as 0" [0] The correct information is available in CL37_ANSGM_STS, which contains the actual link status and negotiated speed/duplex. This bug was previously masked by phylink core, which overrides the PCS link state with the PHY state when a PHY is present: /* If we have a phy, the "up" state is the union of both the * PHY and the MAC */ if (phy) link_state.link &= pl->phy_state.link; Thus, when the link is down, the PHY's link_down state is applied on top of whatever the PCS reports, hiding the broken PCS state reading path. Modify xpcs_get_state_c37_sgmii() to: 1. Read link state from CL37_ANSGM_STS 2. If link is up, report speed/duplex from CL37_ANSGM_STS 3. Remove the broken BMCR reading path entirely Also properly set state->an_complete to reflect the AN completion status, and clear CL37_ANCMPLT_INTR when link is down to avoid stale state. [0] https://lore.kernel.org/all/000c01dd1593$2ac0b0f0$804212d0$@trustnetic.com/ Fixes: 2a22b7ae2fa3 ("net: pcs: xpcs: adapt Wangxun NICs for SGMII mode") Cc: stable@vger.kernel.org Tested-by: Jiawen Wu Signed-off-by: Coia Prant Tested-by: Maxime Chevallier Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260717074324.3250043-2-coiaprant@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/pcs/pcs-xpcs.c | 34 ++++++++-------------------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c index e69fa2f0a0e8..0337e2bcc012 100644 --- a/drivers/net/pcs/pcs-xpcs.c +++ b/drivers/net/pcs/pcs-xpcs.c @@ -1058,6 +1058,7 @@ static int xpcs_get_state_c37_sgmii(struct dw_xpcs *xpcs, /* Reset link_state */ state->link = false; + state->an_complete = false; state->speed = SPEED_UNKNOWN; state->duplex = DUPLEX_UNKNOWN; state->pause = 0; @@ -1069,6 +1070,8 @@ static int xpcs_get_state_c37_sgmii(struct dw_xpcs *xpcs, if (ret < 0) return ret; + state->an_complete = ret & DW_VR_MII_AN_STS_C37_ANCMPLT_INTR; + if (ret & DW_VR_MII_C37_ANSGM_SP_LNKSTS) { int speed_value; @@ -1086,35 +1089,14 @@ static int xpcs_get_state_c37_sgmii(struct dw_xpcs *xpcs, state->duplex = DUPLEX_FULL; else state->duplex = DUPLEX_HALF; - } else if (ret == DW_VR_MII_AN_STS_C37_ANCMPLT_INTR) { - int speed, duplex; - state->link = true; - - speed = xpcs_read(xpcs, MDIO_MMD_VEND2, MII_BMCR); - if (speed < 0) - return speed; - - speed &= BMCR_SPEED100 | BMCR_SPEED1000; - if (speed == BMCR_SPEED1000) - state->speed = SPEED_1000; - else if (speed == BMCR_SPEED100) - state->speed = SPEED_100; - else if (speed == 0) - state->speed = SPEED_10; - - duplex = xpcs_read(xpcs, MDIO_MMD_VEND2, MII_ADVERTISE); - if (duplex < 0) - return duplex; - - if (duplex & ADVERTISE_1000XFULL) - state->duplex = DUPLEX_FULL; - else if (duplex & ADVERTISE_1000XHALF) - state->duplex = DUPLEX_HALF; - - xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_INTR_STS, 0); + return 0; } + /* Clear AN complete status or interrupt */ + if (state->an_complete) + xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_INTR_STS, 0); + return 0; } From ffb1873b2df11945b8c395e859169248675c91c5 Mon Sep 17 00:00:00 2001 From: Alexei Lazar Date: Fri, 17 Jul 2026 10:51:24 +0300 Subject: [PATCH 200/234] net/mlx5e: Report zero bandwidth for non-ETS traffic classes The IEEE 802.1Qaz standard defines that bandwidth allocation percentages only apply to Enhanced Transmission Selection (ETS) traffic classes. For STRICT and VENDOR transmission selection algorithms, bandwidth percentage values are not applicable. Currently for non-ETS 100 bandwidth is being reported for all traffic classes in the get operation due to hardware limitation, regardless of their TSA type. Fix this by reporting 0 for non-ETS traffic classes. Fixes: 820c2c5e773d ("net/mlx5e: Read ETS settings directly from firmware") Signed-off-by: Alexei Lazar Reviewed-by: Carolina Jubran Signed-off-by: Tariq Toukan Reviewed-by: Pavan Chebbi Link: https://patch.msgid.link/20260717075125.1244877-2-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en_dcbnl.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_dcbnl.c b/drivers/net/ethernet/mellanox/mlx5/core/en_dcbnl.c index 4b86df6d5b9e..762f0a46c120 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_dcbnl.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_dcbnl.c @@ -173,6 +173,13 @@ static int mlx5e_dcbnl_ieee_getets(struct net_device *netdev, } memcpy(ets->tc_tsa, priv->dcbx.tc_tsa, sizeof(ets->tc_tsa)); + /* Report 0 for non ETS TSA */ + for (i = 0; i < ets->ets_cap; i++) { + if (ets->tc_tx_bw[i] == MLX5E_MAX_BW_ALLOC && + priv->dcbx.tc_tsa[i] != IEEE_8021QAZ_TSA_ETS) + ets->tc_tx_bw[i] = 0; + } + return err; } From 9173e1d3c7c7d49a71eee813091f9e834ec7cee5 Mon Sep 17 00:00:00 2001 From: Alexei Lazar Date: Fri, 17 Jul 2026 10:51:25 +0300 Subject: [PATCH 201/234] net/mlx5e: Reject unsupported CB Shaper TSA in ETS validation Credit Based (CB) TSA is not supported by the mlx5 driver, so reject any configurations that specify it. Fixes: 08fb1dacdd76 ("net/mlx5e: Support DCBNL IEEE ETS") Signed-off-by: Alexei Lazar Reviewed-by: Carolina Jubran Signed-off-by: Tariq Toukan Reviewed-by: Pavan Chebbi Link: https://patch.msgid.link/20260717075125.1244877-3-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en_dcbnl.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_dcbnl.c b/drivers/net/ethernet/mellanox/mlx5/core/en_dcbnl.c index 762f0a46c120..00e706e1ede1 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_dcbnl.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_dcbnl.c @@ -324,6 +324,14 @@ static int mlx5e_dbcnl_validate_ets(struct net_device *netdev, } } + for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) { + if (ets->tc_tsa[i] == IEEE_8021QAZ_TSA_CB_SHAPER) { + netdev_err(netdev, + "Failed to validate ETS: CB Shaper is not supported\n"); + return -EOPNOTSUPP; + } + } + /* Validate Bandwidth Sum */ for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) { if (ets->tc_tsa[i] == IEEE_8021QAZ_TSA_ETS) { From 9dfd800b374c38681364d4b3606a8b831d1478e3 Mon Sep 17 00:00:00 2001 From: Diego Fernando Mancera Gomez Date: Fri, 17 Jul 2026 02:07:04 -0600 Subject: [PATCH 202/234] usb: atm: ueagle-atm: reject descriptors that confuse probe and disconnect uea_probe() distinguishes a pre-firmware device from a post-firmware one using the USB id (UEA_IS_PREFIRM()), and stores a different object as the interface data in each case: a 'struct completion' for a pre-firmware device (to be waited on in .disconnect()), or a 'struct usbatm_data' for a post-firmware one. uea_disconnect() instead tells the two apart by the number of interfaces of the active configuration (a pre-firmware device exposes a single interface, ADI930 has 2 and eagle has 3), and casts the interface data accordingly. Because the two handlers use different criteria, a crafted device that advertises a pre-firmware id together with a multi-interface descriptor (or a post-firmware id with a single interface) makes them disagree: the small 'struct completion' stored by uea_probe() is then passed to usbatm_usb_disconnect(), which casts it to 'struct usbatm_data' and takes instance->serialize, reading past the end of the allocation: BUG: KASAN: slab-out-of-bounds in __mutex_lock+0x152a/0x1b80 Read of size 8 at addr ffff8880470e2c60 by task kworker/1:2/982 ... __mutex_lock+0x152a/0x1b80 usbatm_usb_disconnect+0x70/0x820 uea_disconnect+0x133/0x2c0 usb_unbind_interface+0x1dd/0x9e0 ... which belongs to the cache kmalloc-96 of size 96 The buggy address is located 0 bytes to the right of allocated 96-byte region [ffff8880470e2c00, ffff8880470e2c60) Reject such inconsistent descriptors in uea_probe() so that both handlers always make the same pre/post-firmware decision. Reported-by: syzbot+e62a973f8322b3bbe3ac@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=e62a973f8322b3bbe3ac Fixes: e2674dfbed8a ("usb: atm: ueagle-atm: wait for pre-firmware load in .disconnect()") Signed-off-by: Diego Fernando Mancera Gomez Acked-by: Stanislaw Gruszka Link: https://patch.msgid.link/20260717080704.1264-1-diegomancera.dev@gmail.com Signed-off-by: Jakub Kicinski --- drivers/usb/atm/ueagle-atm.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/usb/atm/ueagle-atm.c b/drivers/usb/atm/ueagle-atm.c index 4e71ed679a76..4266a0cb7e3b 100644 --- a/drivers/usb/atm/ueagle-atm.c +++ b/drivers/usb/atm/ueagle-atm.c @@ -2549,6 +2549,7 @@ static struct usbatm_driver uea_usbatm_driver = { static int uea_probe(struct usb_interface *intf, const struct usb_device_id *id) { struct usb_device *usb = interface_to_usbdev(intf); + bool single_iface = usb->config->desc.bNumInterfaces == 1; int ret; uea_dbg(usb, "ADSL device found with vid (%#X) pid (%#X) Rev (%#X): %s\n", @@ -2557,6 +2558,22 @@ static int uea_probe(struct usb_interface *intf, const struct usb_device_id *id) le16_to_cpu(usb->descriptor.bcdDevice), chip_name[UEA_CHIP_VERSION(id)]); + /* + * uea_probe() decides between the pre-firmware and post-firmware case + * from the USB id and stores a different object as interface data in + * each case: a struct completion for a pre-firmware device, a struct + * usbatm_data for a post-firmware one. uea_disconnect() instead tells + * the two apart by the number of interfaces (a pre-firmware device + * exposes a single interface, ADI930 has 2 and eagle has 3). A crafted + * device advertising a pre-firmware id together with a multi-interface + * descriptor (or the other way around) makes the two disagree, so that + * usbatm_usb_disconnect() treats the small completion object as a + * struct usbatm_data and reads out of bounds. Reject such inconsistent + * descriptors so both paths make the same decision. + */ + if (UEA_IS_PREFIRM(id) != single_iface) + return -ENODEV; + usb_reset_device(usb); if (UEA_IS_PREFIRM(id)) { From a28c4fcbf774e23b4779cae468e3497a5ad1f4a1 Mon Sep 17 00:00:00 2001 From: Yuxiang Yang Date: Fri, 17 Jul 2026 08:14:42 +0000 Subject: [PATCH 203/234] tcp: challenge ACK for non-exact RST in SYN-RECEIVED The SYN-RECEIVED request-socket path in tcp_check_req() accepts an in-window RST without requiring SEG.SEQ to exactly match RCV.NXT. A non-exact RST therefore removes the request instead of eliciting a challenge ACK. RFC 9293 section 3.10.7.4 applies the RFC 5961 reset check in SYN-RECEIVED: an exact RST resets the connection, while a non-exact in-window RST must trigger a challenge ACK and be dropped. Apply that check before the ACK-field validation, following the RFC sequence-number, RST, then ACK processing order. Factor the per-netns challenge ACK quota out of tcp_send_challenge_ack() so request sockets can share it. Use the request socket's send_ack() callback and its own out-of-window ACK timestamp to send and rate-limit the response. Reported-by: Yuxiang Yang Reported-by: Yizhou Zhao Reported-by: Ao Wang Reported-by: Xuewei Feng Reported-by: Qi Li Reported-by: Ke Xu Fixes: 282f23c6ee34 ("tcp: implement RFC 5961 3.2") Cc: stable@vger.kernel.org Signed-off-by: Yuxiang Yang Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260717081443.809393-2-yangyx22@mails.tsinghua.edu.cn Signed-off-by: Jakub Kicinski --- include/net/tcp.h | 2 ++ net/ipv4/tcp_input.c | 56 ++++++++++++++++++++++++++++++---------- net/ipv4/tcp_minisocks.c | 12 ++++++++- 3 files changed, 56 insertions(+), 14 deletions(-) diff --git a/include/net/tcp.h b/include/net/tcp.h index 6d376ea4d1c0..2c5b889530b5 100644 --- a/include/net/tcp.h +++ b/include/net/tcp.h @@ -1974,6 +1974,8 @@ static inline void tcp_fast_path_check(struct sock *sk) bool tcp_oow_rate_limited(struct net *net, const struct sk_buff *skb, int mib_idx, u32 *last_oow_ack_time); +void tcp_reqsk_send_challenge_ack(struct sock *sk, struct sk_buff *skb, + struct request_sock *req); static inline void tcp_mib_init(struct net *net) { diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c index 61045a8886e4..daff93d51342 100644 --- a/net/ipv4/tcp_input.c +++ b/net/ipv4/tcp_input.c @@ -4038,24 +4038,17 @@ static void tcp_send_ack_reflect_ect(struct sock *sk, bool accecn_reflector) __tcp_send_ack(sk, tp->rcv_nxt, flags); } -/* RFC 5961 7 [ACK Throttling] */ -static void tcp_send_challenge_ack(struct sock *sk, bool accecn_reflector) +/* Consume one slot from the per-netns RFC 5961 challenge ACK quota. + * Returns true if a challenge ACK may be sent. + */ +static bool tcp_challenge_ack_allowed(struct net *net) { - struct tcp_sock *tp = tcp_sk(sk); - struct net *net = sock_net(sk); u32 count, now, ack_limit; - /* First check our per-socket dupack rate limit. */ - if (__tcp_oow_rate_limited(net, - LINUX_MIB_TCPACKSKIPPEDCHALLENGE, - &tp->last_oow_ack_time)) - return; - ack_limit = READ_ONCE(net->ipv4.sysctl_tcp_challenge_ack_limit); if (ack_limit == INT_MAX) - goto send_ack; + return true; - /* Then check host-wide RFC 5961 rate limit. */ now = jiffies / HZ; if (now != READ_ONCE(net->ipv4.tcp_challenge_timestamp)) { u32 half = (ack_limit + 1) >> 1; @@ -4067,12 +4060,49 @@ static void tcp_send_challenge_ack(struct sock *sk, bool accecn_reflector) count = READ_ONCE(net->ipv4.tcp_challenge_count); if (count > 0) { WRITE_ONCE(net->ipv4.tcp_challenge_count, count - 1); -send_ack: + return true; + } + return false; +} + +/* RFC 5961 7 [ACK Throttling] */ +static void tcp_send_challenge_ack(struct sock *sk, bool accecn_reflector) +{ + struct tcp_sock *tp = tcp_sk(sk); + struct net *net = sock_net(sk); + + /* First check our per-socket dupack rate limit. */ + if (__tcp_oow_rate_limited(net, + LINUX_MIB_TCPACKSKIPPEDCHALLENGE, + &tp->last_oow_ack_time)) + return; + + /* Then check the per-netns RFC 5961 rate limit. */ + if (tcp_challenge_ack_allowed(net)) { NET_INC_STATS(net, LINUX_MIB_TCPCHALLENGEACK); tcp_send_ack_reflect_ect(sk, accecn_reflector); } } +/* Send a challenge ACK from a SYN-RECEIVED request socket. Uses + * __tcp_oow_rate_limited() directly so that an RST carrying payload + * cannot bypass the per-request rate limit. + */ +void tcp_reqsk_send_challenge_ack(struct sock *sk, struct sk_buff *skb, + struct request_sock *req) +{ + struct net *net = sock_net(sk); + + if (__tcp_oow_rate_limited(net, LINUX_MIB_TCPACKSKIPPEDCHALLENGE, + &tcp_rsk(req)->last_oow_ack_time)) + return; + + if (tcp_challenge_ack_allowed(net)) { + NET_INC_STATS(net, LINUX_MIB_TCPCHALLENGEACK); + req->rsk_ops->send_ack(sk, skb, req); + } +} + static void tcp_store_ts_recent(struct tcp_sock *tp) { tp->rx_opt.ts_recent = tp->rx_opt.rcv_tsval; diff --git a/net/ipv4/tcp_minisocks.c b/net/ipv4/tcp_minisocks.c index ddc4b17a826b..6ab3e3a0b431 100644 --- a/net/ipv4/tcp_minisocks.c +++ b/net/ipv4/tcp_minisocks.c @@ -833,7 +833,7 @@ struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb, * elsewhere and is checked directly against the child socket rather * than req because user data may have been sent out. */ - if ((flg & TCP_FLAG_ACK) && !fastopen && + if ((flg & TCP_FLAG_ACK) && !(flg & TCP_FLAG_RST) && !fastopen && (TCP_SKB_CB(skb)->ack_seq != tcp_rsk(req)->snt_isn + 1)) return sk; @@ -872,6 +872,16 @@ struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb, flg &= ~TCP_FLAG_SYN; } + /* RFC 5961 section 3.2, as clarified by RFC 9293 section + * 3.10.7.4, requires a challenge ACK for a non-exact + * in-window RST in SYN-RECEIVED. + */ + if ((flg & TCP_FLAG_RST) && + TCP_SKB_CB(skb)->seq != tcp_rsk(req)->rcv_nxt) { + tcp_reqsk_send_challenge_ack(sk, skb, req); + return NULL; + } + /* RFC793: "second check the RST bit" and * "fourth, check the SYN bit" */ From 7bb18355e5996a70b15ad571f5597e13d61af00d Mon Sep 17 00:00:00 2001 From: Yuxiang Yang Date: Fri, 17 Jul 2026 08:14:43 +0000 Subject: [PATCH 204/234] selftests/net: packetdrill: cover RST validation in SYN-RECEIVED Add packetdrill coverage for the RFC 9293 reset checks on request sockets in SYN-RECEIVED. Verify that an exact RST removes the request, a non-exact in-window RST sends a challenge ACK without removing it, and an out-of-window RST is silently discarded. Also cover an RST|ACK with an unacceptable ACK number to ensure RST sequence validation runs before ACK-field validation. Signed-off-by: Yuxiang Yang Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260717081443.809393-3-yangyx22@mails.tsinghua.edu.cn Signed-off-by: Jakub Kicinski --- .../packetdrill/tcp_rfc5961_rst-syn-recv.pkt | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 tools/testing/selftests/net/packetdrill/tcp_rfc5961_rst-syn-recv.pkt diff --git a/tools/testing/selftests/net/packetdrill/tcp_rfc5961_rst-syn-recv.pkt b/tools/testing/selftests/net/packetdrill/tcp_rfc5961_rst-syn-recv.pkt new file mode 100644 index 000000000000..3fc2de03658a --- /dev/null +++ b/tools/testing/selftests/net/packetdrill/tcp_rfc5961_rst-syn-recv.pkt @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// RFC 9293 Section 3.10.7.4: in SYN-RECEIVED, an exact RST resets +// the connection. A non-exact in-window RST elicits a challenge ACK, +// while an out-of-window RST is silently discarded. + +`./defaults.sh` + +// An exact RST removes the request socket. + 0 socket(..., SOCK_STREAM|SOCK_NONBLOCK, IPPROTO_TCP) = 3 + +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 + +0 bind(3, ..., ...) = 0 + +0 listen(3, 1) = 0 + +0 < S 0:0(0) win 1000 + +0 > S. 0:0(0) ack 1 <...> + +0 < R 1:1(0) win 1000 + +.1 < . 1:1(0) ack 1 win 1000 + +0 > R 1:1(0) + +0 close(3) = 0 + +// A non-exact in-window RST gets a challenge ACK and the request survives. + +0 socket(..., SOCK_STREAM|SOCK_NONBLOCK, IPPROTO_TCP) = 3 + +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 + +0 bind(3, ..., ...) = 0 + +0 listen(3, 1) = 0 + +0 < S 0:0(0) win 1000 + +0 > S. 0:0(0) ack 1 <...> + +0 < R 2:2(0) win 1000 + +0 > . 1:1(0) ack 1 + +0 < . 1:1(0) ack 1 win 1000 + +0 accept(3, ..., ...) = 4 + +0 close(4) = 0 + +0 close(3) = 0 + +// RST sequence validation precedes ACK validation. Even an RST|ACK +// with an unacceptable ACK value gets a challenge ACK. + +0 socket(..., SOCK_STREAM|SOCK_NONBLOCK, IPPROTO_TCP) = 3 + +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 + +0 bind(3, ..., ...) = 0 + +0 listen(3, 1) = 0 + +0 < S 0:0(0) win 1000 + +0 > S. 0:0(0) ack 1 <...> + +0 < R. 2:2(0) ack 100 win 1000 + +0 > . 1:1(0) ack 1 + +0 < . 1:1(0) ack 1 win 1000 + +0 accept(3, ..., ...) = 4 + +0 close(4) = 0 + +0 close(3) = 0 + +// An out-of-window RST is silent and does not remove the request. + +0 socket(..., SOCK_STREAM|SOCK_NONBLOCK, IPPROTO_TCP) = 3 + +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 + +0 bind(3, ..., ...) = 0 + +0 listen(3, 1) = 0 + +0 < S 0:0(0) win 1000 + +0 > S. 0:0(0) ack 1 <...> + +0 < R 100001:100001(0) win 1000 + +.1 < . 1:1(0) ack 1 win 1000 + +0 accept(3, ..., ...) = 4 + +0 close(4) = 0 + +0 close(3) = 0 From bb0d96ebe5f4d1acccf4dc36ca7f01f9a8fa1ba1 Mon Sep 17 00:00:00 2001 From: Hariprasad Kelam Date: Fri, 17 Jul 2026 14:13:49 +0530 Subject: [PATCH 205/234] octeontx2-pf: tc: fix egress ratelimiting The egress rate calculation computes an incorrect mantissa and exponent, causing up to ~50% deviation from the configured rate at lower speeds. Rework the computation to follow the hardware rate formula: rate = 2 * (1 + mantissa/256) * 2^exp / (1 << div_exp) Keep div_exp = 0 and derive exp and mantissa from half of the requested rate. Rates below 2 Mbps are floored to the smallest encodable step (exp = 0, mantissa = 0). Fixes: e638a83f167e ("octeontx2-pf: TC_MATCHALL egress ratelimiting offload") Signed-off-by: Hariprasad Kelam Signed-off-by: Nitin Shetty J Link: https://patch.msgid.link/20260717084349.2227796-1-nshettyj@marvell.com Signed-off-by: Jakub Kicinski --- .../ethernet/marvell/octeontx2/nic/otx2_tc.c | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_tc.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_tc.c index 40162b08014d..0b46ec29e64e 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_tc.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_tc.c @@ -30,6 +30,7 @@ #define OTX2_UNSUPP_LSE_DEPTH GENMASK(6, 4) #define MCAST_INVALID_GRP (-1U) +#define RATE_MANTISSA_BITS 8 static void otx2_get_egress_burst_cfg(struct otx2_nic *nic, u32 burst, u32 *burst_exp, u32 *burst_mantissa) @@ -66,28 +67,30 @@ static void otx2_get_egress_burst_cfg(struct otx2_nic *nic, u32 burst, static void otx2_get_egress_rate_cfg(u64 maxrate, u32 *exp, u32 *mantissa, u32 *div_exp) { - u64 tmp; - /* Rate calculation by hardware * * PIR_ADD = ((256 + mantissa) << exp) / 256 * rate = (2 * PIR_ADD) / ( 1 << div_exp) * The resultant rate is in Mbps. + * + * Use div_exp = 0 and compute exp/mantissa for maxrate / 2; the + * leading factor of two yields the full rate. Rates below 2 Mbps + * are floored to the smallest step (exp = 0, mantissa = 0). */ - /* 2Mbps to 100Gbps can be expressed with div_exp = 0. - * Setting this to '0' will ease the calculation of - * exponent and mantissa. - */ *div_exp = 0; - if (maxrate) { - *exp = ilog2(maxrate) ? ilog2(maxrate) - 1 : 0; - tmp = maxrate - rounddown_pow_of_two(maxrate); - if (maxrate < MAX_RATE_MANTISSA) - *mantissa = tmp * 2; - else - *mantissa = tmp / (1ULL << (*exp - 7)); + maxrate = maxrate / 2; + if (!maxrate) { + /* Rates below 2 Mbps map to the smallest step */ + *exp = 0; + *mantissa = 0; + } else { + *exp = ilog2(maxrate); + /* Clear MSB and derive fractional bits */ + maxrate &= ~BIT(*exp); + *mantissa = (maxrate << RATE_MANTISSA_BITS) >> *exp; + } } else { /* Instead of disabling rate limiting, set all values to max */ *exp = MAX_RATE_EXPONENT; From 440e274da4d1b93c7df2cb0ce893c3009dd4db55 Mon Sep 17 00:00:00 2001 From: Li RongQing Date: Fri, 17 Jul 2026 22:32:30 +0800 Subject: [PATCH 206/234] net: ipv6: fix dif and sdif mismatch in raw6_icmp_error In raw6_icmp_error(), raw_v6_match() is called with inet6_iif(skb) passed to both the 'dif' and 'sdif' arguments. This is a copy-paste or typo error, as the last argument should represent the secondary interface index (sdif). This mismatch breaks ICMPv6 error handling for IPv6 raw sockets in VRF (Virtual Routing and Forwarding) environments. When a raw socket is bound to a VRF master device, raw_v6_match() fails to find a match because it is not given the correct sdif value, causing the socket to miss relevant ICMPv6 error notifications. Fix this by properly passing inet6_sdif(skb) as the last argument to raw_v6_match(). Fixes: 5108ab4bf446fa ("net: ipv6: add second dif to raw socket lookups") Signed-off-by: Li RongQing Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260717143230.1836-1-lirongqing@baidu.com Signed-off-by: Jakub Kicinski --- net/ipv6/raw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ipv6/raw.c b/net/ipv6/raw.c index 3cc58698cbbd..b88d364e78aa 100644 --- a/net/ipv6/raw.c +++ b/net/ipv6/raw.c @@ -349,7 +349,7 @@ void raw6_icmp_error(struct sk_buff *skb, int nexthdr, const struct ipv6hdr *ip6h = (const struct ipv6hdr *)skb->data; if (!raw_v6_match(net, sk, nexthdr, &ip6h->saddr, &ip6h->daddr, - inet6_iif(skb), inet6_iif(skb))) + inet6_iif(skb), inet6_sdif(skb))) continue; rawv6_err(sk, skb, type, code, inner_offset, info); } From 99d0f42b0e5c57e4c02070a908aaff082881293a Mon Sep 17 00:00:00 2001 From: Vincent Chen Date: Fri, 17 Jul 2026 11:53:23 -0700 Subject: [PATCH 207/234] ice: allow creating VFs when !CONFIG_ICE_SWITCHDEV Currently ice_eswitch_attach_vf() is called unconditionally in ice_start_vfs(), which causes VF creation to fail when CONFIG_ICE_SWITCHDEV is not defined. Fix this by adding switchdev mode checks at the call sites before calling ice_eswitch_attach_vf(), consistent with how ice_eswitch_attach_sf() is already handled in ice_devlink_port_new(). This is similar to commit aacca7a83b97 ("ice: allow creating VFs for !CONFIG_NET_SWITCHDEV") which fixed the same issue for the previous ice_eswitch_configure() API. Fixes: 415db8399d06 ("ice: make representor code generic") Signed-off-by: Vincent Chen Reviewed-by: Aleksandr Loktionov Tested-by: Rafal Romanowski Signed-off-by: Tony Nguyen Link: https://patch.msgid.link/20260717185340.3595286-2-anthony.l.nguyen@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/ice/ice_eswitch.c | 3 --- drivers/net/ethernet/intel/ice/ice_sriov.c | 14 ++++++++------ drivers/net/ethernet/intel/ice/ice_vf_lib.c | 3 ++- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice_eswitch.c b/drivers/net/ethernet/intel/ice/ice_eswitch.c index c30e27bbfe6e..b069e6c514fb 100644 --- a/drivers/net/ethernet/intel/ice/ice_eswitch.c +++ b/drivers/net/ethernet/intel/ice/ice_eswitch.c @@ -512,9 +512,6 @@ int ice_eswitch_attach_vf(struct ice_pf *pf, struct ice_vf *vf) struct ice_repr *repr; int err; - if (!ice_is_eswitch_mode_switchdev(pf)) - return 0; - repr = ice_repr_create_vf(vf); if (IS_ERR(repr)) return PTR_ERR(repr); diff --git a/drivers/net/ethernet/intel/ice/ice_sriov.c b/drivers/net/ethernet/intel/ice/ice_sriov.c index 7e00e091756d..e04de0215596 100644 --- a/drivers/net/ethernet/intel/ice/ice_sriov.c +++ b/drivers/net/ethernet/intel/ice/ice_sriov.c @@ -484,12 +484,14 @@ static int ice_start_vfs(struct ice_pf *pf) goto teardown; } - retval = ice_eswitch_attach_vf(pf, vf); - if (retval) { - dev_err(ice_pf_to_dev(pf), "Failed to attach VF %d to eswitch, error %d", - vf->vf_id, retval); - ice_vf_vsi_release(vf); - goto teardown; + if (ice_is_eswitch_mode_switchdev(pf)) { + retval = ice_eswitch_attach_vf(pf, vf); + if (retval) { + dev_err(ice_pf_to_dev(pf), "Failed to attach VF %d to eswitch, error %d", + vf->vf_id, retval); + ice_vf_vsi_release(vf); + goto teardown; + } } set_bit(ICE_VF_STATE_INIT, vf->vf_states); diff --git a/drivers/net/ethernet/intel/ice/ice_vf_lib.c b/drivers/net/ethernet/intel/ice/ice_vf_lib.c index 27e4acb1620f..9052e71e9c99 100644 --- a/drivers/net/ethernet/intel/ice/ice_vf_lib.c +++ b/drivers/net/ethernet/intel/ice/ice_vf_lib.c @@ -812,7 +812,8 @@ void ice_reset_all_vfs(struct ice_pf *pf) } ice_vf_post_vsi_rebuild(vf); - ice_eswitch_attach_vf(pf, vf); + if (ice_is_eswitch_mode_switchdev(pf)) + ice_eswitch_attach_vf(pf, vf); mutex_unlock(&vf->cfg_lock); } From 2d19302f628853742c4828381abbd668c1315598 Mon Sep 17 00:00:00 2001 From: Michal Swiatkowski Date: Fri, 17 Jul 2026 11:53:25 -0700 Subject: [PATCH 208/234] ice: pass the return value of skb_checksum_help() skb_checksum_help() can fail. Pass its return value back to the caller. Commonize this software path in goto. Instead of just returning error try calculating software checksum first. There is a check for TSO in checksum_sw_fb. Reviewed-by: Aleksandr Loktionov Signed-off-by: Michal Swiatkowski Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen Link: https://patch.msgid.link/20260717185340.3595286-4-anthony.l.nguyen@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/ice/ice_txrx.c | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice_txrx.c b/drivers/net/ethernet/intel/ice/ice_txrx.c index 4ca1a0602307..c04c5856dad6 100644 --- a/drivers/net/ethernet/intel/ice/ice_txrx.c +++ b/drivers/net/ethernet/intel/ice/ice_txrx.c @@ -1654,7 +1654,7 @@ int ice_tx_csum(struct ice_tx_buf *first, struct ice_tx_offload_params *off) ret = ipv6_skip_exthdr(skb, exthdr - skb->data, &l4_proto, &frag_off); if (ret < 0) - return -1; + goto checksum_sw_fb; } /* define outer transport */ @@ -1673,11 +1673,7 @@ int ice_tx_csum(struct ice_tx_buf *first, struct ice_tx_offload_params *off) l4.hdr = skb_inner_network_header(skb); break; default: - if (first->tx_flags & ICE_TX_FLAGS_TSO) - return -1; - - skb_checksum_help(skb); - return 0; + goto checksum_sw_fb; } /* compute outer L3 header size */ @@ -1736,7 +1732,7 @@ int ice_tx_csum(struct ice_tx_buf *first, struct ice_tx_offload_params *off) ipv6_skip_exthdr(skb, exthdr - skb->data, &l4_proto, &frag_off); } else { - return -1; + goto checksum_sw_fb; } /* compute inner L3 header size */ @@ -1789,15 +1785,17 @@ int ice_tx_csum(struct ice_tx_buf *first, struct ice_tx_offload_params *off) break; default: - if (first->tx_flags & ICE_TX_FLAGS_TSO) - return -1; - skb_checksum_help(skb); - return 0; + goto checksum_sw_fb; } off->td_cmd |= cmd; off->td_offset |= offset; return 1; + +checksum_sw_fb: + if (first->tx_flags & ICE_TX_FLAGS_TSO) + return -1; + return skb_checksum_help(skb); } /** From d6da9b7d48599db078aea6144997a381f8d90d45 Mon Sep 17 00:00:00 2001 From: Marcin Szycik Date: Fri, 17 Jul 2026 11:53:28 -0700 Subject: [PATCH 209/234] ice: fix LAG recipe to profile association ice_init_lag() associates recipes to profiles, assuming that Link Aggregation-related profiles will always have profile ID lower than 70 (ICE_PROFID_IPV6_GTPU_IPV6_TCP_INNER). This value seems arbitrary and might not always be valid for some versions of DDP package, i.e. LAG profiles may have profile ID greater than 70. This would lead to misconfigured switch and LAG not working properly. Fix it by checking up to maximum profile ID. Fixes: 1e0f9881ef79 ("ice: Flesh out implementation of support for SRIOV on bonded interface") Signed-off-by: Marcin Szycik Reviewed-by: Michal Swiatkowski Reviewed-by: Aleksandr Loktionov Reviewed-by: Dave Ertman Reviewed-by: Simon Horman Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen Link: https://patch.msgid.link/20260717185340.3595286-7-anthony.l.nguyen@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/ice/ice_lag.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/intel/ice/ice_lag.c b/drivers/net/ethernet/intel/ice/ice_lag.c index 310e8fe2925c..08a17ded0ad5 100644 --- a/drivers/net/ethernet/intel/ice/ice_lag.c +++ b/drivers/net/ethernet/intel/ice/ice_lag.c @@ -2623,7 +2623,7 @@ int ice_init_lag(struct ice_pf *pf) goto free_lport_res; /* associate recipes to profiles */ - for (n = 0; n < ICE_PROFID_IPV6_GTPU_IPV6_TCP_INNER; n++) { + for (n = 0; n < ICE_MAX_NUM_PROFILES; n++) { err = ice_aq_get_recipe_to_profile(&pf->hw, n, &recipe_bits, NULL); if (err) From 2915681b89f817677ab9f1166d95b595bc144f5f Mon Sep 17 00:00:00 2001 From: Sergey Temerkhanov Date: Fri, 17 Jul 2026 11:53:30 -0700 Subject: [PATCH 210/234] ice: use READ_ONCE() to access cached PHC time ptp.cached_phc_time is a 64-bit value updated by a periodic work item on one CPU and read locklessly on another. On 32-bit or non-atomic architectures this can result in a torn read. Use READ_ONCE() to enforce a single atomic load. Fixes: 77a781155a65 ("ice: enable receive hardware timestamping") Cc: stable@vger.kernel.org Signed-off-by: Sergey Temerkhanov Signed-off-by: Aleksandr Loktionov Reviewed-by: Simon Horman Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen Link: https://patch.msgid.link/20260717185340.3595286-9-anthony.l.nguyen@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/ice/ice_ptp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c index ec3d89d8d4d3..1469038bc895 100644 --- a/drivers/net/ethernet/intel/ice/ice_ptp.c +++ b/drivers/net/ethernet/intel/ice/ice_ptp.c @@ -346,7 +346,7 @@ static u64 ice_ptp_extend_40b_ts(struct ice_pf *pf, u64 in_tstamp) return 0; } - return ice_ptp_extend_32b_ts(pf->ptp.cached_phc_time, + return ice_ptp_extend_32b_ts(READ_ONCE(pf->ptp.cached_phc_time), (in_tstamp >> 8) & mask); } From f6a7e00b81e35ef1325234925f2fe1e53b466f92 Mon Sep 17 00:00:00 2001 From: Paul Greenwalt Date: Fri, 17 Jul 2026 11:53:31 -0700 Subject: [PATCH 211/234] ice: fix PTP Call Trace during PTP release If a PF reset occurs when the PTP state is ICE_PTP_UNINIT, then ice_ptp_rebuild() will update the state to ICE_PTP_ERROR. This will result in the following PTP release call trace during driver unload: kernel BUG at lib/list_debug.c:52! ice_ptp_release+0x332/0x3c0 [ice] ice_deinit_features.part.0+0x10e/0x120 [ice] ice_remove+0x100/0x220 [ice] This was observed when passing PF1 through to a VM. ice_ptp_init() fails because ctrl_pf is NULL and sets the state to ICE_PTP_UNINIT. Fix by detecting the ICE_PTP_UNINIT state in ice_ptp_rebuild() and returning without error, preventing the invalid state transition to ICE_PTP_ERROR. The only valid path to ICE_PTP_ERROR is from ICE_PTP_RESETTING after a failed rebuild. Fixes: 8293e4cb2ff5 ("ice: introduce PTP state machine") Cc: stable@vger.kernel.org Signed-off-by: Paul Greenwalt Signed-off-by: Aleksandr Loktionov Reviewed-by: Simon Horman Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen Link: https://patch.msgid.link/20260717185340.3595286-10-anthony.l.nguyen@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/ice/ice_ptp.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c index 1469038bc895..eaec36ab6ae3 100644 --- a/drivers/net/ethernet/intel/ice/ice_ptp.c +++ b/drivers/net/ethernet/intel/ice/ice_ptp.c @@ -3037,6 +3037,11 @@ void ice_ptp_rebuild(struct ice_pf *pf, enum ice_reset_req reset_type) struct ice_ptp *ptp = &pf->ptp; int err; + if (ptp->state == ICE_PTP_UNINIT) { + dev_dbg(ice_pf_to_dev(pf), "PTP was not initialized, skipping rebuild\n"); + return; + } + if (ptp->state == ICE_PTP_READY) { ice_ptp_prepare_for_reset(pf, reset_type); } else if (ptp->state != ICE_PTP_RESETTING) { From 144539bbfd3cea1ab0fb6f5216d6004c1f4f029b Mon Sep 17 00:00:00 2001 From: Paul Greenwalt Date: Fri, 17 Jul 2026 11:53:32 -0700 Subject: [PATCH 212/234] ice: prevent tstamp ring allocation for non-PF VSI types The pf->txtime_txqs bitmap tracks which Tx queues have ETF (Earliest TxTime First) offload enabled. This bitmap is indexed by queue number and is set by ice_offload_txtime(), which only operates on PF VSI queues. However, ice_is_txtime_ena() does not check the VSI type before consulting the bitmap. When ETF offload is enabled on PF Tx queue 0, bit 0 is set in pf->txtime_txqs. During a subsequent PCI reset rebuild, the CTRL VSI's Tx queue 0 is reconfigured and ice_is_txtime_ena() is called for that ring. Since it only checks pf->txtime_txqs by queue index without distinguishing VSI type, it finds bit 0 set and returns true, matching the PF VSI's ETF queue, not the CTRL VSI's. This causes ice_vsi_cfg_txq() to spuriously allocate a tstamp_ring for the CTRL VSI ring. Since CTRL VSI rings have no associated netdev, ice_clean_tx_ring() takes an early return at the !netdev check before reaching ice_free_tx_tstamp_ring(), leaking the allocation. Each PCI reset leaks one 64-byte tstamp_ring. Fix this by restricting ice_is_txtime_ena() to return true only for PF VSI rings, since txtime_txqs is only meaningful for PF VSI queues. Fixes: ccde82e90946 ("ice: add E830 Earliest TxTime First Offload support") Signed-off-by: Paul Greenwalt Reviewed-by: Przemek Kitszel Reviewed-by: Aleksandr Loktionov Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen Link: https://patch.msgid.link/20260717185340.3595286-11-anthony.l.nguyen@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/ice/ice.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/intel/ice/ice.h b/drivers/net/ethernet/intel/ice/ice.h index f72bb1aa4067..fc91b6665f90 100644 --- a/drivers/net/ethernet/intel/ice/ice.h +++ b/drivers/net/ethernet/intel/ice/ice.h @@ -767,6 +767,9 @@ static inline bool ice_is_txtime_ena(const struct ice_tx_ring *ring) struct ice_vsi *vsi = ring->vsi; struct ice_pf *pf = vsi->back; + if (vsi->type != ICE_VSI_PF) + return false; + return test_bit(ring->q_index, pf->txtime_txqs); } From 59abb87159c53605c063f6e2ceb215b5eba43ee6 Mon Sep 17 00:00:00 2001 From: Aleksandr Loktionov Date: Fri, 17 Jul 2026 11:53:33 -0700 Subject: [PATCH 213/234] ice: reject out-of-range ptype in ice_parser_profile_init set_bit(rslt->ptype, prof->ptypes) operates on a DECLARE_BITMAP of ICE_FLOW_PTYPE_MAX (1024) bits. Nothing prevents a malicious VF from providing ptype >= 1024 through VIRTCHNL, resulting in a write past the end of the bitmap and a kernel page fault. Reproduced with a custom kernel module injecting a crafted VIRTCHNL_OP_ADD_RSS_CFG on E810-C QSFP (8086:1592), FW 4.91 0x800214af 1.3909.0, ICE COMMS DDP 1.3.53.0, kernel 7.1.0-rc1. crash_parser: ice_parser_profile_init @ ffffffffc0d61b60 crash_parser: setting ptype=0xffff (max valid=1023) crash_parser: calling ice_parser_profile_init -- expect OOB crash! BUG: kernel NULL pointer dereference, address: 0000000000000000 Oops: Oops: 0002 [#1] SMP NOPTI CPU: 56 UID: 0 PID: 165011 Comm: insmod Kdump: loaded Tainted: G S U OE 7.1.0-rc1 #1 Hardware name: Intel Corporation S2600BPB/S2600BPB RIP: 0010:ice_parser_profile_init+0x2d/0x1d0 [ice] Call Trace: ? __pfx_ice_parser_profile_init+0x10/0x10 [ice] crash_init+0x127/0xff0 [crash_parser] do_one_initcall+0x45/0x310 do_init_module+0x64/0x270 init_module_from_file+0xcc/0xf0 idempotent_init_module+0x17b/0x280 __x64_sys_finit_module+0x6e/0xe0 Bail out early with -EINVAL when ptype is out of range. Fixes: e312b3a1e209 ("ice: add API for parser profile initialization") Cc: stable@vger.kernel.org Signed-off-by: Aleksandr Loktionov Reviewed-by: Marcin Szycik Tested-by: Rafal Romanowski Signed-off-by: Tony Nguyen Link: https://patch.msgid.link/20260717185340.3595286-12-anthony.l.nguyen@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/ice/ice_parser.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/intel/ice/ice_parser.c b/drivers/net/ethernet/intel/ice/ice_parser.c index f8e69630fb72..3ede4c1a5a8a 100644 --- a/drivers/net/ethernet/intel/ice/ice_parser.c +++ b/drivers/net/ethernet/intel/ice/ice_parser.c @@ -2368,6 +2368,9 @@ int ice_parser_profile_init(struct ice_parser_result *rslt, u16 proto_off = 0; u16 off; + if (rslt->ptype >= ICE_FLOW_PTYPE_MAX) + return -EINVAL; + memset(prof, 0, sizeof(*prof)); set_bit(rslt->ptype, prof->ptypes); if (blk == ICE_BLK_SW) { From 237f1f7653b8729169af11fae79f01b90d00b87e Mon Sep 17 00:00:00 2001 From: Emil Tantilov Date: Fri, 17 Jul 2026 11:53:34 -0700 Subject: [PATCH 214/234] idpf: fix max_vport related crash on allocation error during init Set adapter->max_vports only after successful allocation of vports, netdevs and vport_config buffers. This fixes possible crashes on reset or rmmod, following failed allocation on init [ 305.981402] idpf 0000:83:00.0: enabling device (0100 -> 0102) [ 305.994464] idpf 0000:83:00.0: Device HW Reset initiated [ 320.416872] BUG: kernel NULL pointer dereference, address: 0000000000000000 [ 320.416918] #PF: supervisor read access in kernel mode [ 320.416942] #PF: error_code(0x0000) - not-present page [ 320.416963] PGD 2099657067 P4D 0 [ 320.416983] Oops: Oops: 0000 [#1] SMP NOPTI ... [ 320.417093] RIP: 0010:idpf_remove+0x118/0x200 [idpf] [ 320.417130] Code: 8b bb 98 09 00 00 e8 17 0f 5b e5 48 8b bb e8 08 00 00 e8 0b 0f 5b e5 66 83 bb 28 06 00 00 00 48 8b bb 20 06 00 00 74 49 31 ed <48> 8b 04 ef 48 85 c0 74 2f 48 8b 78 20 e8 66 58 91 e5 48 8b 83 20 [ 320.417183] RSP: 0018:ff7322212903fdb8 EFLAGS: 00010246 [ 320.417205] RAX: 0000000000000000 RBX: ff4463de40300000 RCX: ff7322212903fd4c [ 320.417228] RDX: 0000000000000001 RSI: ffffffffa7f7d100 RDI: 0000000000000000 [ 320.417250] RBP: 0000000000000000 R08: 0000000000000001 R09: 0000000000000000 [ 320.417272] R10: 0000000000000001 R11: ff4463de3a638f58 R12: ff4463be89ac7000 [ 320.417294] R13: ff4463be89ac7198 R14: ff4463be94fc7198 R15: ffffffffc0f10f20 [ 320.417317] FS: 00007f963c0e6740(0000) GS:ff4463fdd65d8000(0000) knlGS:0000000000000000 [ 320.417342] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 320.417362] CR2: 0000000000000000 CR3: 00000020ba674002 CR4: 0000000000773ef0 [ 320.417385] PKRU: 55555554 [ 320.417398] Call Trace: [ 320.417412] [ 320.417429] pci_device_remove+0x42/0xb0 [ 320.417459] device_release_driver_internal+0x1a9/0x210 [ 320.417492] driver_detach+0x4b/0x90 [ 320.417516] bus_remove_driver+0x70/0x100 [ 320.417539] pci_unregister_driver+0x2e/0xb0 [ 320.417564] __do_sys_delete_module.constprop.0+0x190/0x2f0 [ 320.417592] ? kmem_cache_free+0x31e/0x550 [ 320.417619] ? lockdep_hardirqs_on_prepare+0xde/0x190 [ 320.417644] ? do_syscall_64+0x38/0x6b0 [ 320.417665] do_syscall_64+0xc8/0x6b0 [ 320.417683] ? clear_bhb_loop+0x30/0x80 [ 320.417706] entry_SYSCALL_64_after_hwframe+0x76/0x7e [ 320.417727] RIP: 0033:0x7f963bb30beb Fixes: 0fe45467a104 ("idpf: add create vport and netdev configuration") Reviewed-by: Madhu Chittim Signed-off-by: Emil Tantilov Reviewed-by: Aleksandr Loktionov Reviewed-by: Simon Horman Tested-by: Samuel Salin Signed-off-by: Tony Nguyen Link: https://patch.msgid.link/20260717185340.3595286-13-anthony.l.nguyen@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/idpf/idpf_virtchnl.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c index be66f9b2e101..dc5ad784f456 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c +++ b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c @@ -3555,7 +3555,6 @@ int idpf_vc_core_init(struct idpf_adapter *adapter) pci_sriov_set_totalvfs(adapter->pdev, idpf_get_max_vfs(adapter)); num_max_vports = idpf_get_max_vports(adapter); - adapter->max_vports = num_max_vports; adapter->vports = kzalloc_objs(*adapter->vports, num_max_vports); if (!adapter->vports) return -ENOMEM; @@ -3576,6 +3575,12 @@ int idpf_vc_core_init(struct idpf_adapter *adapter) goto err_netdev_alloc; } + /* Set max_vports only after vports, netdevs and vport_config buffers + * are allocated to make sure max_vport bound loops don't end up + * crashing, following allocation errors on init. + */ + adapter->max_vports = num_max_vports; + /* Start the mailbox task before requesting vectors. This will ensure * vector information response from mailbox is handled */ From ee7f9bb9320add61f7b367d7e6cd55e3a3a4d65d Mon Sep 17 00:00:00 2001 From: Sungmin Kang <726ksm@gmail.com> Date: Sat, 18 Jul 2026 16:36:30 +0900 Subject: [PATCH 215/234] net: slip: serialize receive against buffer reallocation sl_realloc_bufs() replaces rbuff and updates buffsize while holding sl->lock. slip_receive_buf() reads those fields and writes through rbuff without holding the lock. An MTU change can therefore race with receive processing. An MTU shrink can expose the new smaller rbuff with the old larger bound, causing an out-of-bounds write. A receive callback which already loaded the old rbuff can instead continue writing after that buffer has been freed. Serialize receive processing with sl_realloc_bufs() by holding sl->lock while consuming each receive batch. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Sungmin Kang <726ksm@gmail.com> Link: https://patch.msgid.link/20260718073631.1674-1-726ksm@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/slip/slip.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/slip/slip.c b/drivers/net/slip/slip.c index 820e1a8fc956..faae711cf793 100644 --- a/drivers/net/slip/slip.c +++ b/drivers/net/slip/slip.c @@ -693,6 +693,8 @@ static void slip_receive_buf(struct tty_struct *tty, const u8 *cp, const u8 *fp, if (!sl || sl->magic != SLIP_MAGIC || !netif_running(sl->dev)) return; + spin_lock_bh(&sl->lock); + /* Read the characters out of the buffer */ while (count--) { if (fp && *fp++) { @@ -708,6 +710,8 @@ static void slip_receive_buf(struct tty_struct *tty, const u8 *cp, const u8 *fp, #endif slip_unesc(sl, *cp++); } + + spin_unlock_bh(&sl->lock); } /************************************ From 313a123e1fca8827bb463db1f4bb211309764563 Mon Sep 17 00:00:00 2001 From: "Nikola Z. Ivanov" Date: Sun, 19 Jul 2026 13:57:59 +0300 Subject: [PATCH 216/234] ipv6: Change allocation flags to match rcu_read_lock section requirements Since the call to __ip6_del_rt_siblings has been converted under rcu read lock and it only has one call point we should no longer block or yield. Our stack trace from the syzbot reproducer looks as follows: __ip6_del_rt_siblings rtnl_notify (Here we pass gfp_any() -> GFP_KERNEL) nlmsg_notify nlmsg_multicast nlmsg_multicast_filtered netlink_broadcast_filtered (GFP_KERNEL passed from earlier) netlink_broadcast_filtered can yield if GFP_KERNEL is passed, which we do not want to happen. Fix this by changing the allocation flag of rtnl_notify. Also change the flag passed to nlmsg_new. Even though it is not related to the syzbot generated bug it still falls under the same requirements. Reported-by: syzbot+84d4a405ed798b40c96d@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=84d4a405ed798b40c96d Fixes: bd11ff421d36 ("ipv6: Get rid of RTNL for SIOCDELRT and RTM_DELROUTE.") Signed-off-by: Nikola Z. Ivanov Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260719105759.558050-1-zlatistiv@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv6/route.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/ipv6/route.c b/net/ipv6/route.c index a1301334da48..fc42d67e5822 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -4022,7 +4022,7 @@ static int __ip6_del_rt_siblings(struct fib6_info *rt, struct fib6_config *cfg) struct fib6_node *fn; /* prefer to send a single notification with all hops */ - skb = nlmsg_new(rt6_nlmsg_size(rt), gfp_any()); + skb = nlmsg_new(rt6_nlmsg_size(rt), GFP_ATOMIC); if (skb) { u32 seq = info->nlh ? info->nlh->nlmsg_seq : 0; @@ -4078,7 +4078,7 @@ static int __ip6_del_rt_siblings(struct fib6_info *rt, struct fib6_config *cfg) if (skb) { rtnl_notify(skb, net, info->portid, RTNLGRP_IPV6_ROUTE, - info->nlh, gfp_any()); + info->nlh, GFP_ATOMIC); } return err; } From 167e54c703ccd4fa028feb568b0d1002020cff86 Mon Sep 17 00:00:00 2001 From: "Cen Zhang (Microsoft)" Date: Sun, 19 Jul 2026 17:03:57 -0400 Subject: [PATCH 217/234] rds: tcp: unregister sysctl before tearing down listen socket rds_tcp_exit_net() frees the per-netns RDS TCP listen socket via rds_tcp_kill_sock() before unregistering the per-netns sysctl table. Since rds_tcp_skbuf_handler() derives the netns from rtn->rds_tcp_listen_sock->sk, a concurrent sysctl write can race with netns teardown and dereference the freed socket/sk. KASAN reports the race as: BUG: KASAN: slab-use-after-free in rds_tcp_skbuf_handler+0x2aa/0x2e0 rds_tcp_skbuf_handler net/rds/tcp.c:721 proc_sys_call_handler fs/proc/proc_sysctl.c vfs_write fs/read_write.c __x64_sys_pwrite64 fs/read_write.c Fix this by unregistering the RDS TCP sysctl table before calling rds_tcp_kill_sock(). unregister_net_sysctl_table() prevents new sysctl handlers from starting and waits for in-flight handlers to finish, so the listen socket can then be released safely. The fix was tested against the linked reproducer. Fixes: 7f5611cbc487 ("rds: sysctl: rds_tcp_{rcv,snd}buf: avoid using current->nsproxy") Reported-by: AutonomousCodeSecurity@microsoft.com Link: https://lore.kernel.org/all/20260719203718.9680-1-blbllhy@gmail.com Reviewed-by: Allison Henderson Signed-off-by: Cen Zhang (Microsoft) Link: https://patch.msgid.link/20260719210357.10179-1-blbllhy@gmail.com Signed-off-by: Jakub Kicinski --- net/rds/tcp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/rds/tcp.c b/net/rds/tcp.c index 955d92277d5a..5de35d556f29 100644 --- a/net/rds/tcp.c +++ b/net/rds/tcp.c @@ -657,13 +657,13 @@ static void __net_exit rds_tcp_exit_net(struct net *net) { struct rds_tcp_net *rtn = net_generic(net, rds_tcp_netid); - rds_tcp_kill_sock(net); - if (rtn->rds_tcp_sysctl) unregister_net_sysctl_table(rtn->rds_tcp_sysctl); if (net != &init_net) kfree(rtn->ctl_table); + + rds_tcp_kill_sock(net); } static struct pernet_operations rds_tcp_net_ops = { From c3f2fc231a39e29fe9f0adc14a3ecc3c1260d3c5 Mon Sep 17 00:00:00 2001 From: Clark Wang Date: Mon, 20 Jul 2026 09:25:08 +0800 Subject: [PATCH 218/234] ptp: netc: explicitly clear TMR_OFF during initialization The NETC timer does not support function level reset, so TMR_OFF_L/H registers are not cleared by pcie_flr(). If TMR_OFF was set to a non-zero value in a previous binding, it will persist across driver rebind and cause inaccurate PTP time. There is also a hardware issue: after a warm reset or soft reset, TMR_OFF_L/H registers appear to be cleared to zero, but the timer clock domain internally retains the stale value. When the timer is re-enabled, TMR_CUR_TIME continues to track the old offset until TMR_OFF is written explicitly. This can cause incorrect PTP timestamps and even PTP clock synchronization failures. Per the recommendation from the IP team, explicitly write 0 to TMR_OFF in netc_timer_init() to flush the internally cached value and ensure TMR_CUR_TIME follows the freshly initialized counter. Fixes: 87a201d59963 ("ptp: netc: add NETC V4 Timer PTP driver support") Signed-off-by: Clark Wang Signed-off-by: Wei Fang Reviewed-by: Vadim Fedorenko Reviewed-by: Breno Leitao Link: https://patch.msgid.link/20260720012508.23227-1-wei.fang@oss.nxp.com Signed-off-by: Jakub Kicinski --- drivers/ptp/ptp_netc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/ptp/ptp_netc.c b/drivers/ptp/ptp_netc.c index 94e952ee6990..5e381c354d74 100644 --- a/drivers/ptp/ptp_netc.c +++ b/drivers/ptp/ptp_netc.c @@ -779,6 +779,7 @@ static void netc_timer_init(struct netc_timer *priv) netc_timer_wr(priv, NETC_TMR_FIPER_CTRL, fiper_ctrl); netc_timer_wr(priv, NETC_TMR_ECTRL, NETC_TMR_DEFAULT_ETTF_THR); + netc_timer_offset_write(priv, 0); ktime_get_real_ts64(&now); ns = timespec64_to_ns(&now); netc_timer_cnt_write(priv, ns); From d9a33cadc70a94c1582f65e6042e81027cd200c6 Mon Sep 17 00:00:00 2001 From: Minhong He Date: Mon, 20 Jul 2026 15:25:18 +0800 Subject: [PATCH 219/234] mctp: check register_netdevice_notifier() error in mctp_device_init() mctp_device_init() handles errors from rtnl_af_register() and rtnl_register_many(), but ignores the return value of register_netdevice_notifier(). If notifier registration fails, init can still return success while the module is only partially initialized. Check the notifier registration error and fail module init early. Fixes: 583be982d934 ("mctp: Add device handling and netlink interface") Signed-off-by: Minhong He Link: https://patch.msgid.link/20260720072518.112614-1-heminhong@kylinos.cn Signed-off-by: Jakub Kicinski --- net/mctp/device.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/mctp/device.c b/net/mctp/device.c index 2c84df674669..822120e860c8 100644 --- a/net/mctp/device.c +++ b/net/mctp/device.c @@ -536,7 +536,9 @@ int __init mctp_device_init(void) { int err; - register_netdevice_notifier(&mctp_dev_nb); + err = register_netdevice_notifier(&mctp_dev_nb); + if (err) + return err; err = rtnl_af_register(&mctp_af_ops); if (err) From 649ea07fc25a17aa51bff710baac1ab161022a7c Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Mon, 20 Jul 2026 13:22:28 +0200 Subject: [PATCH 220/234] net: airoha: fix ETS channel derivation in airoha_tc_setup_qdisc_ets() Derive the hardware QoS channel from opt->parent instead of opt->handle in airoha_tc_setup_qdisc_ets(). The ETS qdisc handle is either user-specified or auto-allocated by qdisc_alloc_handle() and bears no relation to the HTB leaf classid that identifies the hardware channel. HTB derives the channel from TC_H_MIN(opt->classid), and ETS is always attached as a child of an HTB leaf, so its opt->parent matches that classid. Using opt->handle instead can cause two ETS qdiscs on different HTB leaves to collide on the same hardware channel, corrupting scheduler configuration and stats. Fixes: 20bf7d07c956 ("net: airoha: Add sched ETS offload support") Reviewed-by: Simon Horman Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260720-airoha-ets-handle-fix-v2-1-6f7129ddc06f@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/airoha/airoha_eth.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c index 90aa8b0210bd..79418e682f71 100644 --- a/drivers/net/ethernet/airoha/airoha_eth.c +++ b/drivers/net/ethernet/airoha/airoha_eth.c @@ -2543,8 +2543,7 @@ static int airoha_tc_setup_qdisc_ets(struct net_device *dev, if (opt->parent == TC_H_ROOT) return -EINVAL; - channel = TC_H_MAJ(opt->handle) >> 16; - channel = channel % AIROHA_NUM_QOS_CHANNELS; + channel = TC_H_MIN(opt->parent) % AIROHA_NUM_QOS_CHANNELS; switch (opt->command) { case TC_ETS_REPLACE: From 47f42ff521b4eeb46e82f9a46a4783a99f7570d7 Mon Sep 17 00:00:00 2001 From: "Cen Zhang (Microsoft)" Date: Mon, 20 Jul 2026 17:41:03 -0400 Subject: [PATCH 221/234] tipc: fix integer overflow in tipc_recvmsg() and tipc_recvstream() In tipc_recvmsg(), the copy length is computed as: copy = min_t(int, dlen - offset, buflen); buflen is size_t but min_t(int, ...) casts it to int. When buflen exceeds INT_MAX (e.g. 0xFFFFFFFF via io_uring provided buffers), it wraps negative, wins the comparison, and the negative copy length propagates to simple_copy_to_iter() where int-to-size_t promotion makes it SIZE_MAX, triggering a WARN_ON. tipc_recvstream() has the same pattern. Kernel panic - not syncing: kernel: panic_on_warn set ... RIP: 0010:simple_copy_to_iter+0x9e/0xd0 (net/core/datagram.c:521) Call Trace: __skb_datagram_iter+0x123/0x8b0 (net/core/datagram.c:402) skb_copy_datagram_iter+0x77/0x1a0 (net/core/datagram.c:534) tipc_recvmsg+0x3d7/0xe80 (net/tipc/socket.c:1934) io_recvmsg+0x47e/0xda0 Fix by changing min_t(int, ...) to min_t(size_t, ...) in both functions. The result is always <= (dlen - offset), which is bounded by TIPC maximum message size (0x1ffff bytes), so the implicit narrowing on assignment to int copy is always safe. Fixes: e9f8b10101c6 ("tipc: refactor function tipc_sk_recvmsg()") Fixes: ec8a09fbbeff ("tipc: refactor function tipc_sk_recv_stream()") Reported-by: AutonomousCodeSecurity@microsoft.com Signed-off-by: Cen Zhang (Microsoft) Reviewed-by: Tung Nguyen Link: https://patch.msgid.link/20260720214103.47732-1-blbllhy@gmail.com Signed-off-by: Jakub Kicinski --- net/tipc/socket.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/tipc/socket.c b/net/tipc/socket.c index 55e695748332..185c24003b82 100644 --- a/net/tipc/socket.c +++ b/net/tipc/socket.c @@ -1936,7 +1936,7 @@ static int tipc_recvmsg(struct socket *sock, struct msghdr *m, if (likely(!err)) { int offset = skb_cb->bytes_read; - copy = min_t(int, dlen - offset, buflen); + copy = min_t(size_t, dlen - offset, buflen); rc = skb_copy_datagram_msg(skb, hlen + offset, m, copy); if (unlikely(rc)) goto exit; @@ -2068,7 +2068,7 @@ static int tipc_recvstream(struct socket *sock, struct msghdr *m, /* Copy data if msg ok, otherwise return error/partial data */ if (likely(!err)) { offset = skb_cb->bytes_read; - copy = min_t(int, dlen - offset, buflen - copied); + copy = min_t(size_t, dlen - offset, buflen - copied); rc = skb_copy_datagram_msg(skb, hlen + offset, m, copy); if (unlikely(rc)) break; From b9e558976bb968162c35ddccdb076a77fc906993 Mon Sep 17 00:00:00 2001 From: Vikas Gupta Date: Tue, 21 Jul 2026 12:07:31 +0530 Subject: [PATCH 222/234] bnge/bng_re: fix ring ID widths Firmware requires more than 16 bits to address TX ring IDs for its internal QP management. Widen the associated HSI ring ID fields to 32 bits. The values firmware assigns remain within 24 bits, bounded by the hardware doorbell XID field. The fw_ring_id field belongs to bnge_ring_struct, a common struct shared by all ring types, so widening it to u32 applies uniformly across TX, RX, CP, and NQ rings but firmware assigns values within 16-bit range for all ring types except TX, which requires the wider field. Note that, Thor Ultra hardware has not yet been deployed and no firmware has been released to field, so backward compatibility is not a concern. Fixes: 42d1c54d6248 ("bnge/bng_re: Add a new HSI") Signed-off-by: Vikas Gupta Reviewed-by: Siva Reddy Kallam Reviewed-by: Dharmender Garg Reviewed-by: Yendapally Reddy Dhananjaya Reddy Link: https://patch.msgid.link/20260721063731.2622500-1-vikas.gupta@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/infiniband/hw/bng_re/bng_dev.c | 6 +-- drivers/net/ethernet/broadcom/bnge/bnge.h | 1 + .../ethernet/broadcom/bnge/bnge_hwrm_lib.c | 8 +-- .../ethernet/broadcom/bnge/bnge_hwrm_lib.h | 2 +- .../net/ethernet/broadcom/bnge/bnge_netdev.c | 50 +++++++++---------- .../net/ethernet/broadcom/bnge/bnge_netdev.h | 4 +- .../net/ethernet/broadcom/bnge/bnge_rmem.h | 2 +- include/linux/bnge/hsi.h | 7 ++- 8 files changed, 39 insertions(+), 41 deletions(-) diff --git a/drivers/infiniband/hw/bng_re/bng_dev.c b/drivers/infiniband/hw/bng_re/bng_dev.c index 71a7ca2196ad..311c8bc93160 100644 --- a/drivers/infiniband/hw/bng_re/bng_dev.c +++ b/drivers/infiniband/hw/bng_re/bng_dev.c @@ -113,7 +113,7 @@ static void bng_re_fill_fw_msg(struct bnge_fw_msg *fw_msg, void *msg, } static int bng_re_net_ring_free(struct bng_re_dev *rdev, - u16 fw_ring_id, int type) + u32 fw_ring_id, int type) { struct bnge_auxr_dev *aux_dev = rdev->aux_dev; struct hwrm_ring_free_input req = {}; @@ -123,7 +123,7 @@ static int bng_re_net_ring_free(struct bng_re_dev *rdev, bng_re_init_hwrm_hdr((void *)&req, HWRM_RING_FREE); req.ring_type = type; - req.ring_id = cpu_to_le16(fw_ring_id); + req.ring_id = cpu_to_le32(fw_ring_id); bng_re_fill_fw_msg(&fw_msg, (void *)&req, sizeof(req), (void *)&resp, sizeof(resp), BNGE_DFLT_HWRM_CMD_TIMEOUT); rc = bnge_send_msg(aux_dev, &fw_msg); @@ -161,7 +161,7 @@ static int bng_re_net_ring_alloc(struct bng_re_dev *rdev, sizeof(resp), BNGE_DFLT_HWRM_CMD_TIMEOUT); rc = bnge_send_msg(aux_dev, &fw_msg); if (!rc) - *fw_ring_id = le16_to_cpu(resp.ring_id); + *fw_ring_id = (u16)le32_to_cpu(resp.ring_id); return rc; } diff --git a/drivers/net/ethernet/broadcom/bnge/bnge.h b/drivers/net/ethernet/broadcom/bnge/bnge.h index f21cff651fd4..4479ccd071f5 100644 --- a/drivers/net/ethernet/broadcom/bnge/bnge.h +++ b/drivers/net/ethernet/broadcom/bnge/bnge.h @@ -36,6 +36,7 @@ struct bnge_pf_info { }; #define INVALID_HW_RING_ID ((u16)-1) +#define INVALID_HW_RING_ID_32BIT (U32_MAX) enum { BNGE_FW_CAP_SHORT_CMD = BIT_ULL(0), diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.c b/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.c index 1c9cfec1b633..651c5e783516 100644 --- a/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.c +++ b/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.c @@ -1283,7 +1283,7 @@ int bnge_hwrm_stat_ctx_alloc(struct bnge_net *bn) int hwrm_ring_free_send_msg(struct bnge_net *bn, struct bnge_ring_struct *ring, - u32 ring_type, int cmpl_ring_id) + u32 ring_type, u32 cmpl_ring_id) { struct hwrm_ring_free_input *req; struct bnge_dev *bd = bn->bd; @@ -1295,7 +1295,7 @@ int hwrm_ring_free_send_msg(struct bnge_net *bn, req->cmpl_ring = cpu_to_le16(cmpl_ring_id); req->ring_type = ring_type; - req->ring_id = cpu_to_le16(ring->fw_ring_id); + req->ring_id = cpu_to_le32(ring->fw_ring_id); bnge_hwrm_req_hold(bd, req); rc = bnge_hwrm_req_send(bd, req); @@ -1317,7 +1317,7 @@ int hwrm_ring_alloc_send_msg(struct bnge_net *bn, struct hwrm_ring_alloc_output *resp; struct hwrm_ring_alloc_input *req; struct bnge_dev *bd = bn->bd; - u16 ring_id, flags = 0; + u32 ring_id, flags = 0; int rc; rc = bnge_hwrm_req_init(bd, req, HWRM_RING_ALLOC); @@ -1401,7 +1401,7 @@ int hwrm_ring_alloc_send_msg(struct bnge_net *bn, resp = bnge_hwrm_req_hold(bd, req); rc = bnge_hwrm_req_send(bd, req); - ring_id = le16_to_cpu(resp->ring_id); + ring_id = le32_to_cpu(resp->ring_id); bnge_hwrm_req_drop(bd, req); exit: diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.h b/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.h index 3501de7a89b9..bf452e390d5b 100644 --- a/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.h +++ b/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.h @@ -50,7 +50,7 @@ int bnge_hwrm_cfa_l2_set_rx_mask(struct bnge_dev *bd, void bnge_hwrm_stat_ctx_free(struct bnge_net *bn); int bnge_hwrm_stat_ctx_alloc(struct bnge_net *bn); int hwrm_ring_free_send_msg(struct bnge_net *bn, struct bnge_ring_struct *ring, - u32 ring_type, int cmpl_ring_id); + u32 ring_type, u32 cmpl_ring_id); int hwrm_ring_alloc_send_msg(struct bnge_net *bn, struct bnge_ring_struct *ring, u32 ring_type, u32 map_index); diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c b/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c index 70768193004c..6f7ef506d4e1 100644 --- a/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c +++ b/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c @@ -1327,12 +1327,12 @@ static int bnge_alloc_core(struct bnge_net *bn) return rc; } -u16 bnge_cp_ring_for_rx(struct bnge_rx_ring_info *rxr) +u32 bnge_cp_ring_for_rx(struct bnge_rx_ring_info *rxr) { return rxr->rx_cpr->ring_struct.fw_ring_id; } -u16 bnge_cp_ring_for_tx(struct bnge_tx_ring_info *txr) +u32 bnge_cp_ring_for_tx(struct bnge_tx_ring_info *txr) { return txr->tx_cpr->ring_struct.fw_ring_id; } @@ -1375,12 +1375,12 @@ static void bnge_init_nq_tree(struct bnge_net *bn) struct bnge_nq_ring_info *nqr = &bn->bnapi[i]->nq_ring; struct bnge_ring_struct *ring = &nqr->ring_struct; - ring->fw_ring_id = INVALID_HW_RING_ID; + ring->fw_ring_id = INVALID_HW_RING_ID_32BIT; for (j = 0; j < nqr->cp_ring_count; j++) { struct bnge_cp_ring_info *cpr = &nqr->cp_ring_arr[j]; ring = &cpr->ring_struct; - ring->fw_ring_id = INVALID_HW_RING_ID; + ring->fw_ring_id = INVALID_HW_RING_ID_32BIT; } } } @@ -1637,7 +1637,7 @@ static void bnge_init_one_rx_ring_rxbd(struct bnge_net *bn, ring = &rxr->rx_ring_struct; bnge_init_rxbd_pages(ring, type); - ring->fw_ring_id = INVALID_HW_RING_ID; + ring->fw_ring_id = INVALID_HW_RING_ID_32BIT; } static void bnge_init_one_agg_ring_rxbd(struct bnge_net *bn, @@ -1647,7 +1647,7 @@ static void bnge_init_one_agg_ring_rxbd(struct bnge_net *bn, u32 type; ring = &rxr->rx_agg_ring_struct; - ring->fw_ring_id = INVALID_HW_RING_ID; + ring->fw_ring_id = INVALID_HW_RING_ID_32BIT; if (bnge_is_agg_reqd(bn->bd)) { type = ((u32)BNGE_RX_PAGE_SIZE << RX_BD_LEN_SHIFT) | RX_BD_TYPE_RX_AGG_BD | RX_BD_FLAGS_SOP; @@ -1708,7 +1708,7 @@ static void bnge_init_tx_rings(struct bnge_net *bn) struct bnge_tx_ring_info *txr = &bn->tx_ring[i]; struct bnge_ring_struct *ring = &txr->tx_ring_struct; - ring->fw_ring_id = INVALID_HW_RING_ID; + ring->fw_ring_id = INVALID_HW_RING_ID_32BIT; netif_queue_set_napi(bn->netdev, i, NETDEV_QUEUE_TYPE_TX, &txr->bnapi->napi); @@ -1867,7 +1867,7 @@ static int bnge_hwrm_rx_agg_ring_alloc(struct bnge_net *bn, ring->fw_ring_id); bnge_db_write(bn->bd, &rxr->rx_agg_db, rxr->rx_agg_prod); bnge_db_write(bn->bd, &rxr->rx_db, rxr->rx_prod); - bn->grp_info[grp_idx].agg_fw_ring_id = ring->fw_ring_id; + bn->grp_info[grp_idx].agg_fw_ring_id = (u16)ring->fw_ring_id; return 0; } @@ -1886,7 +1886,7 @@ static int bnge_hwrm_rx_ring_alloc(struct bnge_net *bn, return rc; bnge_set_db(bn, &rxr->rx_db, type, map_idx, ring->fw_ring_id); - bn->grp_info[map_idx].rx_fw_ring_id = ring->fw_ring_id; + bn->grp_info[map_idx].rx_fw_ring_id = (u16)ring->fw_ring_id; return 0; } @@ -1916,7 +1916,7 @@ static int bnge_hwrm_ring_alloc(struct bnge_net *bn) bnge_set_db(bn, &nqr->nq_db, type, map_idx, ring->fw_ring_id); bnge_db_nq(bn, &nqr->nq_db, nqr->nq_raw_cons); enable_irq(vector); - bn->grp_info[i].nq_fw_ring_id = ring->fw_ring_id; + bn->grp_info[i].nq_fw_ring_id = (u16)ring->fw_ring_id; if (!i) { rc = bnge_hwrm_set_async_event_cr(bd, ring->fw_ring_id); @@ -1986,15 +1986,13 @@ void bnge_fill_hw_rss_tbl(struct bnge_net *bn, struct bnge_vnic_info *vnic) tbl_size = bnge_get_rxfh_indir_size(bd); for (i = 0; i < tbl_size; i++) { - u16 ring_id, j; + u32 j; j = bd->rss_indir_tbl[i]; rxr = &bn->rx_ring[j]; - ring_id = rxr->rx_ring_struct.fw_ring_id; - *ring_tbl++ = cpu_to_le16(ring_id); - ring_id = bnge_cp_ring_for_rx(rxr); - *ring_tbl++ = cpu_to_le16(ring_id); + *ring_tbl++ = cpu_to_le16(rxr->rx_ring_struct.fw_ring_id); + *ring_tbl++ = cpu_to_le16(bnge_cp_ring_for_rx(rxr)); } } @@ -2285,7 +2283,7 @@ static void bnge_disable_int(struct bnge_net *bn) nqr = &bnapi->nq_ring; ring = &nqr->ring_struct; - if (ring->fw_ring_id != INVALID_HW_RING_ID) + if (ring->fw_ring_id != INVALID_HW_RING_ID_32BIT) bnge_db_nq(bn, &nqr->nq_db, nqr->nq_raw_cons); } } @@ -2401,7 +2399,7 @@ static void bnge_hwrm_rx_ring_free(struct bnge_net *bn, u32 grp_idx = rxr->bnapi->index; u32 cmpl_ring_id; - if (ring->fw_ring_id == INVALID_HW_RING_ID) + if (ring->fw_ring_id == INVALID_HW_RING_ID_32BIT) return; cmpl_ring_id = bnge_cp_ring_for_rx(rxr); @@ -2409,7 +2407,7 @@ static void bnge_hwrm_rx_ring_free(struct bnge_net *bn, RING_FREE_REQ_RING_TYPE_RX, close_path ? cmpl_ring_id : INVALID_HW_RING_ID); - ring->fw_ring_id = INVALID_HW_RING_ID; + ring->fw_ring_id = INVALID_HW_RING_ID_32BIT; bn->grp_info[grp_idx].rx_fw_ring_id = INVALID_HW_RING_ID; } @@ -2421,14 +2419,14 @@ static void bnge_hwrm_rx_agg_ring_free(struct bnge_net *bn, u32 grp_idx = rxr->bnapi->index; u32 cmpl_ring_id; - if (ring->fw_ring_id == INVALID_HW_RING_ID) + if (ring->fw_ring_id == INVALID_HW_RING_ID_32BIT) return; cmpl_ring_id = bnge_cp_ring_for_rx(rxr); hwrm_ring_free_send_msg(bn, ring, RING_FREE_REQ_RING_TYPE_RX_AGG, close_path ? cmpl_ring_id : INVALID_HW_RING_ID); - ring->fw_ring_id = INVALID_HW_RING_ID; + ring->fw_ring_id = INVALID_HW_RING_ID_32BIT; bn->grp_info[grp_idx].agg_fw_ring_id = INVALID_HW_RING_ID; } @@ -2439,14 +2437,14 @@ static void bnge_hwrm_tx_ring_free(struct bnge_net *bn, struct bnge_ring_struct *ring = &txr->tx_ring_struct; u32 cmpl_ring_id; - if (ring->fw_ring_id == INVALID_HW_RING_ID) + if (ring->fw_ring_id == INVALID_HW_RING_ID_32BIT) return; cmpl_ring_id = close_path ? bnge_cp_ring_for_tx(txr) : INVALID_HW_RING_ID; hwrm_ring_free_send_msg(bn, ring, RING_FREE_REQ_RING_TYPE_TX, cmpl_ring_id); - ring->fw_ring_id = INVALID_HW_RING_ID; + ring->fw_ring_id = INVALID_HW_RING_ID_32BIT; } static void bnge_hwrm_cp_ring_free(struct bnge_net *bn, @@ -2455,12 +2453,12 @@ static void bnge_hwrm_cp_ring_free(struct bnge_net *bn, struct bnge_ring_struct *ring; ring = &cpr->ring_struct; - if (ring->fw_ring_id == INVALID_HW_RING_ID) + if (ring->fw_ring_id == INVALID_HW_RING_ID_32BIT) return; hwrm_ring_free_send_msg(bn, ring, RING_FREE_REQ_RING_TYPE_L2_CMPL, INVALID_HW_RING_ID); - ring->fw_ring_id = INVALID_HW_RING_ID; + ring->fw_ring_id = INVALID_HW_RING_ID_32BIT; } static void bnge_hwrm_ring_free(struct bnge_net *bn, bool close_path) @@ -2496,11 +2494,11 @@ static void bnge_hwrm_ring_free(struct bnge_net *bn, bool close_path) bnge_hwrm_cp_ring_free(bn, &nqr->cp_ring_arr[j]); ring = &nqr->ring_struct; - if (ring->fw_ring_id != INVALID_HW_RING_ID) { + if (ring->fw_ring_id != INVALID_HW_RING_ID_32BIT) { hwrm_ring_free_send_msg(bn, ring, RING_FREE_REQ_RING_TYPE_NQ, INVALID_HW_RING_ID); - ring->fw_ring_id = INVALID_HW_RING_ID; + ring->fw_ring_id = INVALID_HW_RING_ID_32BIT; bn->grp_info[i].nq_fw_ring_id = INVALID_HW_RING_ID; } } diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_netdev.h b/drivers/net/ethernet/broadcom/bnge/bnge_netdev.h index f4636b5b0cf3..d177919c2e11 100644 --- a/drivers/net/ethernet/broadcom/bnge/bnge_netdev.h +++ b/drivers/net/ethernet/broadcom/bnge/bnge_netdev.h @@ -630,8 +630,8 @@ struct bnge_l2_filter { refcount_t refcnt; }; -u16 bnge_cp_ring_for_rx(struct bnge_rx_ring_info *rxr); -u16 bnge_cp_ring_for_tx(struct bnge_tx_ring_info *txr); +u32 bnge_cp_ring_for_rx(struct bnge_rx_ring_info *rxr); +u32 bnge_cp_ring_for_tx(struct bnge_tx_ring_info *txr); void bnge_fill_hw_rss_tbl(struct bnge_net *bn, struct bnge_vnic_info *vnic); int bnge_alloc_rx_data(struct bnge_net *bn, struct bnge_rx_ring_info *rxr, u16 prod, gfp_t gfp); diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_rmem.h b/drivers/net/ethernet/broadcom/bnge/bnge_rmem.h index 341c7f81ed09..bb0c79a1ee60 100644 --- a/drivers/net/ethernet/broadcom/bnge/bnge_rmem.h +++ b/drivers/net/ethernet/broadcom/bnge/bnge_rmem.h @@ -184,7 +184,7 @@ struct bnge_ctx_mem_info { struct bnge_ring_struct { struct bnge_ring_mem_info ring_mem; - u16 fw_ring_id; + u32 fw_ring_id; union { u16 grp_idx; u16 map_idx; /* Used by NQs */ diff --git a/include/linux/bnge/hsi.h b/include/linux/bnge/hsi.h index 8ea13d5407ee..1f7bd96415a5 100644 --- a/include/linux/bnge/hsi.h +++ b/include/linux/bnge/hsi.h @@ -8317,8 +8317,7 @@ struct hwrm_ring_alloc_output { __le16 req_type; __le16 seq_id; __le16 resp_len; - __le16 ring_id; - __le16 logical_ring_id; + __le32 ring_id; u8 push_buffer_index; #define RING_ALLOC_RESP_PUSH_BUFFER_INDEX_PING_BUFFER 0x0UL #define RING_ALLOC_RESP_PUSH_BUFFER_INDEX_PONG_BUFFER 0x1UL @@ -8345,10 +8344,10 @@ struct hwrm_ring_free_input { u8 flags; #define RING_FREE_REQ_FLAGS_VIRTIO_RING_VALID 0x1UL #define RING_FREE_REQ_FLAGS_LAST RING_FREE_REQ_FLAGS_VIRTIO_RING_VALID - __le16 ring_id; + __le16 unused_1; __le32 prod_idx; __le32 opaque; - __le32 unused_1; + __le32 ring_id; }; /* hwrm_ring_free_output (size:128b/16B) */ From 0f71f852a96af9685858ce59fda34ecbf85c283d Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Tue, 21 Jul 2026 01:58:45 -0700 Subject: [PATCH 223/234] phonet: pep: fix use-after-free in pep_get_sb() pep_get_sb() doesn't consider that pskb_may_pull() might have relocated the skb data, and continue to access the older pointer, causing UAF. Reproduced under KASAN: BUG: KASAN: slab-use-after-free in pep_get_sb+0x234/0x3b0 Read of size 1 at addr ff11000105510f50 by task repro/157 pep_get_sb+0x234/0x3b0 pipe_handler_do_rcv+0x5f7/0xa10 pep_do_rcv+0x203/0x410 __sk_receive_skb+0x471/0x4a0 phonet_rcv+0x5b3/0x6c0 __netif_receive_skb+0xcc/0x1d0 Refetch the header with skb_header_pointer() after pskb_may_pull(), so the possibly stale pointer is no longer dereferenced. There are better ways to solve this, but, this is the less instrusive one. Fixes: 9641458d3ec4 ("Phonet: Pipe End Point for Phonet Pipes protocol") Cc: stable@vger.kernel.org Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260721-phonet_get_sb_uaf-v1-1-95fd7881cc4e@debian.org Signed-off-by: Jakub Kicinski --- net/phonet/pep.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/phonet/pep.c b/net/phonet/pep.c index 706927139393..31b29e3ca7bc 100644 --- a/net/phonet/pep.c +++ b/net/phonet/pep.c @@ -55,6 +55,8 @@ static unsigned char *pep_get_sb(struct sk_buff *skb, u8 *ptype, u8 *plen, ph = skb_header_pointer(skb, 0, 2, &h); if (ph == NULL || ph->sb_len < 2 || !pskb_may_pull(skb, ph->sb_len)) return NULL; + /* pskb_may_pull() may have reallocated the head; refetch ph. */ + ph = skb_header_pointer(skb, 0, 2, &h); ph->sb_len -= 2; *ptype = ph->sb_type; *plen = ph->sb_len; From d1ff66b66151c14b084e88040512a064b1c1e493 Mon Sep 17 00:00:00 2001 From: Minhong He Date: Tue, 21 Jul 2026 17:39:56 +0800 Subject: [PATCH 224/234] phonet: check register_netdevice_notifier() error in phonet_device_init() phonet_device_init() registers a netdevice notifier before calling phonet_netlink_register(), but does not check whether notifier registration succeeded. On failure, netlink setup still proceeds and init may return success without the notifier in place. Also, the existing phonet_netlink_register() failure path called phonet_device_exit(), which runs rtnl_unregister_all() even though rtnl_register_many() already unwound any partial registration. Calling the full exit helper on a partial init is not correct. Check each registration error, including proc_create_net(), and unwind only the steps that have succeeded so far, in reverse order. Signed-off-by: Minhong He Link: https://patch.msgid.link/20260721093956.162617-1-heminhong@kylinos.cn Signed-off-by: Jakub Kicinski --- net/phonet/pn_dev.c | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/net/phonet/pn_dev.c b/net/phonet/pn_dev.c index ad44831d6745..1272d49cd038 100644 --- a/net/phonet/pn_dev.c +++ b/net/phonet/pn_dev.c @@ -350,16 +350,34 @@ static struct pernet_operations phonet_net_ops = { /* Initialize Phonet devices list */ int __init phonet_device_init(void) { - int err = register_pernet_subsys(&phonet_net_ops); + int err; + + err = register_pernet_subsys(&phonet_net_ops); if (err) return err; - proc_create_net("pnresource", 0, init_net.proc_net, &pn_res_seq_ops, - sizeof(struct seq_net_private)); - register_netdevice_notifier(&phonet_device_notifier); + if (!proc_create_net("pnresource", 0, init_net.proc_net, + &pn_res_seq_ops, sizeof(struct seq_net_private))) { + err = -ENOMEM; + goto err_pernet; + } + + err = register_netdevice_notifier(&phonet_device_notifier); + if (err) + goto err_proc; + err = phonet_netlink_register(); if (err) - phonet_device_exit(); + goto err_notifier; + + return 0; + +err_notifier: + unregister_netdevice_notifier(&phonet_device_notifier); +err_proc: + remove_proc_entry("pnresource", init_net.proc_net); +err_pernet: + unregister_pernet_subsys(&phonet_net_ops); return err; } @@ -367,8 +385,8 @@ void phonet_device_exit(void) { rtnl_unregister_all(PF_PHONET); unregister_netdevice_notifier(&phonet_device_notifier); - unregister_pernet_subsys(&phonet_net_ops); remove_proc_entry("pnresource", init_net.proc_net); + unregister_pernet_subsys(&phonet_net_ops); } int phonet_route_add(struct net_device *dev, u8 daddr) From 9b2854f86f0b56e9027d68e7a3fc909d1a9b566f Mon Sep 17 00:00:00 2001 From: Jun Yang Date: Tue, 21 Jul 2026 21:14:05 +0800 Subject: [PATCH 225/234] sctp: don't free the ASCONF's own transport in DEL-IP processing sctp_process_asconf() caches the transport the ASCONF chunk is processed against in asconf->transport (== chunk->transport, set once in sctp_rcv()). For an ASCONF located through its Address Parameter by __sctp_rcv_asconf_lookup(), that cached transport corresponds to the Address Parameter, which need not be the packet's source address. sctp_process_asconf_param() rejects a DEL-IP for the packet source address (ADDIP D8, SCTP_ERROR_DEL_SRC_IP), but nothing protects asconf->transport. A single ASCONF can therefore carry, in order: [Address Parameter L] [DEL-IP L] [DEL-IP 0.0.0.0] where L differs from the source. The DEL-IP for L passes the D8 check and calls sctp_assoc_rm_peer() on the transport that asconf->transport still points at, freeing it (RCU-deferred). The following wildcard DEL-IP then reuses the now-dangling asconf->transport in sctp_assoc_set_primary() and sctp_assoc_del_nonprimary_peers(): set_primary() dereferences the freed transport (->ipaddr, ->state) and plants the dangling pointer into asoc->peer.primary_path / active_path, and del_nonprimary_peers(), keeping only the pointer that is no longer on the list, removes every real transport, leaving the association with a transport_count of 0 and primary_path/active_path pointing at freed memory. Reject a DEL-IP that targets the transport the ASCONF is being processed against, mirroring the existing source-address guard, so the wildcard branch can never reuse a freed transport. Fixes: 42e30bf3463c ("[SCTP]: Handle the wildcard ADD-IP Address parameter") Cc: stable@kernel.org Signed-off-by: Jun Yang Acked-by: Xin Long Link: https://patch.msgid.link/tencent_73762ED1DF08CC9D5F5F61954B01350CFE0A@qq.com Signed-off-by: Jakub Kicinski --- net/sctp/sm_make_chunk.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/sctp/sm_make_chunk.c b/net/sctp/sm_make_chunk.c index 8adac9e0cd66..c02809264075 100644 --- a/net/sctp/sm_make_chunk.c +++ b/net/sctp/sm_make_chunk.c @@ -3153,6 +3153,12 @@ static __be16 sctp_process_asconf_param(struct sctp_association *asoc, if (!peer) return SCTP_ERROR_DNS_FAILED; + /* Don't free asconf->transport; a later wildcard DEL-IP + * parameter reuses it. + */ + if (peer == asconf->transport) + return SCTP_ERROR_REQ_REFUSED; + sctp_assoc_rm_peer(asoc, peer); break; case SCTP_PARAM_SET_PRIMARY: From 234e5e898b713bc0b3a631b6f002897f43d046c8 Mon Sep 17 00:00:00 2001 From: Ibrahim Hashimov Date: Tue, 21 Jul 2026 23:12:28 +0200 Subject: [PATCH 226/234] mac802154: hold an interface reference across the scan worker mac802154_scan_worker() captures the scanning sub-interface under RCU and then keeps dereferencing sdata->dev after rcu_read_unlock() and outside the rtnl -- in the failure traces, in mac802154_transmit_beacon_req() (skb->dev = sdata->dev), and in the end_scan cleanup. Nothing keeps that netdev alive across the worker iteration. A concurrent DEL_INTERFACE or PHY removal can unregister the interface once the worker drops the rtnl between its two drv_set_channel() sections. unregister_netdevice() frees the netdev asynchronously from netdev_run_todo() with the rtnl already dropped, so neither holding the rtnl nor the per-PHY IEEE802154_IS_SCANNING flag prevents a stale worker iteration from dereferencing the freed netdev -- a KASAN slab-use-after-free, reachable by racing TRIGGER_SCAN against DEL_INTERFACE (both CAP_NET_ADMIN). Pin the netdev with netdev_hold() while the RCU read lock is still held, and release it at every worker exit. Fixes: 57588c71177f ("mac802154: Handle passive scanning") Cc: stable@vger.kernel.org Signed-off-by: Ibrahim Hashimov Link: https://patch.msgid.link/20260721211228.34578-1-security@auditcode.ai Signed-off-by: Jakub Kicinski --- net/mac802154/scan.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/net/mac802154/scan.c b/net/mac802154/scan.c index 300d4584533e..65089826ff59 100644 --- a/net/mac802154/scan.c +++ b/net/mac802154/scan.c @@ -179,6 +179,7 @@ void mac802154_scan_worker(struct work_struct *work) enum nl802154_scan_types scan_req_type; struct ieee802154_sub_if_data *sdata; unsigned int scan_duration = 0; + netdevice_tracker dev_tracker; struct wpan_phy *wpan_phy; u8 scan_req_duration; u8 page, channel; @@ -209,6 +210,14 @@ void mac802154_scan_worker(struct work_struct *work) return; } + /* + * sdata->dev is dereferenced below after rcu_read_unlock() and outside + * the rtnl, and a concurrent DEL_INTERFACE / PHY teardown can free it + * asynchronously from netdev_run_todo(). Pin it with a reference taken + * while the RCU read lock is still held, and drop it at every exit. + */ + netdev_hold(sdata->dev, &dev_tracker, GFP_ATOMIC); + wpan_phy = scan_req->wpan_phy; scan_req_type = scan_req->type; scan_req_duration = scan_req->duration; @@ -262,12 +271,14 @@ void mac802154_scan_worker(struct work_struct *work) "Scan page %u channel %u for %ums\n", page, channel, jiffies_to_msecs(scan_duration)); queue_delayed_work(local->mac_wq, &local->scan_work, scan_duration); + netdev_put(sdata->dev, &dev_tracker); return; end_scan: rtnl_lock(); mac802154_scan_cleanup_locked(local, sdata, false); rtnl_unlock(); + netdev_put(sdata->dev, &dev_tracker); } int mac802154_trigger_scan_locked(struct ieee802154_sub_if_data *sdata, From f3ca0ee2cc308e33896536789cbc5f3a12ca7b30 Mon Sep 17 00:00:00 2001 From: Chenguang Zhao Date: Wed, 22 Jul 2026 00:14:38 +0200 Subject: [PATCH 227/234] mptcp: decrement subflows counter on failed passive join mptcp_pm_allow_new_subflow() increments extra_subflows before __mptcp_finish_join() on the passive MP_JOIN path. In case of race conditions, the subflow is dropped without calling mptcp_close_ssk(), so the counter is not rolled back. Call mptcp_pm_close_subflow() when the join completion fails to decrement the subflows counter. Fixes: 10f6d46c943d ("mptcp: fix race between MP_JOIN and close") Cc: stable@vger.kernel.org Signed-off-by: Chenguang Zhao Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260722-net-mptcp-misc-fixes-7-2-rc5-v1-1-6fb595bc86ef@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/protocol.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index cb9515f505aa..b32f0cd262a7 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -3907,6 +3907,7 @@ bool mptcp_finish_join(struct sock *ssk) mptcp_data_unlock(parent); if (!ret) { + mptcp_pm_close_subflow(msk); err_prohibited: subflow->reset_reason = MPTCP_RST_EPROHIBIT; return false; From 9bc6d5e4ca9f3cbb41d43400b3a31cb0403796c9 Mon Sep 17 00:00:00 2001 From: Geliang Tang Date: Wed, 22 Jul 2026 00:14:39 +0200 Subject: [PATCH 228/234] mptcp: pm: userspace: fix use-after-free in get_local_id In mptcp_pm_userspace_get_local_id(), the address entry is looked up under spinlock, but its id is read after dropping the lock. A concurrent deletion can free the entry between the unlock and the read, leading to UAF. The race window is narrow. It was reproduced only with a locally constructed stress test that repeatedly overlaps an MP_JOIN SYN with a MPTCP_PM_CMD_SUBFLOW_DESTROY request. However, the KASAN report below confirms that the race is reachable: [ 666.319376] BUG: KASAN: slab-use-after-free in mptcp_userspace_pm_get_local_id+0x1dc/0x1f0 [ 666.319386] Read of size 1 at addr ffff888124845610 by task swapper/0/0 ... [ 666.319401] Call Trace: [ 666.319405] [ 666.319408] dump_stack_lvl+0x53/0x70 [ 666.319412] print_address_description.constprop.0+0x2c/0x3b0 [ 666.319418] print_report+0xbe/0x2b0 [ 666.319421] ? mptcp_userspace_pm_get_local_id+0x1dc/0x1f0 [ 666.319423] kasan_report+0xce/0x100 [ 666.319426] ? mptcp_userspace_pm_get_local_id+0x1dc/0x1f0 [ 666.319429] mptcp_userspace_pm_get_local_id+0x1dc/0x1f0 [ 666.319433] mptcp_pm_get_local_id+0x371/0x440 ... [ 666.319821] Allocated by task 45539: [ 666.319844] kasan_save_stack+0x33/0x60 [ 666.319855] kasan_save_track+0x14/0x30 [ 666.319858] __kasan_kmalloc+0x8f/0xa0 [ 666.319863] __kmalloc_noprof+0x1e7/0x520 [ 666.319867] sock_kmalloc+0xdf/0x130 [ 666.319885] sock_kmemdup+0x1b/0x40 [ 666.319888] mptcp_userspace_pm_append_new_local_addr+0x261/0x500 [ 666.319910] mptcp_pm_nl_announce_doit+0x16a/0x610 ... [ 666.319967] Freed by task 45560: [ 666.319988] kasan_save_stack+0x33/0x60 [ 666.319991] kasan_save_track+0x14/0x30 [ 666.319994] kasan_save_free_info+0x3b/0x60 [ 666.319998] __kasan_slab_free+0x43/0x70 [ 666.320000] kfree+0x166/0x440 [ 666.320003] sock_kfree_s+0x1d/0x50 [ 666.320007] mptcp_userspace_pm_delete_local_addr.isra.0+0x157/0x200 [ 666.320011] mptcp_pm_nl_subflow_destroy_doit+0x51d/0xea0 Fix by copying the id into a local variable while still holding the lock, and use -1 as a "not found" sentinel. Fixes: f012d796a6de ("mptcp: check addrs list in userspace_pm_get_local_id") Cc: stable@vger.kernel.org Signed-off-by: Geliang Tang Tested-by: Xuanqiang Luo Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260722-net-mptcp-misc-fixes-7-2-rc5-v1-2-6fb595bc86ef@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/pm_userspace.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/net/mptcp/pm_userspace.c b/net/mptcp/pm_userspace.c index d100867e9202..945aa5afc2dd 100644 --- a/net/mptcp/pm_userspace.c +++ b/net/mptcp/pm_userspace.c @@ -132,12 +132,15 @@ int mptcp_userspace_pm_get_local_id(struct mptcp_sock *msk, __be16 msk_sport = ((struct inet_sock *) inet_sk((struct sock *)msk))->inet_sport; struct mptcp_pm_addr_entry *entry; + int id; spin_lock_bh(&msk->pm.lock); entry = mptcp_userspace_pm_lookup_addr(msk, &skc->addr); + id = entry ? entry->addr.id : -1; spin_unlock_bh(&msk->pm.lock); - if (entry) - return entry->addr.id; + + if (id != -1) + return id; if (skc->addr.port == msk_sport) skc->addr.port = 0; From bd7aae448f6ee9d82599a4474664de1e6e91a535 Mon Sep 17 00:00:00 2001 From: Kalpan Jani Date: Wed, 22 Jul 2026 00:14:40 +0200 Subject: [PATCH 229/234] mptcp: fix stale skb->sk reference on subflow close The backlog list is updated by mptcp_data_ready() under mptcp_data_lock(). The cleanup of backlog references to a closing subflow, however, was performed in mptcp_close_ssk(), before __mptcp_close_ssk() acquires the ssk lock, and while holding neither the ssk lock nor mptcp_data_lock(). Because that traversal ran without mptcp_data_lock(), concurrent softirq RX processing on another CPU (subflow_data_ready() -> mptcp_data_ready() -> __mptcp_add_backlog(), under mptcp_data_lock()) could add a backlog entry referencing the ssk while the cleanup loop was in progress. Such an entry could be missed by the cleanup, or the concurrent list update could corrupt the traversal, leaving skb->sk pointing at the ssk after it is freed. A later mptcp_backlog_purge() then dereferences the stale pointer, triggering a warning in inet_sock_destruct() (ssk->sk_rmem_alloc != 0) followed by a use-after-free in mptcp_backlog_purge(). Fix this by moving the backlog cleanup into __mptcp_close_ssk(), after subflow->closing is set to 1 and while the ssk lock is still held, serialized under mptcp_data_lock(). The cleanup runs only on the push path (MPTCP_CF_PUSH), where backlog references accumulate; on other teardown paths the caller already handles cleanup. With subflow->closing set and mptcp_data_lock() held across the purge, any concurrent mptcp_data_ready() either completes its enqueue before the purge runs and is caught, or observes closing=1 and bails out. Once mptcp_data_unlock() is reached, no new skb referencing the ssk can be enqueued, so the cleanup is exhaustive. Remove the unprotected traversal from mptcp_close_ssk() entirely. Fixes: ee458a3f314e ("mptcp: introduce mptcp-level backlog") Cc: stable@vger.kernel.org Suggested-by: Paolo Abeni Reported-by: Matthieu Baerts (NGI0) Closes: https://github.com/multipath-tcp/mptcp_net-next/issues/621 Signed-off-by: Kalpan Jani Acked-by: Paolo Abeni Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260722-net-mptcp-misc-fixes-7-2-rc5-v1-3-6fb595bc86ef@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/protocol.c | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index b32f0cd262a7..ca644ec53eed 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -2545,6 +2545,22 @@ static void __mptcp_subflow_disconnect(struct sock *ssk, } } +static void mptcp_cleanup_ssk_backlog(struct sock *sk, struct sock *ssk) +{ + struct mptcp_sock *msk = mptcp_sk(sk); + struct sk_buff *skb; + + mptcp_data_lock(sk); + list_for_each_entry(skb, &msk->backlog_list, list) { + if (skb->sk != ssk) + continue; + + atomic_sub(skb->truesize, &skb->sk->sk_rmem_alloc); + skb->sk = NULL; + } + mptcp_data_unlock(sk); +} + /* subflow sockets can be either outgoing (connect) or incoming * (accept). * @@ -2568,6 +2584,9 @@ static void __mptcp_close_ssk(struct sock *sk, struct sock *ssk, lock_sock_nested(ssk, SINGLE_DEPTH_NESTING); subflow->closing = 1; + if (flags & MPTCP_CF_PUSH) + mptcp_cleanup_ssk_backlog(sk, ssk); + /* Borrow the fwd allocated page left-over; fwd memory for the subflow * could be negative at this point, but will be reach zero soon - when * the data allocated using such fragment will be freed. @@ -2659,9 +2678,6 @@ static void __mptcp_close_ssk(struct sock *sk, struct sock *ssk, void mptcp_close_ssk(struct sock *sk, struct sock *ssk, struct mptcp_subflow_context *subflow) { - struct mptcp_sock *msk = mptcp_sk(sk); - struct sk_buff *skb; - /* The first subflow can already be closed or disconnected */ if (subflow->close_event_done || READ_ONCE(subflow->local_id) < 0) return; @@ -2671,17 +2687,6 @@ void mptcp_close_ssk(struct sock *sk, struct sock *ssk, if (sk->sk_state == TCP_ESTABLISHED) mptcp_event(MPTCP_EVENT_SUB_CLOSED, mptcp_sk(sk), ssk, GFP_KERNEL); - /* Remove any reference from the backlog to this ssk; backlog skbs consume - * space in the msk receive queue, no need to touch sk->sk_rmem_alloc - */ - list_for_each_entry(skb, &msk->backlog_list, list) { - if (skb->sk != ssk) - continue; - - atomic_sub(skb->truesize, &skb->sk->sk_rmem_alloc); - skb->sk = NULL; - } - /* subflow aborted before reaching the fully_established status * attempt the creation of the next subflow */ From e3213292c4fd69ba442c6ed4693f91a92b753140 Mon Sep 17 00:00:00 2001 From: Geliang Tang Date: Wed, 22 Jul 2026 00:14:41 +0200 Subject: [PATCH 230/234] selftests: mptcp: userspace_pm: fix undefined variable port In make_connection(), the variable "port" is used but never defined. This leads to an empty argument being passed to wait_local_port_listen(), causing "printf: : invalid number" errors: # INFO: Init # 01 Created network namespaces ns1, ns2 [ OK ] # INFO: Make connections # ./../lib.sh: line 651: printf: : invalid number # 02 Established IPv4 MPTCP Connection ns2 => ns1 [ OK ] # INFO: Connection info: 10.0.1.2:59516 -> 10.0.1.1:50002 # ./../lib.sh: line 651: printf: : invalid number # 03 Established IPv6 MPTCP Connection ns2 => ns1 [ OK ] Fix it by using the correctly defined variable "app_port", which holds the appropriate port number for the connection. Fixes: 39348f5f2f13 ("selftests: mptcp: wait for port instead of sleep") Cc: stable@vger.kernel.org Signed-off-by: Geliang Tang Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260722-net-mptcp-misc-fixes-7-2-rc5-v1-4-6fb595bc86ef@kernel.org Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/mptcp/userspace_pm.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/net/mptcp/userspace_pm.sh b/tools/testing/selftests/net/mptcp/userspace_pm.sh index e9ae1806ab07..30a809752d1b 100755 --- a/tools/testing/selftests/net/mptcp/userspace_pm.sh +++ b/tools/testing/selftests/net/mptcp/userspace_pm.sh @@ -212,7 +212,7 @@ make_connection() ./mptcp_connect -s MPTCP -w 300 -p $app_port -l $listen_addr > /dev/null 2>&1 & local server_pid=$! - mptcp_lib_wait_local_port_listen "${ns1}" "${port}" + mptcp_lib_wait_local_port_listen "${ns1}" "${app_port}" # Run the client, transfer $file and stay connected to the server # to conduct tests From 133cca19d75b9264bc2bbcdf2c3b80e3da207649 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Wed, 22 Jul 2026 00:14:42 +0200 Subject: [PATCH 231/234] mptcp: fix BUILD_BUG_ON on legacy ARM config The 0-day bot managed to find kernel configs that cause build failures, e.g. when using the StrongARM SA1100 target (ARMv4). On such legacy ARM architecture, all structures are apparently aligned to 32 bits, causing build issue here. Indeed, on such architecture, 'flags' size is not equivalent to sizeof(u16) as expected, but to sizeof(u32). Instead, use memset(). It was not used before to ensure a simple clear operation was used by the compiler. But at the end, it shouldn't matter, and the compiler should optimise this to the same operation with or without memset() when -O above 0 is used. So let's switch to memset() to fix this issue, and reduce this complexity. Fixes: 5e939544f9d2 ("mptcp: fix uninit-value in mptcp_established_options") Cc: stable@vger.kernel.org Suggested-by: Frank Ranner Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202605312026.Srgsz7Tp-lkp@intel.com/ Closes: https://lore.kernel.org/oe-kbuild-all/202607031100.upQfRZTM-lkp@intel.com/ Reviewed-by: Mat Martineau Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260722-net-mptcp-misc-fixes-7-2-rc5-v1-5-6fb595bc86ef@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/options.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/net/mptcp/options.c b/net/mptcp/options.c index 1b74ca5b6a59..c664023d37ba 100644 --- a/net/mptcp/options.c +++ b/net/mptcp/options.c @@ -571,10 +571,7 @@ static bool mptcp_established_options_dss(struct sock *sk, struct sk_buff *skb, bool ret = false; /* Zero `use_ack` and `use_map` flags with one shot. */ - BUILD_BUG_ON(sizeof_field(struct mptcp_ext, flags) != sizeof(u16)); - BUILD_BUG_ON(!IS_ALIGNED(offsetof(struct mptcp_ext, flags), - sizeof(u16))); - *(u16 *)&opts->ext_copy.flags = 0; + memset(&opts->ext_copy.flags, 0, sizeof(opts->ext_copy.flags)); opts->csum_reqd = READ_ONCE(msk->csum_enabled); mpext = skb ? mptcp_get_ext(skb) : NULL; From 5e9c8baee0329fbefe7c67aea945e2a07f15e98b Mon Sep 17 00:00:00 2001 From: Yehyeong Lee Date: Wed, 22 Jul 2026 21:28:17 +0900 Subject: [PATCH 232/234] net: drop_monitor: fix info leak in NET_DM_ATTR_PAYLOAD net_dm_packet_report_fill() and net_dm_hw_packet_report_fill() open code the NET_DM_ATTR_PAYLOAD attribute to avoid zeroing the packet payload before overwriting it with skb_copy_bits(). skb_put() reserves nla_total_size(payload_len), i.e. the header plus the NLA_ALIGN() padding, but only payload_len bytes are copied in. When payload_len is not a multiple of 4 the 1-3 padding bytes are never initialized and are leaked to user space inside the netlink message. KMSAN confirms the leak for the software path when the packet payload length is not 4-byte aligned: BUG: KMSAN: kernel-infoleak in _copy_to_iter _copy_to_iter __skb_datagram_iter skb_copy_datagram_iter netlink_recvmsg sock_recvmsg __sys_recvfrom Uninit was created at: kmem_cache_alloc_node_noprof __alloc_skb net_dm_packet_work Bytes 173-175 of 176 are uninitialized Use __nla_reserve(), which sets up the attribute header and zeroes the padding, instead of open coding the attribute construction. Fixes: ca30707dee2b ("drop_monitor: Add packet alert mode") Fixes: 5e58109b1ea4 ("drop_monitor: Add support for packet alert mode for hardware drops") Suggested-by: Eric Dumazet Signed-off-by: Yehyeong Lee Link: https://patch.msgid.link/20260722122817.5548-1-yhlee@isslab.korea.ac.kr Signed-off-by: Jakub Kicinski --- net/core/drop_monitor.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/net/core/drop_monitor.c b/net/core/drop_monitor.c index 2bf3cab5e557..b4d1ff2829b6 100644 --- a/net/core/drop_monitor.c +++ b/net/core/drop_monitor.c @@ -671,9 +671,7 @@ static int net_dm_packet_report_fill(struct sk_buff *msg, struct sk_buff *skb, if (nla_put_u16(msg, NET_DM_ATTR_PROTO, be16_to_cpu(skb->protocol))) goto nla_put_failure; - attr = skb_put(msg, nla_total_size(payload_len)); - attr->nla_type = NET_DM_ATTR_PAYLOAD; - attr->nla_len = nla_attr_size(payload_len); + attr = __nla_reserve(msg, NET_DM_ATTR_PAYLOAD, payload_len); if (skb_copy_bits(skb, 0, nla_data(attr), payload_len)) goto nla_put_failure; @@ -831,9 +829,7 @@ static int net_dm_hw_packet_report_fill(struct sk_buff *msg, if (nla_put_u16(msg, NET_DM_ATTR_PROTO, be16_to_cpu(skb->protocol))) goto nla_put_failure; - attr = skb_put(msg, nla_total_size(payload_len)); - attr->nla_type = NET_DM_ATTR_PAYLOAD; - attr->nla_len = nla_attr_size(payload_len); + attr = __nla_reserve(msg, NET_DM_ATTR_PAYLOAD, payload_len); if (skb_copy_bits(skb, 0, nla_data(attr), payload_len)) goto nla_put_failure; From 7089f7ab99c89f443c92d8fcc585e63f2727f0b3 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 22 Jul 2026 14:17:42 +0000 Subject: [PATCH 233/234] drop_monitor: fix size calculations for 64-bit attributes net_dm_packet_report_fill() and net_dm_hw_packet_report_fill() use nla_put_u64_64bit() to append 64-bit attributes (NET_DM_ATTR_PC and NET_DM_ATTR_TIMESTAMP). On 32-bit architectures without CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS, nla_put_u64_64bit() may append a 4-byte NET_DM_ATTR_PAD attribute for 64-bit alignment. However, net_dm_packet_report_size() and net_dm_hw_packet_report_size() used nla_total_size(sizeof(u64)) instead of nla_total_size_64bit(sizeof(u64)), budgeting 12 bytes instead of up to 16 bytes. This under-estimation of SKB size can lead to an skb_over_panic() when __nla_reserve() or skb_put() is subsequently called. Fix this by using nla_total_size_64bit(sizeof(u64)) in both size calculations. Fixes: ca30707dee2b ("drop_monitor: Add packet alert mode") Fixes: 5e58109b1ea4 ("drop_monitor: Add support for packet alert mode for hardware drops") Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260722141743.3266924-2-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/core/drop_monitor.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/net/core/drop_monitor.c b/net/core/drop_monitor.c index b4d1ff2829b6..9abcdee8c76e 100644 --- a/net/core/drop_monitor.c +++ b/net/core/drop_monitor.c @@ -566,13 +566,13 @@ static size_t net_dm_packet_report_size(size_t payload_len) /* NET_DM_ATTR_ORIGIN */ nla_total_size(sizeof(u16)) + /* NET_DM_ATTR_PC */ - nla_total_size(sizeof(u64)) + + nla_total_size_64bit(sizeof(u64)) + /* NET_DM_ATTR_SYMBOL */ nla_total_size(NET_DM_MAX_SYMBOL_LEN + 1) + /* NET_DM_ATTR_IN_PORT */ net_dm_in_port_size() + /* NET_DM_ATTR_TIMESTAMP */ - nla_total_size(sizeof(u64)) + + nla_total_size_64bit(sizeof(u64)) + /* NET_DM_ATTR_ORIG_LEN */ nla_total_size(sizeof(u32)) + /* NET_DM_ATTR_PROTO */ @@ -766,7 +766,7 @@ net_dm_hw_packet_report_size(size_t payload_len, /* NET_DM_ATTR_FLOW_ACTION_COOKIE */ net_dm_flow_action_cookie_size(hw_metadata) + /* NET_DM_ATTR_TIMESTAMP */ - nla_total_size(sizeof(u64)) + + nla_total_size_64bit(sizeof(u64)) + /* NET_DM_ATTR_ORIG_LEN */ nla_total_size(sizeof(u32)) + /* NET_DM_ATTR_PROTO */ From fd098a23bf8fda7eae48db9b06e7c34fc4d228fa Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 22 Jul 2026 14:17:43 +0000 Subject: [PATCH 234/234] drop_monitor: perform u64_stats updates under IRQ-disabled section In net_dm_packet_trace_kfree_skb_hit() and net_dm_hw_trap_packet_probe(), u64_stats_update_begin() / u64_stats_inc() / u64_stats_update_end() were called after spin_unlock_irqrestore(&...drop_queue.lock, flags), when local IRQs had already been re-enabled. Tracepoint probes can execute in IRQ or softirq context. On 32-bit architectures, u64_stats_update_begin() disables preemption but not interrupts, relying on seqcount writes. If a nested interrupt occurs on the same CPU during the 64-bit stats update, the reentrant seqcount update can corrupt the seqcount state or stats value. Fix this by performing the 64-bit per-CPU stats update before releasing drop_queue.lock via spin_unlock_irqrestore(), ensuring local interrupts remain disabled during the u64_stats update. Fixes: e9feb58020f9 ("drop_monitor: Expose tail drop counter") Fixes: 5e58109b1ea4 ("drop_monitor: Add support for packet alert mode for hardware drops") Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260722141743.3266924-3-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/core/drop_monitor.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/core/drop_monitor.c b/net/core/drop_monitor.c index 9abcdee8c76e..abaf108ac4db 100644 --- a/net/core/drop_monitor.c +++ b/net/core/drop_monitor.c @@ -530,10 +530,10 @@ static void net_dm_packet_trace_kfree_skb_hit(void *ignore, return; unlock_free: - spin_unlock_irqrestore(&data->drop_queue.lock, flags); u64_stats_update_begin(&data->stats.syncp); u64_stats_inc(&data->stats.dropped); u64_stats_update_end(&data->stats.syncp); + spin_unlock_irqrestore(&data->drop_queue.lock, flags); consume_skb(nskb); } @@ -997,10 +997,10 @@ net_dm_hw_trap_packet_probe(void *ignore, const struct devlink *devlink, return; unlock_free: - spin_unlock_irqrestore(&hw_data->drop_queue.lock, flags); u64_stats_update_begin(&hw_data->stats.syncp); u64_stats_inc(&hw_data->stats.dropped); u64_stats_update_end(&hw_data->stats.syncp); + spin_unlock_irqrestore(&hw_data->drop_queue.lock, flags); net_dm_hw_metadata_free(n_hw_metadata); free: consume_skb(nskb);