From 9d1afbf60ed53c5b051cd124153e0ae3078c8b93 Mon Sep 17 00:00:00 2001 From: Maxim Skokov Date: Tue, 15 Sep 2026 10:00:46 +0300 Subject: [PATCH 001/128] Bluetooth: RFCOMM: free the skb when the DLC has no owner rfcomm_recv_data() hands the skb to d->data_ready() and returns 0 without freeing it, so the callback owns the skb from that point on. The tty implementation frees it when the DLC has been detached from its device: static void rfcomm_dev_data_ready(struct rfcomm_dlc *dlc, struct sk_buff *skb) { struct rfcomm_dev *dev = dlc->owner; if (!dev) { kfree_skb(skb); return; } The socket implementation returns without freeing it, and the skb is leaked. A connected DLC can have no owner: rfcomm_sock_destruct() clears d->owner, while the DLC itself stays on the session until it is closed. Data frames that arrive in that window still find d->state == BT_CONNECTED in rfcomm_recv_data() and are handed to the callback, one leaked skb each. The leak does not stop on its own either. The remote runs out of credits only once RFCOMM_RX_THROTTLED is set, and for a socket the only place that sets it is rfcomm_sk_data_ready() itself, below the return. rfcomm_process_dlcs() keeps granting credits, so the remote keeps sending and the leak keeps growing. Free the skb, as the tty side does. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: Mikhail Gavrilov Closes: https://lore.kernel.org/linux-bluetooth/CABXGCsPe5wykd8Aj-hm+SJ7SGQyb8yoGNnx3XDpPmBathj+EGg@mail.gmail.com/ Cc: stable@vger.kernel.org Signed-off-by: Maxim Skokov Assisted-by: Claude:claude-opus-5 Reviewed-by: Mikhail Gavrilov Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/rfcomm/sock.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/rfcomm/sock.c b/net/bluetooth/rfcomm/sock.c index fb924d0e34ec..b26918dc9e9e 100644 --- a/net/bluetooth/rfcomm/sock.c +++ b/net/bluetooth/rfcomm/sock.c @@ -48,8 +48,11 @@ static void rfcomm_sock_kill(struct sock *sk); static void rfcomm_sk_data_ready(struct rfcomm_dlc *d, struct sk_buff *skb) { struct sock *sk = d->owner; - if (!sk) + + if (!sk) { + kfree_skb(skb); return; + } atomic_add(skb->len, &sk->sk_rmem_alloc); skb_queue_tail(&sk->sk_receive_queue, skb); From 74bbbc9359a592e3c0736b289c00489c1afa928c Mon Sep 17 00:00:00 2001 From: Jose Ignacio Tornos Martinez Date: Tue, 28 Apr 2026 17:26:44 +0200 Subject: [PATCH 002/128] i40e: skip unnecessary VF reset when setting trust The current implementation triggers a VF reset when changing the trust setting, causing a ~10 second delay during bonding setup. In all the cases, the reset causes a ~10 second delay during which: - VF must reinitialize completely - Any in-progress operations (like bonding enslave) fail with timeouts - VF is unavailable When granting trust, no reset is needed - we can just set the capability flag to allow privileged operations. When revoking trust, we only need to reset (conservative approach) if the VF has actually configured advanced features that require cleanup (ADQ/cloud filters, promiscuous mode). For VFs in a clean state, we can safely change the trust setting without the disruptive reset. When we don't reset, we manually handle capability flag via helper function, eliminating the delay. Signed-off-by: Jose Ignacio Tornos Martinez Tested-by: Rafal Romanowski Signed-off-by: Tony Nguyen --- .../ethernet/intel/i40e/i40e_virtchnl_pf.c | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.c b/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.c index a26c3d47ec15..c6732a24b640 100644 --- a/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.c +++ b/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.c @@ -4943,6 +4943,20 @@ out: return ret; } +/** + * i40e_setup_vf_trust - Enable/disable VF trust mode without reset + * @vf: VF to configure + * @setting: trust setting + * + * Update VF flags when changing trust without performing a VF reset. + * This is only called when it's safe to skip the reset (VF has no advanced + * features configured that need cleanup). + */ +static void i40e_setup_vf_trust(struct i40e_vf *vf, bool setting) +{ + assign_bit(I40E_VIRTCHNL_VF_CAP_PRIVILEGE, &vf->vf_caps, setting); +} + /** * i40e_ndo_set_vf_trust * @netdev: network interface device structure of the pf @@ -4987,19 +5001,20 @@ int i40e_ndo_set_vf_trust(struct net_device *netdev, int vf_id, bool setting) set_bit(__I40E_MACVLAN_SYNC_PENDING, pf->state); pf->vsi[vf->lan_vsi_idx]->flags |= I40E_VSI_FLAG_FILTER_CHANGED; - i40e_vc_reset_vf(vf, true); + /* Reset only if revoking trust and VF has advanced features configured */ + if (!setting && + (vf->adq_enabled || vf->num_cloud_filters > 0 || + test_bit(I40E_VF_STATE_UC_PROMISC, &vf->vf_states) || + test_bit(I40E_VF_STATE_MC_PROMISC, &vf->vf_states))) { + i40e_vc_reset_vf(vf, true); + i40e_del_all_cloud_filters(vf); + } else { + i40e_setup_vf_trust(vf, setting); + } + dev_info(&pf->pdev->dev, "VF %u is now %strusted\n", vf_id, setting ? "" : "un"); - if (vf->adq_enabled) { - if (!vf->trusted) { - dev_info(&pf->pdev->dev, - "VF %u no longer Trusted, deleting all cloud filters\n", - vf_id); - i40e_del_all_cloud_filters(vf); - } - } - out: clear_bit(__I40E_VIRTCHNL_OP_PENDING, pf->state); return ret; From 436729a56dd4dac611d10b569c5359d2cf705b19 Mon Sep 17 00:00:00 2001 From: Jose Ignacio Tornos Martinez Date: Mon, 22 Jun 2026 15:58:24 +0200 Subject: [PATCH 003/128] iavf: send MAC change request synchronously After commit ad7c7b2172c3 ("net: hold netdev instance lock during sysfs operations"), iavf_set_mac() is called with the netdev instance lock already held. The function queues a MAC address change request via iavf_replace_primary_mac() and then waits for completion. However, in the current flow, the actual virtchnl message is sent by the watchdog task, which also needs to acquire the netdev lock to run. Additionally, the adminq_task which processes virtchnl responses also needs the netdev lock. This creates a deadlock scenario: 1. iavf_set_mac() holds netdev lock and waits for MAC change 2. Watchdog needs netdev lock to send the request -> blocked 3. Even if request is sent, adminq_task needs netdev lock to process PF response -> blocked 4. MAC change times out after 2.5 seconds 5. iavf_set_mac() returns -EAGAIN This particularly affects VFs during bonding setup when multiple VFs are enslaved in quick succession. Fix by implementing a synchronous MAC change operation similar to the approach used in commit fdadbf6e84c4 ("iavf: fix incorrect reset handling in callbacks"). The solution: 1. Send the virtchnl ADD_ETH_ADDR message directly (not via watchdog) 2. Poll the admin queue hardware directly for responses 3. Process all received messages (including non-MAC messages) 4. Return when MAC change completes or times out A new generic function iavf_poll_virtchnl_response() is introduced that can be reused for any future synchronous virtchnl operations. It takes a callback to check completion, allowing flexible condition checking. This allows the operation to complete synchronously while holding netdev_lock, without relying on watchdog or adminq_task. The function can sleep for up to 2.5 seconds polling hardware, but this is acceptable since netdev_lock is per-device and only serializes operations on the same interface. To support this, change iavf_add_ether_addrs() to return an error code instead of void, allowing callers to detect failures. Additionally, export iavf_mac_add_reject() to enable proper rollback on local failures (timeouts, send errors) - PF rejections are already handled automatically by iavf_virtchnl_completion(). Remove vc_waitqueue entirely because iavf_set_mac was the only waiter on this waitqueue and after the changes it is not needed. Fixes: ad7c7b2172c3 ("net: hold netdev instance lock during sysfs operations") cc: stable@vger.kernel.org Signed-off-by: Jose Ignacio Tornos Martinez Reviewed-by: Przemek Kitszel Tested-by: Rafal Romanowski Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/iavf/iavf.h | 11 ++- drivers/net/ethernet/intel/iavf/iavf_main.c | 85 ++++++++++++---- .../net/ethernet/intel/iavf/iavf_virtchnl.c | 99 +++++++++++++++++-- 3 files changed, 165 insertions(+), 30 deletions(-) diff --git a/drivers/net/ethernet/intel/iavf/iavf.h b/drivers/net/ethernet/intel/iavf/iavf.h index dc31202b2a94..8c45536fd502 100644 --- a/drivers/net/ethernet/intel/iavf/iavf.h +++ b/drivers/net/ethernet/intel/iavf/iavf.h @@ -259,7 +259,6 @@ struct iavf_adapter { struct work_struct adminq_task; struct work_struct finish_config; wait_queue_head_t down_waitqueue; - wait_queue_head_t vc_waitqueue; struct iavf_q_vector *q_vectors; struct list_head vlan_filter_list; int num_vlan_filters; @@ -588,8 +587,9 @@ void iavf_configure_queues(struct iavf_adapter *adapter); void iavf_enable_queues(struct iavf_adapter *adapter); void iavf_disable_queues(struct iavf_adapter *adapter); void iavf_map_queues(struct iavf_adapter *adapter); -void iavf_add_ether_addrs(struct iavf_adapter *adapter); +int iavf_add_ether_addrs(struct iavf_adapter *adapter); void iavf_del_ether_addrs(struct iavf_adapter *adapter); +void iavf_mac_add_reject(struct iavf_adapter *adapter); void iavf_add_vlans(struct iavf_adapter *adapter); void iavf_del_vlans(struct iavf_adapter *adapter); void iavf_set_promiscuous(struct iavf_adapter *adapter); @@ -606,6 +606,13 @@ void iavf_disable_vlan_stripping(struct iavf_adapter *adapter); void iavf_virtchnl_completion(struct iavf_adapter *adapter, enum virtchnl_ops v_opcode, enum iavf_status v_retval, u8 *msg, u16 msglen); +int iavf_poll_virtchnl_response(struct iavf_adapter *adapter, + struct iavf_arq_event_info *event, + bool (*condition)(struct iavf_adapter *adapter, + const void *data, + enum virtchnl_ops v_op), + const void *cond_data, + unsigned int timeout_ms); int iavf_config_rss(struct iavf_adapter *adapter); void iavf_cfg_queues_bw(struct iavf_adapter *adapter); void iavf_cfg_queues_quanta_size(struct iavf_adapter *adapter); diff --git a/drivers/net/ethernet/intel/iavf/iavf_main.c b/drivers/net/ethernet/intel/iavf/iavf_main.c index 29b8403a066b..2f8a80a11336 100644 --- a/drivers/net/ethernet/intel/iavf/iavf_main.c +++ b/drivers/net/ethernet/intel/iavf/iavf_main.c @@ -1029,6 +1029,60 @@ static bool iavf_is_mac_set_handled(struct net_device *netdev, return ret; } +/** + * iavf_mac_change_done - Check if MAC change completed + * @adapter: board private structure + * @data: MAC address being checked (as const void *) + * @v_op: virtchnl opcode from processed message + * + * Callback for iavf_poll_virtchnl_response() to check if MAC change completed. + * + * Return: true if MAC change completed, false otherwise + */ +static bool iavf_mac_change_done(struct iavf_adapter *adapter, + const void *data, enum virtchnl_ops v_op) +{ + const u8 *addr = data; + + return iavf_is_mac_set_handled(adapter->netdev, addr); +} + +/** + * iavf_set_mac_sync - Synchronously change MAC address + * @adapter: board private structure + * @addr: MAC address to set + * + * Send MAC change request to PF and poll admin queue for response. + * Caller must hold netdev_lock. This can sleep for up to 2.5 seconds. + * Event buffer is allocated before sending to avoid state mismatch if + * allocation fails after message is sent to PF. + * + * Return: 0 on success, negative on failure + */ +static int iavf_set_mac_sync(struct iavf_adapter *adapter, const u8 *addr) +{ + struct iavf_arq_event_info event; + int ret; + + netdev_assert_locked(adapter->netdev); + + event.buf_len = IAVF_MAX_AQ_BUF_SIZE; + event.msg_buf = kzalloc(event.buf_len, GFP_KERNEL); + if (!event.msg_buf) + return -ENOMEM; + + ret = iavf_add_ether_addrs(adapter); + if (ret) + goto out; + + ret = iavf_poll_virtchnl_response(adapter, &event, + iavf_mac_change_done, addr, 2500); + +out: + kfree(event.msg_buf); + return ret; +} + /** * iavf_set_mac - NDO callback to set port MAC address * @netdev: network interface device structure @@ -1046,25 +1100,23 @@ static int iavf_set_mac(struct net_device *netdev, void *p) return -EADDRNOTAVAIL; ret = iavf_replace_primary_mac(adapter, addr->sa_data); - if (ret) return ret; - ret = wait_event_interruptible_timeout(adapter->vc_waitqueue, - iavf_is_mac_set_handled(netdev, addr->sa_data), - msecs_to_jiffies(2500)); - - /* If ret < 0 then it means wait was interrupted. - * If ret == 0 then it means we got a timeout. - * else it means we got response for set MAC from PF, - * check if netdev MAC was updated to requested MAC, - * if yes then set MAC succeeded otherwise it failed return -EACCES - */ - if (ret < 0) + ret = iavf_set_mac_sync(adapter, addr->sa_data); + if (ret) { + /* Rollback only if send failed (message never reached PF). + * Don't rollback on timeout (-EAGAIN) because the message was + * sent and PF will eventually respond. When the response arrives, + * iavf_virtchnl_completion() will handle rollback (on PF error) + * or acceptance (on PF success) automatically. + */ + if (ret != -EAGAIN) { + iavf_mac_add_reject(adapter); + ether_addr_copy(adapter->hw.mac.addr, netdev->dev_addr); + } return ret; - - if (!ret) - return -EAGAIN; + } if (!ether_addr_equal(netdev->dev_addr, addr->sa_data)) return -EACCES; @@ -5394,9 +5446,6 @@ static int iavf_probe(struct pci_dev *pdev, const struct pci_device_id *ent) /* Setup the wait queue for indicating transition to down status */ init_waitqueue_head(&adapter->down_waitqueue); - /* Setup the wait queue for indicating virtchannel events */ - init_waitqueue_head(&adapter->vc_waitqueue); - INIT_LIST_HEAD(&adapter->ptp.aq_cmds); init_waitqueue_head(&adapter->ptp.phc_time_waitqueue); mutex_init(&adapter->ptp.aq_cmd_lock); diff --git a/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c b/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c index ec234cc8bd9d..e6b7e8f82c7c 100644 --- a/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c +++ b/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c @@ -2,6 +2,7 @@ /* Copyright(c) 2013 - 2018 Intel Corporation. */ #include +#include #include "iavf.h" #include "iavf_ptp.h" @@ -555,20 +556,23 @@ iavf_set_mac_addr_type(struct virtchnl_ether_addr *virtchnl_ether_addr, * @adapter: adapter structure * * Request that the PF add one or more addresses to our filters. - **/ -void iavf_add_ether_addrs(struct iavf_adapter *adapter) + * + * Return: 0 on success, negative on failure + */ +int iavf_add_ether_addrs(struct iavf_adapter *adapter) { struct virtchnl_ether_addr_list *veal; struct iavf_mac_filter *f; int i = 0, count = 0; bool more = false; size_t len; + int ret; if (adapter->current_op != VIRTCHNL_OP_UNKNOWN) { /* bail because we already have a command pending */ dev_err(&adapter->pdev->dev, "Cannot add filters, command %d pending\n", adapter->current_op); - return; + return -EBUSY; } spin_lock_bh(&adapter->mac_vlan_list_lock); @@ -580,7 +584,7 @@ void iavf_add_ether_addrs(struct iavf_adapter *adapter) if (!count) { adapter->aq_required &= ~IAVF_FLAG_AQ_ADD_MAC_FILTER; spin_unlock_bh(&adapter->mac_vlan_list_lock); - return; + return 0; } adapter->current_op = VIRTCHNL_OP_ADD_ETH_ADDR; @@ -594,8 +598,9 @@ void iavf_add_ether_addrs(struct iavf_adapter *adapter) veal = kzalloc(len, GFP_ATOMIC); if (!veal) { + adapter->current_op = VIRTCHNL_OP_UNKNOWN; spin_unlock_bh(&adapter->mac_vlan_list_lock); - return; + return -ENOMEM; } veal->vsi_id = adapter->vsi_res->vsi_id; @@ -615,8 +620,15 @@ void iavf_add_ether_addrs(struct iavf_adapter *adapter) spin_unlock_bh(&adapter->mac_vlan_list_lock); - iavf_send_pf_msg(adapter, VIRTCHNL_OP_ADD_ETH_ADDR, (u8 *)veal, len); + ret = iavf_send_pf_msg(adapter, VIRTCHNL_OP_ADD_ETH_ADDR, (u8 *)veal, len); kfree(veal); + if (ret) { + dev_err(&adapter->pdev->dev, + "Unable to send ADD_ETH_ADDR message to PF, error %d\n", ret); + adapter->current_op = VIRTCHNL_OP_UNKNOWN; + } + + return ret; } /** @@ -712,8 +724,8 @@ static void iavf_mac_add_ok(struct iavf_adapter *adapter) * @adapter: adapter structure * * Remove filters from list based on PF response. - **/ -static void iavf_mac_add_reject(struct iavf_adapter *adapter) + */ +void iavf_mac_add_reject(struct iavf_adapter *adapter) { struct net_device *netdev = adapter->netdev; struct iavf_mac_filter *f, *ftmp; @@ -2364,7 +2376,6 @@ void iavf_virtchnl_completion(struct iavf_adapter *adapter, iavf_mac_add_reject(adapter); /* restore administratively set MAC address */ ether_addr_copy(adapter->hw.mac.addr, netdev->dev_addr); - wake_up(&adapter->vc_waitqueue); break; case VIRTCHNL_OP_DEL_ETH_ADDR: dev_err(&adapter->pdev->dev, "Failed to delete MAC filter, error %s\n", @@ -2555,7 +2566,6 @@ void iavf_virtchnl_completion(struct iavf_adapter *adapter, eth_hw_addr_set(netdev, adapter->hw.mac.addr); netif_addr_unlock_bh(netdev); } - wake_up(&adapter->vc_waitqueue); break; case VIRTCHNL_OP_GET_STATS: { struct iavf_eth_stats *stats = @@ -2950,3 +2960,72 @@ void iavf_virtchnl_completion(struct iavf_adapter *adapter, } /* switch v_opcode */ adapter->current_op = VIRTCHNL_OP_UNKNOWN; } + +/** + * iavf_poll_virtchnl_response - Poll admin queue for virtchnl response + * @adapter: adapter structure + * @event: pre-allocated event buffer to use for polling + * @condition: callback to check if desired response received + * @cond_data: context data passed to condition callback + * @timeout_ms: maximum time to wait in milliseconds + * + * Polls the admin queue and processes all incoming virtchnl messages. + * After processing each valid message, calls the condition callback to check + * if the expected response has been received. The callback receives the opcode + * of the processed message to identify which response was received. Continues + * polling until the callback returns true or timeout expires. + * + * Caller must allocate event buffer before sending any messages to PF to avoid + * state mismatch if allocation fails after message is sent. + * + * Caller must hold netdev_lock. This can sleep for up to timeout_ms while + * polling hardware. + * + * Return: 0 on success (condition met), -EAGAIN on timeout, or error code + */ +int iavf_poll_virtchnl_response(struct iavf_adapter *adapter, + struct iavf_arq_event_info *event, + bool (*condition)(struct iavf_adapter *adapter, + const void *data, + enum virtchnl_ops v_op), + const void *cond_data, + unsigned int timeout_ms) +{ + struct iavf_hw *hw = &adapter->hw; + enum virtchnl_ops received_op; + unsigned long timeout; + int ret = -EAGAIN; + u16 pending = 0; + u32 v_retval; + + netdev_assert_locked(adapter->netdev); + + timeout = jiffies + msecs_to_jiffies(timeout_ms); + do { + if (!pending) + usleep_range(50, 75); + + if (iavf_clean_arq_element(hw, event, &pending) == IAVF_SUCCESS) { + received_op = (enum virtchnl_ops)le32_to_cpu(event->desc.cookie_high); + if (received_op != VIRTCHNL_OP_UNKNOWN) { + v_retval = le32_to_cpu(event->desc.cookie_low); + + iavf_virtchnl_completion(adapter, received_op, + (enum iavf_status)v_retval, + event->msg_buf, event->msg_len); + + if (condition(adapter, cond_data, received_op)) { + ret = 0; + break; + } + } + + memset(event->msg_buf, 0, IAVF_MAX_AQ_BUF_SIZE); + + if (pending) + continue; + } + } while (time_before(jiffies, timeout)); + + return ret; +} From 0a01e1df137fd7e51f045a5e97d40f42fb58ae0c Mon Sep 17 00:00:00 2001 From: Jose Ignacio Tornos Martinez Date: Mon, 15 Jun 2026 20:00:00 +0200 Subject: [PATCH 004/128] ice: skip unnecessary VF reset when setting trust Similar to the i40e fix, ice_set_vf_trust() unconditionally calls ice_reset_vf() when the trust setting changes. While the delay is smaller than i40e, this reset is still unnecessary in most cases. When granting trust, no reset is needed - we can just set the capability flag to allow privileged operations. When revoking trust, we only need to reset (conservative approach) if the VF has actually configured advanced features that require cleanup (MAC LLDP filters, promiscuous mode). For VFs in a clean state, we can safely change the trust setting without the disruptive reset. When we do reset, we maintain the original ice pattern that has been reliable in production: cleanup LLDP filters first, then set vf->trusted, then reset. This ensures the privilege capability bit is handled correctly during reset rebuild. When we don't reset, we manually handle the capability flag via helper function, eliminating the delay. Signed-off-by: Jose Ignacio Tornos Martinez Reviewed-by: Aleksandr Loktionov Tested-by: Rafal Romanowski Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/ice/ice_sriov.c | 32 ++++++++++++++++++---- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice_sriov.c b/drivers/net/ethernet/intel/ice/ice_sriov.c index e04de0215596..f893dff39aac 100644 --- a/drivers/net/ethernet/intel/ice/ice_sriov.c +++ b/drivers/net/ethernet/intel/ice/ice_sriov.c @@ -1366,6 +1366,20 @@ int ice_set_vf_mac(struct net_device *netdev, int vf_id, u8 *mac) return __ice_set_vf_mac(ice_netdev_to_pf(netdev), vf_id, mac); } +/** + * ice_setup_vf_trust - Enable/disable VF trust mode without reset + * @vf: VF to configure + * @setting: trust setting + * + * Update VF flags when changing trust without performing a VF reset. + * This is only called when it's safe to skip the reset (VF has no advanced + * features configured that need cleanup). + */ +static void ice_setup_vf_trust(struct ice_vf *vf, bool setting) +{ + assign_bit(ICE_VIRTCHNL_VF_CAP_PRIVILEGE, &vf->vf_caps, setting); +} + /** * ice_set_vf_trust * @netdev: network interface device structure @@ -1401,11 +1415,19 @@ int ice_set_vf_trust(struct net_device *netdev, int vf_id, bool trusted) mutex_lock(&vf->cfg_lock); - while (!trusted && vf->num_mac_lldp) - ice_vf_update_mac_lldp_num(vf, ice_get_vf_vsi(vf), false); - - vf->trusted = trusted; - ice_reset_vf(vf, ICE_VF_RESET_NOTIFY); + /* Reset only if revoking trust and VF has advanced features configured */ + if (!trusted && + (vf->num_mac_lldp > 0 || + test_bit(ICE_VF_STATE_UC_PROMISC, vf->vf_states) || + test_bit(ICE_VF_STATE_MC_PROMISC, vf->vf_states))) { + while (vf->num_mac_lldp) + ice_vf_update_mac_lldp_num(vf, ice_get_vf_vsi(vf), false); + vf->trusted = trusted; + ice_reset_vf(vf, ICE_VF_RESET_NOTIFY); + } else { + vf->trusted = trusted; + ice_setup_vf_trust(vf, trusted); + } dev_info(ice_pf_to_dev(pf), "VF %u is now %strusted\n", vf_id, trusted ? "" : "un"); From 816fb1590a4c8cf8d05cd076057616be02e276f0 Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Sat, 26 Sep 2026 18:21:45 -0300 Subject: [PATCH 005/128] Bluetooth: SCO: serialise sco_conn lifetime against sco_recv_scodata() sco_recv_scodata() upgrades the weak hcon->sco_data back-pointer to a strong reference under hdev->lock: hci_dev_lock(hdev); hcon = hci_conn_hash_lookup_handle(hdev, handle); ... conn = sco_conn_hold_unless_zero(hcon->sco_data); hci_dev_unlock(hdev); but the pointer is cleared from the other side without that lock. When the last sco_conn reference is dropped, sco_conn_free() ran conn->hcon->sco_data = NULL; with no hdev->lock held, so the RX path could read hcon->sco_data and call kref_get_unless_zero() on an sco_conn that was concurrently freed: BUG: KASAN: slab-use-after-free in sco_conn_hold_unless_zero+0xbe/0x160 Write of size 4 by task kworker/u17:0 Workqueue: hci0 hci_rx_work Call Trace: sco_conn_hold_unless_zero+0xbe/0x160 sco_recv_scodata+0x13f/0x490 hci_rx_work+0x3af/0x730 kref_get_unless_zero() only guards against a zero refcount, not against the backing memory already being freed. Give hcon->sco_data an actual reference on the sco_conn it points to, so the object cannot be freed while the pointer is still installed, and only clear and drop it from sco_conn_del(), which runs under hdev->lock (its callers, sco_connect_cfm() and sco_disconn_cfm(), hold it). The reader in sco_recv_scodata() also takes hdev->lock, so the store and the read are now serialised: the RX path either observes NULL or a reference that is guaranteed to stay valid until it drops its own. sco_conn_add() no longer hands its kref_init() reference to the caller as the connection's only reference; that initial reference is the one owned by hcon->sco_data, and callers get their own via sco_conn_hold(). Because the sco_conn can now outlive the socket (the association keeps it alive until the link goes down), the hci_conn can no longer be dropped from sco_conn_free() without regressing socket close: closing a connected SCO socket must still tear the link down. Move hci_conn ownership to the socket instead: __sco_chan_add() takes an hci_conn reference and it is released from sco_chan_del() and sco_sock_destruct(), exactly once. The sco_conn no longer owns an hci_conn reference, so sco_conn_add() stops consuming one and sco_connect() drops the hci_connect_sco() reference on every path; sco_connect_cfm() no longer needs its hci_conn_hold() dance. Fixes: e6720779ae61 ("Bluetooth: SCO: Use kref to track lifetime of sco_conn") Cc: stable@vger.kernel.org Suggested-by: Pauli Virtanen Signed-off-by: Aldo Ariel Panzardo Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/sco.c | 87 +++++++++++++++++++++++++++++++++------------ 1 file changed, 65 insertions(+), 22 deletions(-) diff --git a/net/bluetooth/sco.c b/net/bluetooth/sco.c index 3d4362a09df4..f4a20d7b5f9c 100644 --- a/net/bluetooth/sco.c +++ b/net/bluetooth/sco.c @@ -84,12 +84,14 @@ static void sco_conn_free(struct kref *ref) if (conn->sk) sco_pi(conn->sk)->conn = NULL; - if (conn->hcon) { - conn->hcon->sco_data = NULL; - hci_conn_drop(conn->hcon); - } + /* hcon->sco_data is cleared and the association's reference on the + * sco_conn is dropped in sco_conn_del() under hdev->lock, and the + * hci_conn is now owned by the socket (held in __sco_chan_add() and + * dropped in sco_chan_del()/sco_sock_destruct()), so there is nothing + * left to release towards hcon here. + */ - /* Ensure no more work items will run since hci_conn has been dropped */ + /* Ensure no more work items will run before the connection is freed */ disable_delayed_work_sync(&conn->timeout_work); kfree(conn); @@ -188,25 +190,19 @@ static void sco_sock_clear_timer(struct sock *sk) } /* ---- SCO connections ---- */ -/* Consumes a reference on @hcon, which the returned sco_conn owns until it is - * freed. On failure (NULL return) the reference is left for the caller to drop. +/* Returns a new reference the caller must drop with sco_conn_put(). The + * hcon->sco_data association holds its own reference on the sco_conn for the + * connection's lifetime; it is dropped in sco_conn_del() under hdev->lock. + * @hcon is not consumed: the hci_conn reference is taken and owned by the + * socket in __sco_chan_add(). */ static struct sco_conn *sco_conn_add(struct hci_conn *hcon) { struct sco_conn *conn = hcon->sco_data; conn = sco_conn_hold_unless_zero(conn); - if (conn) { - if (!conn->hcon) { - sco_conn_lock(conn); - conn->hcon = hcon; - sco_conn_unlock(conn); - } else { - /* conn already owns a reference on hcon */ - hci_conn_drop(hcon); - } + if (conn) return conn; - } conn = kzalloc_obj(struct sco_conn); if (!conn) @@ -227,7 +223,10 @@ static struct sco_conn *sco_conn_add(struct hci_conn *hcon) BT_DBG("hcon %p conn %p", hcon, conn); - return conn; + /* kref_init() above set the association reference owned by + * hcon->sco_data; hand the caller its own reference. + */ + return sco_conn_hold(conn); } /* Delete channel. @@ -242,6 +241,19 @@ static void sco_chan_del(struct sock *sk, int err) BT_DBG("sk %p, conn %p, err %d", sk, conn, err); if (conn) { + struct hci_conn *hcon; + + sco_conn_lock(conn); + hcon = conn->hcon; + sco_conn_unlock(conn); + + /* Drop the socket's hci_conn reference BEFORE clearing + * conn->sk, so sco_conn_del() on another CPU cannot free + * the hci_conn while we still hold a pointer to it. + */ + if (hcon) + hci_conn_drop(hcon); + sco_conn_lock(conn); conn->sk = NULL; sco_conn_unlock(conn); @@ -266,6 +278,13 @@ static void sco_conn_del(struct hci_conn *hcon, int err) BT_DBG("hcon %p conn %p, err %d", hcon, conn, err); + /* Detach from the hci_conn and drop the association's reference. + * The caller holds hdev->lock, which serialises this against the + * read of hcon->sco_data in sco_recv_scodata(). + */ + hcon->sco_data = NULL; + sco_conn_put(conn); + sco_conn_lock(conn); sk = sco_sock_hold(conn); sco_conn_unlock(conn); @@ -290,6 +309,11 @@ static void __sco_chan_add(struct sco_conn *conn, struct sock *sk, sco_pi(sk)->conn = sco_conn_hold(conn); conn->sk = sk; + /* The socket owns an hci_conn reference for as long as it stays + * attached; it is dropped in sco_chan_del()/sco_sock_destruct(). + */ + hci_conn_hold(conn->hcon); + if (parent) bt_accept_enqueue(parent, sk, true); } @@ -371,6 +395,7 @@ static int sco_connect(struct sock *sk) if (sk->sk_state != BT_OPEN && sk->sk_state != BT_BOUND) { release_sock(sk); sco_conn_put(conn); + hci_conn_drop(hcon); err = -EBADFD; goto unlock; } @@ -379,9 +404,13 @@ static int sco_connect(struct sock *sk) sco_conn_put(conn); if (err) { release_sock(sk); + hci_conn_drop(hcon); goto unlock; } + /* __sco_chan_add() took its own hci_conn reference; drop ours. */ + hci_conn_drop(hcon); + /* Update source addr of the socket */ bacpy(&sco_pi(sk)->src, &hcon->src); @@ -495,9 +524,25 @@ static struct sock *sco_get_sock_listen(bdaddr_t *src) static void sco_sock_destruct(struct sock *sk) { + struct sco_conn *conn = sco_pi(sk)->conn; + BT_DBG("sk %p", sk); - sco_conn_put(sco_pi(sk)->conn); + /* If the channel was not already torn down via sco_chan_del(), drop + * the socket's own references here. + */ + if (conn) { + struct hci_conn *hcon; + + sco_conn_lock(conn); + hcon = conn->hcon; + sco_conn_unlock(conn); + + if (hcon) + hci_conn_drop(hcon); + sco_pi(sk)->conn = NULL; + sco_conn_put(conn); + } skb_queue_purge(&sk->sk_receive_queue); skb_queue_purge(&sk->sk_write_queue); @@ -1511,12 +1556,10 @@ static void sco_connect_cfm(struct hci_conn *hcon, __u8 status) if (!status) { struct sco_conn *conn; - conn = sco_conn_add(hci_conn_hold(hcon)); + conn = sco_conn_add(hcon); if (conn) { sco_conn_ready(conn); sco_conn_put(conn); - } else { - hci_conn_drop(hcon); } } else sco_conn_del(hcon, bt_to_errno(status)); From baa53af641c9e3644e717b8dfd1b95a55a0c111b Mon Sep 17 00:00:00 2001 From: Ravindra Date: Tue, 29 Sep 2026 12:26:41 +0530 Subject: [PATCH 006/128] Bluetooth: btintel_pcie: fix TX descriptor bounds check btintel_pcie_prepare_tx() uses tfd_index to access the TFD and data buffer arrays before the index is advanced modulo txq->count. An index equal to txq->count is one past the end of both arrays, so reject it in the bounds check. Fixes: 6e65a09f9275 ("Bluetooth: btintel_pcie: Add *setup* function to download firmware") Signed-off-by: Ravindra Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel_pcie.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index f333b01f7465..d525269be501 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -401,7 +401,7 @@ static int btintel_pcie_send_sync(struct btintel_pcie_data *data, tfd_index = data->ia.tr_hia[BTINTEL_PCIE_TXQ_NUM]; - if (tfd_index > txq->count) + if (tfd_index >= txq->count) return -ERANGE; if (skb->len > BTINTEL_PCIE_BUFFER_SIZE - BTINTEL_PCIE_HCI_TYPE_LEN) { From ac022970e9fb0a5f7e7d1f6c8e104c1ed924dd1c Mon Sep 17 00:00:00 2001 From: Mikhail Gavrilov Date: Tue, 29 Sep 2026 17:22:26 +0500 Subject: [PATCH 007/128] Bluetooth: RFCOMM: connect the session socket without rfcomm_mutex An RFCOMM connect() issued while a BR/EDR link is being authenticated makes lockdep report a circular dependency, and the reported cycle is a real AB/BA between rfcomm_mutex and hdev->lock. rfcomm_security_cfm() is called from the HCI event path, which already holds hdev->lock: hci_rx_work() hci_event_packet() hci_cc_read_enc_key_size() [hdev->lock] hci_encrypt_cfm() [hci_cb_list_lock] rfcomm_security_cfm() [rfcomm_mutex] while an RFCOMM connect() from userspace takes the same two locks the other way round: rfcomm_sock_connect() rfcomm_dlc_open() [rfcomm_mutex] __rfcomm_dlc_open() rfcomm_session_create() kernel_connect() l2cap_sock_connect() l2cap_chan_connect() [hdev->lock] WARNING: possible circular locking dependency detected kworker/u131:1/1128 is trying to acquire lock: rfcomm_mutex, at: rfcomm_security_cfm+0x31/0x3e0 [rfcomm] but task is already holding lock: hci_cb_list_lock, at: hci_cc_read_enc_key_size+0x1d2/0xcc0 Chain exists of: rfcomm_mutex --> &hdev->lock --> hci_cb_list_lock hci_auth_complete_evt() and hci_encrypt_change_evt() reach the callback the same way. Both orders have to be seen in the same boot, which is why a BR/EDR connection alone is not enough to show it: a session set up by the remote side is created by rfcomm_accept_connection() in krfcommd, which calls kernel_accept() and never takes hdev->lock under rfcomm_mutex. Connecting a device that authenticates and encrypts the link and then calling connect() on an RFCOMM socket towards any address - the connect does not have to succeed, the order is recorded before the page timeout - reports it every time. Only the session socket has to be connected with the lock held, and it does not: nothing else can see the socket before it is put on the session list. So connect it first and take rfcomm_mutex afterwards, which removes the rfcomm_mutex -> hdev->lock order for good, rather than keeping the HCI event path out of rfcomm_mutex. rfcomm_session_create() becomes rfcomm_session_connect(), which returns the connected socket without touching the session list, and rfcomm_dlc_open() adds the session once it holds the lock again. If another opener added a session for the same pair while this socket was connecting, that session is used and this socket is dropped. __rfcomm_dlc_open() now takes the session it should use, and its state check runs after the lock is re-acquired, so a DLC that was opened or closed in the meantime is still handled. Over an existing ACL link the connection can complete before the session reaches the list, and the wakeup from the socket callback is then lost, so krfcommd is woken once the session is visible. Connecting without the lock opens a window in which the socket can be closed. The DLC is not on a session yet, so rfcomm_dlc_close() finds nothing to do and returns without touching it, and rfcomm_dlc_open() would then attach a DLC whose owner is gone. rfcomm_dlc_close() now marks such a DLC with RFCOMM_CLOSED, and rfcomm_dlc_open() checks the mark once it holds rfcomm_mutex again and drops the socket instead of attaching the DLC. The mark is cleared when an open starts, so a DLC that is reused is not affected. This covers both callers of rfcomm_dlc_open(), the socket and the tty layer. Fixes: 759c185d0bbd ("Bluetooth: RFCOMM: serialize security confirmation handling") Suggested-by: Pauli Virtanen Reported-by: Pauli Virtanen Closes: https://lore.kernel.org/linux-bluetooth/5e76a95e934e451e7006db28827c2d64af5a88be.camel@iki.fi/ Reported-by: syzbot+74071deb72339c215b2e@syzkaller.appspotmail.com Closes: https://lore.kernel.org/linux-bluetooth/6a92fadc.08e933ee.dbf97.008f.GAE@google.com/ Reported-by: Jiaming Zhang Closes: https://lore.kernel.org/all/CANypQFabseTuRiyz2pGkc_Qj0moGDnY7KEp1qdTOqkw6gAL3hw@mail.gmail.com/ Cc: stable@vger.kernel.org Signed-off-by: Mikhail Gavrilov Tested-by: syzbot+74071deb72339c215b2e@syzkaller.appspotmail.com Signed-off-by: Luiz Augusto von Dentz --- include/net/bluetooth/rfcomm.h | 1 + net/bluetooth/rfcomm/core.c | 134 +++++++++++++++++++++++---------- 2 files changed, 96 insertions(+), 39 deletions(-) diff --git a/include/net/bluetooth/rfcomm.h b/include/net/bluetooth/rfcomm.h index 102c278e3584..9dc5fd4cb8c8 100644 --- a/include/net/bluetooth/rfcomm.h +++ b/include/net/bluetooth/rfcomm.h @@ -207,6 +207,7 @@ struct rfcomm_dlc { #define RFCOMM_AUTH_REJECT 7 #define RFCOMM_DEFER_SETUP 8 #define RFCOMM_ENC_DROP 9 +#define RFCOMM_CLOSED 10 /* Scheduling flags and events */ #define RFCOMM_SCHED_WAKEUP 31 diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c index d91e2a6ee26c..54ec1136f87e 100644 --- a/net/bluetooth/rfcomm/core.c +++ b/net/bluetooth/rfcomm/core.c @@ -62,10 +62,9 @@ static void rfcomm_make_uih(struct sk_buff *skb, u8 addr); static void rfcomm_process_connect(struct rfcomm_session *s); -static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src, - bdaddr_t *dst, - u8 sec_level, - int *err); +static struct socket *rfcomm_session_connect(bdaddr_t *src, bdaddr_t *dst, + u8 sec_level, int *err); +static struct rfcomm_session *rfcomm_session_add(struct socket *sock, int state); static struct rfcomm_session *rfcomm_session_get(bdaddr_t *src, bdaddr_t *dst); static struct rfcomm_session *rfcomm_session_del(struct rfcomm_session *s); @@ -365,28 +364,17 @@ static int rfcomm_check_channel(u8 channel) return channel < 1 || channel > 30; } -static int __rfcomm_dlc_open(struct rfcomm_dlc *d, bdaddr_t *src, bdaddr_t *dst, u8 channel) +static int __rfcomm_dlc_open(struct rfcomm_dlc *d, struct rfcomm_session *s, + u8 channel) { - struct rfcomm_session *s; - int err = 0; u8 dlci; - BT_DBG("dlc %p state %ld %pMR -> %pMR channel %d", - d, d->state, src, dst, channel); - - if (rfcomm_check_channel(channel)) - return -EINVAL; + BT_DBG("dlc %p state %ld session %p channel %d", + d, d->state, s, channel); if (d->state != BT_OPEN && d->state != BT_CLOSED) return 0; - s = rfcomm_session_get(src, dst); - if (!s) { - s = rfcomm_session_create(src, dst, d->sec_level, &err); - if (!s) - return err; - } - dlci = __dlci(__session_dir(s), channel); /* Check if DLCI already exists */ @@ -421,14 +409,84 @@ static int __rfcomm_dlc_open(struct rfcomm_dlc *d, bdaddr_t *src, bdaddr_t *dst, int rfcomm_dlc_open(struct rfcomm_dlc *d, bdaddr_t *src, bdaddr_t *dst, u8 channel) { - int r; + struct rfcomm_session *s; + struct socket *sock; + int err; + + BT_DBG("dlc %p state %ld %pMR -> %pMR channel %d", + d, d->state, src, dst, channel); + + if (rfcomm_check_channel(channel)) + return -EINVAL; rfcomm_lock(); - r = __rfcomm_dlc_open(d, src, dst, channel); + clear_bit(RFCOMM_CLOSED, &d->flags); + + /* Do not page the remote device for a DLC that cannot be opened + * anyway. __rfcomm_dlc_open() looks at the state again once the + * lock has been re-acquired below. + */ + if (d->state != BT_OPEN && d->state != BT_CLOSED) { + rfcomm_unlock(); + return 0; + } + + s = rfcomm_session_get(src, dst); + if (s) { + err = __rfcomm_dlc_open(d, s, channel); + rfcomm_unlock(); + return err; + } + rfcomm_unlock(); + + /* There is no session for this pair yet. kernel_connect() ends up in + * l2cap_chan_connect(), which takes hdev->lock, and the HCI event + * path takes rfcomm_mutex while holding hdev->lock, so the socket has + * to be connected with rfcomm_mutex released. + */ + sock = rfcomm_session_connect(src, dst, d->sec_level, &err); + if (!sock) + return err; + + rfcomm_lock(); + + /* The DLC may have been closed while the socket was connecting. It + * was not on a session, so rfcomm_dlc_close() could only mark it; + * attaching it now would leave it with no owner. + */ + if (test_bit(RFCOMM_CLOSED, &d->flags)) { + rfcomm_unlock(); + sock_release(sock); + return -ECONNRESET; + } + + /* Another opener may have added a session for the same pair in the + * meantime; that one is used and this socket is dropped. + */ + s = rfcomm_session_get(src, dst); + if (!s) { + s = rfcomm_session_add(sock, BT_BOUND); + if (s) { + s->initiator = 1; + sock = NULL; + } + } + + err = s ? __rfcomm_dlc_open(d, s, channel) : -ENOMEM; rfcomm_unlock(); - return r; + + if (sock) + sock_release(sock); + + /* Over an existing ACL link the connection can complete before the + * session reaches the list, and that wakeup is then lost, so let + * krfcommd look at the socket state now. + */ + rfcomm_schedule(); + + return err; } static void __rfcomm_dlc_disconn(struct rfcomm_dlc *d) @@ -508,8 +566,14 @@ int rfcomm_dlc_close(struct rfcomm_dlc *d, int err) rfcomm_lock(); s = d->session; - if (!s) + if (!s) { + /* Not on a session yet: rfcomm_dlc_open() may be connecting a + * socket for it with rfcomm_mutex released. Leave a mark so + * that it does not attach the DLC once it holds the lock again. + */ + set_bit(RFCOMM_CLOSED, &d->flags); goto no_session; + } /* after waiting on the mutex check the session still exists * then check the dlc still exists @@ -757,12 +821,12 @@ static struct rfcomm_session *rfcomm_session_close(struct rfcomm_session *s, return rfcomm_session_del(s); } -static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src, - bdaddr_t *dst, - u8 sec_level, - int *err) +/* Creates the L2CAP socket a new session will run on and starts connecting + * it. Must be called with rfcomm_mutex released. + */ +static struct socket *rfcomm_session_connect(bdaddr_t *src, bdaddr_t *dst, + u8 sec_level, int *err) { - struct rfcomm_session *s = NULL; struct sockaddr_l2 addr; struct socket *sock; struct sock *sk; @@ -792,24 +856,16 @@ static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src, l2cap_pi(sk)->chan->mode = L2CAP_MODE_ERTM; release_sock(sk); - s = rfcomm_session_add(sock, BT_BOUND); - if (!s) { - *err = -ENOMEM; - goto failed; - } - - s->initiator = 1; - bacpy(&addr.l2_bdaddr, dst); addr.l2_family = AF_BLUETOOTH; addr.l2_psm = cpu_to_le16(L2CAP_PSM_RFCOMM); addr.l2_cid = 0; addr.l2_bdaddr_type = BDADDR_BREDR; *err = kernel_connect(sock, (struct sockaddr_unsized *)&addr, sizeof(addr), O_NONBLOCK); - if (*err == 0 || *err == -EINPROGRESS) - return s; + if (*err && *err != -EINPROGRESS) + goto failed; - return rfcomm_session_del(s); + return sock; failed: sock_release(sock); From 1465494e8d07426fa41c0021530cccb2a1ac38fa Mon Sep 17 00:00:00 2001 From: Sai Teja Aluvala Date: Tue, 29 Sep 2026 11:42:33 +0530 Subject: [PATCH 008/128] Bluetooth: btintel_pcie: Add shared HW reset for clean start Reset shared Bluetooth hardware after mapping the BAR and before setting up MSI-X interrupts, so stale device state is cleared before IRQ handlers and interrupt masks are configured. Propagate reset errors so probe aborts rather than continuing when the reset fails. Fixes: c2b636b3f788 ("Bluetooth: btintel_pcie: Add support for PCIe transport") Signed-off-by: Sai Teja Aluvala Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel_pcie.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index d525269be501..0194badeeff3 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -1877,6 +1877,13 @@ static int btintel_pcie_config_pcie(struct pci_dev *pdev, if (IS_ERR(data->base_addr)) return PTR_ERR(data->base_addr); + /* Do shared hardware reset to ensure a clean start before + * configuring interrupts. + */ + err = btintel_pcie_reset_bt(data); + if (err) + return err; + err = btintel_pcie_setup_irq(data); if (err) return err; From 86ef0f58bdecdedb3a1240971c56d71b7e4ce3fc Mon Sep 17 00:00:00 2001 From: Iaroslav Voitovych Date: Thu, 24 Sep 2026 19:11:58 +0200 Subject: [PATCH 009/128] Bluetooth: MGMT: Fix status of pending commands flushed on power off cmd_complete_rsp() receives a struct cmd_lookup and, for a pending command that has no cmd_complete callback, falls through to cmd_status_rsp(cmd, data). cmd_status_rsp() reads data as a u8 *, so the status sent comes from the first byte of match->sk's representation, not from match->mgmt_status. commit f53e1c9c726d ("Bluetooth: MGMT: Fix possible crash on mgmt_index_removed") changed the callers' data from &status to &match and updated the cmd_complete branch to match->mgmt_status, but left the fall-through passing data unchanged. In mgmt_index_removed() match->sk is NULL, so every such command is answered with status 0x00 (Success) instead of MGMT_STATUS_INVALID_INDEX. In __mgmt_power_off() it is NULL too, unless a Set Powered command was pending: then settings_rsp() has stored that command's socket in match->sk, and the byte sent comes from the socket pointer. Either way the status the caller set, MGMT_STATUS_NOT_POWERED (or MGMT_STATUS_INVALID_INDEX when the device is being unregistered), is lost. Pass the status the callers set. This was seen on a QCA9377 laptop: bluetoothd's Start Discovery, pending when the adapter was powered off, was answered with Command Status 0x00 and no parameters, and bluetoothd 5.72 crashed on the zero-length success. BlueZ master has since added the length check on that path ("adapter: Fix crash on short start discovery reply", BlueZ a734b06059cb), but userspace should still be told Not Powered, not Success. Tested on Ubuntu 7.0.0-31 with a virtual controller (hci_vhci): power off is held in HCI Write Scan Enable while a Start Discovery is submitted, and the flush answers it with status 0x00 before this change and 0x0f (Not Powered) after. Fixes: f53e1c9c726d ("Bluetooth: MGMT: Fix possible crash on mgmt_index_removed") Cc: stable@vger.kernel.org Signed-off-by: Iaroslav Voitovych Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/mgmt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 41956cdde982..251a896babd5 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -1495,7 +1495,7 @@ static void cmd_complete_rsp(struct mgmt_pending_cmd *cmd, void *data) return; } - cmd_status_rsp(cmd, data); + cmd_status_rsp(cmd, &match->mgmt_status); } static int generic_cmd_complete(struct mgmt_pending_cmd *cmd, u8 status) From 0a04c0cb8606ff88399489d03ef255afe90d0d5f Mon Sep 17 00:00:00 2001 From: zjamg Date: Thu, 17 Sep 2026 20:37:16 +0800 Subject: [PATCH 010/128] can: dev: init_can_skb(): restore skb header initialization Commit 9f10374bb024 ("can: remove private CAN skb headroom infrastructure") removed the skb_reset_mac_header()/skb_reset_network_header()/ skb_reset_transport_header() calls from init_can_skb(). As a result, RX skbs from alloc_can_skb() and friends again carry mac_header = 0xFFFF. When such an skb reaches packet_rcv_spkt() (SOCK_PACKET), the push length calculation overflows and triggers skb_under_panic -> kernel BUG -> full machine panic. The same issue was originally reported in 2014 on linux-can and fixed by commit 969439016d2c ("can: add missing initialisations in CAN related skbuffs"). packet_rcv_spkt() itself has never been hardened: only packet_rcv() and tpacket_rcv() gained dev_has_header() checks in commit d549699048b4 ("net/packet: fix packet receive on L3 devices without visible hard header"). Fixes: 9f10374bb024 ("can: remove private CAN skb headroom infrastructure") Cc: stable@vger.kernel.org Reviewed-by: Oliver Hartkopp Acked-by: Oliver Hartkopp Signed-off-by: zjamg Tested-by: Quchaosheng Link: https://patch.msgid.link/20260917123716.63116-1-ndaugoing@gmail.com Reported-by: Shaunak Datar Closes: https://lore.kernel.org/all/20260918151606.765490-1-shaunakkdatar@gmail.com Signed-off-by: Marc Kleine-Budde --- drivers/net/can/dev/skb.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/can/dev/skb.c b/drivers/net/can/dev/skb.c index 95fcdc1026f8..14edec5afb57 100644 --- a/drivers/net/can/dev/skb.c +++ b/drivers/net/can/dev/skb.c @@ -210,6 +210,10 @@ static void init_can_skb(struct sk_buff *skb) { skb->pkt_type = PACKET_BROADCAST; skb->ip_summed = CHECKSUM_UNNECESSARY; + + skb_reset_mac_header(skb); + skb_reset_network_header(skb); + skb_reset_transport_header(skb); } struct sk_buff *alloc_can_skb(struct net_device *dev, struct can_frame **cf) From 7093c3b8314a4ef7405c4066a2521398c455efe7 Mon Sep 17 00:00:00 2001 From: Oliver Hartkopp Date: Thu, 1 Oct 2026 13:57:24 +0200 Subject: [PATCH 011/128] can: fix unique skb identifier regression under RPS Commit d4fb6514ff8e ("can: use skb hash instead of private variable in headroom") moved the per-skb unique identifier used for raw_rcv() duplicate detection into skb->hash. With RPS enabled, get_rps_cpu() calls skb_get_hash() before the frame reaches the CAN subsystem. Since CAN skbs have no L3/L4 headers, the flow dissector assigns every CAN frame the same non-zero software hash. can_set_skb_uid() only generated a new identifier when skb->hash was 0, so it kept this constant hash. When the SLAB allocator later reused the same skb address, raw_rcv() mistook the next legitimate frame for a duplicate and dropped it. Fix this by storing the CAN UID in the CAN skb extension (struct can_skb_ext::can_skb_uid) instead of skb->hash, decoupling it from any hash the network stack may compute. can_set_skb_uid() keeps its "assign only if unset" behaviour, which preserves UIDs set before transmission (e.g. by isotp to identify echo frames) across the local loopback path. Frames whose extension is shared with another clone (e.g. via tc mirred or netem duplicate) are given a private extension copy before the UID is assigned, so that independently received skb clones from real CAN interfaces (can_skb_uid = 0) don't end up with the same UID. The limitation that tc mirred/netem skb duplicates might be dropped in raw_rcv for looped back isotp echo frame skbs is accepted. There is no valid use-case for tc mirred or netem together with isotp which is not capable to operate on different CAN interfaces simultaneously. For routing and modifying CAN frames together with isotp use can-gw. can-gw and vxcan additionally clear the UID of forwarded/duplicated frames so each newly routed frame gets its own unique identifier. Fixes: d4fb6514ff8e ("can: use skb hash instead of private variable in headroom") Reported-by: Joerg Willmann Closes: https://lore.kernel.org/linux-can/2859AD3D-C805-41A0-9036-C5E8EE152419@clnt.de/ Cc: stable@vger.kernel.org Tested-by: Oliver Hartkopp Tested-by: Joerg Willmann Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20261001115724.27192-1-socketcan@hartkopp.net Signed-off-by: Marc Kleine-Budde --- drivers/net/can/vxcan.c | 3 +++ include/linux/can/core.h | 3 ++- include/linux/can/skb.h | 4 +++- include/net/can.h | 2 ++ net/can/af_can.c | 47 ++++++++++++++++++++++++++++++---------- net/can/gw.c | 3 +++ net/can/isotp.c | 16 +++++++++----- net/can/raw.c | 10 ++++++--- 8 files changed, 66 insertions(+), 22 deletions(-) diff --git a/drivers/net/can/vxcan.c b/drivers/net/can/vxcan.c index 9e2e25d02471..51148a81d1d9 100644 --- a/drivers/net/can/vxcan.c +++ b/drivers/net/can/vxcan.c @@ -79,6 +79,9 @@ static netdev_tx_t vxcan_xmit(struct sk_buff *oskb, struct net_device *dev) /* reset CAN GW hop counter */ csx->can_gw_hops = 0; + /* start with new CAN skb UID in the other namespace */ + csx->can_skb_uid = 0; + skb->pkt_type = PACKET_BROADCAST; skb->dev = peer; skb->ip_summed = CHECKSUM_UNNECESSARY; diff --git a/include/linux/can/core.h b/include/linux/can/core.h index 3287232e3cad..2de74c2b78b6 100644 --- a/include/linux/can/core.h +++ b/include/linux/can/core.h @@ -17,6 +17,7 @@ #include #include #include +#include #define DNAME(dev) ((dev) ? (dev)->name : "any") @@ -58,7 +59,7 @@ extern void can_rx_unregister(struct net *net, struct net_device *dev, void *data); extern int can_send(struct sk_buff *skb, int loop); -void can_set_skb_uid(struct sk_buff *skb); +void can_set_skb_uid(struct can_skb_ext *csx); void can_sock_destruct(struct sock *sk); #endif /* !_CAN_CORE_H */ diff --git a/include/linux/can/skb.h b/include/linux/can/skb.h index a70a02967071..5d27843862fc 100644 --- a/include/linux/can/skb.h +++ b/include/linux/can/skb.h @@ -43,8 +43,10 @@ static inline struct can_skb_ext *can_skb_ext_add(struct sk_buff *skb) struct can_skb_ext *csx = skb_ext_add(skb, SKB_EXT_CAN); /* skb_ext_add() returns uninitialized space */ - if (csx) + if (csx) { csx->can_gw_hops = 0; + csx->can_skb_uid = 0; + } return csx; } diff --git a/include/net/can.h b/include/net/can.h index 6db9e826f0e0..2b8af2598732 100644 --- a/include/net/can.h +++ b/include/net/can.h @@ -17,12 +17,14 @@ * @can_framelen: cached echo CAN frame length for bql * @can_gw_hops: can-gw CAN frame time-to-live counter * @can_ext_flags: CAN skb extensions flags + * @can_skb_uid: CAN skb UID for raw_rcv and isotp echo handling */ struct can_skb_ext { int can_iif; u16 can_framelen; u8 can_gw_hops; u8 can_ext_flags; + u32 can_skb_uid; }; #endif /* _NET_CAN_H */ diff --git a/net/can/af_can.c b/net/can/af_can.c index 7bc86b176b4d..34fe3b28d576 100644 --- a/net/can/af_can.c +++ b/net/can/af_can.c @@ -641,13 +641,10 @@ static int can_rcv_filter(struct can_dev_rcv_lists *dev_rcv_lists, struct sk_buf return matches; } -void can_set_skb_uid(struct sk_buff *skb) +void can_set_skb_uid(struct can_skb_ext *csx) { - /* create non-zero unique skb identifier together with *skb */ - while (!(skb->hash)) - skb->hash = atomic_inc_return(&skbcounter); - - skb->sw_hash = 1; + while (!(csx->can_skb_uid)) + csx->can_skb_uid = atomic_inc_return(&skbcounter); } EXPORT_SYMBOL(can_set_skb_uid); @@ -662,8 +659,6 @@ static void can_receive(struct sk_buff *skb, struct net_device *dev) atomic_long_inc(&pkg_stats->rx_frames); atomic_long_inc(&pkg_stats->rx_frames_delta); - can_set_skb_uid(skb); - rcu_read_lock(); /* deliver the packet to sockets listening on all devices */ @@ -687,8 +682,10 @@ static void can_receive(struct sk_buff *skb, struct net_device *dev) static int can_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev) { + struct can_skb_ext *csx = can_skb_ext_find(skb); + if (unlikely(dev->type != ARPHRD_CAN || !can_get_ml_priv(dev) || - !can_skb_ext_find(skb) || !can_is_can_skb(skb))) { + !csx || !can_is_can_skb(skb))) { pr_warn_once("PF_CAN: dropped non conform CAN skbuff: dev type %d, len %d\n", dev->type, skb->len); @@ -696,6 +693,14 @@ static int can_rcv(struct sk_buff *skb, struct net_device *dev, return NET_RX_DROP; } + /* create unshared CAN skb_extension for netem/mirred skb clones */ + csx = skb_ext_add(skb, SKB_EXT_CAN); + if (unlikely(!csx)) { + kfree_skb_reason(skb, SKB_DROP_REASON_NOMEM); + return NET_RX_DROP; + } + + can_set_skb_uid(csx); can_receive(skb, dev); return NET_RX_SUCCESS; } @@ -703,8 +708,10 @@ static int can_rcv(struct sk_buff *skb, struct net_device *dev, static int canfd_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev) { + struct can_skb_ext *csx = can_skb_ext_find(skb); + if (unlikely(dev->type != ARPHRD_CAN || !can_get_ml_priv(dev) || - !can_skb_ext_find(skb) || !can_is_canfd_skb(skb))) { + !csx || !can_is_canfd_skb(skb))) { pr_warn_once("PF_CAN: dropped non conform CAN FD skbuff: dev type %d, len %d\n", dev->type, skb->len); @@ -712,6 +719,14 @@ static int canfd_rcv(struct sk_buff *skb, struct net_device *dev, return NET_RX_DROP; } + /* create unshared CAN skb_extension for netem/mirred skb clones */ + csx = skb_ext_add(skb, SKB_EXT_CAN); + if (unlikely(!csx)) { + kfree_skb_reason(skb, SKB_DROP_REASON_NOMEM); + return NET_RX_DROP; + } + + can_set_skb_uid(csx); can_receive(skb, dev); return NET_RX_SUCCESS; } @@ -719,8 +734,10 @@ static int canfd_rcv(struct sk_buff *skb, struct net_device *dev, static int canxl_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev) { + struct can_skb_ext *csx = can_skb_ext_find(skb); + if (unlikely(dev->type != ARPHRD_CAN || !can_get_ml_priv(dev) || - !can_skb_ext_find(skb) || !can_is_canxl_skb(skb))) { + !csx || !can_is_canxl_skb(skb))) { pr_warn_once("PF_CAN: dropped non conform CAN XL skbuff: dev type %d, len %d\n", dev->type, skb->len); @@ -728,6 +745,14 @@ static int canxl_rcv(struct sk_buff *skb, struct net_device *dev, return NET_RX_DROP; } + /* create unshared CAN skb_extension for netem/mirred skb clones */ + csx = skb_ext_add(skb, SKB_EXT_CAN); + if (unlikely(!csx)) { + kfree_skb_reason(skb, SKB_DROP_REASON_NOMEM); + return NET_RX_DROP; + } + + can_set_skb_uid(csx); can_receive(skb, dev); return NET_RX_SUCCESS; } diff --git a/net/can/gw.c b/net/can/gw.c index 0ec99f68aa45..5793cb2420ed 100644 --- a/net/can/gw.c +++ b/net/can/gw.c @@ -527,6 +527,9 @@ static void can_can_gw_rcv(struct sk_buff *skb, void *data) /* put the incremented hop counter in the cloned skb */ ncsx->can_gw_hops = csx->can_gw_hops + 1; + /* force a new CAN UID generation for the routed frame */ + ncsx->can_skb_uid = 0; + /* first processing of this CAN frame -> adjust to private hop limit */ if (gwj->limit_hops && ncsx->can_gw_hops == 1) ncsx->can_gw_hops = max_hops - gwj->limit_hops + 1; diff --git a/net/can/isotp.c b/net/can/isotp.c index 155530aedce2..f5dc9d04bd68 100644 --- a/net/can/isotp.c +++ b/net/can/isotp.c @@ -891,7 +891,7 @@ static void isotp_send_cframe(struct isotp_sock *so) csx->can_iif = dev->ifindex; /* set uid in tx skb to identify CF echo frames */ - can_set_skb_uid(skb); + can_set_skb_uid(csx); cf = (struct canfd_frame *)skb->data; skb_put_zero(skb, so->ll.mtu); @@ -917,7 +917,7 @@ static void isotp_send_cframe(struct isotp_sock *so) pr_notice_once("can-isotp: cfecho is %08X != 0\n", old_cfecho); /* set consecutive frame echo tag */ - WRITE_ONCE(so->cfecho, skb->hash); + WRITE_ONCE(so->cfecho, csx->can_skb_uid); /* send frame with local echo enabled */ can_send_ret = can_send(skb, 1); @@ -969,18 +969,22 @@ static void isotp_rcv_echo(struct sk_buff *skb, void *data) { struct sock *sk = (struct sock *)data; struct isotp_sock *so = isotp_sk(sk); + struct can_skb_ext *csx = can_skb_ext_find(skb); /* only handle my own local echo CF/SF skb's (no FF!) */ if (skb->sk != sk) return; + if (WARN_ON_ONCE(!csx)) + return; + /* unlike isotp_rcv_fc()/isotp_rcv_cf(), not already under so->rx_lock * (no isotp_rcv() caller here), so take it ourselves */ spin_lock(&so->rx_lock); /* so->cfecho may since belong to a new transfer; recheck under lock */ - if (READ_ONCE(so->cfecho) != skb->hash) + if (READ_ONCE(so->cfecho) != csx->can_skb_uid) goto out_unlock; /* cancel local echo timeout */ @@ -1222,7 +1226,7 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) csx->can_iif = dev->ifindex; /* set uid in tx skb to identify CF echo frames */ - can_set_skb_uid(skb); + can_set_skb_uid(csx); so->tx.len = size; so->tx.idx = 0; @@ -1261,7 +1265,7 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) cf->data[ae] |= size; /* set CF echo tag for isotp_rcv_echo() (SF-mode) */ - WRITE_ONCE(so->cfecho, skb->hash); + WRITE_ONCE(so->cfecho, csx->can_skb_uid); } else { /* send first frame */ @@ -1278,7 +1282,7 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) so->txfc.bs = 0; /* set CF echo tag for isotp_rcv_echo() (CF-mode) */ - WRITE_ONCE(so->cfecho, skb->hash); + WRITE_ONCE(so->cfecho, csx->can_skb_uid); } else { /* standard flow control check */ new_state = ISOTP_WAIT_FIRST_FC; diff --git a/net/can/raw.c b/net/can/raw.c index 82d9c0499c95..0acd4f6c6dd6 100644 --- a/net/can/raw.c +++ b/net/can/raw.c @@ -77,7 +77,7 @@ MODULE_ALIAS("can-proto-1"); struct uniqframe { const struct sk_buff *skb; - u32 hash; + u32 can_skb_uid; unsigned int join_rx_count; }; @@ -133,12 +133,16 @@ static void raw_rcv(struct sk_buff *oskb, void *data) enum skb_drop_reason reason; struct sockaddr_can *addr; struct sk_buff *skb; + struct can_skb_ext *csx = can_skb_ext_find(oskb); unsigned int *pflags; /* check the received tx sock reference */ if (!ro->recv_own_msgs && oskb->sk == sk) return; + if (WARN_ON_ONCE(!csx)) + return; + /* make sure to not pass oversized frames to the socket */ if (!ro->fd_frames && can_is_canfd_skb(oskb)) return; @@ -165,7 +169,7 @@ static void raw_rcv(struct sk_buff *oskb, void *data) /* eliminate multiple filter matches for the same skb */ if (this_cpu_ptr(ro->uniq)->skb == oskb && - this_cpu_ptr(ro->uniq)->hash == oskb->hash) { + this_cpu_ptr(ro->uniq)->can_skb_uid == csx->can_skb_uid) { if (!ro->join_filters) return; @@ -175,7 +179,7 @@ static void raw_rcv(struct sk_buff *oskb, void *data) return; } else { this_cpu_ptr(ro->uniq)->skb = oskb; - this_cpu_ptr(ro->uniq)->hash = oskb->hash; + this_cpu_ptr(ro->uniq)->can_skb_uid = csx->can_skb_uid; this_cpu_ptr(ro->uniq)->join_rx_count = 1; /* drop first frame to check all enabled filters? */ if (ro->join_filters && ro->count > 1) From 7f85d1170575645f9f327062d78017a23b2714eb Mon Sep 17 00:00:00 2001 From: "Ji-Ze Hong (Peter Hong)" Date: Mon, 24 Aug 2026 21:18:14 +0800 Subject: [PATCH 012/128] usb: f81604: fix struct f81604_int_data size mismatch The struct f81604_int_data defines 9 bytes of interrupt data: - Byte 0: Status register (sr) - Byte 1: Interrupt register (isrc) - Byte 2: Interrupt enable register (ier) - Byte 3: Arbitration lost capture (alc) - Byte 4: Error code capture (ecc) - Byte 5: Error warning limit register (ewlr) - Byte 6: RX error counter (rxerr) - Byte 7: TX error counter (txerr) - Byte 8: Reserved (val) The hardware sends exactly 9 bytes for the interrupt endpoint. However, the struct was defined with __aligned(4) attribute which caused the compiler to pad the struct to 12 bytes. This causes a problem in f81604_read_int_callback() where the short URB check compares urb->actual_length against sizeof(*data). When sizeof(struct f81604_int_data) is 12 but the hardware only sends 9 bytes, the check fails and valid interrupt messages are discarded. This results in the driver only being able to transmit once because the TX complete interrupt is never processed. Fix this by removing the __aligned(4) attribute so the struct size matches the actual hardware data size of 9 bytes. Fixes: 7299b1b39a25 ("can: usb: f81604: handle short interrupt urb messages properly") Cc: stable@vger.kernel.org Reported-by: Dynetrex, Admin Closes: https://lore.kernel.org/all/A3834A07-5639-4779-844F-C5843DFC3928@dynetrex.com/ Signed-off-by: Ji-Ze Hong (Peter Hong) Acked-by: Greg Kroah-Hartman Tested-by: Drew Willey Link: https://patch.msgid.link/20260824-f81604-fix-v2-1-fc9be5581394@fintek.com.tw Signed-off-by: Marc Kleine-Budde --- drivers/net/can/usb/f81604.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/can/usb/f81604.c b/drivers/net/can/usb/f81604.c index f12318268e46..4c147b9d6d69 100644 --- a/drivers/net/can/usb/f81604.c +++ b/drivers/net/can/usb/f81604.c @@ -169,7 +169,7 @@ struct f81604_int_data { u8 rxerr; u8 txerr; u8 val; -} __packed __aligned(4); +} __packed; struct f81604_sff { __be16 id; From af0524bf4ce1a4cbd2946aa81751582951cfcab5 Mon Sep 17 00:00:00 2001 From: Mingming Cao Date: Fri, 25 Sep 2026 11:05:29 -0700 Subject: [PATCH 013/128] ibmveth: h_free logical LAN on open-fail after register ibmveth_open() registers the logical LAN, then allocates the RX buffer pools. A pool failure jumps to out_free_buffer_pools without h_free_logical_lan(), so PHYP still owns the buffer-list page when it is unmapped. request_irq() failure already issued the hypercall before taking the same label. Move that h_free to the shared post-register unwind so both paths deregister before the buffer list is unmapped. Fixes: d43732ce021f ("ibmveth: properly unwind on init errors") Cc: stable@vger.kernel.org Signed-off-by: Mingming Cao Reviewed-by: Dave Marquardt Link: https://patch.msgid.link/7a56fbfb9e0674f011f4e310fbd7dfd8e67c30aa.1790357373.git.mmc@linux.ibm.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/ibm/ibmveth.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/ibm/ibmveth.c b/drivers/net/ethernet/ibm/ibmveth.c index 73e051d26b9d..3e44f6b714d4 100644 --- a/drivers/net/ethernet/ibm/ibmveth.c +++ b/drivers/net/ethernet/ibm/ibmveth.c @@ -718,10 +718,6 @@ static int ibmveth_open(struct net_device *netdev) if (rc != 0) { netdev_err(netdev, "unable to request irq 0x%x, rc %d\n", netdev->irq, rc); - do { - lpar_rc = h_free_logical_lan(adapter->vdev->unit_address); - } while (H_IS_LONG_BUSY(lpar_rc) || (lpar_rc == H_BUSY)); - goto out_free_buffer_pools; } @@ -737,6 +733,9 @@ static int ibmveth_open(struct net_device *netdev) return 0; out_free_buffer_pools: + do { + lpar_rc = h_free_logical_lan(adapter->vdev->unit_address); + } while (H_IS_LONG_BUSY(lpar_rc) || (lpar_rc == H_BUSY)); while (--i >= 0) { if (adapter->rx_buff_pool[i].active) ibmveth_free_buffer_pool(adapter, From 84bec0bf035258a1073ffd6f6dc8ea61806aa7d4 Mon Sep 17 00:00:00 2001 From: Mingming Cao Date: Fri, 25 Sep 2026 11:05:30 -0700 Subject: [PATCH 014/128] ibmveth: fix TX LTB and filter unwind on open-fail out_free_buffer_pools reuses open()'s loop index for the TX LTB walk. After the pool unwind i is -1, so the TX buffers allocated earlier are leaked. request_irq() failure has the same leak. The TX-fail goto on that walk also skips dma_unmap of the filter list (pre-existing since the LTB was added). Rewrite the TX walk to real_num_tx_queues with a pointer check, and unmap the filter list before those frees. Fixes: d926793c1de9 ("ibmveth: Implement multi queue on xmit") Cc: stable@vger.kernel.org Signed-off-by: Mingming Cao Reviewed-by: Dave Marquardt Link: https://patch.msgid.link/d14314c2b10f125f5e578782484ba3cc50564d53.1790357373.git.mmc@linux.ibm.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/ibm/ibmveth.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/ibm/ibmveth.c b/drivers/net/ethernet/ibm/ibmveth.c index 3e44f6b714d4..abebdb1fc262 100644 --- a/drivers/net/ethernet/ibm/ibmveth.c +++ b/drivers/net/ethernet/ibm/ibmveth.c @@ -553,10 +553,15 @@ static int ibmveth_rxq_harvest_buffer(struct ibmveth_adapter *adapter, static void ibmveth_free_tx_ltb(struct ibmveth_adapter *adapter, int idx) { + void *ptr = adapter->tx_ltb_ptr[idx]; + + if (!ptr) + return; + + adapter->tx_ltb_ptr[idx] = NULL; dma_unmap_single(&adapter->vdev->dev, adapter->tx_ltb_dma[idx], adapter->tx_ltb_size, DMA_TO_DEVICE); - kfree(adapter->tx_ltb_ptr[idx]); - adapter->tx_ltb_ptr[idx] = NULL; + kfree(ptr); } static int ibmveth_allocate_tx_ltb(struct ibmveth_adapter *adapter, int idx) @@ -667,7 +672,7 @@ static int ibmveth_open(struct net_device *netdev) for (i = 0; i < netdev->real_num_tx_queues; i++) { if (ibmveth_allocate_tx_ltb(adapter, i)) - goto out_free_tx_ltb; + goto out_unmap_filter_list; } adapter->rx_queue.index = 0; @@ -745,10 +750,8 @@ out_unmap_filter_list: dma_unmap_single(dev, adapter->filter_list_dma, 4096, DMA_BIDIRECTIONAL); -out_free_tx_ltb: - while (--i >= 0) { + for (i = netdev->real_num_tx_queues - 1; i >= 0; i--) ibmveth_free_tx_ltb(adapter, i); - } out_unmap_buffer_list: dma_unmap_single(dev, adapter->buffer_list_dma, 4096, From 17e73bae400f6a434dc006e3bee92c94cf781954 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Tue, 29 Sep 2026 00:12:46 +0800 Subject: [PATCH 015/128] tipc: hold a reference to nodes found by link name tipc_node_find_by_name() returns a node after dropping its RCU read lock without taking a reference. The LINK_SET, LINK_GET and LINK_RESET_STATS handlers then lock and access the node, racing with timer-driven cleanup of a down peer. Generic netlink serialization does not exclude the node timer. The following interleaving can leave a handler using a freed node: CPU 0: find the node under RCU and release the node read lock CPU 1: tipc_node_timeout() clears the links and unlinks the down node CPU 1: drop the list and timer references, queuing tipc_node_free() CPU 0: leave the RCU read-side critical section CPU 1: complete the grace period and free the node CPU 0: acquire the node lock through the stale pointer LINK_SET also uses the node's media address after releasing the node lock, when passing queued packets to tipc_bearer_xmit(). KASAN on v7.3-rc5 reported: BUG: KASAN: slab-use-after-free in _raw_read_lock_bh Write of size 4 at addr ffff88807e06f008 by task poc/92 Call Trace: _raw_read_lock_bh kernel/locking/spinlock.c:287 tipc_nl_node_set_link net/tipc/node.c:2475 genl_family_rcv_msg_doit net/netlink/genetlink.c:1114 genl_rcv_msg net/netlink/genetlink.c:1209 netlink_rcv_skb net/netlink/af_netlink.c:2575 Allocated by task 0: tipc_node_create net/tipc/node.c:539 tipc_node_check_dest net/tipc/node.c:1196 tipc_disc_rcv net/tipc/discover.c:252 Freed by task 92: kfree mm/slub.c:6801 rcu_core kernel/rcu/tree.c:2919 Last potentially related work creation: __call_rcu_common.constprop.0 kernel/rcu/tree.c:3181 tipc_node_timeout net/tipc/node.c:814 Acquire a reference to the selected node with kref_get_unless_zero() before leaving RCU, returning NULL if the node has already been released. Release that reference on every caller exit after the last node access, including transmission in LINK_SET. Keep the existing link lookup order and locking so concurrent link removal still takes the existing error paths. Fixes: 6a939f365bdb ("tipc: Auto removal of peer down node instance") Cc: stable@vger.kernel.org Signed-off-by: Chengfeng Ye Link: https://patch.msgid.link/20260928161246.1895273-1-nicoyip.dev@gmail.com Signed-off-by: Jakub Kicinski --- net/tipc/node.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/net/tipc/node.c b/net/tipc/node.c index bd91378b7540..d7cbfa786c13 100644 --- a/net/tipc/node.c +++ b/net/tipc/node.c @@ -2421,8 +2421,11 @@ static struct tipc_node *tipc_node_find_by_name(struct net *net, } } tipc_node_read_unlock(n); - if (found_node) + if (found_node) { + if (!kref_get_unless_zero(&found_node->kref)) + found_node = NULL; break; + } } rcu_read_unlock(); @@ -2507,6 +2510,7 @@ out: tipc_node_read_unlock(node); tipc_bearer_xmit(net, bearer_id, &xmitq, &node->links[bearer_id].maddr, NULL); + tipc_node_put(node); return res; } @@ -2558,12 +2562,14 @@ int tipc_nl_node_get_link(struct sk_buff *skb, struct genl_info *info) link = node->links[bearer_id].link; if (!link) { tipc_node_read_unlock(node); + tipc_node_put(node); err = -EINVAL; goto err_free; } err = __tipc_nl_add_link(net, &msg, link, 0); tipc_node_read_unlock(node); + tipc_node_put(node); if (err) goto err_free; } @@ -2634,11 +2640,13 @@ int tipc_nl_node_reset_link_stats(struct sk_buff *skb, struct genl_info *info) if (!link) { spin_unlock_bh(&le->lock); tipc_node_read_unlock(node); + tipc_node_put(node); return -EINVAL; } tipc_link_reset_stats(link); spin_unlock_bh(&le->lock); tipc_node_read_unlock(node); + tipc_node_put(node); return 0; } From c9728fcf2e7fb4a7a8d08ebd9524c3b02ae1a021 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Wed, 30 Sep 2026 03:31:02 -0400 Subject: [PATCH 016/128] net/sched: em_text: reject unterminated algo before autoload This is a follow-up to commit 9c572a83037a ("net/sched: fix potential stack infoleak in em_text_dump()"), which zero-initialised the dump-side struct. Sashiko pointed at a pre-existing bug that exposed the change-side input-validation gap open. em_text_change() casts the raw netlink attribute payload to struct tcf_em_text and passes conf->algo, a 16-byte array with no enforced NUL terminator, to textsearch_prepare(). On the TS_AUTOLOAD retry textsearch_prepare() calls request_module("ts_%s", algo); vsnprintf() walks %s until it finds a NUL, so an algo[] with no NUL reads past the array into the adjacent struct fields and payload and feeds those bytes to the usermode helper command line. This is an out-of-bounds read and a kernel memory disclosure. Reject the attribute when no NUL exists within TC_EM_TEXT_ALGOSIZ bytes, matching the check xt_string has had since 3ab720881b6e. Conditions to recreate the bug: - CAP_NET_ADMIN on the target netns (namespace-local via unshare -Urn is enough) - install clsact on an interface, then add a basic filter with one text ematch whose algo[] is 16 bytes with no NUL (e.g. "A"*16) and pattern_len at least 1 - the add fails with -ENOENT and the kernel invokes modprobe with a module name made of "ts_" plus the 16 bytes and the adjacent payload Fixes: d675c989ed2d4 ("[PKT_SCHED]: Packet classification based on textsearch (ematch)") Reported-by: Sashiko (gemini) Closes: https://sashiko.dev/#/patchset/20260918133953.12494-1-bernard.ladenthin%40gmail.com Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-2DIU.v1.20260929183844@mojatatu.com Signed-off-by: Jakub Kicinski --- net/sched/em_text.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/sched/em_text.c b/net/sched/em_text.c index 4132f8c3c5fc..bc45edb8c03b 100644 --- a/net/sched/em_text.c +++ b/net/sched/em_text.c @@ -58,6 +58,9 @@ static int em_text_change(struct net *net, void *data, int len, if (len < sizeof(*conf) || len < (sizeof(*conf) + conf->pattern_len)) return -EINVAL; + if (!memchr(conf->algo, '\0', sizeof(conf->algo))) + return -EINVAL; + if (conf->from_layer > conf->to_layer) return -EINVAL; From afae89de73dd693cfa329580ba0fba63bd3f0a11 Mon Sep 17 00:00:00 2001 From: Omar Ramadan Date: Mon, 28 Sep 2026 23:23:11 +0300 Subject: [PATCH 017/128] amt: send the relay's General Query directly from the receive path An skb queued in a qdisc can outlive the tunnel it references through a raw pointer in skb->cb. For example, with igmp_qrv set to 1 on the relay a tunnel lives for 135s, so a netem delay of 180s on the amt device outlives it; when the tunnel expires and is freed, the subsequent dequeue triggers a use-after-free in amt_dev_xmit(). BUG: KASAN: slab-use-after-free in amt_dev_xmit+0x2763/0x2e20 Call Trace: amt_dev_xmit+0x2763/0x2e20 [drivers/net/amt.c:1262] dev_hard_start_xmit+0x22f/0x620 sch_direct_xmit+0x12e/0xac0 netem_dequeue+0x333/0xc50 net_tx_action+0x35c/0xa60 amt_send_igmp_gq() and amt_send_mld_gq() are only called from amt_request_handler(), inside the rcu_read_lock_bh() section of amt_rcv(). amt_request_handler() already has the tunnel the query is for: it found or created it inside that section. Queuing the query with dev_queue_xmit() only leads back into amt_dev_xmit(), which strips the Ethernet header and calls amt_send_membership_query() for that tunnel. Make that call directly from the two senders instead, the same way amt_send_advertisement() transmits from the receive path. The query never waits in a qdisc, the tunnel is only dereferenced inside the RCU section that found or created it, and nothing is stored in skb->cb, so no lookup or refcount is needed. Remove the query branch of amt_dev_xmit(), amt_skb_cb() and struct amt_skb_cb, which have no users left. Behaviour changes: - The relay's own General Queries no longer pass through the amt device's egress path: its qdisc, tc egress (clsact/tcx), the netfilter egress hook and packet taps. They are still visible as UDP on the underlay. - A query that is sent successfully is no longer counted as tx_dropped. The old query branch left through the unlock label, which counted every sent query as dropped. - A query that reaches amt_dev_xmit() on a relay from elsewhere, such as a userspace querier, is now dropped at the IGMP/MLD type switch. Before, it trusted whatever skb->cb held, and a NULL tunnel hit the WARN_ON(1). Fixes: cbc21dc1cfe9 ("amt: add data plane of amt interface") Reported-by: AutonomousCodeSecurity@microsoft.com Reported-by: Xiang Mei (Microsoft) Reported-by: Cen Zhang (Microsoft Security FORGE Labs) Signed-off-by: Omar Ramadan Link: https://patch.msgid.link/20260928202312.74574-2-omar@blockcast.net Signed-off-by: Jakub Kicinski --- drivers/net/amt.c | 48 +++++++++++++++-------------------------------- include/net/amt.h | 4 ---- 2 files changed, 15 insertions(+), 37 deletions(-) diff --git a/drivers/net/amt.c b/drivers/net/amt.c index bddc24e1856d..b53f8ec55661 100644 --- a/drivers/net/amt.c +++ b/drivers/net/amt.c @@ -80,15 +80,6 @@ static struct in6_addr mld2_all_node = MLD2_ALL_NODE_INIT; static struct mld2_grec mldv2_zero_grec; #endif -static struct amt_skb_cb *amt_skb_cb(struct sk_buff *skb) -{ - BUILD_BUG_ON(sizeof(struct amt_skb_cb) + sizeof(struct tc_skb_cb) > - sizeof_field(struct sk_buff, cb)); - - return (struct amt_skb_cb *)((void *)skb->cb + - sizeof(struct tc_skb_cb)); -} - static void __amt_source_gc_work(void) { struct amt_source_node *snode; @@ -791,6 +782,11 @@ out: rcu_read_unlock(); } +static bool amt_send_membership_query(struct amt_dev *amt, + struct sk_buff *skb, + struct amt_tunnel_list *tunnel, + bool v6); + static void amt_send_igmp_gq(struct amt_dev *amt, struct amt_tunnel_list *tunnel) { @@ -800,8 +796,11 @@ static void amt_send_igmp_gq(struct amt_dev *amt, if (!skb) return; - amt_skb_cb(skb)->tunnel = tunnel; - dev_queue_xmit(skb); + skb_pull(skb, sizeof(struct ethhdr)); + if (amt_send_membership_query(amt, skb, tunnel, false)) { + amt->dev->stats.tx_dropped++; + kfree_skb(skb); + } } #if IS_ENABLED(CONFIG_IPV6) @@ -885,8 +884,11 @@ static void amt_send_mld_gq(struct amt_dev *amt, struct amt_tunnel_list *tunnel) if (!skb) return; - amt_skb_cb(skb)->tunnel = tunnel; - dev_queue_xmit(skb); + skb_pull(skb, sizeof(struct ethhdr)); + if (amt_send_membership_query(amt, skb, tunnel, true)) { + amt->dev->stats.tx_dropped++; + kfree_skb(skb); + } } #else static void amt_send_mld_gq(struct amt_dev *amt, struct amt_tunnel_list *tunnel) @@ -1186,7 +1188,6 @@ static netdev_tx_t amt_dev_xmit(struct sk_buff *skb, struct net_device *dev) #endif bool report = false; struct igmphdr *ih; - bool query = false; struct iphdr *iph; bool data = false; bool v6 = false; @@ -1204,9 +1205,6 @@ static netdev_tx_t amt_dev_xmit(struct sk_buff *skb, struct net_device *dev) case IGMP_HOST_MEMBERSHIP_REPORT: report = true; break; - case IGMP_HOST_MEMBERSHIP_QUERY: - query = true; - break; default: goto free; } @@ -1228,9 +1226,6 @@ static netdev_tx_t amt_dev_xmit(struct sk_buff *skb, struct net_device *dev) case ICMPV6_MLD2_REPORT: report = true; break; - case ICMPV6_MGM_QUERY: - query = true; - break; default: goto free; } @@ -1261,19 +1256,6 @@ static netdev_tx_t amt_dev_xmit(struct sk_buff *skb, struct net_device *dev) goto free; goto unlock; } else if (amt->mode == AMT_MODE_RELAY) { - if (query) { - tunnel = amt_skb_cb(skb)->tunnel; - if (!tunnel) { - WARN_ON(1); - goto free; - } - - /* Do not forward unexpected query */ - if (amt_send_membership_query(amt, skb, tunnel, v6)) - goto free; - goto unlock; - } - if (!data) goto free; list_for_each_entry_rcu(tunnel, &amt->tunnel_list, list) { diff --git a/include/net/amt.h b/include/net/amt.h index a0255491f5b0..2846dde0cadc 100644 --- a/include/net/amt.h +++ b/include/net/amt.h @@ -231,10 +231,6 @@ struct amt_relay_headers { }; } __packed; -struct amt_skb_cb { - struct amt_tunnel_list *tunnel; -}; - struct amt_tunnel_list { struct list_head list; /* Protect All resources under an amt_tunne_list */ From c41817fcaf397f6ff7c530b682581f5eb5465844 Mon Sep 17 00:00:00 2001 From: Omar Ramadan Date: Mon, 28 Sep 2026 23:23:12 +0300 Subject: [PATCH 018/128] selftests: net: amt: check that the relay's queries bypass the amt device The relay used to hand its General Queries to dev_queue_xmit() on the amt device, where a query could wait in a qdisc and outlive the tunnel it pointed to. The previous patch sends them directly from the receive path instead. Count the IGMP and MLD queries that leave the relay through amtr with tc flower filters on its egress, installed before the gateway comes up, and check that there are none. The forwarding tests before it already show that the gateway received its queries, since it cannot join without one. Without the previous patch the new test fails (one run counted 7 IGMP and 6 MLD queries); with it, all of amt.sh passes. Signed-off-by: Omar Ramadan Link: https://patch.msgid.link/20260928202312.74574-3-omar@blockcast.net Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/amt.sh | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tools/testing/selftests/net/amt.sh b/tools/testing/selftests/net/amt.sh index 663744305e52..d13b20cccba9 100755 --- a/tools/testing/selftests/net/amt.sh +++ b/tools/testing/selftests/net/amt.sh @@ -150,6 +150,13 @@ setup_interface() ip netns exec "${RELAY}" ip a a 10.0.0.2/24 dev relay_gw ip netns exec "${RELAY}" ip link add amtr type amt mode relay \ local 10.0.0.2 dev relay_gw relay_port 2268 max_tunnels 4 + # Count the IGMP and MLD queries that leave the relay through its own + # amt device; test_query_egress expects none. + ip netns exec "${RELAY}" tc qdisc add dev amtr clsact + ip netns exec "${RELAY}" tc filter add dev amtr egress pref 1 \ + protocol ip flower ip_proto 0x2 action pass + ip netns exec "${RELAY}" tc filter add dev amtr egress pref 2 \ + protocol ipv6 flower ip_proto icmpv6 type 130 action pass ip netns exec "${RELAY}" ip a a 172.17.0.1/24 dev relay_src ip netns exec "${RELAY}" ip a a 2001:db8:3::1/64 dev relay_src ip netns exec "${SOURCE}" ip a a 172.17.0.2/24 dev src_relay @@ -246,6 +253,27 @@ test_ipv6_forward() fi } +# The relay sends its General Queries straight from the receive path, in +# the same context that found the tunnel. A query queued on the amt device +# instead could outlive the tunnel it was built for. The forwarding tests +# above show that the gateway got its queries. +test_query_egress() +{ + local n4 n6 + + n4=$(ip netns exec "${RELAY}" tc -s -j filter show dev amtr egress \ + pref 1 | jq '[.[].options.actions[0].stats.packets // empty] | add // 0') + n6=$(ip netns exec "${RELAY}" tc -s -j filter show dev amtr egress \ + pref 2 | jq '[.[].options.actions[0].stats.packets // empty] | add // 0') + if [ "$n4" -eq 0 ] && [ "$n6" -eq 0 ]; then + printf "TEST: %-60s [ OK ]\n" "amt relay queries bypass the amt device" + else + printf "TEST: %-60s [FAIL]\n" "amt relay queries bypass the amt device" + echo "IGMP queries on amtr egress: $n4, MLD queries: $n6" >&2 + ERR=1 + fi +} + send_mcast4() { sleep 5 @@ -287,6 +315,7 @@ wait $pid || err=$? if [ $err -eq 1 ]; then ERR=1 fi +test_query_egress printf "TEST: %-50s" "IPv4 amt traffic forwarding torture" send_mcast_torture4 printf " [ OK ]\n" From 28bc1ef699610ee09ce3d46a00552f5a0a0144bd Mon Sep 17 00:00:00 2001 From: Quchaosheng Date: Mon, 28 Sep 2026 19:31:08 +0800 Subject: [PATCH 019/128] net/packet: guard the ll header push in packet_rcv_spkt() packet_rcv_spkt() restores the link layer header with skb_push(skb, skb->data - skb_mac_header(skb)); That subtraction is only meaningful when the device actually has a link layer header. packet_rcv() and tpacket_rcv() both wrap it in dev_has_header(), which is also the predicate the block comment at the top of the file states the restore in terms of; packet_rcv_spkt() does not. commit d549699048b4 ("net/packet: fix packet receive on L3 devices without visible hard header") introduced the helper and changed the two call sites, and this one stayed behind. A device without a visible ll header can leave skb->mac_header at the 0xFFFF sentinel that __alloc_skb() initialises it to. A CAN skb does: init_can_skb() sets pkt_type and ip_summed but does not reset the headers, and commit 9f10374bb024 ("can: remove private CAN skb headroom infrastructure") dropped the skb_reset_*_header() calls that used to be there. skb_mac_header() is then 0xFFFF, the length becomes a large negative number and skb_push() reports it through skb_under_panic() -- from softirq context, so it is a full system panic even with panic_on_oops=0: skbuff: skb_under_panic: text:ffffffff8bd21bc1 len:-65455 put:-65471 head:... data:... tail:0x50 end:0x180 dev:can0 kernel BUG at net/core/skbuff.c:214! RIP: 0010:skb_panic+0x50/0x60 Call Trace: skb_push+0x38/0x40 packet_rcv_spkt+0xe1/0x170 __netif_receive_skb_core.constprop.0+0x7e8/0xd30 ... Kernel panic - not syncing: Fatal exception in interrupt The socket type is reachable: packet_create() accepts SOCK_PACKET alongside SOCK_RAW and SOCK_DGRAM behind the same CAP_NET_RAW check, and neither the socket length nor a capability check keeps it away from a CAN interface. The missing skb_reset_*_header() calls in init_can_skb() are a regression in their own right and are being fixed separately, but a packet socket should not turn a link layer that did not initialise its mac header into a kernel panic. Guard the push the way the other two receive paths do. Tested on v7.3-rc5 under QEMU with a slcan device on a pty, which is the driver RX path: vcan does not reproduce it, because can_send() resets the headers on the way out. One SOCK_PACKET socket bound to can0 and one frame written into the line discipline panics an unpatched kernel with the trace above; the same image with this patch prints no panic and powers off normally. Both kernels are this tree, defconfig plus CONFIG_CAN_SLCAN=y, differing only in this hunk. Fixes: d549699048b4 ("net/packet: fix packet receive on L3 devices without visible hard header") Cc: stable@vger.kernel.org Signed-off-by: Quchaosheng Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260928113108.2127215-1-quchaosheng000406@163.com Signed-off-by: Jakub Kicinski --- net/packet/af_packet.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c index 8865c1a42667..6ff16eef24f4 100644 --- a/net/packet/af_packet.c +++ b/net/packet/af_packet.c @@ -1911,7 +1911,8 @@ static int packet_rcv_spkt(struct sk_buff *skb, struct net_device *dev, spkt = &PACKET_SKB_CB(skb)->sa.pkt; - skb_push(skb, skb->data - skb_mac_header(skb)); + if (dev_has_header(dev)) + skb_push(skb, skb->data - skb_mac_header(skb)); /* * The SOCK_PACKET socket receives _all_ frames. From 0843b23890642a02ff85c36406224e8874e30664 Mon Sep 17 00:00:00 2001 From: Shashank Mohan Jain Date: Sun, 27 Sep 2026 10:47:42 +0530 Subject: [PATCH 020/128] lib/dim: fix 32-bit overflow in dim_calc_stats() rates dim_calc_stats() computes the per-millisecond rates as DIV_ROUND_UP(nbytes * USEC_PER_MSEC, delta_us) where nbytes is a u32 and USEC_PER_MSEC is 1000L. On 64-bit the product is done in 64-bit long arithmetic, but on 32-bit architectures long is 32 bits wide and the product wraps as soon as a measurement window carries more than 4294967 bytes (about 4.3 MB). The same applies to the packet and completion counts, although those need more than 4.29 million packets or completions per window. A DIM window spans DIM_NEVENTS (64) events. Drivers count events per interrupt or per NAPI poll, so under sustained load a window can easily carry more than 4.3 MB: 64 full NAPI polls of 64 MTU-sized frames are already 6.2 MB, and drivers such as mtk_eth_soc count one event per interrupt while NAPI keeps polling with the interrupt masked. On 32-bit users of the library (for example mtk_eth_soc on MT7621, bcmgenet and bcmsysport on 32-bit ARM, or virtio_net in a 32-bit guest) bpms then becomes the product modulo 2^32 divided by the window length, and net_dim_stats_compare() makes its BETTER/WORSE decisions on a value that has little to do with the real throughput. For example, a 1 Gbit/s link at line rate that moves 5 MB in a 40 ms window gives bpms = 125000 on 64-bit but 17626 on 32-bit, and 5 million packets in 2 s gives ppms = 353 instead of 2500. Widen the products to 64 bits and divide with DIV_ROUND_UP_ULL(). The results are unchanged on 64-bit. Fixes: cb3c7fd4f839 ("net/mlx5e: Support adaptive RX coalescing") Fixes: 4c4dbb4a7363 ("net/mlx5e: Move dynamic interrupt coalescing code to include/linux") Assisted-by: LLM Signed-off-by: Shashank Mohan Jain Signed-off-by: David S. Miller --- lib/dim/dim.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/dim/dim.c b/lib/dim/dim.c index 97c3d084ebf0..7f3eebae73cb 100644 --- a/lib/dim/dim.c +++ b/lib/dim/dim.c @@ -69,11 +69,15 @@ bool dim_calc_stats(const struct dim_sample *start, if (!delta_us) return false; - curr_stats->ppms = DIV_ROUND_UP(npkts * USEC_PER_MSEC, delta_us); - curr_stats->bpms = DIV_ROUND_UP(nbytes * USEC_PER_MSEC, delta_us); + /* u32 * USEC_PER_MSEC overflows a 32-bit long */ + curr_stats->ppms = DIV_ROUND_UP_ULL((u64)npkts * USEC_PER_MSEC, + delta_us); + curr_stats->bpms = DIV_ROUND_UP_ULL((u64)nbytes * USEC_PER_MSEC, + delta_us); curr_stats->epms = DIV_ROUND_UP(DIM_NEVENTS * USEC_PER_MSEC, delta_us); - curr_stats->cpms = DIV_ROUND_UP(ncomps * USEC_PER_MSEC, delta_us); + curr_stats->cpms = DIV_ROUND_UP_ULL((u64)ncomps * USEC_PER_MSEC, + delta_us); if (curr_stats->epms != 0) curr_stats->cpe_ratio = DIV_ROUND_DOWN_ULL( curr_stats->cpms * 100, curr_stats->epms); From 636035157a814f289f293fd0edea1a367a637797 Mon Sep 17 00:00:00 2001 From: Shashank Mohan Jain Date: Sun, 27 Sep 2026 10:47:43 +0530 Subject: [PATCH 021/128] lib/dim: add KUnit test for dim_calc_stats() Add a KUnit suite for the DIM library that checks the packet, byte, event and completion rates computed by dim_calc_stats(), including counter wraparound and windows whose byte or packet count times USEC_PER_MSEC does not fit in 32 bits. The latter cases fail on 32-bit architectures without the previous commit. Assisted-by: LLM Signed-off-by: Shashank Mohan Jain Signed-off-by: David S. Miller --- lib/Kconfig.debug | 15 ++++++ lib/dim/Makefile | 2 + lib/dim/dim_kunit.c | 126 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 lib/dim/dim_kunit.c diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 134b15a44625..6b24a2c89b84 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -2906,6 +2906,21 @@ config LIVEUPDATE_TEST If unsure, say N +config DIMLIB_KUNIT_TEST + tristate "KUnit test for the DIM library" if !KUNIT_ALL_TESTS + depends on KUNIT && NET + select DIMLIB + default KUNIT_ALL_TESTS + help + This builds the KUnit test for the Dynamic Interrupt Moderation + library (lib/dim/), which checks the rate statistics computed by + dim_calc_stats(). + + For more information on KUnit and unit tests in general please refer + to the KUnit documentation in Documentation/dev-tools/kunit/. + + If unsure, say N. + config CMDLINE_KUNIT_TEST tristate "KUnit test for cmdline API" if !KUNIT_ALL_TESTS depends on KUNIT diff --git a/lib/dim/Makefile b/lib/dim/Makefile index 5b9bfaac7ac1..47e1c36230e4 100644 --- a/lib/dim/Makefile +++ b/lib/dim/Makefile @@ -5,3 +5,5 @@ obj-$(CONFIG_DIMLIB) += dimlib.o dimlib-y := dim.o net_dim.o rdma_dim.o + +obj-$(CONFIG_DIMLIB_KUNIT_TEST) += dim_kunit.o diff --git a/lib/dim/dim_kunit.c b/lib/dim/dim_kunit.c new file mode 100644 index 000000000000..2e984f6b9fe4 --- /dev/null +++ b/lib/dim/dim_kunit.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit tests for the Dynamic Interrupt Moderation library + */ + +#include +#include +#include +#include + +struct dim_calc_stats_case { + const char *name; + u32 delta_us; + u32 start_pkts, end_pkts; + u32 start_bytes, end_bytes; + u32 start_comps, end_comps; + int ppms, bpms, cpms; +}; + +static const struct dim_calc_stats_case dim_calc_stats_cases[] = { + { + .name = "small", + .delta_us = 1000, + .end_pkts = 640, .end_bytes = 640 * 1500, .end_comps = 64, + .ppms = 640, .bpms = 960000, .cpms = 64, + }, + { + .name = "round_up", + .delta_us = 3000, + .end_pkts = 10, .end_bytes = 10, .end_comps = 1, + .ppms = 4, .bpms = 4, .cpms = 1, + }, + { + .name = "counter_wrap", + .delta_us = 1000, + .start_pkts = 0xffffff00, .end_pkts = 0x100, + .start_bytes = 0xfffff000, .end_bytes = 0x1000, + .start_comps = 0xfffffff0, .end_comps = 0x10, + .ppms = 0x200, .bpms = 0x2000, .cpms = 0x20, + }, + { + /* 30 MB in 10 ms (24 Gbit/s): nbytes * 1000 exceeds 32 bits */ + .name = "many_bytes", + .delta_us = 10000, + .end_pkts = 20000, .end_bytes = 30000000, .end_comps = 64, + .ppms = 2000, .bpms = 3000000, .cpms = 7, + }, + { + /* 4.3 MB in 16 ms (2.15 Gbit/s), just above the 32-bit limit */ + .name = "bytes_32bit_limit", + .delta_us = 16000, + .end_pkts = 2900, .end_bytes = 4300000, .end_comps = 64, + .ppms = 182, .bpms = 268750, .cpms = 4, + }, + { + /* 5 MB in 40 ms: a 1 Gbit/s link at line rate */ + .name = "gigabit", + .delta_us = 40000, + .end_pkts = 3300, .end_bytes = 5000000, .end_comps = 64, + .ppms = 83, .bpms = 125000, .cpms = 2, + }, + { + /* 5 million packets and completions in 2 s */ + .name = "many_packets", + .delta_us = 2000000, + .end_pkts = 5000000, .end_bytes = 5000000, .end_comps = 5000000, + .ppms = 2500, .bpms = 2500, .cpms = 2500, + }, +}; + +static void dim_calc_stats_case_desc(const struct dim_calc_stats_case *t, + char *desc) +{ + strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE); +} + +KUNIT_ARRAY_PARAM(dim_calc_stats, dim_calc_stats_cases, + dim_calc_stats_case_desc); + +static void dim_calc_stats_test(struct kunit *test) +{ + const struct dim_calc_stats_case *t = test->param_value; + struct dim_sample start = {}, end = {}; + struct dim_stats stats = {}; + + dim_update_sample_with_comps(0, t->start_pkts, t->start_bytes, + t->start_comps, &start); + dim_update_sample_with_comps(DIM_NEVENTS, t->end_pkts, t->end_bytes, + t->end_comps, &end); + /* dim_update_sample() stamps ktime_get(); use fixed times instead */ + start.time = ktime_set(1000, 0); + end.time = ktime_add_us(start.time, t->delta_us); + + KUNIT_ASSERT_TRUE(test, dim_calc_stats(&start, &end, &stats)); + KUNIT_EXPECT_EQ(test, stats.ppms, t->ppms); + KUNIT_EXPECT_EQ(test, stats.bpms, t->bpms); + KUNIT_EXPECT_EQ(test, stats.cpms, t->cpms); + KUNIT_EXPECT_EQ(test, stats.epms, + (int)DIV_ROUND_UP(DIM_NEVENTS * USEC_PER_MSEC, + t->delta_us)); +} + +static void dim_calc_stats_no_time_test(struct kunit *test) +{ + struct dim_sample sample = {}; + struct dim_stats stats = {}; + + dim_update_sample_with_comps(0, 100, 1000, 10, &sample); + KUNIT_EXPECT_FALSE(test, dim_calc_stats(&sample, &sample, &stats)); +} + +static struct kunit_case dim_test_cases[] = { + KUNIT_CASE_PARAM(dim_calc_stats_test, dim_calc_stats_gen_params), + KUNIT_CASE(dim_calc_stats_no_time_test), + {} +}; + +static struct kunit_suite dim_test_suite = { + .name = "dim", + .test_cases = dim_test_cases, +}; + +kunit_test_suite(dim_test_suite); + +MODULE_DESCRIPTION("KUnit tests for the DIM library"); +MODULE_LICENSE("GPL"); From 3acdd44385bc7e2e57605e073691ce69cc5a960d Mon Sep 17 00:00:00 2001 From: Yuqi Xu Date: Mon, 28 Sep 2026 16:24:38 +0800 Subject: [PATCH 022/128] tipc: destroy topsrv workqueues before closing connections tipc_topsrv_stop() closed subscriber connections while the topology server's send and receive workqueues were still running. Socket callbacks and subscription events could then queue more work, and in-flight send/recv work could drop the last connection reference during the conn_idr walk. That race produced several teardown failures: refcount_t addition on 0 from conn_get() on a connection whose release was blocked on idr_lock, a subsequent use-after-free in tipc_conn_close(), queue_work() on an already destroyed workqueue from the listener data-ready callback, and an RCU stall in tipc_topsrv_exit_net() while the walk spun under idr_lock. Clear srv->listener under idr_lock so it acts as a shutdown flag, skip queue_work() once it is NULL, destroy the workqueues to flush in-flight work, and only then close the remaining connections. Refuse tipc_conn_lookup() after that flag is cleared, and drop idr_lock when the teardown walk finds no connection, so an in-flight subscription event cannot pin idr_in_use while the walk holds the lock. v3 supersedes the narrower in-thread diff Tung Quang Nguyen posted on 2026-09-23. It keeps his teardown order and adds the lookup refusal and the empty-idr unlock. Fixes: c5fa7b3cf3cb ("tipc: introduce new TIPC server infrastructure") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: LLM Signed-off-by: Yuqi Xu Reviewed-by: Ren Wei Signed-off-by: David S. Miller --- net/tipc/topsrv.c | 80 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 65 insertions(+), 15 deletions(-) diff --git a/net/tipc/topsrv.c b/net/tipc/topsrv.c index af530c9ed840..82e9f44e2fe4 100644 --- a/net/tipc/topsrv.c +++ b/net/tipc/topsrv.c @@ -55,7 +55,7 @@ /** * struct tipc_topsrv - TIPC server structure * @conn_idr: identifier set of connection - * @idr_lock: protect the connection identifier set + * @idr_lock: protect the connection identifier set and listener * @idr_in_use: amount of allocated identifier entry * @net: network namespace instance * @awork: accept work item @@ -218,6 +218,10 @@ static struct tipc_conn *tipc_conn_lookup(struct tipc_topsrv *s, int conid) struct tipc_conn *con; spin_lock_bh(&s->idr_lock); + if (!s->listener) { + spin_unlock_bh(&s->idr_lock); + return NULL; + } con = idr_find(&s->conn_idr, conid); if (!connected(con) || !kref_get_unless_zero(&con->kref)) con = NULL; @@ -301,10 +305,20 @@ static void tipc_conn_send_to_sock(struct tipc_conn *con) static void tipc_conn_send_work(struct work_struct *work) { struct tipc_conn *con = container_of(work, struct tipc_conn, swork); + struct tipc_topsrv *srv; + + srv = con->server; + spin_lock_bh(&srv->idr_lock); + if (!srv->listener) { + spin_unlock_bh(&srv->idr_lock); + goto out; + } + spin_unlock_bh(&srv->idr_lock); if (connected(con)) tipc_conn_send_to_sock(con); +out: conn_put(con); } @@ -334,8 +348,14 @@ void tipc_topsrv_queue_evt(struct net *net, int conid, list_add_tail(&e->list, &con->outqueue); spin_unlock_bh(&con->outqueue_lock); - if (queue_work(srv->send_wq, &con->swork)) - return; + spin_lock_bh(&srv->idr_lock); + if (srv->listener) { + if (queue_work(srv->send_wq, &con->swork)) { + spin_unlock_bh(&srv->idr_lock); + return; + } + } + spin_unlock_bh(&srv->idr_lock); err: conn_put(con); } @@ -346,14 +366,20 @@ err: */ static void tipc_conn_write_space(struct sock *sk) { + struct tipc_topsrv *srv; struct tipc_conn *con; read_lock_bh(&sk->sk_callback_lock); con = sk->sk_user_data; if (connected(con)) { - conn_get(con); - if (!queue_work(con->server->send_wq, &con->swork)) - conn_put(con); + srv = con->server; + spin_lock_bh(&srv->idr_lock); + if (srv->listener) { + conn_get(con); + if (!queue_work(srv->send_wq, &con->swork)) + conn_put(con); + } + spin_unlock_bh(&srv->idr_lock); } read_unlock_bh(&sk->sk_callback_lock); } @@ -418,8 +444,17 @@ static int tipc_conn_rcv_from_sock(struct tipc_conn *con) static void tipc_conn_recv_work(struct work_struct *work) { struct tipc_conn *con = container_of(work, struct tipc_conn, rwork); + struct tipc_topsrv *srv; int count = 0; + srv = con->server; + spin_lock_bh(&srv->idr_lock); + if (!srv->listener) { + spin_unlock_bh(&srv->idr_lock); + goto out; + } + spin_unlock_bh(&srv->idr_lock); + while (connected(con)) { if (tipc_conn_rcv_from_sock(con)) break; @@ -430,6 +465,7 @@ static void tipc_conn_recv_work(struct work_struct *work) count = 0; } } +out: conn_put(con); } @@ -438,6 +474,7 @@ static void tipc_conn_recv_work(struct work_struct *work) */ static void tipc_conn_data_ready(struct sock *sk) { + struct tipc_topsrv *srv; struct tipc_conn *con; trace_sk_data_ready(sk); @@ -445,9 +482,14 @@ static void tipc_conn_data_ready(struct sock *sk) read_lock_bh(&sk->sk_callback_lock); con = sk->sk_user_data; if (connected(con)) { - conn_get(con); - if (!queue_work(con->server->rcv_wq, &con->rwork)) - conn_put(con); + srv = con->server; + spin_lock_bh(&srv->idr_lock); + if (srv->listener) { + conn_get(con); + if (!queue_work(srv->rcv_wq, &con->rwork)) + conn_put(con); + } + spin_unlock_bh(&srv->idr_lock); } read_unlock_bh(&sk->sk_callback_lock); } @@ -503,8 +545,12 @@ static void tipc_topsrv_listener_data_ready(struct sock *sk) read_lock_bh(&sk->sk_callback_lock); srv = sk->sk_user_data; - if (srv) - queue_work(srv->rcv_wq, &srv->awork); + if (srv) { + spin_lock_bh(&srv->idr_lock); + if (srv->listener) + queue_work(srv->rcv_wq, &srv->awork); + spin_unlock_bh(&srv->idr_lock); + } read_unlock_bh(&sk->sk_callback_lock); } @@ -700,23 +746,27 @@ static void tipc_topsrv_stop(struct net *net) struct tipc_conn *con; int id; + spin_lock_bh(&srv->idr_lock); + srv->listener = NULL; + spin_unlock_bh(&srv->idr_lock); + tipc_topsrv_work_stop(srv); + spin_lock_bh(&srv->idr_lock); for (id = 0; srv->idr_in_use; id++) { con = idr_find(&srv->conn_idr, id); if (con) { - conn_get(con); spin_unlock_bh(&srv->idr_lock); tipc_conn_close(con); - conn_put(con); spin_lock_bh(&srv->idr_lock); + continue; } + spin_unlock_bh(&srv->idr_lock); + spin_lock_bh(&srv->idr_lock); } __module_get(lsock->ops->owner); __module_get(lsock->sk->sk_prot_creator->owner); - srv->listener = NULL; spin_unlock_bh(&srv->idr_lock); - tipc_topsrv_work_stop(srv); sock_release(lsock); idr_destroy(&srv->conn_idr); kfree(srv); From 4c9dc0faca98084922caeb689579d338cf3e1c29 Mon Sep 17 00:00:00 2001 From: Sang-Hoon Choi Date: Wed, 30 Sep 2026 02:37:34 +0900 Subject: [PATCH 023/128] cxgb4/ch_ktls: disable softirqs around tid_list erases chcr_ktls_cpl_act_open_rpl() inserts into tid_list from the receive path using xa_insert_bh(). The array is shared by the adapter's connections and initialized with XA_FLAGS_LOCK_BH. However, chcr_ktls_dev_del() and the chcr_ktls_dev_add() error path use xa_erase(), which takes the XArray lock without disabling softirqs. When cleanup runs with softirqs enabled, a receive softirq for another connection on the same CPU can interrupt the erase and spin on the lock held by the interrupted task. Use xa_erase_bh() at both sites to match the insertion path. Fixes: 65e302a9bd57 ("cxgb4/ch_ktls: Clear resources when pf4 device is removed") Reported-by: Changyul Lee Assisted-by: LLM Signed-off-by: Sang-Hoon Choi Reviewed-by: Ayush Sawal Signed-off-by: David S. Miller --- .../net/ethernet/chelsio/inline_crypto/ch_ktls/chcr_ktls.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/chelsio/inline_crypto/ch_ktls/chcr_ktls.c b/drivers/net/ethernet/chelsio/inline_crypto/ch_ktls/chcr_ktls.c index f5acd4be1e69..dbec95b49777 100644 --- a/drivers/net/ethernet/chelsio/inline_crypto/ch_ktls/chcr_ktls.c +++ b/drivers/net/ethernet/chelsio/inline_crypto/ch_ktls/chcr_ktls.c @@ -389,7 +389,7 @@ static void chcr_ktls_dev_del(struct net_device *netdev, cxgb4_remove_tid(&tx_info->adap->tids, tx_info->tx_chan, tx_info->tid, tx_info->ip_family); - xa_erase(&u_ctx->tid_list, tx_info->tid); + xa_erase_bh(&u_ctx->tid_list, tx_info->tid); } port_stats = &tx_info->adap->ch_ktls_stats.ktls_port[tx_info->port_id]; @@ -577,7 +577,7 @@ free_tid: cxgb4_remove_tid(&tx_info->adap->tids, tx_info->tx_chan, tx_info->tid, tx_info->ip_family); - xa_erase(&u_ctx->tid_list, tx_info->tid); + xa_erase_bh(&u_ctx->tid_list, tx_info->tid); put_module: /* release module refcount */ From f5e0b20f6c6d548d985acdd620c5110776c915c2 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Tue, 18 Aug 2026 12:58:21 +0000 Subject: [PATCH 024/128] wifi: mt76: mt7996: program a link again if the driver holds it The firmware needs the driver to remove active links from the BSSINFO table when it destroys the interface. A link therefore stays registered from its first chanctx assignment until the interface goes away. If the driver holds the link already, a second add does nothing. mac80211 gives a non-AP MLD a new address each time it sets a link up. A link that comes back after a failed attempt thus carries an address that the hardware does not know, on a BSS that the driver never programs again. The station then authenticates from an address that nothing answers. The failure repeats until the driver tears the interface down for good. Program the address and the BSS again. This is the pair that the initial add sets up. For a station it does what mt7996_mcu_mld_link_oper() does for an AP. Fixes: 08813703ac41 ("wifi: mt76: mt7996: Destroy vif active links in mt7996_remove_interface()") Link: https://patch.msgid.link/20260818125825.395538-1-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index e218856b0c45..d17d1dbb2038 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -310,7 +310,14 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, if (vif->type == NL80211_IFTYPE_AP) return mt7996_mcu_mld_link_oper(dev, link_conf, link, true); - return 0; + + /* update the link address */ + ret = mt7996_mcu_add_dev_info(phy, vif, link_conf, mlink, true); + if (ret) + return ret; + + return mt7996_mcu_add_bss_info(phy, vif, link_conf, mlink, + msta_link, true); } mlink->idx = __ffs64(~dev->mt76.vif_mask); From 232d49dd4b40a666283de9e722899f088ed581b2 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Tue, 29 Sep 2026 15:10:11 +0200 Subject: [PATCH 025/128] net: stmmac: propagate PTP addend and system time programming errors stmmac_update_subsecond_increment() ignores the error returned by stmmac_config_addend(), and stmmac_init_tstamp_counter() discards the addend and system time programming errors, always returning success. A failure to program the addend (PTP_TCR_TSADDREG) or to initialize the system time counter (PTP_TCR_TSINIT) is therefore silently swallowed, leaving the hardware timestamp counter in a non-running or partially configured state while the driver keeps operating as if timestamping were up. This matters for TAPRIO/EST offloading, which derives the gate base time from the hardware timestamp counter. The same hooks are also called from the PHC callbacks: settime64 and adjfine drop the error and report success to clock_settime() and clock_adjtime(), so a dead PTP reference clock goes unnoticed by ptp4l/phc2sys. Return error codes from stmmac_update_subsecond_increment(), stmmac_init_tstamp_counter(), stmmac_dl_ts_coarse_set() and the settime64/adjfine callbacks instead of silently returning success. On failure, roll back the partially applied configuration so the hardware and the driver bookkeeping stay consistent, and report the reason through the devlink extack. Also guard against a zero sub-second increment, which would otherwise divide by zero when computing the addend. Reset the persistent timestamping state (hwts_tx_en, hwts_rx_en, tstamp_config, systime_flags and tsfupdt_coarse) when (re)initializing timestamping, so a failed init does not leave TX/RX timestamping enabled on a counter that never started. Fixes: cc4c9001ce31 ("net: stmmac: Switch stmmac_hwtimestamp to generic HW Interface Helpers") Signed-off-by: Lorenzo Bianconi Reviewed-by: Maxime Chevallier Signed-off-by: David S. Miller --- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 106 +++++++++++++----- .../net/ethernet/stmicro/stmmac/stmmac_ptp.c | 10 +- 2 files changed, 84 insertions(+), 32 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index ec62fa7418f4..9741f97fa37a 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -601,31 +601,64 @@ static void stmmac_get_rx_hwtstamp(struct stmmac_priv *priv, struct dma_desc *p, } } -static void stmmac_update_subsecond_increment(struct stmmac_priv *priv) +static void stmmac_restore_subsecond_increment(struct stmmac_priv *priv, + u32 default_addend) { bool xmac = dwmac_is_xmac(priv->plat->core_type); u32 sec_inc = 0; - u64 temp = 0; + stmmac_config_addend(priv, priv->ptpaddr, default_addend); stmmac_config_hw_tstamping(priv, priv->ptpaddr, priv->systime_flags); + stmmac_config_sub_second_increment(priv, priv->ptpaddr, + priv->plat->clk_ptp_rate, + xmac, &sec_inc); + priv->default_addend = default_addend; + priv->sub_second_inc = sec_inc; +} + +static int stmmac_update_subsecond_increment(struct stmmac_priv *priv, + u32 systime_flags) +{ + bool xmac = dwmac_is_xmac(priv->plat->core_type); + u32 sec_inc = 0, val; + u64 temp = 0; + int ret; + + stmmac_config_hw_tstamping(priv, priv->ptpaddr, systime_flags); /* program Sub Second Increment reg */ stmmac_config_sub_second_increment(priv, priv->ptpaddr, priv->plat->clk_ptp_rate, xmac, &sec_inc); - temp = div_u64(1000000000ULL, sec_inc); - - /* Store sub second increment for later use */ - priv->sub_second_inc = sec_inc; + if (!sec_inc) { + ret = -EINVAL; + goto error; + } /* calculate default added value: * formula is : * addend = (2^32)/freq_div_ratio; * where, freq_div_ratio = 1e9ns/sec_inc */ + temp = div_u64(1000000000ULL, sec_inc); temp = (u64)(temp << 32); - priv->default_addend = div_u64(temp, priv->plat->clk_ptp_rate); - stmmac_config_addend(priv, priv->ptpaddr, priv->default_addend); + val = div_u64(temp, priv->plat->clk_ptp_rate); + + ret = stmmac_config_addend(priv, priv->ptpaddr, val); + if (ret) + goto error; + + priv->sub_second_inc = sec_inc; + priv->default_addend = val; + + return 0; +error: + /* Restore previous configuration */ + stmmac_config_hw_tstamping(priv, priv->ptpaddr, priv->systime_flags); + stmmac_config_sub_second_increment(priv, priv->ptpaddr, + priv->plat->clk_ptp_rate, xmac, + NULL); + return ret; } /** @@ -854,35 +887,42 @@ static int stmmac_hwtstamp_get(struct net_device *dev, /** * stmmac_init_tstamp_counter - init hardware timestamping counter * @priv: driver private structure - * @systime_flags: timestamping flags * Description: * Initialize hardware counter for packet timestamping. * This is valid as long as the interface is open and not suspended. * Will be rerun after resuming from suspend, case in which the timestamping * flags updated by stmmac_hwtstamp_set() also need to be restored. */ -static int stmmac_init_tstamp_counter(struct stmmac_priv *priv, - u32 systime_flags) +static int stmmac_init_tstamp_counter(struct stmmac_priv *priv) { + u32 default_addend = priv->default_addend; struct timespec64 now; + int ret; if (!priv->plat->clk_ptp_rate) { netdev_err(priv->dev, "Invalid PTP clock rate"); return -EINVAL; } - stmmac_config_hw_tstamping(priv, priv->ptpaddr, systime_flags); - priv->systime_flags = systime_flags; - - stmmac_update_subsecond_increment(priv); + ret = stmmac_update_subsecond_increment(priv, priv->systime_flags); + if (ret) + return ret; /* initialize system time */ ktime_get_real_ts64(&now); /* lower 32 bits of tv_sec are safe until y2106 */ - stmmac_init_systime(priv, priv->ptpaddr, (u32)now.tv_sec, now.tv_nsec); + ret = stmmac_init_systime(priv, priv->ptpaddr, (u32)now.tv_sec, + now.tv_nsec); + if (ret) + goto error; return 0; +error: + /* Restore previous configuration */ + stmmac_restore_subsecond_increment(priv, default_addend); + + return ret; } /** @@ -905,8 +945,14 @@ static int stmmac_init_timestamping(struct stmmac_priv *priv) return -EOPNOTSUPP; } - ret = stmmac_init_tstamp_counter(priv, STMMAC_HWTS_ACTIVE | - PTP_TCR_TSCFUPDT); + /* Reset hw ts configuration */ + memset(&priv->tstamp_config, 0, sizeof(priv->tstamp_config)); + priv->systime_flags = STMMAC_HWTS_ACTIVE | PTP_TCR_TSCFUPDT; + priv->tsfupdt_coarse = false; + priv->hwts_tx_en = 0; + priv->hwts_rx_en = 0; + + ret = stmmac_init_tstamp_counter(priv); if (ret) { netdev_warn(priv->dev, "PTP init failed\n"); return ret; @@ -927,10 +973,6 @@ static int stmmac_init_timestamping(struct stmmac_priv *priv) netdev_info(priv->dev, "IEEE 1588-2008 Advanced Timestamp supported\n"); - memset(&priv->tstamp_config, 0, sizeof(priv->tstamp_config)); - priv->hwts_tx_en = 0; - priv->hwts_rx_en = 0; - if (priv->plat->flags & STMMAC_FLAG_HWTSTAMP_CORRECT_LATENCY) stmmac_hwtstamp_correct_latency(priv, priv); @@ -7711,18 +7753,26 @@ static int stmmac_dl_ts_coarse_set(struct devlink *dl, u32 id, { struct stmmac_devlink_priv *dl_priv = devlink_priv(dl); struct stmmac_priv *priv = dl_priv->stmmac_priv; + u32 systime_flags = priv->systime_flags; + int ret; - priv->tsfupdt_coarse = ctx->val.vbool; - - if (priv->tsfupdt_coarse) - priv->systime_flags &= ~PTP_TCR_TSCFUPDT; + if (ctx->val.vbool) + systime_flags &= ~PTP_TCR_TSCFUPDT; else - priv->systime_flags |= PTP_TCR_TSCFUPDT; + systime_flags |= PTP_TCR_TSCFUPDT; /* In Coarse mode, we can use a smaller subsecond increment, let's * reconfigure the systime, subsecond increment and addend. */ - stmmac_update_subsecond_increment(priv); + ret = stmmac_update_subsecond_increment(priv, systime_flags); + if (ret) { + NL_SET_ERR_MSG_MOD(extack, + "failed to reconfigure PTP adjustment"); + return ret; + } + + priv->tsfupdt_coarse = ctx->val.vbool; + priv->systime_flags = systime_flags; return 0; } diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c index 3bfcc9760dce..493c5d81a36b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c @@ -28,14 +28,15 @@ static int stmmac_adjust_freq(struct ptp_clock_info *ptp, long scaled_ppm) container_of(ptp, struct stmmac_priv, ptp_clock_ops); unsigned long flags; u32 addend; + int ret; addend = adjust_by_scaled_ppm(priv->default_addend, scaled_ppm); write_lock_irqsave(&priv->ptp_lock, flags); - stmmac_config_addend(priv, priv->ptpaddr, addend); + ret = stmmac_config_addend(priv, priv->ptpaddr, addend); write_unlock_irqrestore(&priv->ptp_lock, flags); - return 0; + return ret; } /** @@ -153,12 +154,13 @@ static int stmmac_set_time(struct ptp_clock_info *ptp, struct stmmac_priv *priv = container_of(ptp, struct stmmac_priv, ptp_clock_ops); unsigned long flags; + int ret; write_lock_irqsave(&priv->ptp_lock, flags); - stmmac_init_systime(priv, priv->ptpaddr, ts->tv_sec, ts->tv_nsec); + ret = stmmac_init_systime(priv, priv->ptpaddr, ts->tv_sec, ts->tv_nsec); write_unlock_irqrestore(&priv->ptp_lock, flags); - return 0; + return ret; } static int stmmac_enable(struct ptp_clock_info *ptp, From 71a77ab76e74131a101f4d2d2afb0dcbf81b4e3c Mon Sep 17 00:00:00 2001 From: Frank Li Date: Tue, 29 Sep 2026 15:06:36 -0400 Subject: [PATCH 026/128] net: allwinner: remove dmaengine_desc_free() dmaengine_desc_free() is designed for DMA_CTRL_REUSE. If DMA_CTRL_REUSE is not set, dmaengine_desc_free() will return -EPERM. if (!dmaengine_desc_test_reuse(desc)) return -EPERM; So calling dmaengine_desc_free() does nothing. dmaengine_terminate_(a)sync() will free all already allocated descriptors. Signed-off-by: Frank Li Reviewed-by: Joe Damato Signed-off-by: David S. Miller --- drivers/net/ethernet/allwinner/sun4i-emac.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/net/ethernet/allwinner/sun4i-emac.c b/drivers/net/ethernet/allwinner/sun4i-emac.c index 0ba67a55705d..09b2abdfc9e8 100644 --- a/drivers/net/ethernet/allwinner/sun4i-emac.c +++ b/drivers/net/ethernet/allwinner/sun4i-emac.c @@ -300,7 +300,7 @@ static int emac_dma_inblk_32bit(struct emac_board_info *db, if (!req) { dev_err(db->dev, "alloc emac dma req error.\n"); ret = -ENOMEM; - goto alloc_req_err; + goto prepare_err; } desc->callback_param = req; @@ -319,9 +319,6 @@ static int emac_dma_inblk_32bit(struct emac_board_info *db, submit_err: emac_free_dma_req(req); -alloc_req_err: - dmaengine_desc_free(desc); - prepare_err: dma_unmap_single(db->dev, rxbuf, count, DMA_FROM_DEVICE); return ret; From 25016fe8c1ed0fccae107bd629089139ffbafe08 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Wed, 30 Sep 2026 15:44:53 +0800 Subject: [PATCH 027/128] Bluetooth: btintel_pcie: use managed IRQ teardown The PCIe driver registers its MSI-X handlers with devm_request_threaded_irq(), but remove() uses free_irq() directly. The devres actions then retain registrations and attempt to release them again during device cleanup. Use devm_free_irq() for the remove path and for already registered vectors when a later setup request fails. This removes the managed resource at the same time as the IRQ is released and keeps MSI-X vector teardown ordered after IRQ ownership is gone. Fixes: c2b636b3f788 ("Bluetooth: btintel_pcie: Add support for PCIe transport") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Runyu Xiao Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel_pcie.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index 0194badeeff3..ac7193400769 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -1777,7 +1777,7 @@ static irqreturn_t btintel_pcie_irq_msix_handler(int irq, void *dev_id) static int btintel_pcie_setup_irq(struct btintel_pcie_data *data) { int err; - int num_irqs, i; + int num_irqs, i, j; for (i = 0; i < BTINTEL_PCIE_MSIX_VEC_MAX; i++) data->msix_entries[i].entry = i; @@ -1806,6 +1806,10 @@ static int btintel_pcie_setup_irq(struct btintel_pcie_data *data) KBUILD_MODNAME, msix_entry); if (err) { + for (j = 0; j < i; j++) + devm_free_irq(&data->pdev->dev, + data->msix_entries[j].vector, + &data->msix_entries[j]); pci_free_irq_vectors(data->pdev); data->alloc_vecs = 0; return err; @@ -3145,7 +3149,7 @@ static void btintel_pcie_remove(struct pci_dev *pdev) struct msix_entry *msix_entry; msix_entry = &data->msix_entries[i]; - free_irq(msix_entry->vector, msix_entry); + devm_free_irq(&pdev->dev, msix_entry->vector, msix_entry); } pci_free_irq_vectors(pdev); From 08e90633377f1b2567ab5ad6810b74c552246a07 Mon Sep 17 00:00:00 2001 From: Palla Raghunath Date: Fri, 2 Oct 2026 08:07:52 +0100 Subject: [PATCH 028/128] Bluetooth: RFCOMM: Fix NULL tty_dev dereference in rfcomm_dev_shutdown syzbot hit a NULL pointer dereference in rfcomm_dev_shutdown() when an RFCOMM tty is closed. rfcomm_dev_add() puts the new device on rfcomm_dev_list first and only then registers the tty device and saves it in dev->tty_dev. If that registration fails, dev->tty_dev is never set at all. The device can be looked up in the meantime, so an open() of /dev/rfcommN gets through rfcomm_tty_install() and attaches to it. When that tty is closed again, rfcomm_dev_shutdown() uses dev->tty_dev without checking it: Oops: general protection fault, probably for non-canonical address KASAN: null-ptr-deref in range [0x0000000000000040-0x0000000000000047] RIP: 0010:rfcomm_dev_shutdown+0x48/0xb0 net/bluetooth/rfcomm/tty.c:131 Call Trace: tty_port_shutdown+0x1bf/0x220 drivers/tty/tty_port.c:372 tty_port_close+0x4e/0x150 drivers/tty/tty_port.c:705 tty_release+0x359/0x1670 drivers/tty/tty_io.c:1718 __fput+0x418/0xa50 fs/file_table.c:512 rfcomm_dev_destruct() already copes with a NULL dev->tty_dev. Add the same check to rfcomm_dev_shutdown(), so the tty device is only moved when it was actually registered. Fixes: cad348a17e17 ("Bluetooth: Implement .activate, .shutdown and .carrier_raised methods") Reported-by: syzbot+4a807caf7680652d53b6@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=4a807caf7680652d53b6 Cc: stable@vger.kernel.org Signed-off-by: Palla Raghunath Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/rfcomm/tty.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/bluetooth/rfcomm/tty.c b/net/bluetooth/rfcomm/tty.c index dc3cdf614def..198c7dd1a95b 100644 --- a/net/bluetooth/rfcomm/tty.c +++ b/net/bluetooth/rfcomm/tty.c @@ -128,7 +128,7 @@ static void rfcomm_dev_shutdown(struct tty_port *port) { struct rfcomm_dev *dev = container_of(port, struct rfcomm_dev, port); - if (dev->tty_dev->parent) + if (dev->tty_dev && dev->tty_dev->parent) device_move(dev->tty_dev, NULL, DPM_ORDER_DEV_LAST); /* close the dlc */ From 184b5ddf82d45d6f75cc83719a2e453392512bd2 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Wed, 30 Sep 2026 18:26:39 -0700 Subject: [PATCH 029/128] bonding: fix the program leak when XDP is replaced We need to release the reference on the old BPF prog, whether we are going to no-prog, or replacing the prog with another. Spotted by AI while running tests for the propagation series (leaked programs were accumulating). Selftest will come in the net-next series to avoid conflicts. Fixes: 9e2ee5c7e7c3 ("net, bonding: Add XDP support to the bonding driver") Fixes: 6d5f1ef83868 ("bonding: Fix negative jump label count on nested bonding") Acked-by: Daniel Borkmann Reviewed-by: Jiayuan Chen Link: https://patch.msgid.link/20261001012639.298690-1-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/bonding/bond_main.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index de2489c3d9bf..09aa45d71fab 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -5732,12 +5732,12 @@ static int bond_xdp_set(struct net_device *dev, struct bpf_prog *prog, bpf_prog_inc(prog); } - if (prog) { + if (prog && !old_prog) static_branch_inc(&bpf_master_redirect_enabled_key); - } else if (old_prog) { - bpf_prog_put(old_prog); + else if (!prog && old_prog) static_branch_dec(&bpf_master_redirect_enabled_key); - } + if (old_prog) + bpf_prog_put(old_prog); return 0; From bcf2573a68a808a5b54cadafa72b6672e662c2bf Mon Sep 17 00:00:00 2001 From: Suraj Gupta Date: Tue, 29 Sep 2026 00:12:07 +0530 Subject: [PATCH 030/128] net: xilinx: axienet: Free outstanding DMA buffers on dmaengine stop In the dmaengine path the driver pre-submits RX buffers and holds in-flight TX buffers whose SKBs are DMA-mapped by the driver and freed only in the completion callbacks. On ndo_stop() dmaengine_terminate_sync() aborts these descriptors without running their callbacks, and the driver then frees only the ring shells, leaking every SKB still owned by the engine and its DMA mapping on each ifdown. With 128 RX buffers pre-posted per channel, the mapping leak can eventually exhaust a limited IOMMU aperture. Clear the slot's skb in the TX and RX callbacks so a non-NULL skb marks a slot that still owns a live, DMA-mapped buffer, and on stop unmap and free every such buffer. axienet_dma_rx_cb() runs from the DMA tasklet and re-arms the RX ring on each completion, so it can race axienet_stop(): a completion may submit a fresh buffer after dmaengine_terminate_sync() has returned, leaving the channel armed with a buffer the teardown then frees while the engine may still write into it (dma_release_channel() does not stop it either). Add a lock that axienet_dma_rx_cb() holds across the @stopping check and the resubmit, and axienet_stop() holds to set @stopping before terminating. Once @stopping is set no callback can arm a new buffer, and any armed just before is aborted by the terminate, so teardown only frees buffers the engine no longer owns. Fixes: 6a91b846af85 ("net: axienet: Introduce dmaengine support") Cc: stable@vger.kernel.org Signed-off-by: Suraj Gupta Link: https://patch.msgid.link/20260928184207.2361931-1-suraj.gupta2@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/xilinx/xilinx_axienet.h | 7 ++- .../net/ethernet/xilinx/xilinx_axienet_main.c | 53 +++++++++++++++---- 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet.h b/drivers/net/ethernet/xilinx/xilinx_axienet.h index fcd3aaef27fc..c63d99686d0b 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet.h +++ b/drivers/net/ethernet/xilinx/xilinx_axienet.h @@ -523,8 +523,9 @@ struct skbuf_dma_descriptor { * @stats_work: Work for reading the hardware statistics counters often enough * to catch overflows. * @dma_err_task: Work structure to process Axi DMA errors - * @stopping: Set when @dma_err_task shouldn't do anything because we are - * about to stop the device. + * @stopping: Set when we are about to stop the device: makes @dma_err_task + * a no-op (legacy DMA path) and fences RX descriptor + * resubmission in axienet_dma_rx_cb() (dmaengine path). * @tx_irq: Axidma TX IRQ number * @rx_irq: Axidma RX IRQ number * @eth_irq: Ethernet core IRQ number @@ -545,6 +546,7 @@ struct skbuf_dma_descriptor { * @tx_ring_tail: TX skb ring buffer tail index. * @rx_ring_head: RX skb ring buffer head index. * @rx_ring_tail: RX skb ring buffer tail index. + * @rx_submit_lock: Protects RX ring resubmission vs teardown in dmaengine path. */ struct axienet_local { struct net_device *ndev; @@ -626,6 +628,7 @@ struct axienet_local { int tx_ring_tail; int rx_ring_head; int rx_ring_tail; + spinlock_t rx_submit_lock; }; /** diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c index 1722b7038f34..09443623a3e2 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c @@ -881,6 +881,7 @@ static void axienet_dma_tx_cb(void *data, const struct dmaengine_result *result) u64_stats_update_end(&lp->tx_stat_sync); dma_unmap_sg(lp->dev, skbuf_dma->sgl, skbuf_dma->sg_len, DMA_TO_DEVICE); dev_consume_skb_any(skbuf_dma->skb); + skbuf_dma->skb = NULL; netif_txq_completed_wake(txq, 1, len, CIRC_SPACE(lp->tx_ring_head, lp->tx_ring_tail, TX_BD_NUM_MAX), 2); @@ -1171,6 +1172,7 @@ static void axienet_dma_rx_cb(void *data, const struct dmaengine_result *result) &meta_max_len); dma_unmap_single(lp->dev, skbuf_dma->dma_address, lp->max_frm_size, DMA_FROM_DEVICE); + skbuf_dma->skb = NULL; if (IS_ERR(app_metadata)) { if (net_ratelimit()) @@ -1193,10 +1195,17 @@ static void axienet_dma_rx_cb(void *data, const struct dmaengine_result *result) u64_stats_update_end(&lp->rx_stat_sync); rx_submit: + spin_lock(&lp->rx_submit_lock); + if (lp->stopping) { + spin_unlock(&lp->rx_submit_lock); + return; + } + for (i = 0; i < CIRC_SPACE(lp->rx_ring_head, lp->rx_ring_tail, RX_BUF_NUM_DEFAULT); i++) axienet_rx_submit_desc(lp->ndev); dma_async_issue_pending(lp->rx_chan); + spin_unlock(&lp->rx_submit_lock); } /** @@ -1541,6 +1550,7 @@ static int axienet_init_dmaengine(struct net_device *ndev) lp->tx_ring_head = 0; lp->rx_ring_tail = 0; lp->rx_ring_head = 0; + lp->stopping = false; lp->tx_skb_ring = kzalloc_objs(*lp->tx_skb_ring, TX_BD_NUM_MAX); if (!lp->tx_skb_ring) { ret = -ENOMEM; @@ -1752,20 +1762,42 @@ static int axienet_stop(struct net_device *ndev) free_irq(lp->rx_irq, ndev); axienet_dma_bd_release(ndev); } else { - dmaengine_terminate_sync(lp->tx_chan); - dmaengine_synchronize(lp->tx_chan); - dmaengine_terminate_sync(lp->rx_chan); - dmaengine_synchronize(lp->rx_chan); + struct skbuf_dma_descriptor *skbuf_dma; - for (i = 0; i < TX_BD_NUM_MAX; i++) - kfree(lp->tx_skb_ring[i]); - kfree(lp->tx_skb_ring); - for (i = 0; i < RX_BUF_NUM_DEFAULT; i++) - kfree(lp->rx_skb_ring[i]); - kfree(lp->rx_skb_ring); + spin_lock_bh(&lp->rx_submit_lock); + lp->stopping = true; + spin_unlock_bh(&lp->rx_submit_lock); + + dmaengine_terminate_sync(lp->tx_chan); + dmaengine_terminate_sync(lp->rx_chan); dma_release_channel(lp->rx_chan); dma_release_channel(lp->tx_chan); + + /* Unmap and free any buffer the terminate did not reclaim, so it + * is not leaked; a non-NULL skb marks such a slot. + */ + for (i = 0; i < TX_BD_NUM_MAX; i++) { + skbuf_dma = lp->tx_skb_ring[i]; + if (skbuf_dma && skbuf_dma->skb) { + dma_unmap_sg(lp->dev, skbuf_dma->sgl, + skbuf_dma->sg_len, DMA_TO_DEVICE); + dev_kfree_skb_any(skbuf_dma->skb); + } + kfree(skbuf_dma); + } + kfree(lp->tx_skb_ring); + + for (i = 0; i < RX_BUF_NUM_DEFAULT; i++) { + skbuf_dma = lp->rx_skb_ring[i]; + if (skbuf_dma && skbuf_dma->skb) { + dma_unmap_single(lp->dev, skbuf_dma->dma_address, + lp->max_frm_size, DMA_FROM_DEVICE); + dev_kfree_skb_any(skbuf_dma->skb); + } + kfree(skbuf_dma); + } + kfree(lp->rx_skb_ring); } netdev_reset_queue(ndev); @@ -3065,6 +3097,7 @@ static int axienet_probe(struct platform_device *pdev) spin_lock_init(&lp->rx_cr_lock); spin_lock_init(&lp->tx_cr_lock); + spin_lock_init(&lp->rx_submit_lock); INIT_WORK(&lp->rx_dim.work, axienet_rx_dim_work); lp->rx_dim_enabled = true; lp->rx_dim.profile_ix = 1; From 8b1f0af1fd5fd113432233ed8a3862ee7c30a84f Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Wed, 30 Sep 2026 19:25:24 +0900 Subject: [PATCH 031/128] tcp: reject net_iov in zerocopy receive mapping hints After copying a readable prefix, receive_fallback_to_copy() asks tcp_zerocopy_set_hint_for_skb() where page mapping can resume. If the next skb is unreadable, find_next_mappable_frag() passes its net_iov fragment to can_map_frag(). skb_frag_page() returns NULL for a net_iov, but can_map_frag() dereferences it in PageCompound(). A v7.2 KASAN run on a connected TCP socket with 64 readable bytes followed by a 4096-byte NET_IOV_DMABUF fragment reported: BUG: KASAN: null-ptr-deref in can_map_frag tcp_zerocopy_receive -> can_map_frag Kernel panic - not syncing: KASAN: panic_on_warn set The diagnostic inserted the net_iov directly because the test host has no devmem-capable NIC. Hardware end-to-end reachability remains untested and requires CONFIG_NET_DEVMEM plus a supported DMA-buf-bound RX queue. Reject all net_iov fragments before skb_frag_page(). This covers both DMABUF and IOURING net_iov types while leaving page-backed checks unchanged. With the guard, the same queue copied the readable prefix, returned a 4096-byte skip hint, and completed without a fault. Fixes: 9f6b619edf2e ("net: support non paged skb frags") Cc: stable@vger.kernel.org Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Reviewed-by: Mina Almasry Link: https://patch.msgid.link/20260930102524.1659847-1-4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv4/tcp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index 562752352afe..87ef6d5cbfeb 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -1908,6 +1908,8 @@ static bool can_map_frag(const skb_frag_t *frag) if (skb_frag_size(frag) != PAGE_SIZE || skb_frag_off(frag)) return false; + if (skb_frag_is_net_iov(frag)) + return false; page = skb_frag_page(frag); From 1402fc67d2f4e2e1fc7bf8ee0825b8ad994557ff Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Mon, 28 Sep 2026 16:04:23 -0700 Subject: [PATCH 032/128] ice: fix use-after-free in dynamic port cleanup ice_dealloc_dynamic_port() uses dyn_port->vsi->idx to erase the dynamic port from pf->dyn_ports. However, it frees the VSI before reading the index for the erase, resulting in a use-after-free. Follow the reverse of the allocation order in ice_alloc_dynamic_port() by erasing the xarray entry before freeing the VSI. Fixes: eda69d654c7e ("ice: add basic devlink subfunctions support") Cc: stable@vger.kernel.org Signed-off-by: Xuanqiang Luo Reviewed-by: Marcin Szycik Reviewed-by: Aleksandr Loktionov Tested-by: Patryk Holda Signed-off-by: Tony Nguyen Link: https://patch.msgid.link/20260928230429.495442-3-anthony.l.nguyen@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/ice/devlink/port.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/intel/ice/devlink/port.c b/drivers/net/ethernet/intel/ice/devlink/port.c index 2a2e56777f9f..3ede24649002 100644 --- a/drivers/net/ethernet/intel/ice/devlink/port.c +++ b/drivers/net/ethernet/intel/ice/devlink/port.c @@ -590,8 +590,8 @@ static void ice_dealloc_dynamic_port(struct ice_dynamic_port *dyn_port) xa_erase(&pf->sf_nums, devlink_port->attrs.pci_sf.sf); ice_eswitch_detach_sf(pf, dyn_port); - ice_vsi_free(dyn_port->vsi); xa_erase(&pf->dyn_ports, dyn_port->vsi->idx); + ice_vsi_free(dyn_port->vsi); kfree(dyn_port); } From 7a579dec8423bba7bec96fcfcb2e30838b0858ee Mon Sep 17 00:00:00 2001 From: Bryan Fraschetti Date: Mon, 28 Sep 2026 16:04:24 -0700 Subject: [PATCH 033/128] ice: Restore Ordered MMIO Writes for Tx Doorbells The DQL accounting state which tracks the number of bytes queued for the NIC to transmit, namely dql->num_queued, is updated by the ICE driver when it invokes __netdev_tx_sent_queue(). Subsequently the driver updates the hardware queue tail allowing the NIC to begin processing the work queue. The queue accounting update should be ordered before the NIC begins transmitting descriptors. The transmit path currently updates the hardware doorbell using writel_relaxed(), which (on arm64) does not provide the same ordering guarantees between writes to normal memory and writes to MMIO registers that writel() does. This introduces a potential race where the NIC begins transmitting descriptors before the dql->num_queued update is globally visible. If the NIC finishes before the update is observed by dql_completed(), it detects an invalid state where more bytes have been completed than have been queued. When this happens the following BUG_ON is triggered. BUG_ON(count > num_queued - dql->num_completed); This has been observed and manifests as the following crash (note that the trace has been trimmed) in an environment with sustained network load that uses an Intel Corporation Ethernet Controller E810-XXV for SFP (rev 02) on an arm64 machine. Replacing writel_relaxed() with writel() in a test kernel eliminated the crash in the user's workload, which previously reproduced the issue reliably. kernel BUG at lib/dynamic_queue_limits.c:99 Internal error: Oops - BUG: 00000000f2000800 [#1] SMP pc : dql_completed+0x268/0x2a0 lr : ice_clean_tx_irq+0x1d4/0x620 [ice] Call trace: dql_completed+0x268/0x2a0 (P) ice_napi_poll+0x94/0x520 [ice] __napi_poll+0x48/0x3f0 net_rx_action+0x194/0x420 This restores the behaviour prior to commit ccde82e90946 ("ice: add E830 Earliest TxTime First Offload support"), which changed the notification mechanism from writel() to writel_relaxed(). Link: https://bugs.launchpad.net/ubuntu/+source/linux/+bug/2161572 Fixes: ccde82e90946 ("ice: add E830 Earliest TxTime First Offload support") Signed-off-by: Bryan Fraschetti Tested-by: Alexander Nowlin Signed-off-by: Tony Nguyen Link: https://patch.msgid.link/20260928230429.495442-4-anthony.l.nguyen@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/ice/ice_txrx.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice_txrx.c b/drivers/net/ethernet/intel/ice/ice_txrx.c index 31303ab5be17..a2c7c4962882 100644 --- a/drivers/net/ethernet/intel/ice/ice_txrx.c +++ b/drivers/net/ethernet/intel/ice/ice_txrx.c @@ -1561,10 +1561,10 @@ ice_tx_map(struct ice_tx_ring *tx_ring, struct ice_tx_buf *first, } } tstamp_ring->next_to_use = j; - writel_relaxed(j, tstamp_ring->tail); + writel(j, tstamp_ring->tail); } else { ring_kick: - writel_relaxed(i, tx_ring->tail); + writel(i, tx_ring->tail); } return; From 58858b8f14803a7eeac297b3ce20f3b21bf2ff66 Mon Sep 17 00:00:00 2001 From: Tristan Madani Date: Mon, 28 Sep 2026 16:04:25 -0700 Subject: [PATCH 034/128] ice: fix metadata_dst refcount handling on representor teardown ice_eswitch_release_repr() uses metadata_dst_free() to release the representor's metadata_dst. metadata_dst_free() directly frees the underlying memory without checking the dst_entry refcount. When ice_eswitch_port_start_xmit() processes a packet, it takes a reference via dst_hold() and attaches the metadata_dst to the skb. If the representor is torn down while packets are still queued on the lower device (e.g. in a qdisc), the metadata_dst is freed while references are still held. Use dst_release() instead, which correctly decrements the refcount and only frees the object when all references are dropped. The dst subsystem already handles metadata_dst cleanup in dst_destroy() when DST_METADATA is set. Other drivers sharing this pattern (nfp, airoha, bnxt) already use dst_release() for their metadata_dst lifecycle. Fixes: f5396b8a663f7 ("ice: switchdev slow path") Cc: stable@vger.kernel.org Signed-off-by: Tristan Madani Reviewed-by: Simon Horman Signed-off-by: Tony Nguyen Link: https://patch.msgid.link/20260928230429.495442-5-anthony.l.nguyen@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/ice/ice_eswitch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/intel/ice/ice_eswitch.c b/drivers/net/ethernet/intel/ice/ice_eswitch.c index b069e6c514fb..6e7bba473898 100644 --- a/drivers/net/ethernet/intel/ice/ice_eswitch.c +++ b/drivers/net/ethernet/intel/ice/ice_eswitch.c @@ -95,7 +95,7 @@ ice_eswitch_release_repr(struct ice_pf *pf, struct ice_repr *repr) return; ice_vsi_update_security(vsi, ice_vsi_ctx_set_antispoof); - metadata_dst_free(repr->dst); + dst_release(&repr->dst->dst); repr->dst = NULL; ice_fltr_add_mac_and_broadcast(vsi, repr->parent_mac, ICE_FWD_TO_VSI); From 5158353121bae4f552cb677ce8c5c3ef52f93a0d Mon Sep 17 00:00:00 2001 From: Zixuan Chai Date: Sun, 27 Sep 2026 01:04:39 +0800 Subject: [PATCH 035/128] net: bridge: avoid recursive multicast port cleanup br_multicast_sg_del_exclude_ports() removes automatically added STAR_EXCL port groups by calling br_multicast_del_pg(). For S,G entries, that deletion calls back into br_multicast_sg_del_exclude_ports(), so a large number of STAR_EXCL ports consumes one kernel stack frame per port and can hit the stack guard page. Keep the complete port-group deletion path for cleanup, but suppress the S,G exclude-port cleanup while that helper is already walking the same entry. Normal deletion callers retain the existing behavior. Fixes: 8266a0491e92 ("net: bridge: mcast: handle port group filter modes") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: LLM Signed-off-by: Zixuan Chai Signed-off-by: Ren Wei Signed-off-by: David S. Miller --- net/bridge/br_multicast.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c index 2f9bb30e1a1f..3fbad7b59769 100644 --- a/net/bridge/br_multicast.c +++ b/net/bridge/br_multicast.c @@ -81,6 +81,10 @@ __br_multicast_add_group(struct net_bridge_mcast *brmctx, bool blocked); static void br_multicast_find_del_pg(struct net_bridge *br, struct net_bridge_port_group *pg); +static void __br_multicast_del_pg(struct net_bridge_mdb_entry *mp, + struct net_bridge_port_group *pg, + struct net_bridge_port_group __rcu **pp, + bool sg_del_exclude_ports); static void __br_multicast_stop(struct net_bridge_mcast *brmctx); static int br_mc_disabled_update(struct net_device *dev, bool value, @@ -458,7 +462,7 @@ static void br_multicast_sg_del_exclude_ports(struct net_bridge_mdb_entry *sgmp) for (pp = &sgmp->ports; (p = mlock_dereference(*pp, sgmp->br)) != NULL;) { if (!(p->flags & MDB_PG_FLAGS_PERMANENT)) - br_multicast_del_pg(sgmp, p, pp); + __br_multicast_del_pg(sgmp, p, pp, false); else pp = &p->next; } @@ -799,9 +803,10 @@ static void br_multicast_destroy_port_group(struct net_bridge_mcast_gc *gc) kfree_rcu(pg, rcu); } -void br_multicast_del_pg(struct net_bridge_mdb_entry *mp, - struct net_bridge_port_group *pg, - struct net_bridge_port_group __rcu **pp) +static void __br_multicast_del_pg(struct net_bridge_mdb_entry *mp, + struct net_bridge_port_group *pg, + struct net_bridge_port_group __rcu **pp, + bool sg_del_exclude_ports) { struct net_bridge *br = pg->key.port->br; struct net_bridge_group_src *ent; @@ -820,7 +825,8 @@ void br_multicast_del_pg(struct net_bridge_mdb_entry *mp, if (!br_multicast_is_star_g(&mp->addr)) { rhashtable_remove_fast(&br->sg_port_tbl, &pg->rhnode, br_sg_port_rht_params); - br_multicast_sg_del_exclude_ports(mp); + if (sg_del_exclude_ports) + br_multicast_sg_del_exclude_ports(mp); } else { br_multicast_star_g_handle_mode(pg, MCAST_INCLUDE); } @@ -832,6 +838,13 @@ void br_multicast_del_pg(struct net_bridge_mdb_entry *mp, mod_timer(&mp->timer, jiffies); } +void br_multicast_del_pg(struct net_bridge_mdb_entry *mp, + struct net_bridge_port_group *pg, + struct net_bridge_port_group __rcu **pp) +{ + __br_multicast_del_pg(mp, pg, pp, true); +} + static void br_multicast_find_del_pg(struct net_bridge *br, struct net_bridge_port_group *pg) { From aaaaf87ea99b8766c9a8aa0e71aa42e6bc8a5320 Mon Sep 17 00:00:00 2001 From: "Denis V. Lunev" Date: Tue, 29 Sep 2026 09:25:19 +0200 Subject: [PATCH 036/128] openvswitch: fix soft lockup in the netlink flow dump A production compute node carrying a few thousand datapath flows hit a soft lockup inside a single netlink flow dump and panicked. ovs_flow_cmd_dump() calls ovs_flow_stats_get() for every flow it emits, and that releases stats->lock with spin_unlock_bh() once per CPU that has touched the flow. Every release is a local_bh_enable(), and each one runs the pending softirq backlog in the dumping thread's own context. The skb bounds the flows one callback emits, but not the softirq work it absorbs. On a CPU that carries the box's packet load the backlog refills as fast as it drains, so the dumping thread becomes that CPU's softirq engine. It never sleeps and it has no reschedule point, so under voluntary preemption nothing can take the CPU away from it: neither the ksoftirqd the kernel woke to take the work over, nor the stopper thread the softlockup detector dispatches to refresh its timestamp. Hold BH off across the whole callback instead, the way ctnetlink_dump_table() does, so the nested spin_unlock_bh() stop draining softirqs. The loop already runs under rcu_read_lock() and cannot sleep. What it gives up is preemption under CONFIG_PREEMPT, since a BH-off region is not preemptible outside PREEMPT_RT. The region stays short: the skb caps the flows one callback emits, and empty buckets cost no skb space but are each visited once per dump, as the cursor only moves forward. The table grows on insert and shrinks only on flush, so the walk is bounded by the largest flow count the datapath has held. The softirq backlog the callback used to absorb has no bound at all. Fixes: 63e7959c4b9b ("openvswitch: Per NUMA node flow stats.") Cc: stable@vger.kernel.org Signed-off-by: Denis V. Lunev Reviewed-by: Ilya Maximets Signed-off-by: David S. Miller --- net/openvswitch/datapath.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c index 631a03136fa1..4fc5d0bebd85 100644 --- a/net/openvswitch/datapath.c +++ b/net/openvswitch/datapath.c @@ -1532,6 +1532,12 @@ static int ovs_flow_cmd_dump(struct sk_buff *skb, struct netlink_callback *cb) return -ENODEV; } + /* + * Not needed for safety. Stops every spin_unlock_bh() in + * ovs_flow_stats_get() from running the softirq backlog. + */ + local_bh_disable(); + ti = rcu_dereference(dp->table.ti); for (;;) { struct sw_flow *flow; @@ -1552,6 +1558,8 @@ static int ovs_flow_cmd_dump(struct sk_buff *skb, struct netlink_callback *cb) cb->args[0] = bucket; cb->args[1] = obj; } + + local_bh_enable(); rcu_read_unlock(); return skb->len; } From 381a0925c556a8d91a80a9b9e3e86651d60ffee3 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Mon, 5 Oct 2026 00:24:42 +0800 Subject: [PATCH 037/128] Bluetooth: hci_sock: Serialize dead-device detachment Rebinding an HCI socket after its controller is unregistered can race monitor control replay and crash the kernel. hci_sock_bind() clears hci_pi(sk)->hdev and drops the device reference under only the socket lock, whereas send_monitor_control_replay() holds hci_sk_list.lock for reading. Replay can observe a non-NULL hdev, then bind can clear it and drop the last reference before create_monitor_ctrl_open() reads hdev->id. This can cause a NULL pointer dereference if the pointer is reloaded, or a use-after-free if the old pointer is retained. The kernel reported: KASAN: null-ptr-deref in range [0x0000000000000068-0x000000000000006f] RIP: 0010:create_monitor_ctrl_open+0x579/0x800 Call Trace: hci_sock_bind+0xd96/0x1190 __sys_bind+0x166/0x200 __x64_sys_bind+0x6d/0xb0 do_syscall_64+0xdd/0x4a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f The same missing serialization lets hci_send_to_sock() select a socket for the old device and queue its frame after bind has detached the socket or installed a new binding. Take hci_sk_list.lock for writing when clearing the device pointer and resetting the socket state. This waits for existing replay and delivery readers to finish before detachment and prevents later readers from using the old binding. Keep hci_dev_put() outside the critical section because the final device release can sleep. Fixes: e04480920d1e ("Bluetooth: defer cleanup of resources in hci_unregister_dev()") Cc: stable@vger.kernel.org Assisted-by: GPT-6 Astra Signed-off-by: Chengfeng Ye Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sock.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/bluetooth/hci_sock.c b/net/bluetooth/hci_sock.c index 6d56c77741e1..81068b585764 100644 --- a/net/bluetooth/hci_sock.c +++ b/net/bluetooth/hci_sock.c @@ -1223,8 +1223,10 @@ static int hci_sock_bind(struct socket *sock, struct sockaddr_unsized *addr, */ hdev = hci_pi(sk)->hdev; if (hdev && hci_dev_test_flag(hdev, HCI_UNREGISTER)) { + write_lock(&hci_sk_list.lock); hci_pi(sk)->hdev = NULL; sk->sk_state = BT_OPEN; + write_unlock(&hci_sk_list.lock); hci_dev_put(hdev); } hdev = NULL; From 9af12272061d206af8bc3e40215cb694421ab73a Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Mon, 5 Oct 2026 00:26:12 +0800 Subject: [PATCH 038/128] Bluetooth: hci_sync: Fix command skb lifetime during scan setup During PA sync scan setup, hci_le_set_ext_scan_param_sync() gets the address used for the PA_LINK lookup from hci_sent_cmd_data(). The returned pointer borrows storage from sent_cmd or req_skb, without holding a reference to either skb. The scan worker runs on req_workqueue while hci_cmd_work() runs on the separate device workqueue. After the scan worker loads sent_cmd, the command worker can free and replace it before hci_sent_cmd_data() dereferences the skb. The command payload can also be freed between returning from the helper and comparing the address. The connection lookup's RCU critical section does not protect the command skb. KASAN reported: BUG: KASAN: slab-use-after-free in hci_sent_cmd_data+0x27c/0x2f0 Workqueue: hci0 hci_cmd_sync_work Call Trace: hci_sent_cmd_data+0x27c/0x2f0 hci_le_set_scan_param_sync+0x43a/0x850 hci_passive_scan_sync+0xb09/0x1460 hci_update_passive_scan_sync+0x47c/0x6e0 hci_le_pa_create_sync+0x200/0xa70 hci_cmd_sync_work+0x13c/0x290 Allocated by task 95: skb_clone+0x13f/0x340 hci_cmd_work+0x2a9/0x7e0 Freed by task 95: kmem_cache_free+0xba/0x3a0 hci_cmd_work+0x29c/0x7e0 Use hdev->lock to serialize the address snapshot with sent_cmd replacement. Protect req_skb replacement and completion cleanup with the same lock because the helper falls back to that skb. Copy the address before releasing the lock and use the copy for the existing RCU-protected connection lookup. Release the mutex before sending or waiting for commands, preserving the lookup and completion ordering. Fixes: 22cbf4f84c00 ("Bluetooth: hci_sync: Use QoS to determine which PHY to scan") Cc: stable@vger.kernel.org Assisted-by: GPT-6 Astra Signed-off-by: Chengfeng Ye Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_core.c | 4 ++++ net/bluetooth/hci_sync.c | 10 +++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 2076689eb302..74d9865e08c2 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -4115,9 +4115,11 @@ static int hci_send_cmd_sync(struct hci_dev *hdev, struct sk_buff *skb) bt_dev_dbg(hdev, "skb %p", skb); + hci_dev_lock(hdev); kfree_skb(hdev->sent_cmd); hdev->sent_cmd = skb_clone(skb, GFP_KERNEL); + hci_dev_unlock(hdev); if (!hdev->sent_cmd) { skb_queue_head(&hdev->cmd_q, skb); queue_work(hdev->workqueue, &hdev->cmd_work); @@ -4138,8 +4140,10 @@ static int hci_send_cmd_sync(struct hci_dev *hdev, struct sk_buff *skb) if (READ_ONCE(hdev->req_status) == HCI_REQ_PEND && !hci_dev_test_and_set_flag(hdev, HCI_CMD_PENDING)) { + hci_dev_lock(hdev); kfree_skb(hdev->req_skb); hdev->req_skb = skb_get(hdev->sent_cmd); + hci_dev_unlock(hdev); } return err; diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index c01c8b58d9e8..a92a81846e9d 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -32,8 +32,10 @@ static void hci_cmd_sync_complete(struct hci_dev *hdev, u8 result, u16 opcode, WRITE_ONCE(hdev->req_status, HCI_REQ_DONE); /* Free the request command so it is not used as response */ + hci_dev_lock(hdev); kfree_skb(hdev->req_skb); hdev->req_skb = NULL; + hci_dev_unlock(hdev); if (skb) { struct sock *sk = hci_skb_sk(skb); @@ -3064,15 +3066,21 @@ static int hci_le_set_ext_scan_param_sync(struct hci_dev *hdev, u8 type, */ if (hci_dev_test_flag(hdev, HCI_PA_SYNC)) { struct hci_cp_le_add_to_accept_list *sent; + bdaddr_t bdaddr; + hci_dev_lock(hdev); sent = hci_sent_cmd_data(hdev, HCI_OP_LE_ADD_TO_ACCEPT_LIST); + if (sent) + bacpy(&bdaddr, &sent->bdaddr); + hci_dev_unlock(hdev); + if (sent) { struct hci_conn *conn; rcu_read_lock(); conn = hci_conn_hash_lookup_ba(hdev, PA_LINK, - &sent->bdaddr); + &bdaddr); if (conn) { struct bt_iso_qos *qos = &conn->iso_qos; From 555ff78fa719f50ff4cdbdb40aa1acc6e8e327d3 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Mon, 5 Oct 2026 00:25:48 +0800 Subject: [PATCH 039/128] Bluetooth: ISO: Reject concurrent BIS listener setup iso_sock_listen() drops the socket lock before calling iso_listen_bis() to preserve the hdev->lock ordering. Two concurrent listen() calls on the same broadcast socket can therefore both observe BT_BOUND. Caller A can attach connA and release the locks, then caller B can acquire them and attach connB before A publishes BT_LISTEN. __iso_chan_add() checks whether connB already has a socket, but not whether the socket already has a connection. The second attachment overwrites iso_pi(sk)->conn, leaking the connA reference and leaving connA->sk intact. Socket teardown follows connB, so later events on connA can cause a stale socket access. Recheck BT_BOUND and require iso_pi(sk)->conn to be NULL after taking both the HCI device and socket locks in iso_listen_bis(). The connection check also covers the interval between attachment and publication of BT_LISTEN. Reject a competing setup with -EBADFD through the existing unlock path before allocating another connection. A recorded KASAN report from an instrumented kernel shows: BUG: KASAN: slab-use-after-free in iso_sock_hold+0x119/0x1f0 Call Trace: iso_sock_hold+0x119/0x1f0 iso_conn_del+0xe4/0x270 hci_disconn_complete_evt+0x351/0x8c0 hci_event_packet+0x71b/0xb20 hci_rx_work+0x293/0x730 Allocated by task 93: sk_prot_alloc+0x113/0x220 sk_alloc+0x2b/0x6d0 bt_sock_alloc+0x29/0x370 iso_sock_alloc.constprop.0+0x19/0x300 iso_sock_create+0x94/0x100 Freed by task 93: kfree+0x121/0x3c0 __sk_destruct+0x42b/0x540 iso_sock_release+0x29d/0x340 __sock_release+0xa1/0x260 sock_close+0x10/0x20 Fixes: 168e28305b87 ("Bluetooth: iso: Fix circular lock in iso_listen_bis") Cc: stable@vger.kernel.org Assisted-by: GPT-6 Astra Signed-off-by: Chengfeng Ye Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/iso.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index 7657c2a0abbf..2e70c0df9ef3 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -1340,6 +1340,11 @@ static int iso_listen_bis(struct sock *sk) hci_dev_lock(hdev); lock_sock(sk); + if (sk->sk_state != BT_BOUND || iso_pi(sk)->conn) { + err = -EBADFD; + goto unlock; + } + /* Fail if user set invalid QoS */ if (iso_pi(sk)->qos_user_set && !check_bcast_qos(&iso_pi(sk)->qos)) { iso_pi(sk)->qos = default_qos; From e32d80293a0e1a160d5e5101daa8c82148c07510 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Mon, 5 Oct 2026 15:50:31 +0800 Subject: [PATCH 040/128] Bluetooth: ISO: Serialize concurrent connect calls iso_sock_connect() checks the socket state before taking the socket lock and drops the lock again before setting up a connection. Two callers can both pass the admission check while the socket is open or bound. Caller A can copy its destination for route selection, then caller B can replace the socket destination before A binds or connects the CIS. A then uses B's destination with the route selected for its own request. B can also proceed after A attaches a connection and sets BT_CONNECT. For deferred BIS setup, B can bind a second BIS and overwrite iso_pi(sk)->conn, leaving the first connection's reference and conn->sk back-pointer stranded. Concurrent CIS connects can likewise replace the socket's connection. Later teardown only detaches the current connection, so a callback on the old connection can race with socket release and access a freed socket. KASAN reported: BUG: KASAN: slab-use-after-free in iso_sock_hold+0xf7/0x1b0 Call Trace: iso_sock_hold+0xf7/0x1b0 iso_conn_del+0x7b/0x1d0 iso_connect_cfm+0x186/0x16a0 hci_conn_failed+0x154/0x280 hci_abort_conn_sync+0x3dc/0x7d0 hci_cmd_sync_work+0x173/0x300 Allocated by task 121: sk_alloc+0x2b/0x6d0 bt_sock_alloc+0x29/0x370 iso_sock_alloc.constprop.0+0x19/0x300 iso_sock_create+0x94/0x100 Freed by task 121: kfree+0x121/0x3c0 __sk_destruct+0x42b/0x540 iso_sock_release+0x29d/0x340 __sock_release+0xa1/0x260 Check admission under the socket lock and mark the connect operation in progress before publishing its destination. Keep that flag set across route lookup and connection setup, rejecting another connect with -EBADFD before it can change the destination or attach a connection. Clear the flag after either helper returns, including on failure, so a failed setup can be retried. A separate flag is needed because the socket lock must be released before acquiring the HCI device lock, while BT_CONNECT requires an attached connection. Keep the existing state transitions and the deferred CIS completion through iso_sock_recvmsg(). Reject an existing socket attachment in __iso_chan_add() before taking a new reference or publishing either pointer, so every caller preserves the connection association. Keep the same-socket, same-connection success case for deferred CIS setup. Reject an attached socket before BIS setup so a reusable BIS is not claimed before the attachment is rejected. In CIS setup, reject a different HCI connection before iso_conn_add() and drop the per-attempt HCI hold directly. An unowned iso_conn may still carry another reference after detachment, so its destruction cannot be relied on to release that hold. iso_listen_bis() creates a fresh PA HCI/ISO pair under the device lock, so the temporary iso_conn_put() frees the ISO candidate and drops its HCI hold on rejection. iso_conn_ready() uses a freshly allocated, unpublished child with no connection, making the socket-side rejection unreachable. Fixes: ccf74f2390d6 ("Bluetooth: Add BTPROTO_ISO socket type") Cc: stable@vger.kernel.org Suggested-by: Pauli Virtanen Assisted-by: GPT-6 Astra Signed-off-by: Chengfeng Ye Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/iso.c | 47 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index 2e70c0df9ef3..d6ac5b1f49bc 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -61,6 +61,7 @@ enum { BT_SK_BIG_SYNC, BT_SK_PA_SYNC, BT_SK_KILLED, + BT_SK_CONNECTING, }; struct iso_pinfo { @@ -350,6 +351,9 @@ static int __iso_chan_add(struct iso_conn *conn, struct sock *sk, return -EBUSY; } + if (iso_pi(sk)->conn) + return -EISCONN; + if (!conn->hcon) { BT_ERR("conn->hcon missing"); return -EIO; @@ -410,6 +414,11 @@ static int iso_connect_bis(struct sock *sk) hci_dev_lock(hdev); lock_sock(sk); + if (iso_pi(sk)->conn) { + err = -EISCONN; + goto unlock; + } + if (!bis_capable(hdev)) { err = -EOPNOTSUPP; goto unlock; @@ -562,6 +571,13 @@ static int iso_connect_cis(struct sock *sk) lockdep_assert_held(&hcon->hdev->lock); + /* The socket lock keeps the current attachment and its hcon stable. */ + if (iso_pi(sk)->conn && iso_pi(sk)->conn->hcon != hcon) { + hci_conn_drop(hcon); + err = -EISCONN; + goto unlock; + } + conn = iso_conn_add(hcon); if (!conn) { hci_conn_drop(hcon); @@ -1269,17 +1285,26 @@ static int iso_sock_connect(struct socket *sock, struct sockaddr_unsized *addr, addr->sa_family != AF_BLUETOOTH) return -EINVAL; - if (sk->sk_state != BT_OPEN && sk->sk_state != BT_BOUND) - return -EBADFD; + lock_sock(sk); - if (sk->sk_type != SOCK_SEQPACKET) - return -EINVAL; + if ((sk->sk_state != BT_OPEN && sk->sk_state != BT_BOUND) || + test_bit(BT_SK_CONNECTING, &iso_pi(sk)->flags)) { + err = -EBADFD; + goto done; + } + + if (sk->sk_type != SOCK_SEQPACKET) { + err = -EINVAL; + goto done; + } /* Check if the address type is of LE type */ - if (!bdaddr_type_is_le(sa->iso_bdaddr_type)) - return -EINVAL; + if (!bdaddr_type_is_le(sa->iso_bdaddr_type)) { + err = -EINVAL; + goto done; + } - lock_sock(sk); + set_bit(BT_SK_CONNECTING, &iso_pi(sk)->flags); bacpy(&iso_pi(sk)->dst, &sa->iso_bdaddr); iso_pi(sk)->dst_type = sa->iso_bdaddr_type; @@ -1291,16 +1316,18 @@ static int iso_sock_connect(struct socket *sock, struct sockaddr_unsized *addr, else err = iso_connect_bis(sk); - if (err) - return err; - lock_sock(sk); + clear_bit(BT_SK_CONNECTING, &iso_pi(sk)->flags); + if (err) + goto done; + if (!test_bit(BT_SK_DEFER_SETUP, &bt_sk(sk)->flags)) { err = bt_sock_wait_state(sk, BT_CONNECTED, sock_sndtimeo(sk, flags & O_NONBLOCK)); } +done: release_sock(sk); return err; } From d333c8bca6dc3c5e978f6698d39dd799f3b0dc8d Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Wed, 30 Sep 2026 18:25:59 -0700 Subject: [PATCH 041/128] net: make dev_xdp_sb_prog_count() see programs attached through a link ethtool refuses to enable tcp-data-split on a device running a single-buffer XDP program (which can't deal with the payload landing in a separate buffer). dev_xdp_sb_prog_count() only looks at xdp_state[].prog though, ignoring bpf_link integration. Get the prog pointer from dev_xdp_prog(), which will consult both. bpf_xdp_link_update() needs a bit of a touch-up, too, since ethtool calls dev_xdp_sb_prog_count() with just the instance lock (for ops-locked devices) - we now have to make sure the link updates happen under that lock. Spotted by AI while reviewing the XDP propagation series, verified and tested on netdevsim with a C test along these lines: link = bpf_program__attach_xdp(prog, ifindex); system("ethtool -G eth0 tcp-data-split on") Fixes: 197258f0ef68 ("net: ethtool: add hds_config member in ethtool_netdev_state") Acked-by: Stanislav Fomichev Link: https://patch.msgid.link/20261001012559.297751-1-kuba@kernel.org Signed-off-by: Jakub Kicinski --- net/core/dev.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/net/core/dev.c b/net/core/dev.c index 18dc88990510..e76762e29360 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -10364,13 +10364,15 @@ EXPORT_SYMBOL_GPL(dev_xdp_prog_count); u8 dev_xdp_sb_prog_count(struct net_device *dev) { + struct bpf_prog *prog; u8 count = 0; int i; - for (i = 0; i < __MAX_XDP_MODE; i++) - if (dev->xdp_state[i].prog && - !dev->xdp_state[i].prog->aux->xdp_has_frags) + for (i = 0; i < __MAX_XDP_MODE; i++) { + prog = dev_xdp_prog(dev, i); + if (prog && !prog->aux->xdp_has_frags) count++; + } return count; } @@ -10750,11 +10752,12 @@ static int bpf_xdp_link_update(struct bpf_link *link, struct bpf_prog *new_prog, bpf_op = dev_xdp_bpf_op(xdp_link->dev, mode); err = dev_xdp_install(xdp_link->dev, mode, bpf_op, NULL, xdp_link->flags, new_prog); + if (!err) + old_prog = xchg(&link->prog, new_prog); netdev_unlock_ops(xdp_link->dev); if (err) goto out_unlock; - old_prog = xchg(&link->prog, new_prog); bpf_prog_put(old_prog); out_unlock: From fe4114221364899ab33c3f9574da6b108d3fa79e Mon Sep 17 00:00:00 2001 From: Or Har-Toov Date: Wed, 30 Sep 2026 20:04:06 +0300 Subject: [PATCH 042/128] net/mlx5: Lag, split aggregate speed into oper and max helpers mlx5_lag_sum_devices_speed computes the LAG aggregate by summing oper speeds across all ports. This has two bugs. First, it relies on the assumption that a port whose carrier is down will report an oper speed of zero and therefore not contribute to the sum. This assumption does not always hold: when the link partner disconnects the port transitions to DOWN state but firmware may still report a non-zero oper speed, causing the aggregate to include a port that is not actively carrying traffic. Second, in active-backup mode only one port transmits at a time, so the aggregate should reflect a single port speed rather than the sum of all ports. Fix this by splitting mlx5_lag_sum_devices_speed into two helpers. mlx5_lag_get_devices_oper_speed reflects the speed currently available: it queries the vport state of each port and skips any port that is not UP, rather than relying on oper speed being zero. mlx5_lag_get_devices_max_speed is state-independent and returns the maximum achievable speed used as a fallback when speed is 0; for active-backup it takes the maximum single-port speed instead of the sum. The bugs were discovered during integration and reproduced on a back-to-back ConnectX-8 setup. The first bug (stale oper speed when link goes down) was observed: after the link partner disconnected, the aggregate TX speed reported to vports remained full speed because firmware still returned the last oper speed, causing vports to be rate-limited to the old aggregate speed. The second bug (active-backup summing all port speeds instead of the active port's speed) was also triggered: in active-backup mode the computed aggregate was double the expected value since both port speeds were summed even though only one port was active. Fixes: 28ea6036dad2 ("net/mlx5: Handle port and vport speed change events in MPESW") Signed-off-by: Or Har-Toov Reviewed-by: Shay Drori Reviewed-by: Mark Bloch Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260930170406.148548-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/mellanox/mlx5/core/lag/lag.c | 79 +++++++++++++------ 1 file changed, 57 insertions(+), 22 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c b/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c index 3b34bec559e0..4e173b08cb37 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c @@ -1432,30 +1432,40 @@ static bool mlx5_lag_should_disable_lag(struct mlx5_lag *ldev, bool do_bond) } #ifdef CONFIG_MLX5_ESWITCH -static int -mlx5_lag_sum_devices_speed(struct mlx5_lag *ldev, u32 *sum_speed, - int (*get_speed)(struct mlx5_core_dev *, u32 *)) +static int mlx5_lag_get_devices_oper_speed(struct mlx5_lag *ldev, + u32 *sum_speed) { - struct mlx5_core_dev *pf_mdev; - struct lag_func *pf; int pf_idx; - u32 speed; - int ret; *sum_speed = 0; mlx5_ldev_for_each(pf_idx, 0, ldev) { + u8 opmod = MLX5_VPORT_STATE_OP_MOD_VNIC_VPORT; + struct mlx5_core_dev *pf_mdev; + struct lag_func *pf; + u32 speed; + u8 state; + int ret; + pf = mlx5_lag_pf(ldev, pf_idx); if (!pf) continue; pf_mdev = pf->dev; if (!pf_mdev) continue; + ret = mlx5_query_vport_max_tx_speed(pf_mdev, opmod, 0, 0, + &speed, &state); + if (ret) { + mlx5_core_dbg(pf_mdev, "State query failed (err=%d)\n", + ret); + return ret; + } + if (state != VPORT_STATE_UP) + continue; - ret = get_speed(pf_mdev, &speed); + ret = mlx5_port_oper_linkspeed(pf_mdev, &speed); if (ret) { mlx5_core_dbg(pf_mdev, - "Failed to get device speed using %ps. Device %s speed is not available (err=%d)\n", - get_speed, dev_name(pf_mdev->device), + "Failed to get oper speed (err=%d)\n", ret); return ret; } @@ -1466,17 +1476,42 @@ mlx5_lag_sum_devices_speed(struct mlx5_lag *ldev, u32 *sum_speed, return 0; } -static int mlx5_lag_sum_devices_max_speed(struct mlx5_lag *ldev, u32 *max_speed) +static int mlx5_lag_get_devices_max_speed(struct mlx5_lag *ldev, u32 *max_speed) { - return mlx5_lag_sum_devices_speed(ldev, max_speed, - mlx5_port_max_linkspeed); -} + bool take_max; + int pf_idx; -static int mlx5_lag_sum_devices_oper_speed(struct mlx5_lag *ldev, - u32 *oper_speed) -{ - return mlx5_lag_sum_devices_speed(ldev, oper_speed, - mlx5_port_oper_linkspeed); + take_max = ldev->tracker.tx_type == NETDEV_LAG_TX_TYPE_ACTIVEBACKUP; + if (ldev->mode == MLX5_LAG_MODE_MPESW) + take_max = false; + + *max_speed = 0; + mlx5_ldev_for_each(pf_idx, 0, ldev) { + struct mlx5_core_dev *pf_mdev; + struct lag_func *pf; + u32 speed; + int ret; + + pf = mlx5_lag_pf(ldev, pf_idx); + if (!pf) + continue; + pf_mdev = pf->dev; + if (!pf_mdev) + continue; + + ret = mlx5_port_max_linkspeed(pf_mdev, &speed); + if (ret) { + mlx5_core_dbg(pf_mdev, + "Failed to get max speed (err=%d)\n", + ret); + return ret; + } + + *max_speed = take_max ? + max(*max_speed, speed) : *max_speed + speed; + } + + return 0; } static void mlx5_lag_modify_device_vports_speed(struct mlx5_core_dev *mdev, @@ -1525,7 +1560,7 @@ void mlx5_lag_set_vports_agg_speed(struct mlx5_lag *ldev) int pf_idx; if (ldev->mode == MLX5_LAG_MODE_MPESW) { - if (mlx5_lag_sum_devices_oper_speed(ldev, &speed)) + if (mlx5_lag_get_devices_oper_speed(ldev, &speed)) return; } else { speed = ldev->tracker.bond_speed_mbps; @@ -1533,8 +1568,8 @@ void mlx5_lag_set_vports_agg_speed(struct mlx5_lag *ldev) return; } - /* If speed is not set, use the sum of max speeds of all PFs */ - if (!speed && mlx5_lag_sum_devices_max_speed(ldev, &speed)) + /* If speed is not set, fall back to the max achievable speed */ + if (!speed && mlx5_lag_get_devices_max_speed(ldev, &speed)) return; speed = speed / MLX5_MAX_TX_SPEED_UNIT; From e3f33b0a1d89d738e2b913dab519226eaf76e15b Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 1 Oct 2026 22:12:53 +0000 Subject: [PATCH 043/128] ipv4: free inet_opt and ireq_opt after an RCU grace period tcp_v4_syn_recv_sock() transfers ownership of ireq->ireq_opt to the child socket (newinet->inet_opt) without copying it. Another cpu can concurrently process a retransmitted SYN for the same request socket, and send a SYNACK from tcp_check_req(). tcp_v4_send_synack() and inet_csk_route_req() read ireq->ireq_opt under rcu_read_lock() only, and ip_build_and_send_pkt() and ip_options_build() then read opt->optlen twice. Note that the SYNACK timer itself is not an issue: it holds a reference on its request socket, and inet_csk_reqsk_queue_drop() calls timer_delete_sync() before the child can be freed. Since commit 079096f103fa ("tcp/dccp: install syn_recv requests into ehash table"), request sockets are processed without holding the listener lock, so nothing prevents the child socket from being freed while the SYNACK is still being built. TCP child sockets do not have SOCK_RCU_FREE, and inet_sock_destruct() frees inet_opt with a plain kfree(), leading to a use-after-free in ip_options_build(). A similar issue exists with request socket migration (net.ipv4.tcp_migrate_req=1, or a BPF_SK_REUSEPORT_SELECT_OR_MIGRATE program). reqsk_timer_handler() clones the request socket with inet_reqsk_clone(), so that the old request socket and its clone share the same ireq_opt, then reqsk_migrate_reset() clears the pointer in the old one. Another cpu holding a reference on the old request socket can still be using these options (sending a SYNACK, or creating a child in tcp_v4_syn_recv_sock()) when the clone is freed, and tcp_v4_reqsk_destructor() also uses a plain kfree(). Readers already use RCU, and other paths replacing inet_opt (do_ip_setsockopt(), cipso_v4_sock_setattr()...) already use kfree_rcu(). Use kfree_rcu() in inet_sock_destruct() and tcp_v4_reqsk_destructor() as well. IPv6 is not affected by the first issue, because tcp_v6_syn_recv_sock() duplicates the options. tcp_v6_reqsk_destructor() has the same migration issue with ipv6_opt, which is only set by CALIPSO. This will be addressed in a separate patch. Fixes: 079096f103fa ("tcp/dccp: install syn_recv requests into ehash table") Fixes: c905dee62232 ("tcp: Migrate TCP_NEW_SYN_RECV requests at retransmitting SYN+ACKs.") Reported-by: Xinyang Ge Signed-off-by: Eric Dumazet Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20261001221253.2964024-1-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- net/ipv4/af_inet.c | 2 +- net/ipv4/tcp_ipv4.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c index 4ce38c99fef9..14ce01092fda 100644 --- a/net/ipv4/af_inet.c +++ b/net/ipv4/af_inet.c @@ -161,7 +161,7 @@ void inet_sock_destruct(struct sock *sk) WARN_ON_ONCE(sk->sk_wmem_queued); WARN_ON_ONCE(sk->sk_forward_alloc); - kfree(rcu_dereference_protected(inet->inet_opt, 1)); + kfree_rcu(rcu_dereference_protected(inet->inet_opt, 1), rcu); dst_release(rcu_dereference_protected(sk->sk_dst_cache, 1)); dst_release(rcu_dereference_protected(sk->sk_rx_dst, 1)); psp_sk_assoc_free(sk); diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c index 04dbb2babbcd..bebc5a8d1ab6 100644 --- a/net/ipv4/tcp_ipv4.c +++ b/net/ipv4/tcp_ipv4.c @@ -1209,7 +1209,7 @@ static int tcp_v4_send_synack(const struct sock *sk, struct dst_entry *dst, */ static void tcp_v4_reqsk_destructor(struct request_sock *req) { - kfree(rcu_dereference_protected(inet_rsk(req)->ireq_opt, 1)); + kfree_rcu(rcu_dereference_protected(inet_rsk(req)->ireq_opt, 1), rcu); } #ifdef CONFIG_TCP_MD5SIG From 11705541e7d421706a54f7f6ead8a3316c91c2bc Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Mon, 28 Sep 2026 20:34:37 +0000 Subject: [PATCH 044/128] net: stmmac: Remove VLAN perfect matching dead code stmmac_vlan_update() falls back to "perfect matching" when the VLAN hash filter is unavailable (!priv->dma_cap.vlhash). This fallback has been unreachable in normal operation since its introduction in commit c7ab0b8088d7 ("net: stmmac: Fallback to VLAN Perfect filtering if HASH is not available") because the NETIF_F_HW_VLAN_{CTAG,STAG}_FILTER features are advertised only when priv->dma_cap.vlhash is true. The fallback is also duplicating the code in vlan_add_hw_rx_fltr(), which is always available since stmmac_get_num_vlan() returns at least 1. Therefore, remove it. Signed-off-by: Ovidiu Panait Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260928203441.34876-2-ovidiu.panait.rb@renesas.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/hwif.h | 2 +- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 13 +----- .../net/ethernet/stmicro/stmmac/stmmac_vlan.c | 41 +------------------ 3 files changed, 4 insertions(+), 52 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index 857f7562c6c6..df2126d71c2f 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -633,7 +633,7 @@ struct stmmac_est_ops { struct stmmac_vlan_ops { /* VLAN */ void (*update_vlan_hash)(struct mac_device_info *hw, u32 hash, - u16 perfect_match, bool is_double); + bool is_double); void (*enable_vlan)(struct mac_device_info *hw, u32 type); void (*rx_hw_vlan)(struct mac_device_info *hw, struct dma_desc *rx_desc, struct sk_buff *skb); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 9741f97fa37a..a92940473ccd 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -6895,29 +6895,18 @@ static u32 stmmac_vid_crc32_le(__le16 vid_le) static int stmmac_vlan_update(struct stmmac_priv *priv, bool is_double) { u32 crc, hash = 0; - u16 pmatch = 0; - int count = 0; u16 vid = 0; for_each_set_bit(vid, priv->active_vlans, VLAN_N_VID) { __le16 vid_le = cpu_to_le16(vid); crc = bitrev32(~stmmac_vid_crc32_le(vid_le)) >> 28; hash |= (1 << crc); - count++; - } - - if (!priv->dma_cap.vlhash) { - if (count > 2) /* VID = 0 always passes filter */ - return -EOPNOTSUPP; - - pmatch = vid; - hash = 0; } if (!netif_running(priv->dev)) return 0; - return stmmac_update_vlan_hash(priv, priv->hw, hash, pmatch, is_double); + return stmmac_update_vlan_hash(priv, priv->hw, hash, is_double); } /* FIXME: This may need RXC to be running, but it may be called with BH diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c index e24efe3bfedb..983a90cb9767 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c @@ -162,7 +162,7 @@ static void vlan_restore_hw_rx_fltr(struct net_device *dev, } static void vlan_update_hash(struct mac_device_info *hw, u32 hash, - u16 perfect_match, bool is_double) + bool is_double) { void __iomem *ioaddr = hw->pcsr; u32 value; @@ -184,20 +184,6 @@ static void vlan_update_hash(struct mac_device_info *hw, u32 hash, } writel(value, ioaddr + VLAN_TAG); - } else if (perfect_match) { - u32 value = VLAN_ETV; - - if (is_double) { - value |= VLAN_EDVLP; - value |= VLAN_ESVL; - value |= VLAN_DOVLTC; - } else { - value &= ~VLAN_EDVLP; - value &= ~VLAN_ESVL; - value &= ~VLAN_DOVLTC; - } - - writel(value | perfect_match, ioaddr + VLAN_TAG); } else { value &= ~(VLAN_VTHM | VLAN_ETV); value &= ~(VLAN_EDVLP | VLAN_ESVL); @@ -251,7 +237,7 @@ static void vlan_set_hw_mode(struct mac_device_info *hw) } static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash, - u16 perfect_match, bool is_double) + bool is_double) { void __iomem *ioaddr = hw->pcsr; @@ -279,29 +265,6 @@ static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash, value &= ~VLAN_VID; writel(value, ioaddr + VLAN_TAG); - } else if (perfect_match) { - u32 value = readl(ioaddr + XGMAC_PACKET_FILTER); - - value |= XGMAC_FILTER_VTFE; - - writel(value, ioaddr + XGMAC_PACKET_FILTER); - - value = readl(ioaddr + VLAN_TAG); - - value &= ~VLAN_VTHM; - value |= VLAN_ETV; - if (is_double) { - value |= VLAN_EDVLP; - value |= VLAN_ESVL; - value |= VLAN_DOVLTC; - } else { - value &= ~VLAN_EDVLP; - value &= ~VLAN_ESVL; - value &= ~VLAN_DOVLTC; - } - - value &= ~VLAN_VID; - writel(value | perfect_match, ioaddr + VLAN_TAG); } else { u32 value = readl(ioaddr + XGMAC_PACKET_FILTER); From 69aa9e76677c97ba992df03a3c74d839470e7363 Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Mon, 28 Sep 2026 20:34:38 +0000 Subject: [PATCH 045/128] net: stmmac: Stop toggling the EDVLP bit Currently, the double VLAN EDVLP bit is toggled whenever an 802.1ad VLAN is registered. This bit enables processing of the inner VLAN tag, which is completely unrelated to S-Tag VLAN handling. Move EDVLP handling into vlan_set_hw_mode() instead, and keep it always enabled, so that COE can work for packets with an inner VLAN header. Add a dedicated callback for dwxlgmac2, as it doesn't implement the set_hw_vlan_mode callback, like the other cores. Suggested-by: Joseph Steel Signed-off-by: Ovidiu Panait Link: https://patch.msgid.link/20260928203441.34876-3-ovidiu.panait.rb@renesas.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/stmicro/stmmac/stmmac_vlan.c | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c index 983a90cb9767..200b34588c7f 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c @@ -174,19 +174,16 @@ static void vlan_update_hash(struct mac_device_info *hw, u32 hash, if (hash) { value |= VLAN_VTHM | VLAN_ETV; if (is_double) { - value |= VLAN_EDVLP; value |= VLAN_ESVL; value |= VLAN_DOVLTC; } else { - value &= ~VLAN_EDVLP; value &= ~VLAN_ESVL; value &= ~VLAN_DOVLTC; } writel(value, ioaddr + VLAN_TAG); } else { - value &= ~(VLAN_VTHM | VLAN_ETV); - value &= ~(VLAN_EDVLP | VLAN_ESVL); + value &= ~(VLAN_VTHM | VLAN_ETV | VLAN_ESVL); value &= ~VLAN_DOVLTC; value &= ~VLAN_VID; @@ -222,6 +219,7 @@ static void vlan_set_hw_mode(struct mac_device_info *hw) void __iomem *ioaddr = hw->pcsr; u32 value = readl(ioaddr + VLAN_TAG); + value |= VLAN_EDVLP; value &= ~VLAN_TAG_CTRL_EVLS_MASK; if (hw->hw_vlan_en) @@ -254,11 +252,9 @@ static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash, value |= VLAN_VTHM | VLAN_ETV; if (is_double) { - value |= VLAN_EDVLP; value |= VLAN_ESVL; value |= VLAN_DOVLTC; } else { - value &= ~VLAN_EDVLP; value &= ~VLAN_ESVL; value &= ~VLAN_DOVLTC; } @@ -274,8 +270,7 @@ static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash, value = readl(ioaddr + VLAN_TAG); - value &= ~(VLAN_VTHM | VLAN_ETV); - value &= ~(VLAN_EDVLP | VLAN_ESVL); + value &= ~(VLAN_VTHM | VLAN_ETV | VLAN_ESVL); value &= ~VLAN_DOVLTC; value &= ~VLAN_VID; @@ -283,6 +278,14 @@ static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash, } } +static void dwxlgmac2_set_hw_vlan_mode(struct mac_device_info *hw) +{ + void __iomem *ioaddr = hw->pcsr; + u32 value = readl(ioaddr + VLAN_TAG); + + writel(value | VLAN_EDVLP, ioaddr + VLAN_TAG); +} + const struct stmmac_vlan_ops dwmac_vlan_ops = { .update_vlan_hash = vlan_update_hash, .enable_vlan = vlan_enable, @@ -296,6 +299,7 @@ const struct stmmac_vlan_ops dwmac_vlan_ops = { const struct stmmac_vlan_ops dwxlgmac2_vlan_ops = { .update_vlan_hash = dwxgmac2_update_vlan_hash, .enable_vlan = vlan_enable, + .set_hw_vlan_mode = dwxlgmac2_set_hw_vlan_mode, }; const struct stmmac_vlan_ops dwxgmac210_vlan_ops = { From 354b07bc1dcaa94b40a5904159c1a53b33d7cf59 Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Mon, 28 Sep 2026 20:34:39 +0000 Subject: [PATCH 046/128] net: stmmac: Rename double VLAN references to svlan The ESVL and DOVLTC bits control S-VLAN tag processing and have nothing to do with the double VLAN feature, which only provides a way to process an additional inner VLAN tag. However, the driver code that handles them always refers to "double VLAN", which is unrelated and makes the implementation confusing. The driver does not use any of the inner VLAN tag features, and the networking core does not support offloads for the inner tag anyway. To reduce the confusion regarding S-Tag vs double VLAN handling, rename double -> svlan. No functional change intended. Suggested-by: Joseph Steel Signed-off-by: Ovidiu Panait Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260928203441.34876-4-ovidiu.panait.rb@renesas.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/hwif.h | 2 +- drivers/net/ethernet/stmicro/stmmac/stmmac.h | 2 +- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 34 +++++++++---------- .../stmicro/stmmac/stmmac_selftests.c | 30 ++++++++-------- .../net/ethernet/stmicro/stmmac/stmmac_vlan.c | 8 ++--- 5 files changed, 38 insertions(+), 38 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index df2126d71c2f..5e2654c91b41 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -633,7 +633,7 @@ struct stmmac_est_ops { struct stmmac_vlan_ops { /* VLAN */ void (*update_vlan_hash)(struct mac_device_info *hw, u32 hash, - bool is_double); + bool is_svlan); void (*enable_vlan)(struct mac_device_info *hw, u32 type); void (*rx_hw_vlan)(struct mac_device_info *hw, struct dma_desc *rx_desc, struct sk_buff *skb); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h index 7582fca63741..d2d387f45c10 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h @@ -344,7 +344,7 @@ struct stmmac_priv { void __iomem *ptpaddr; void __iomem *estaddr; unsigned long active_vlans[BITS_TO_LONGS(VLAN_N_VID)]; - unsigned int num_double_vlans; + unsigned int num_svlans; int sfty_irq; struct stmmac_msi *msi; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index a92940473ccd..26248e042f77 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -6892,7 +6892,7 @@ static u32 stmmac_vid_crc32_le(__le16 vid_le) return crc; } -static int stmmac_vlan_update(struct stmmac_priv *priv, bool is_double) +static int stmmac_vlan_update(struct stmmac_priv *priv, bool is_svlan) { u32 crc, hash = 0; u16 vid = 0; @@ -6906,7 +6906,7 @@ static int stmmac_vlan_update(struct stmmac_priv *priv, bool is_double) if (!netif_running(priv->dev)) return 0; - return stmmac_update_vlan_hash(priv, priv->hw, hash, is_double); + return stmmac_update_vlan_hash(priv, priv->hw, hash, is_svlan); } /* FIXME: This may need RXC to be running, but it may be called with BH @@ -6915,8 +6915,8 @@ static int stmmac_vlan_update(struct stmmac_priv *priv, bool is_double) static int stmmac_vlan_rx_add_vid(struct net_device *ndev, __be16 proto, u16 vid) { struct stmmac_priv *priv = netdev_priv(ndev); - unsigned int num_double_vlans; - bool is_double = false; + unsigned int num_svlans; + bool is_svlan = false; int ret; ret = pm_runtime_resume_and_get(priv->device); @@ -6924,11 +6924,11 @@ static int stmmac_vlan_rx_add_vid(struct net_device *ndev, __be16 proto, u16 vid return ret; if (be16_to_cpu(proto) == ETH_P_8021AD) - is_double = true; + is_svlan = true; set_bit(vid, priv->active_vlans); - num_double_vlans = priv->num_double_vlans + is_double; - ret = stmmac_vlan_update(priv, num_double_vlans); + num_svlans = priv->num_svlans + is_svlan; + ret = stmmac_vlan_update(priv, num_svlans); if (ret) { clear_bit(vid, priv->active_vlans); goto err_pm_put; @@ -6938,12 +6938,12 @@ static int stmmac_vlan_rx_add_vid(struct net_device *ndev, __be16 proto, u16 vid ret = stmmac_add_hw_vlan_rx_fltr(priv, ndev, priv->hw, proto, vid); if (ret) { clear_bit(vid, priv->active_vlans); - stmmac_vlan_update(priv, priv->num_double_vlans); + stmmac_vlan_update(priv, priv->num_svlans); goto err_pm_put; } } - priv->num_double_vlans = num_double_vlans; + priv->num_svlans = num_svlans; err_pm_put: pm_runtime_put(priv->device); @@ -6957,8 +6957,8 @@ err_pm_put: static int stmmac_vlan_rx_kill_vid(struct net_device *ndev, __be16 proto, u16 vid) { struct stmmac_priv *priv = netdev_priv(ndev); - unsigned int num_double_vlans; - bool is_double = false; + unsigned int num_svlans; + bool is_svlan = false; int ret; ret = pm_runtime_resume_and_get(priv->device); @@ -6966,11 +6966,11 @@ static int stmmac_vlan_rx_kill_vid(struct net_device *ndev, __be16 proto, u16 vi return ret; if (be16_to_cpu(proto) == ETH_P_8021AD) - is_double = true; + is_svlan = true; clear_bit(vid, priv->active_vlans); - num_double_vlans = priv->num_double_vlans - is_double; - ret = stmmac_vlan_update(priv, num_double_vlans); + num_svlans = priv->num_svlans - is_svlan; + ret = stmmac_vlan_update(priv, num_svlans); if (ret) { set_bit(vid, priv->active_vlans); goto del_vlan_error; @@ -6980,12 +6980,12 @@ static int stmmac_vlan_rx_kill_vid(struct net_device *ndev, __be16 proto, u16 vi ret = stmmac_del_hw_vlan_rx_fltr(priv, ndev, priv->hw, proto, vid); if (ret) { set_bit(vid, priv->active_vlans); - stmmac_vlan_update(priv, priv->num_double_vlans); + stmmac_vlan_update(priv, priv->num_svlans); goto del_vlan_error; } } - priv->num_double_vlans = num_double_vlans; + priv->num_svlans = num_svlans; del_vlan_error: pm_runtime_put(priv->device); @@ -7001,7 +7001,7 @@ static void stmmac_vlan_restore(struct stmmac_priv *priv) if (priv->hw->num_vlan) stmmac_restore_hw_vlan_rx_fltr(priv, priv->dev, priv->hw); - stmmac_vlan_update(priv, priv->num_double_vlans); + stmmac_vlan_update(priv, priv->num_svlans); } static int stmmac_bpf(struct net_device *dev, struct netdev_bpf *bpf) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c index c25dc9f89270..c485217ba880 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c @@ -243,7 +243,7 @@ struct stmmac_test_priv { int (*func)(struct sk_buff *skb, struct net_device *ndev, struct packet_type *pt, struct net_device *orig_ndev); bool capture_all; - int double_vlan; + int svlan; int vlan_id; int ok; }; @@ -285,7 +285,7 @@ static int stmmac_test_loopback_validate(struct sk_buff *skb, } ihdr = ip_hdr(skb); - if (tpriv->double_vlan) + if (tpriv->svlan) ihdr = (struct iphdr *)(skb_network_header(skb) + 4); if (tpriv->packet->tcp) { @@ -936,7 +936,7 @@ static int stmmac_test_vlan_validate(struct sk_buff *skb, struct iphdr *ihdr; u16 proto; - proto = tpriv->double_vlan ? ETH_P_8021AD : ETH_P_8021Q; + proto = tpriv->svlan ? ETH_P_8021AD : ETH_P_8021Q; skb = skb_unshare(skb, GFP_ATOMIC); if (!skb) @@ -963,7 +963,7 @@ static int stmmac_test_vlan_validate(struct sk_buff *skb, } ihdr = ip_hdr(skb); - if (tpriv->double_vlan) + if (tpriv->svlan) ihdr = (struct iphdr *)(skb_network_header(skb) + 4); if (ihdr->protocol != IPPROTO_UDP) goto out; @@ -1080,7 +1080,7 @@ static int stmmac_test_vlanfilt_perfect(struct stmmac_priv *priv) return ret; } -static int __stmmac_test_dvlanfilt(struct stmmac_priv *priv) +static int __stmmac_test_svlanfilt(struct stmmac_priv *priv) { struct stmmac_packet_attrs attr = { }; struct stmmac_test_priv *tpriv; @@ -1092,7 +1092,7 @@ static int __stmmac_test_dvlanfilt(struct stmmac_priv *priv) return -ENOMEM; tpriv->ok = false; - tpriv->double_vlan = true; + tpriv->svlan = true; init_completion(&tpriv->comp); tpriv->pt.type = htons(ETH_P_8021Q); @@ -1155,15 +1155,15 @@ cleanup: return ret; } -static int stmmac_test_dvlanfilt(struct stmmac_priv *priv) +static int stmmac_test_svlanfilt(struct stmmac_priv *priv) { if (!priv->dma_cap.vlhash) return -EOPNOTSUPP; - return __stmmac_test_dvlanfilt(priv); + return __stmmac_test_svlanfilt(priv); } -static int stmmac_test_dvlanfilt_perfect(struct stmmac_priv *priv) +static int stmmac_test_svlanfilt_perfect(struct stmmac_priv *priv) { int ret, prev_cap = priv->dma_cap.vlhash; @@ -1171,7 +1171,7 @@ static int stmmac_test_dvlanfilt_perfect(struct stmmac_priv *priv) return -EOPNOTSUPP; priv->dma_cap.vlhash = 0; - ret = __stmmac_test_dvlanfilt(priv); + ret = __stmmac_test_svlanfilt(priv); priv->dma_cap.vlhash = prev_cap; return ret; @@ -1372,7 +1372,7 @@ static int stmmac_test_vlanoff_common(struct stmmac_priv *priv, bool svlan) proto = svlan ? ETH_P_8021AD : ETH_P_8021Q; tpriv->ok = false; - tpriv->double_vlan = svlan; + tpriv->svlan = svlan; init_completion(&tpriv->comp); tpriv->pt.type = svlan ? htons(ETH_P_8021Q) : htons(ETH_P_IP); @@ -1960,11 +1960,11 @@ static const struct stmmac_test { .name = "VLAN Filtering (perf) ", .fn = stmmac_test_vlanfilt_perfect, }, { - .name = "Double VLAN Filter ", - .fn = stmmac_test_dvlanfilt, + .name = "SVLAN Filtering ", + .fn = stmmac_test_svlanfilt, }, { - .name = "Double VLAN Filter (perf) ", - .fn = stmmac_test_dvlanfilt_perfect, + .name = "SVLAN Filtering (perf) ", + .fn = stmmac_test_svlanfilt_perfect, }, { .name = "Flexible RX Parser ", .fn = stmmac_test_rxp, diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c index 200b34588c7f..fb9aad748cb3 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c @@ -162,7 +162,7 @@ static void vlan_restore_hw_rx_fltr(struct net_device *dev, } static void vlan_update_hash(struct mac_device_info *hw, u32 hash, - bool is_double) + bool is_svlan) { void __iomem *ioaddr = hw->pcsr; u32 value; @@ -173,7 +173,7 @@ static void vlan_update_hash(struct mac_device_info *hw, u32 hash, if (hash) { value |= VLAN_VTHM | VLAN_ETV; - if (is_double) { + if (is_svlan) { value |= VLAN_ESVL; value |= VLAN_DOVLTC; } else { @@ -235,7 +235,7 @@ static void vlan_set_hw_mode(struct mac_device_info *hw) } static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash, - bool is_double) + bool is_svlan) { void __iomem *ioaddr = hw->pcsr; @@ -251,7 +251,7 @@ static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash, value = readl(ioaddr + VLAN_TAG); value |= VLAN_VTHM | VLAN_ETV; - if (is_double) { + if (is_svlan) { value |= VLAN_ESVL; value |= VLAN_DOVLTC; } else { From c63ce7b9c7e7b68267899e4a0fc14b35d2c1dfcc Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Mon, 28 Sep 2026 20:34:40 +0000 Subject: [PATCH 047/128] net: stmmac: Do not advertise S-VLAN stripping when it is disabled C-VLAN and S-VLAN tag stripping are both controlled by the EVLS bit, so disabling rx-vlan-offload also disables S-VLAN tag stripping. However, rx-vlan-stag-hw-parse keeps being advertised as enabled: root@rzv2h-evk:~# ethtool -K end1 rx-vlan-offload off root@rzv2h-evk:~# ethtool -k end1 | grep -i vlan rx-vlan-offload: off tx-vlan-offload: off [fixed] rx-vlan-filter: on [fixed] vlan-challenged: off [fixed] tx-vlan-stag-hw-insert: off [fixed] rx-vlan-stag-hw-parse: on [fixed] rx-vlan-stag-filter: on [fixed] Fix this inconsistency by making NETIF_F_HW_VLAN_STAG_RX follow NETIF_F_HW_VLAN_CTAG_RX. Fixes: 750011e239a5 ("net: stmmac: Add support for HW-accelerated VLAN stripping") Signed-off-by: Ovidiu Panait Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260928203441.34876-5-ovidiu.panait.rb@renesas.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 26248e042f77..13e11df7f4c8 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -6268,6 +6268,13 @@ static netdev_features_t stmmac_fix_features(struct net_device *dev, if (priv->plat->bugged_jumbo && (dev->mtu > ETH_DATA_LEN)) features &= ~NETIF_F_CSUM_MASK; + if (dwmac_is_xmac(priv->plat->core_type)) { + if (features & NETIF_F_HW_VLAN_CTAG_RX) + features |= NETIF_F_HW_VLAN_STAG_RX; + else + features &= ~NETIF_F_HW_VLAN_STAG_RX; + } + return features; } From da17990421d79f1bfb4142f9d767fd33daecc3d8 Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Mon, 28 Sep 2026 20:34:41 +0000 Subject: [PATCH 048/128] net: stmmac: Disable S-Tag processing on dwmac4 Currently, hardware VLAN stripping is broken for 802.1ad tags. vlan_rx_hw() hardcodes ETH_P_8021Q when putting the hardware tag into the skb, rather than using the actual protocol from the packet. Because of this, packets that contain a 802.1ad outer tag are incorrectly passed up the stack as having an 802.1Q tag. This causes QinQ ping between two hosts to fail. vlan_rx_hw() is shared by dwxgmac2 and dwmac4: on dwxgmac2 the tag type is available in the RDES3 write-back descriptor (the ET_LT field), so the outer tag type can be determined based on that info. However, dwmac4 doesn't seem to provide the tag type. The Length/Type field in RDES3 only indicates whether the packet is single or double-tagged, not which tag type was stripped. Since dwmac4 cannot report the stripped tag type, it cannot support hardware S-Tag stripping correctly. Therefore, restrict the NETIF_F_HW_VLAN_STAG_RX and NETIF_F_HW_VLAN_STAG_FILTER advertisement to dwxgmac2 only. With this, 802.1ad tags are left in place and handled by the software VLAN path. Fixes: 750011e239a5 ("net: stmmac: Add support for HW-accelerated VLAN stripping") Signed-off-by: Ovidiu Panait Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260928203441.34876-6-ovidiu.panait.rb@renesas.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 13e11df7f4c8..77d2bffb8dc1 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -6268,7 +6268,7 @@ static netdev_features_t stmmac_fix_features(struct net_device *dev, if (priv->plat->bugged_jumbo && (dev->mtu > ETH_DATA_LEN)) features &= ~NETIF_F_CSUM_MASK; - if (dwmac_is_xmac(priv->plat->core_type)) { + if (priv->plat->core_type == DWMAC_CORE_XGMAC) { if (features & NETIF_F_HW_VLAN_CTAG_RX) features |= NETIF_F_HW_VLAN_STAG_RX; else @@ -8055,14 +8055,18 @@ static int __stmmac_dvr_probe(struct device *device, ndev->watchdog_timeo = msecs_to_jiffies(watchdog); #ifdef STMMAC_VLAN_TAG_USED /* Both mac100 and gmac support receive VLAN tag detection */ - ndev->features |= NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_STAG_RX; + ndev->features |= NETIF_F_HW_VLAN_CTAG_RX; + if (priv->plat->core_type == DWMAC_CORE_XGMAC) + ndev->features |= NETIF_F_HW_VLAN_STAG_RX; + if (dwmac_is_xmac(priv->plat->core_type)) { ndev->hw_features |= NETIF_F_HW_VLAN_CTAG_RX; priv->hw->hw_vlan_en = true; } if (priv->dma_cap.vlhash) { ndev->features |= NETIF_F_HW_VLAN_CTAG_FILTER; - ndev->features |= NETIF_F_HW_VLAN_STAG_FILTER; + if (priv->plat->core_type == DWMAC_CORE_XGMAC) + ndev->features |= NETIF_F_HW_VLAN_STAG_FILTER; } if (priv->dma_cap.vlins) ndev->features |= NETIF_F_HW_VLAN_CTAG_TX; From 73670ddb91a4d22e1deb3efd962242de1ec48e90 Mon Sep 17 00:00:00 2001 From: Joshua Washington Date: Wed, 30 Sep 2026 14:47:33 -0700 Subject: [PATCH 049/128] gve: fix XSK buffer leak when rings are stopped GVE does not free XSK buffers when resetting ring state as a part of stopping queues. This causes all XSK buffers which are posted to the NIC to be leaked. Free XSK buffers attached to an allocated buf_state when stopping rings. This issue was hit in production. Fixes: c1fffc5d66a7 ("gve: implement DQO RX datapath and control path for AF_XDP zero-copy") Cc: stable@vger.kernel.org Reviewed-by: Tim Hostetler Reviewed-by: Jordan Rhee Signed-off-by: Joshua Washington Link: https://patch.msgid.link/20260930214735.3288780-2-joshwash@google.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/google/gve/gve_rx_dqo.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/ethernet/google/gve/gve_rx_dqo.c b/drivers/net/ethernet/google/gve/gve_rx_dqo.c index 5cf242b28557..a2c4a08ce68d 100644 --- a/drivers/net/ethernet/google/gve/gve_rx_dqo.c +++ b/drivers/net/ethernet/google/gve/gve_rx_dqo.c @@ -113,6 +113,12 @@ static void gve_rx_reset_ring_dqo(struct gve_priv *priv, int idx) gve_free_to_page_pool(rx, bs, false); else gve_free_qpl_page_dqo(bs); + + if (gve_buf_state_is_allocated(rx, bs) && + bs->xsk_buff) { + xsk_buff_free(bs->xsk_buff); + bs->xsk_buff = NULL; + } } } From 9fed6a8ab8a5bacba2c709cb19d2dd6d2fa9112e Mon Sep 17 00:00:00 2001 From: Joshua Washington Date: Wed, 30 Sep 2026 14:47:34 -0700 Subject: [PATCH 050/128] gve: fix XSK buffer leak on error descriptor When the error bit is set in the RX completion descriptor, the buf_state and its attached buffer should be freed. In the case of AF_XDP ZC, the XSK buffer was not freed, leading to a leak. This issue was caught by LLM. Fixes: c1fffc5d66a7 ("gve: implement DQO RX datapath and control path for AF_XDP zero-copy") Cc: stable@vger.kernel.org Reviewed-by: Jordan Rhee Reviewed-by: Tim Hostetler Signed-off-by: Joshua Washington Link: https://patch.msgid.link/20260930214735.3288780-3-joshwash@google.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/google/gve/gve_rx_dqo.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/google/gve/gve_rx_dqo.c b/drivers/net/ethernet/google/gve/gve_rx_dqo.c index a2c4a08ce68d..82ed5e48b00a 100644 --- a/drivers/net/ethernet/google/gve/gve_rx_dqo.c +++ b/drivers/net/ethernet/google/gve/gve_rx_dqo.c @@ -842,7 +842,12 @@ static int gve_rx_dqo(struct napi_struct *napi, struct gve_rx_ring *rx, } if (unlikely(compl_desc->rx_error)) { - gve_free_buffer(rx, buf_state); + if (buf_state->xsk_buff) { + xsk_buff_free(buf_state->xsk_buff); + gve_free_buf_state(rx, buf_state); + } else { + gve_free_buffer(rx, buf_state); + } return -EINVAL; } From 0cb6939a4dfefa822007b7a71986e06bc4f61f54 Mon Sep 17 00:00:00 2001 From: Joshua Washington Date: Wed, 30 Sep 2026 14:47:35 -0700 Subject: [PATCH 051/128] gve: fix napi_disable deadlock when attempting to disable XSK pools When disabling XSK pools, GVE calls the unlocked versions of napi_disable and napi_enable. However, the netdev lock has already been acquired before ndo_bpf is called because GVE supports queue management ops. Calling the unlocked versions of napi_disable/enable results in a deadlock when attempting to disable XSK pools, as the thread attempts to re-acquire a lock it already holds. Update the NAPI calls to use the locked versions. This issue was found in production. Fixes: 606048cbd834 ("net: designate XSK pool pointers in queues as "ops protected"") Cc: stable@vger.kernel.org Reviewed-by: Harshitha Ramamurthy Reviewed-by: Jordan Rhee Signed-off-by: Joshua Washington Link: https://patch.msgid.link/20260930214735.3288780-4-joshwash@google.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/google/gve/gve_main.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/google/gve/gve_main.c b/drivers/net/ethernet/google/gve/gve_main.c index 9cc343a16271..a6c18e2431cc 100644 --- a/drivers/net/ethernet/google/gve/gve_main.c +++ b/drivers/net/ethernet/google/gve/gve_main.c @@ -1708,17 +1708,17 @@ static int gve_xsk_pool_disable(struct net_device *dev, } napi_rx = &priv->ntfy_blocks[priv->rx[qid].ntfy_id].napi; - napi_disable(napi_rx); /* make sure current rx poll is done */ + napi_disable_locked(napi_rx); /* make sure current rx poll is done */ tx_qid = gve_xdp_tx_queue_id(priv, qid); napi_tx = &priv->ntfy_blocks[priv->tx[tx_qid].ntfy_id].napi; - napi_disable(napi_tx); /* make sure current tx poll is done */ + napi_disable_locked(napi_tx); /* make sure current tx poll is done */ gve_unreg_xsk_pool(priv, qid); smp_mb(); /* Make sure it is visible to the workers on datapath */ - napi_enable(napi_rx); - napi_enable(napi_tx); + napi_enable_locked(napi_rx); + napi_enable_locked(napi_tx); if (gve_is_gqi(priv)) { if (gve_rx_work_pending(&priv->rx[qid])) napi_schedule(napi_rx); From 7e170da2edec9fb0717543571cc14b0aabee2b46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leto=20Liu=20=28=E5=88=98=E6=B6=9B=29?= Date: Wed, 30 Sep 2026 08:58:48 +0200 Subject: [PATCH 052/128] net: airoha: Add retry mechanism to airoha_qdma_set_trtcm_param() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CPU accesses QDMA via the bus. When multiple modules are using the bus simultaneously, CPU access to QDMA may encounter bus timeouts and fails, resulting in QDMA configuration failures and potentially causing packet transmission issues. In order to mitigate the issue, introduce a retry mechanism to airoha_qdma_set_trtcm_param routine in order to ensure the configuration is correctly applied to the hardware. It's enough to try a second time for the TRTCM config to be actually applied to make sure we are not in the middle of bucket handling as it does operate on fixed time slot internally. Fixes: ef1ca9271313b ("net: airoha: Add sched HTB offload support") Signed-off-by: Leto Liu (刘涛) [ improve commit description, drop memory block, apply better loop logic] Signed-off-by: Christian Marangi Link: https://patch.msgid.link/20260930065855.46171-1-ansuelsmth@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/airoha/airoha_eth.c | 32 +++++++++++++++++++----- drivers/net/ethernet/airoha/airoha_eth.h | 2 ++ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c index 64619e9a704d..6ca57875b6d6 100644 --- a/drivers/net/ethernet/airoha/airoha_eth.c +++ b/drivers/net/ethernet/airoha/airoha_eth.c @@ -2800,14 +2800,34 @@ static int airoha_qdma_set_trtcm_param(struct airoha_qdma *qdma, int channel, FIELD_PREP(TRTCM_METER_GROUP_MASK, group) | FIELD_PREP(TRTCM_PARAM_INDEX_MASK, idx) | FIELD_PREP(TRTCM_PARAM_RATE_TYPE_MASK, mode); + int i; - airoha_qdma_wr(qdma, REG_TRTCM_DATA_LOW(addr), val); - airoha_qdma_wr(qdma, REG_TRTCM_CFG_PARAM(addr), config); + /* + * HW might be in the middle of bucket handling and might + * require an additional try to actually apply the new TRTCM + * config. + */ + for (i = 0; i < TRTCM_CONFIG_RETRY_MAX; i++) { + u32 data = 0; - return read_poll_timeout(airoha_qdma_rr, val, - val & TRTCM_PARAM_RW_DONE_MASK, - USEC_PER_MSEC, 10 * USEC_PER_MSEC, true, - qdma, REG_TRTCM_CFG_PARAM(addr)); + airoha_qdma_wr(qdma, REG_TRTCM_DATA_LOW(addr), val); + airoha_qdma_wr(qdma, REG_TRTCM_CFG_PARAM(addr), config); + + if (read_poll_timeout(airoha_qdma_rr, data, + data & TRTCM_PARAM_RW_DONE_MASK, + USEC_PER_MSEC, 10 * USEC_PER_MSEC, + true, qdma, REG_TRTCM_CFG_PARAM(addr))) + continue; + + if (airoha_qdma_get_trtcm_param(qdma, channel, addr, param, + mode, &data, NULL)) + continue; + + if (data == val) + return 0; + } + + return -EBUSY; } static int airoha_qdma_set_trtcm_config(struct airoha_qdma *qdma, int channel, diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h index 8277c1c87bb3..338fde03a149 100644 --- a/drivers/net/ethernet/airoha/airoha_eth.h +++ b/drivers/net/ethernet/airoha/airoha_eth.h @@ -171,6 +171,8 @@ enum trtcm_param { #define TRTCM_TOKEN_RATE_MASK GENMASK(23, 6) #define TRTCM_TOKEN_RATE_FRACTION_MASK GENMASK(5, 0) +#define TRTCM_CONFIG_RETRY_MAX 2 + enum airoha_dma_map_type { AIROHA_DMA_UNMAPPED, AIROHA_DMA_MAP_SINGLE, From d5a007b9b457c915ab1a53227e8939e4018aa97a Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Mon, 5 Oct 2026 18:19:20 -0700 Subject: [PATCH 053/128] Revert "net: stmmac: propagate PTP addend and system time programming errors" This reverts commit 232d49dd4b40a666283de9e722899f088ed581b2. Anirudh reports a regression from this change. It is not appropriate for net this late in the release cycle in the first place, so let's revert and revisit in net-next. Reported-by: Anirudh Srinivasan Link: https://lore.kernel.org/CAEev2e8+fr1tJnSErD+2TtLDw5fGJbQOMcM5eefMnLyFUR+m7w@mail.gmail.com Fixes: 232d49dd4b40 ("net: stmmac: propagate PTP addend and system time programming errors") Signed-off-by: Jakub Kicinski --- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 106 +++++------------- .../net/ethernet/stmicro/stmmac/stmmac_ptp.c | 10 +- 2 files changed, 32 insertions(+), 84 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 77d2bffb8dc1..b837e8e27a35 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -601,64 +601,31 @@ static void stmmac_get_rx_hwtstamp(struct stmmac_priv *priv, struct dma_desc *p, } } -static void stmmac_restore_subsecond_increment(struct stmmac_priv *priv, - u32 default_addend) +static void stmmac_update_subsecond_increment(struct stmmac_priv *priv) { bool xmac = dwmac_is_xmac(priv->plat->core_type); u32 sec_inc = 0; - - stmmac_config_addend(priv, priv->ptpaddr, default_addend); - stmmac_config_hw_tstamping(priv, priv->ptpaddr, priv->systime_flags); - stmmac_config_sub_second_increment(priv, priv->ptpaddr, - priv->plat->clk_ptp_rate, - xmac, &sec_inc); - priv->default_addend = default_addend; - priv->sub_second_inc = sec_inc; -} - -static int stmmac_update_subsecond_increment(struct stmmac_priv *priv, - u32 systime_flags) -{ - bool xmac = dwmac_is_xmac(priv->plat->core_type); - u32 sec_inc = 0, val; u64 temp = 0; - int ret; - stmmac_config_hw_tstamping(priv, priv->ptpaddr, systime_flags); + stmmac_config_hw_tstamping(priv, priv->ptpaddr, priv->systime_flags); /* program Sub Second Increment reg */ stmmac_config_sub_second_increment(priv, priv->ptpaddr, priv->plat->clk_ptp_rate, xmac, &sec_inc); - if (!sec_inc) { - ret = -EINVAL; - goto error; - } + temp = div_u64(1000000000ULL, sec_inc); + + /* Store sub second increment for later use */ + priv->sub_second_inc = sec_inc; /* calculate default added value: * formula is : * addend = (2^32)/freq_div_ratio; * where, freq_div_ratio = 1e9ns/sec_inc */ - temp = div_u64(1000000000ULL, sec_inc); temp = (u64)(temp << 32); - val = div_u64(temp, priv->plat->clk_ptp_rate); - - ret = stmmac_config_addend(priv, priv->ptpaddr, val); - if (ret) - goto error; - - priv->sub_second_inc = sec_inc; - priv->default_addend = val; - - return 0; -error: - /* Restore previous configuration */ - stmmac_config_hw_tstamping(priv, priv->ptpaddr, priv->systime_flags); - stmmac_config_sub_second_increment(priv, priv->ptpaddr, - priv->plat->clk_ptp_rate, xmac, - NULL); - return ret; + priv->default_addend = div_u64(temp, priv->plat->clk_ptp_rate); + stmmac_config_addend(priv, priv->ptpaddr, priv->default_addend); } /** @@ -887,42 +854,35 @@ static int stmmac_hwtstamp_get(struct net_device *dev, /** * stmmac_init_tstamp_counter - init hardware timestamping counter * @priv: driver private structure + * @systime_flags: timestamping flags * Description: * Initialize hardware counter for packet timestamping. * This is valid as long as the interface is open and not suspended. * Will be rerun after resuming from suspend, case in which the timestamping * flags updated by stmmac_hwtstamp_set() also need to be restored. */ -static int stmmac_init_tstamp_counter(struct stmmac_priv *priv) +static int stmmac_init_tstamp_counter(struct stmmac_priv *priv, + u32 systime_flags) { - u32 default_addend = priv->default_addend; struct timespec64 now; - int ret; if (!priv->plat->clk_ptp_rate) { netdev_err(priv->dev, "Invalid PTP clock rate"); return -EINVAL; } - ret = stmmac_update_subsecond_increment(priv, priv->systime_flags); - if (ret) - return ret; + stmmac_config_hw_tstamping(priv, priv->ptpaddr, systime_flags); + priv->systime_flags = systime_flags; + + stmmac_update_subsecond_increment(priv); /* initialize system time */ ktime_get_real_ts64(&now); /* lower 32 bits of tv_sec are safe until y2106 */ - ret = stmmac_init_systime(priv, priv->ptpaddr, (u32)now.tv_sec, - now.tv_nsec); - if (ret) - goto error; + stmmac_init_systime(priv, priv->ptpaddr, (u32)now.tv_sec, now.tv_nsec); return 0; -error: - /* Restore previous configuration */ - stmmac_restore_subsecond_increment(priv, default_addend); - - return ret; } /** @@ -945,14 +905,8 @@ static int stmmac_init_timestamping(struct stmmac_priv *priv) return -EOPNOTSUPP; } - /* Reset hw ts configuration */ - memset(&priv->tstamp_config, 0, sizeof(priv->tstamp_config)); - priv->systime_flags = STMMAC_HWTS_ACTIVE | PTP_TCR_TSCFUPDT; - priv->tsfupdt_coarse = false; - priv->hwts_tx_en = 0; - priv->hwts_rx_en = 0; - - ret = stmmac_init_tstamp_counter(priv); + ret = stmmac_init_tstamp_counter(priv, STMMAC_HWTS_ACTIVE | + PTP_TCR_TSCFUPDT); if (ret) { netdev_warn(priv->dev, "PTP init failed\n"); return ret; @@ -973,6 +927,10 @@ static int stmmac_init_timestamping(struct stmmac_priv *priv) netdev_info(priv->dev, "IEEE 1588-2008 Advanced Timestamp supported\n"); + memset(&priv->tstamp_config, 0, sizeof(priv->tstamp_config)); + priv->hwts_tx_en = 0; + priv->hwts_rx_en = 0; + if (priv->plat->flags & STMMAC_FLAG_HWTSTAMP_CORRECT_LATENCY) stmmac_hwtstamp_correct_latency(priv, priv); @@ -7749,26 +7707,18 @@ static int stmmac_dl_ts_coarse_set(struct devlink *dl, u32 id, { struct stmmac_devlink_priv *dl_priv = devlink_priv(dl); struct stmmac_priv *priv = dl_priv->stmmac_priv; - u32 systime_flags = priv->systime_flags; - int ret; - if (ctx->val.vbool) - systime_flags &= ~PTP_TCR_TSCFUPDT; + priv->tsfupdt_coarse = ctx->val.vbool; + + if (priv->tsfupdt_coarse) + priv->systime_flags &= ~PTP_TCR_TSCFUPDT; else - systime_flags |= PTP_TCR_TSCFUPDT; + priv->systime_flags |= PTP_TCR_TSCFUPDT; /* In Coarse mode, we can use a smaller subsecond increment, let's * reconfigure the systime, subsecond increment and addend. */ - ret = stmmac_update_subsecond_increment(priv, systime_flags); - if (ret) { - NL_SET_ERR_MSG_MOD(extack, - "failed to reconfigure PTP adjustment"); - return ret; - } - - priv->tsfupdt_coarse = ctx->val.vbool; - priv->systime_flags = systime_flags; + stmmac_update_subsecond_increment(priv); return 0; } diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c index 493c5d81a36b..3bfcc9760dce 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c @@ -28,15 +28,14 @@ static int stmmac_adjust_freq(struct ptp_clock_info *ptp, long scaled_ppm) container_of(ptp, struct stmmac_priv, ptp_clock_ops); unsigned long flags; u32 addend; - int ret; addend = adjust_by_scaled_ppm(priv->default_addend, scaled_ppm); write_lock_irqsave(&priv->ptp_lock, flags); - ret = stmmac_config_addend(priv, priv->ptpaddr, addend); + stmmac_config_addend(priv, priv->ptpaddr, addend); write_unlock_irqrestore(&priv->ptp_lock, flags); - return ret; + return 0; } /** @@ -154,13 +153,12 @@ static int stmmac_set_time(struct ptp_clock_info *ptp, struct stmmac_priv *priv = container_of(ptp, struct stmmac_priv, ptp_clock_ops); unsigned long flags; - int ret; write_lock_irqsave(&priv->ptp_lock, flags); - ret = stmmac_init_systime(priv, priv->ptpaddr, ts->tv_sec, ts->tv_nsec); + stmmac_init_systime(priv, priv->ptpaddr, ts->tv_sec, ts->tv_nsec); write_unlock_irqrestore(&priv->ptp_lock, flags); - return ret; + return 0; } static int stmmac_enable(struct ptp_clock_info *ptp, From 0ec14f8c8c5da1c9c314bce4f0bf53c009fbc998 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Tue, 18 Aug 2026 12:58:23 +0000 Subject: [PATCH 054/128] wifi: mt76: mt7996: take over connection monitoring mt76 runs its own beacon monitor. mt76_beacon_mon_check() counts a miss for each affiliated link of an MLD, and it stops the count while the phy is off channel. mt7996 supplies it through mt76_rx_beacon(), but it does not set CONNECTION_MONITOR. Therefore mac80211 also starts its legacy monitor. The legacy monitor knows one interface, not a set of links. It also counts while the radio serves a different link. After seven beacon intervals it removes an association that still carries traffic on the other links. Set the CONNECTION_MONITOR flag to disable the legacy monitor. Fixes: 4df75606ece5 ("wifi: mt76: add per-link beacon monitoring for MLO") Link: https://patch.msgid.link/20260818125825.395538-3-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/init.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index fb635a092584..2db63c3be9d1 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -540,6 +540,7 @@ mt7996_init_wiphy(struct ieee80211_hw *hw, struct mtk_wed_device *wed) ieee80211_hw_set(hw, NO_VIRTUAL_MONITOR); ieee80211_hw_set(hw, SUPPORTS_MULTI_BSSID); ieee80211_hw_set(hw, CHANCTX_STA_CSA); + ieee80211_hw_set(hw, CONNECTION_MONITOR); hw->max_tx_fragments = 4; wiphy->txq_memory_limit = 32 << 20; /* 32 MiB */ From 752770d7a58c821c17909c3ca67f9f14ab146a40 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Tue, 18 Aug 2026 12:58:25 +0000 Subject: [PATCH 055/128] wifi: mt76: account non-AQL frames per peer rather than per link The driver increments wcid->non_aql_packets when a frame goes to the hardware queue. The completion path decrements the counter again. For an MLO peer, the hardware reports the completion on the link that it used for the transmission. This link can differ from the link that the driver queued the frame on. In that case, the increment and the decrement go to different link wcids, and the count drifts upwards until it blocks the station. Keep the count on the wcid of the primary link, like the other peer-wide state. Fixes: 86c051f2c418 ("wifi: mt76: mt7925: enabling MLO when the firmware supports it") Link: https://patch.msgid.link/20260818125825.395538-5-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt76.h | 7 +++++++ drivers/net/wireless/mediatek/mt76/tx.c | 11 ++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index 62b41c8bb7c0..2ade848fe3bb 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -1426,6 +1426,13 @@ mtxq_to_txq(struct mt76_txq *mtxq) return container_of(ptr, struct ieee80211_txq, drv_priv); } +/* peer-wide state uses the wcid of the primary link */ +static inline struct mt76_wcid * +mt76_wcid_primary(struct mt76_wcid *wcid) +{ + return wcid->def_wcid ? wcid->def_wcid : wcid; +} + static inline struct ieee80211_sta * wcid_to_sta(struct mt76_wcid *wcid) { diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c index 3707ee19e4ae..b1e894b19cbd 100644 --- a/drivers/net/wireless/mediatek/mt76/tx.c +++ b/drivers/net/wireless/mediatek/mt76/tx.c @@ -234,6 +234,8 @@ mt76_tx_check_non_aql(struct mt76_dev *dev, struct mt76_wcid *wcid, if (!wcid || info->tx_time_est) return; + wcid = mt76_wcid_primary(wcid); + pending = atomic_dec_return(&wcid->non_aql_packets); if (pending < 0) atomic_cmpxchg(&wcid->non_aql_packets, pending, 0); @@ -344,7 +346,8 @@ __mt76_tx_queue_skb(struct mt76_phy *phy, int qid, struct sk_buff *skb, if (!non_aql) return idx; - pending = atomic_inc_return(&wcid->non_aql_packets); + /* the hardware can report the completion on a different link */ + pending = atomic_inc_return(&mt76_wcid_primary(wcid)->non_aql_packets); if (stop && pending >= MT_MAX_NON_AQL_PKT) *stop = true; @@ -506,7 +509,8 @@ mt76_txq_send_burst(struct mt76_phy *phy, struct mt76_queue *q, return 0; } - if (atomic_read(&wcid->non_aql_packets) >= MT_MAX_NON_AQL_PKT) + if (atomic_read(&mt76_wcid_primary(wcid)->non_aql_packets) >= + MT_MAX_NON_AQL_PKT) return 0; skb = mt76_txq_dequeue(phy, mtxq); @@ -603,7 +607,8 @@ mt76_txq_schedule_list(struct mt76_phy *phy, enum mt76_txq_id qid) continue; } - if (atomic_read(&wcid->non_aql_packets) >= MT_MAX_NON_AQL_PKT) + if (atomic_read(&mt76_wcid_primary(wcid)->non_aql_packets) >= + MT_MAX_NON_AQL_PKT) continue; if (dev->queue_ops->tx_cleanup && q->queued + 2 * MT_TXQ_FREE_THR >= q->ndesc) { From 7c2e6b43c3082c2242ecfa5875b2b4b2cd06140f Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Tue, 1 Sep 2026 11:22:32 +0200 Subject: [PATCH 056/128] wifi: mt76: use ALTX queue for packets to disassociated stations Running these packets through regular buffering queues could cause them to hang, especially when the packets were for stations that in disassoc state. Fixes: dedf2ec30fe4 ("wifi: mt76: fix queue assignment for deauth packets") Acked-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260901092232.461379-1-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/main.c | 2 ++ drivers/net/wireless/mediatek/mt76/mt7996/main.c | 1 + drivers/net/wireless/mediatek/mt76/tx.c | 3 ++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/main.c b/drivers/net/wireless/mediatek/mt76/mt7915/main.c index a8286f8becf9..a03d89aabe80 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/main.c @@ -805,6 +805,7 @@ __mt7915_drop_sta(void *ptr, u8 *mac, struct ieee80211_vif *vif) msta = (struct mt7915_sta *)sta->drv_priv; mt7915_mcu_add_sta(data->dev, vif, sta, CONN_STATE_DISCONNECT, false); + msta->wcid.tx_info &= ~MT_WCID_TX_INFO_SET; msta->wcid.sta_disabled = 1; msta->wcid.sta = 0; } @@ -871,6 +872,7 @@ int mt7915_mac_sta_event(struct mt76_dev *mdev, struct ieee80211_vif *vif, mutex_unlock(&dev->mt76.mutex); mt7915_mcu_add_sta(dev, vif, sta, CONN_STATE_DISCONNECT, false); + msta->wcid.tx_info &= ~MT_WCID_TX_INFO_SET; msta->wcid.sta_disabled = 1; msta->wcid.sta = 0; return 0; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index d17d1dbb2038..dd7a01c02a7c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1433,6 +1433,7 @@ mt7996_mac_sta_event(struct mt7996_dev *dev, struct ieee80211_vif *vif, else if (sta->mlo && links == BIT(link_id)) /* last link */ mt7996_mcu_teardown_mld_sta(dev, link, msta_link); + msta_link->wcid.tx_info &= ~MT_WCID_TX_INFO_SET; msta_link->wcid.sta_disabled = 1; msta_link->wcid.sta = 0; links = links & ~BIT(link_id); diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c index b1e894b19cbd..3c623df2e741 100644 --- a/drivers/net/wireless/mediatek/mt76/tx.c +++ b/drivers/net/wireless/mediatek/mt76/tx.c @@ -680,7 +680,8 @@ mt76_txq_schedule_pending_wcid(struct mt76_phy *phy, struct mt76_wcid *wcid, if ((dev->drv->drv_flags & MT_DRV_HW_MGMT_TXQ) && !(info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP) && !ieee80211_is_data_present(hdr->frame_control) && - (!ieee80211_is_bufferable_mmpdu(skb) || + (!(wcid->tx_info & MT_WCID_TX_INFO_SET) || + !ieee80211_is_bufferable_mmpdu(skb) || ieee80211_is_deauth(hdr->frame_control) || ieee80211_is_disassoc(hdr->frame_control) || head == &wcid->tx_offchannel)) From 8f9e5807671ee97cacf1957f4a09e43f4f1017a9 Mon Sep 17 00:00:00 2001 From: Aaron Ma Date: Wed, 26 Aug 2026 12:36:58 +0800 Subject: [PATCH 057/128] wifi: mt76: mt7925: restore the legacy BSS after MLO teardown Removing the last link of an MLD interface leaves the firmware with the MLD BSS and DEV entry: the removal loop skips the deflink, and the hweight16(mvif->valid_links) fallback reads the stale pre-update valid_links, which is still non-zero. The entry keeps the per-link MAC address, so every subsequent legacy association fails authentication until the interface is removed or the module is reloaded. Remove the deflink BSS and add it back as a legacy BSS on the transition to zero links. Fixes: 69acd6d910b0 ("wifi: mt76: mt7925: add mt7925_change_vif_links") Signed-off-by: Aaron Ma Link: https://patch.msgid.link/20260826043658.2255553-1-aaron.ma@canonical.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/main.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index 5993b31e1aae..549f9c06b363 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -2294,6 +2294,18 @@ mt7925_change_vif_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif, mvif->valid_links = new_links; + /* Restore the legacy BSS after disabling MLO */ + if (old_links && !new_links) { + mt792x_mac_link_bss_remove(dev, &mvif->bss_conf, + &mvif->sta.deflink); + err = mt7925_mac_link_bss_add(dev, &vif->bss_conf, + &mvif->sta.deflink); + if (err < 0) { + mt792x_mutex_release(dev); + return err; + } + } + mt792x_mutex_release(dev); return 0; From 9c9e4e4c8b45873e53774ab8207825b37b7565ec Mon Sep 17 00:00:00 2001 From: Tao Gong Date: Sun, 23 Aug 2026 05:37:39 +0000 Subject: [PATCH 058/128] wifi: mt76: mt7996: fix struct mt7996_mcu_wed_rro_ba_delete_event layout Correct the definition of struct mt7996_mcu_wed_rro_ba_delete_event to match the 12-byte event record layout observed from firmware. The event TLV sent by firmware is 12 bytes in length rather than 8 bytes. It contains an additional 4 bytes that appear to be wlan_id and tid. For example, a captured batch deletion of sessions 8, 9, and 10 (TIDs 6, 0, and 1) shows: 00000000: 02 00 0c 00 08 00 01 00 06 00 00 00 02 00 0c 00 00000010: 09 00 01 00 00 00 00 00 02 00 0c 00 0a 00 01 00 00000020: 01 00 00 00 The previous 8-byte structure corrupts the parsing of subsequent TLVs, leading to unhandled delete events and erroneous session deletions. Consequently, clients that create and tear down BA sessions in batches (such as Apple Wi-Fi clients) fail to properly clean up stale sessions, causing HW_RRO to be lost upon subsequent reassociations. Fixes: a5d028d66836 ("wifi: mt76: mt7996: add wed rro delete session garbage collector") Signed-off-by: Tao Gong Link: https://patch.msgid.link/20260823053739.186748-1-gongtao0607@gmail.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mcu.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h index 74b70fb6da3d..1b272e904100 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h @@ -284,6 +284,9 @@ struct mt7996_mcu_wed_rro_ba_delete_event { __le16 len; __le16 session_id; + __le16 wlan_id; + u8 tid; + u8 __rsv1; u8 __rsv2[2]; } __packed; From 916c484d29d82f45c281da2786fcd6a1284430ce Mon Sep 17 00:00:00 2001 From: Nicolas Cavallari Date: Mon, 10 Aug 2026 17:45:10 +0200 Subject: [PATCH 059/128] wifi: mt76: mt7915: disable rx 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 rx napi inside mt7915e itself, even through napi rx is enabled in the mt76 generic code. Disabling it in the mt76 generic code was previously attempted and reverted because napi was disabled too late and rx handlers could access freed structures. Note that the tx side is enabled and disabled by the mt76 generic code. It is disabled in mt7915_stop_hardware() since, unlike mt7915_unregister_device(), is it also called in the probe path if an error happens after rx napi is enabled. Fixes: e57b7901469f ("mt76: add mac80211 driver for MT7915 PCIe-based chipsets") Signed-off-by: Nicolas Cavallari Link: https://patch.msgid.link/20260810154923.32546-1-nicolas.cavallari@green-communications.fr Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/init.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/init.c b/drivers/net/wireless/mediatek/mt76/mt7915/init.c index ca46a203aa48..c8e389db63a7 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/init.c @@ -1238,6 +1238,12 @@ static void mt7915_unregister_ext_phy(struct mt7915_dev *dev) static void mt7915_stop_hardware(struct mt7915_dev *dev) { + int i; + + mt76_for_each_q_rx(&dev->mt76, i) { + if (!mt76_queue_is_wed_rro(&dev->mt76.q_rx[i])) + napi_disable(&dev->mt76.napi[i]); + } mt7915_mcu_exit(dev); mt76_connac2_tx_token_put(&dev->mt76); mt7915_dma_cleanup(dev); From 5796b2bf9ff718342773ac760d86eb126c112a4f Mon Sep 17 00:00:00 2001 From: Devin Wittmayer Date: Thu, 10 Sep 2026 19:44:28 -0700 Subject: [PATCH 060/128] wifi: mt76: mt7925: don't put the band auto marker in TGID band_idx starts out as 0xff, which tells the firmware to pick the band on its own. A remain-on-channel grant normally writes a real index over it before anything else looks. Two cases never see that: an MLD station throws the grant away, and an AP has no association to trigger one. The TX descriptor reads the same field into a two-bit slot, so the marker comes out as 3. Every data frame from those two goes out tagged band 3. Beacons are fine, they reach the firmware separately and never build a descriptor. Same numbers on an MT7925 and on a Netgear A9000: stock patched station, one link 0x00000001 0x00000001 station, two links 0x00003001 0x00000001 AP 0x00003013 0x00000013 real band index, STA 0x00001001 0x00001001 real band index, AP 0x00001013 0x00001013 Only the marker cases move. Fixes: 0cb349d742d1 ("wifi: mt76: mt7925: update mt7925_mac_link_bss_add for MLO") Signed-off-by: Devin Wittmayer Link: https://patch.msgid.link/20260911024428.65244-1-lucid_duck@justthetip.ca Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/mac.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c index 101f571b027f..3b82c9de26c5 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c @@ -798,7 +798,8 @@ mt7925_mac_write_txwi(struct mt76_dev *dev, __le32 *txwi, val = FIELD_PREP(MT_TXD1_WLAN_IDX, wcid->idx) | FIELD_PREP(MT_TXD1_OWN_MAC, omac_idx); - if (band_idx) + /* 0xff means BAND_AUTO to the firmware, and TGID cannot encode it */ + if (band_idx && band_idx != 0xff) val |= FIELD_PREP(MT_TXD1_TGID, band_idx); txwi[1] = cpu_to_le32(val); From 456de496b53ae73e6e3020fec815dbdc88aa7c07 Mon Sep 17 00:00:00 2001 From: Cristian Papa Date: Thu, 3 Sep 2026 22:07:02 -0300 Subject: [PATCH 061/128] wifi: mt76: mt7603: initialize global station WCID mt7603 uses a driver-private global station WCID for frames that have neither a station nor a vif, including frames injected through a monitor interface. Unlike the core global WCID initialized by mt76_register_device(), this WCID is published without ever being passed to mt76_wcid_init(). Since commit 0335c034e726 ("wifi: mt76: fix race condition related to checking tx queue fill status"), mt76_tx() queues every such frame on the WCID's tx_pending or tx_offchannel queue and links its tx_list. The first injected frame therefore reaches __skb_queue_tail() with an uninitialized sk_buff_head and dereferences a NULL prev pointer. Initialize the WCID before publishing it. The fix was tested on an MT7603 PCIe device. Three wildcard probe requests were injected through a monitor interface. The hardware queue head and tail advanced from 0 to 3 and a second radio captured the third request over the air. No oops or reset occurred. After restoring AP mode, a 150 MB transfer also completed without a reset. Fixes: 0335c034e726 ("wifi: mt76: fix race condition related to checking tx queue fill status") Cc: stable@vger.kernel.org Signed-off-by: Cristian Papa Link: https://patch.msgid.link/20260904010702.16306-1-pcristian292@gmail.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7603/init.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7603/init.c b/drivers/net/wireless/mediatek/mt76/mt7603/init.c index 10f2ec70c792..218ec406d2a8 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7603/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7603/init.c @@ -230,6 +230,7 @@ mt7603_mac_init(struct mt7603_dev *dev) eth_broadcast_addr(bc_addr); mt7603_wtbl_init(dev, MT7603_WTBL_RESERVED, -1, bc_addr); + mt76_wcid_init(&dev->global_sta.wcid, 0); dev->global_sta.wcid.idx = MT7603_WTBL_RESERVED; rcu_assign_pointer(dev->mt76.wcid[MT7603_WTBL_RESERVED], &dev->global_sta.wcid); From 45f53d98cbaa078907b6a300ff3974dfb5ae8776 Mon Sep 17 00:00:00 2001 From: Junjie Cao Date: Thu, 10 Sep 2026 09:23:00 +0800 Subject: [PATCH 062/128] wifi: mt76: mt792x: treat 0xff ACPI SAR entries as no limit Some ASUS firmware carries 0xff in MTGS power slots. The driver narrows the byte to s8, so the slot reads as -1 in its 0.5 dBm units, wins the min() against the regulatory limit and caps every rate the driver programs. On an ASUS Zenbook 14 UM3406HA (MT7922) all six MTGS tables hold 0xff in every power byte, and txpower_sku shows tmac 3 on every supported OFDM/HT/VHT/HE rate against EEPROM entries of 25-39, under both the FR and the world regulatory domain. Huy Nguyen Dinh Quang saw the same clamp on a Vivobook S 14 M5406WA, with 5 GHz TX at ~40 Mbps, and proposed skipping the geo entry. Skip a geo entry whose power byte is 0xff. The MTDS entry is narrowed the same way on its way into the geo lookup, so give it the same treatment, and clamp the dynamic value plus offset at 127 so a no-limit entry cannot wrap. Fixes: f965333e491e ("mt76: mt7921: introduce ACPI SAR support") Link: https://bugzilla.redhat.com/show_bug.cgi?id=2515420 Reported-by: Huy Nguyen Dinh Quang Closes: https://lore.kernel.org/linux-wireless/20260827134820.2607530-1-huynguyendinhquang@gmail.com/ Signed-off-by: Junjie Cao Tested-by: Cristian Fleischer Link: https://patch.msgid.link/acb947435fc78d5c06039c7bd5609c735468b0ee.1789002236.git.junjie.cao@intel.com Signed-off-by: Felix Fietkau --- .../wireless/mediatek/mt76/mt792x_acpi_sar.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c index b468051fbe68..0ffb277f9909 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c @@ -279,10 +279,18 @@ mt792x_asar_get_geo_pwr(struct mt792x_phy *phy, if (idx >= max) return dyn_power; - geo_power = (band_pwr + idx)->pwr; - dyn_power += (band_pwr + idx)->offset; + band_pwr += idx; - return min(geo_power, dyn_power); + /* Some OEM tables carry 0xff here; narrowed to s8 that is -1 and + * wins the min() below. Take it as no limit. + */ + if (band_pwr->pwr == 0xff) + return dyn_power; + + geo_power = band_pwr->pwr; + + return min_t(int, geo_power, + min_t(int, dyn_power + band_pwr->offset, 127)); } static s8 @@ -315,7 +323,9 @@ mt792x_asar_range_pwr(struct mt792x_phy *phy, else band = NL80211_BAND_2GHZ; - return mt792x_asar_get_geo_pwr(phy, band, limit[idx]); + /* 0xff: no dynamic limit, see mt792x_asar_get_geo_pwr() */ + return mt792x_asar_get_geo_pwr(phy, band, + limit[idx] == 0xff ? 127 : limit[idx]); } int mt792x_init_acpi_sar_power(struct mt792x_phy *phy, bool set_default) From af97bf5a7e72da9026de184676df8e2fa57e1058 Mon Sep 17 00:00:00 2001 From: Devin Wittmayer Date: Fri, 4 Sep 2026 18:35:51 -0700 Subject: [PATCH 063/128] wifi: mt76: mt792x: pick the SAR table layout from the table itself The SAR power tables are parsed with a version byte from a different ACPI table, never their own. Some firmware declares version 1 and ships version 2 tables. The entries then start a byte early, and the 6 GHz limits the table carries are dropped, because the older layout has no room for them. ASUS GA503RM, MTCL declares v1: MTGS 28 bytes v1: (28-6) % 5 = 2, no fit v2: 3 tables MTDS 19 bytes v1: (19-6) % 6 = 1, no fit v2: 1 table The layouts accept disjoint lengths, so take whichever one fits and keep it per table. Reported-by: Sagun Kayastha Closes: https://github.com/morrownr/mt76/issues/62 Fixes: f965333e491e ("mt76: mt7921: introduce ACPI SAR support") Signed-off-by: Devin Wittmayer Link: https://patch.msgid.link/20260905013551.24635-1-lucid_duck@justthetip.ca Signed-off-by: Felix Fietkau --- .../wireless/mediatek/mt76/mt792x_acpi_sar.c | 116 +++++++++++------- .../wireless/mediatek/mt76/mt792x_acpi_sar.h | 2 + 2 files changed, 76 insertions(+), 42 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c index 0ffb277f9909..e87dfb0f1b3a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c @@ -108,69 +108,99 @@ mt792x_asar_acpi_read_mtcl(struct mt792x_dev *dev, u8 **table, u8 *version) return ret; } +/* A table's layout is decided by the version byte in MTCL, but some firmware + * labels a version 2 table as version 1. Accept whichever layout the table + * actually has: for every accepted entry count the two do not overlap, since + * one is 6 + 5n bytes and the other 7 + 7n. + */ +static bool +mt792x_asar_fits(u8 *table, int len, int prelen, int sarlen, int nr_off, + int min, int max) +{ + int tblcnt; + + if (len < prelen || (len - prelen) % sarlen) + return false; + + tblcnt = (len - prelen) / sarlen; + if (tblcnt < min || tblcnt > max) + return false; + + return tblcnt == table[nr_off]; +} + /* MTDS : Dynamic SAR Power Table */ static int -mt792x_asar_acpi_read_mtds(struct mt792x_dev *dev, u8 **table, u8 version) +mt792x_asar_acpi_read_mtds(struct mt792x_dev *dev, u8 **table, u8 version, + u8 *used) { - int len, ret, sarlen, prelen, tblcnt; - bool enable; + int len, ret; + u8 *t; ret = mt792x_acpi_read(dev, MT792x_ACPI_MTDS, table, &len); if (ret) return ret; - /* Table content validation */ - switch (version) { - case 1: - enable = ((struct mt792x_asar_dyn *)*table)->enable; - sarlen = sizeof(struct mt792x_asar_dyn_limit); - prelen = sizeof(struct mt792x_asar_dyn); - break; - case 2: - enable = ((struct mt792x_asar_dyn_v2 *)*table)->enable; - sarlen = sizeof(struct mt792x_asar_dyn_limit_v2); - prelen = sizeof(struct mt792x_asar_dyn_v2); - break; - default: - return -EINVAL; - } + t = *table; - tblcnt = (len - prelen) / sarlen; - if (!enable || - tblcnt > MT792x_ASAR_MAX_DYN || tblcnt < MT792x_ASAR_MIN_DYN) + /* Table content validation */ + if (mt792x_asar_fits(t, len, sizeof(struct mt792x_asar_dyn), + sizeof(struct mt792x_asar_dyn_limit), + offsetof(struct mt792x_asar_dyn, nr_tbl), + MT792x_ASAR_MIN_DYN, MT792x_ASAR_MAX_DYN)) + *used = 1; + else if (mt792x_asar_fits(t, len, sizeof(struct mt792x_asar_dyn_v2), + sizeof(struct mt792x_asar_dyn_limit_v2), + offsetof(struct mt792x_asar_dyn_v2, nr_tbl), + MT792x_ASAR_MIN_DYN, MT792x_ASAR_MAX_DYN)) + *used = 2; + else return -EINVAL; + if (!t[offsetof(struct mt792x_asar_dyn, enable)]) + return -EINVAL; + + if (version && *used != version) + dev_info(dev->mt76.dev, + "MTDS is v%u, MTCL says v%u; using v%u\n", + *used, version, *used); + return 0; } /* MTGS : Geo SAR Power Table */ static int -mt792x_asar_acpi_read_mtgs(struct mt792x_dev *dev, u8 **table, u8 version) +mt792x_asar_acpi_read_mtgs(struct mt792x_dev *dev, u8 **table, u8 version, + u8 *used) { - int len, ret, sarlen, prelen, tblcnt; + int len, ret; + u8 *t; ret = mt792x_acpi_read(dev, MT792x_ACPI_MTGS, table, &len); if (ret) return ret; - /* Table content validation */ - switch (version) { - case 1: - sarlen = sizeof(struct mt792x_asar_geo_limit); - prelen = sizeof(struct mt792x_asar_geo); - break; - case 2: - sarlen = sizeof(struct mt792x_asar_geo_limit_v2); - prelen = sizeof(struct mt792x_asar_geo_v2); - break; - default: - return -EINVAL; - } + t = *table; - tblcnt = (len - prelen) / sarlen; - if (tblcnt > MT792x_ASAR_MAX_GEO || tblcnt < MT792x_ASAR_MIN_GEO) + /* Table content validation */ + if (mt792x_asar_fits(t, len, sizeof(struct mt792x_asar_geo), + sizeof(struct mt792x_asar_geo_limit), + offsetof(struct mt792x_asar_geo, nr_tbl), + MT792x_ASAR_MIN_GEO, MT792x_ASAR_MAX_GEO)) + *used = 1; + else if (mt792x_asar_fits(t, len, sizeof(struct mt792x_asar_geo_v2), + sizeof(struct mt792x_asar_geo_limit_v2), + offsetof(struct mt792x_asar_geo_v2, nr_tbl), + MT792x_ASAR_MIN_GEO, MT792x_ASAR_MAX_GEO)) + *used = 2; + else return -EINVAL; + if (version && *used != version) + dev_info(dev->mt76.dev, + "MTGS is v%u, MTCL says v%u; using v%u\n", + *used, version, *used); + return 0; } @@ -205,14 +235,16 @@ int mt792x_init_acpi_sar(struct mt792x_dev *dev) asar->countrylist = NULL; } - ret = mt792x_asar_acpi_read_mtds(dev, (u8 **)&asar->dyn, asar->ver); + ret = mt792x_asar_acpi_read_mtds(dev, (u8 **)&asar->dyn, asar->ver, + &asar->dyn_ver); if (ret) { devm_kfree(dev->mt76.dev, asar->dyn); asar->dyn = NULL; } /* MTGS is optional */ - ret = mt792x_asar_acpi_read_mtgs(dev, (u8 **)&asar->geo, asar->ver); + ret = mt792x_asar_acpi_read_mtgs(dev, (u8 **)&asar->geo, asar->ver, + &asar->geo_ver); if (ret) { devm_kfree(dev->mt76.dev, asar->geo); asar->geo = NULL; @@ -254,7 +286,7 @@ mt792x_asar_get_geo_pwr(struct mt792x_phy *phy, break; } - if (asar->ver == 1) { + if (asar->geo_ver == 1) { band_pwr = &asar->geo->tbl[idx].band[0]; max = ARRAY_SIZE(asar->geo->tbl[idx].band); } else { @@ -305,7 +337,7 @@ mt792x_asar_range_pwr(struct mt792x_phy *phy, if (!capa) return 127; - if (asar->ver == 1) { + if (asar->dyn_ver == 1) { limit = &asar->dyn->tbl[0].frp[0]; max = ARRAY_SIZE(asar->dyn->tbl[0].frp); } else { diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.h b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.h index 474033073831..1d42f24b05f6 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.h @@ -105,6 +105,8 @@ struct mt792x_asar_fg { struct mt792x_acpi_sar { u8 ver; + u8 dyn_ver; + u8 geo_ver; union { struct mt792x_asar_dyn *dyn; struct mt792x_asar_dyn_v2 *dyn_v2; From 52f6a065e0810c739a5dabd33a58047f08100f50 Mon Sep 17 00:00:00 2001 From: Lu Huang Date: Wed, 30 Sep 2026 16:54:35 +0800 Subject: [PATCH 064/128] wifi: mt76: mt7996: fix uninitialized buf read in mt7996_variant_fem_init() When the eFuse block covering MT7976C_EFUSE_OFFSET is invalid, mt7996_mcu_get_eeprom() returns -EINVAL without writing buf, but mt7996_variant_fem_init() tolerates -EINVAL and keeps running, so buf[idx] == 0xc reads uninitialized stack memory. A stale 0x0c byte misclassifies a 0x7976 aDie as 7976C, selects MT7996_FEM_INT and the wrong default EEPROM, overriding valid calibration data. Only consult the eFuse byte when the read succeeded; with an invalid eFuse, fall back to the three explicit aDie version checks. Fixes: e8cb33ad546a ("wifi: mt76: mt7996: add support for more variants") Cc: stable@vger.kernel.org Signed-off-by: Lu Huang Link: https://patch.msgid.link/20260930085435.1424493-1-huanglu@kylinos.cn Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/init.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index 2db63c3be9d1..c1d860029801 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -1248,7 +1248,7 @@ static int mt7996_variant_fem_init(struct mt7996_dev *dev) adie_ver = u32_get_bits(regval, MT_ADIE_VERSION_MASK); idx = MT7976C_EFUSE_OFFSET % MT7996_EEPROM_BLOCK_SIZE; is_7976c = adie_ver == 0x8a10 || adie_ver == 0x8b00 || - adie_ver == 0x8c10 || buf[idx] == 0xc; + adie_ver == 0x8c10 || (!ret && buf[idx] == 0xc); adie_id = u32_get_bits(regval, MT_ADIE_CHIP_ID_MASK); if (adie_id == 0x7975 || adie_id == 0x7979 || From ff9b465ddb95d7842282998348df88ff55f62c4a Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Fri, 7 Aug 2026 21:25:53 +0800 Subject: [PATCH 065/128] wifi: nxpwifi: protect sta_list against concurrent add and delete nxpwifi_del_sta_entry() unlinks an entry from priv->sta_list without holding priv->sta_list_spinlock. The comment above it gives the reason: the function is only reached from cfg80211_ops.del_station, which runs under the wiphy mutex. That mutex serialises this callback against other users of the same lock, including wiphy_work items. It covers neither of the two other writers of the same list: nxpwifi_add_sta_entry() from nxpwifi_uap_event_sta_assoc() nxpwifi_del_all_sta_list() from nxpwifi_uap_event_bss_idle() Both run from nxpwifi_process_event() on the driver's main work queue, a plain workqueue that never takes the wiphy lock, and both take sta_list_spinlock around their update. A station associating, or the BSS going idle, can therefore modify priv->sta_list while a hostapd del_station is walking it and unlinking an entry. Take the lock here too. As the traversal then runs under the write lock, list_for_each_entry() replaces the _rcu variant, matching nxpwifi_del_all_sta_list(). The same call path reaches nxpwifi_get_sta_entry() without rcu_read_lock(). Entries are freed with kfree_rcu() and every other caller holds it, so take it around the lookup as well. Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Signed-off-by: Linmao Li Tested-by: Jeff Chen Reviewed-by: Jeff Chen Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/cfg80211.c | 2 ++ drivers/net/wireless/nxp/nxpwifi/util.c | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c index 86304ea3331c..161697e038c0 100644 --- a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c +++ b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c @@ -1539,9 +1539,11 @@ nxpwifi_cfg80211_del_station(struct wiphy *wiphy, struct wireless_dev *wdev, eth_zero_addr(deauth_mac); + rcu_read_lock(); sta_node = nxpwifi_get_sta_entry(priv, params->mac); if (sta_node) ether_addr_copy(deauth_mac, params->mac); + rcu_read_unlock(); if (is_valid_ether_addr(deauth_mac)) { ret = nxpwifi_uap_sta_deauth(priv, deauth_mac); diff --git a/drivers/net/wireless/nxp/nxpwifi/util.c b/drivers/net/wireless/nxp/nxpwifi/util.c index bbfefb81d8d3..ae8444a14105 100644 --- a/drivers/net/wireless/nxp/nxpwifi/util.c +++ b/drivers/net/wireless/nxp/nxpwifi/util.c @@ -1041,19 +1041,23 @@ nxpwifi_set_sta_ht_cap(struct nxpwifi_private *priv, const u8 *ies, } } -/* Delete a station from list; called under cfg80211 mutex. */ +/* Delete a station from list. */ void nxpwifi_del_sta_entry(struct nxpwifi_private *priv, const u8 *mac) { struct nxpwifi_sta_node *node; - list_for_each_entry_rcu(node, &priv->sta_list, list) { + spin_lock_bh(&priv->sta_list_spinlock); + + list_for_each_entry(node, &priv->sta_list, list) { if (!memcmp(node->mac_addr, mac, ETH_ALEN)) { list_del_rcu(&node->list); kfree_rcu(node, rcu); break; } } + + spin_unlock_bh(&priv->sta_list_spinlock); } /* Delete all stations from list. */ From 128411832c62ca517e4abd938229f0ee40823fdb Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Fri, 7 Aug 2026 21:25:54 +0800 Subject: [PATCH 066/128] wifi: nxpwifi: delete the station entry on the uAP deauth event When a station leaves, the firmware reports EVENT_UAP_STA_DEAUTH. nxpwifi_uap_event_sta_deauth() tears down the reorder, BA and WMM state for that peer but leaves its nxpwifi_sta_node on priv->sta_list. Nothing else removes it. cfg80211_del_sta() only reports the departure upwards, it does not call back into cfg80211_ops.del_station, and that callback is the only caller of nxpwifi_del_sta_entry(). The entry therefore survives until the BSS goes idle and nxpwifi_del_all_sta_list() drops everything. Until then the departed station still looks associated to the driver. nxpwifi_handle_uap_rx_forward() finds the stale node for that address, treats the frame as intra-BSS traffic and requeues it for transmission instead of passing it up the stack, so traffic for that address is misrouted. The list also keeps one entry per station that has ever associated, and a station that comes back reuses the stale node along with its old statistics. mwifiex, which nxpwifi was derived from, deletes the entry in the same event handler. Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Signed-off-by: Linmao Li Tested-by: Jeff Chen Reviewed-by: Jeff Chen Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/uap_event.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/nxp/nxpwifi/uap_event.c b/drivers/net/wireless/nxp/nxpwifi/uap_event.c index ab5c15686f54..20b7b53db236 100644 --- a/drivers/net/wireless/nxp/nxpwifi/uap_event.c +++ b/drivers/net/wireless/nxp/nxpwifi/uap_event.c @@ -75,6 +75,7 @@ nxpwifi_uap_event_sta_deauth(struct nxpwifi_private *priv) nxpwifi_del_tx_ba_stream_tbl_by_ra(priv, deauth_mac); } nxpwifi_wmm_del_peer_ra_list(priv, deauth_mac); + nxpwifi_del_sta_entry(priv, deauth_mac); return 0; } From 5c9260d7c6990875fc788ebb86ea2bf08b492704 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 13 Aug 2026 16:23:13 +0800 Subject: [PATCH 067/128] wifi: nxpwifi: wait for the wakeup timer before the adapter is freed nxpwifi_adapter_cleanup() stops adapter->wakeup_timer with timer_delete(), which does not wait for a running callback. The callback goes on using the adapter: wakeup_timer_fn() sets adapter->hw_status, walks the command queues through nxpwifi_cancel_all_pending_cmd() and calls adapter->if_ops.card_reset(). Both paths that reach nxpwifi_adapter_cleanup() free the adapter right afterwards, through nxpwifi_free_adapter() in nxpwifi_remove_card() and in the nxpwifi_add_card() error unwind. Use timer_delete_sync() so the callback has finished before the adapter is released. mwifiex fixed the same issue in commit ae5e95d41574 ("wifi: mwifiex: fix use-after-free in mwifiex_adapter_cleanup()"). Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Signed-off-by: Linmao Li Reviewed-by: Jeff Chen Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/init.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/nxp/nxpwifi/init.c b/drivers/net/wireless/nxp/nxpwifi/init.c index 7c75dca5d0a8..3c4df37333d8 100644 --- a/drivers/net/wireless/nxp/nxpwifi/init.c +++ b/drivers/net/wireless/nxp/nxpwifi/init.c @@ -328,7 +328,7 @@ static void nxpwifi_invalidate_lists(struct nxpwifi_adapter *adapter) static void nxpwifi_adapter_cleanup(struct nxpwifi_adapter *adapter) { - timer_delete(&adapter->wakeup_timer); + timer_delete_sync(&adapter->wakeup_timer); nxpwifi_cancel_all_pending_cmd(adapter); wake_up_interruptible(&adapter->cmd_wait_q.wait); wake_up_interruptible(&adapter->hs_activate_wait_q); From d68d6d8d8bbb5596909479dc6cac58132d8f0dbf Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 13 Aug 2026 16:23:14 +0800 Subject: [PATCH 068/128] wifi: nxpwifi: free the aggregation buffer when the RA list disappears nxpwifi_11n_aggregate_pkt() drops ra_list_spinlock while it copies each subframe, so it rechecks the RA list after taking the lock again. The check inside the aggregation loop returns without releasing the skb_aggr it has been filling, leaking one tx_buf_size buffer along with the subframes already aggregated into it. Release skb_aggr there, the way the same check on the -EBUSY path already does. mwifiex fixed the same issue in commit 990a73dec3fd ("wifi: mwifiex: Fix memory leak in mwifiex_11n_aggregate_pkt()"). Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Signed-off-by: Linmao Li Reviewed-by: Jeff Chen Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/11n_aggr.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/nxp/nxpwifi/11n_aggr.c b/drivers/net/wireless/nxp/nxpwifi/11n_aggr.c index be7080f2a6ce..54933c42c960 100644 --- a/drivers/net/wireless/nxp/nxpwifi/11n_aggr.c +++ b/drivers/net/wireless/nxp/nxpwifi/11n_aggr.c @@ -168,6 +168,7 @@ nxpwifi_11n_aggregate_pkt(struct nxpwifi_private *priv, if (!nxpwifi_is_ralist_valid(priv, pra_list, ptrindex)) { spin_unlock_bh(&priv->wmm.ra_list_spinlock); + nxpwifi_write_data_complete(adapter, skb_aggr, 1, -1); return -ENOENT; } From 050f03bb0effd773c877cfba3b651c6d380b1c15 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 13 Aug 2026 16:23:15 +0800 Subject: [PATCH 069/128] wifi: nxpwifi: zero the channel statistics array adapter->chan_stats comes from vmalloc(), which does not clear the memory, and nxpwifi_cfg80211_dump_survey() reads it as soon as user space asks for survey data. For the entries no scan has filled in, a non-zero cca_scan_dur passes the validity check and stale bytes are reported to user space as noise, time and time_busy. Use kcalloc() instead. The array holds two entries per supported channel, small enough not to need vmalloc(), and the two callers change to kfree() accordingly. mwifiex fixed the same issue in commit 0e20450829ca ("wifi: mwifiex: Initialize the chan_stats array to zero"). Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Signed-off-by: Linmao Li Reviewed-by: Jeff Chen Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/cfg80211.c | 5 +++-- drivers/net/wireless/nxp/nxpwifi/main.c | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c index 161697e038c0..788b83356981 100644 --- a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c +++ b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c @@ -3731,8 +3731,9 @@ int nxpwifi_init_channel_scan_gap(struct nxpwifi_adapter *adapter) * additional active scan request for hidden SSIDs on passive channels. */ adapter->num_in_chan_stats = 2 * (n_channels_bg + n_channels_a); - adapter->chan_stats = vmalloc(array_size(sizeof(*adapter->chan_stats), - adapter->num_in_chan_stats)); + adapter->chan_stats = kcalloc(adapter->num_in_chan_stats, + sizeof(*adapter->chan_stats), + GFP_KERNEL); if (!adapter->chan_stats) return -ENOMEM; diff --git a/drivers/net/wireless/nxp/nxpwifi/main.c b/drivers/net/wireless/nxp/nxpwifi/main.c index 55b962430f37..9e13348105c3 100644 --- a/drivers/net/wireless/nxp/nxpwifi/main.c +++ b/drivers/net/wireless/nxp/nxpwifi/main.c @@ -644,7 +644,7 @@ static int _nxpwifi_fw_dpc(const struct firmware *firmware, void *context) goto done; err_add_intf: - vfree(adapter->chan_stats); + kfree(adapter->chan_stats); err_init_chan_scan: wiphy_unregister(adapter->wiphy); wiphy_free(adapter->wiphy); @@ -1384,7 +1384,7 @@ static void nxpwifi_uninit_sw(struct nxpwifi_adapter *adapter) wiphy_free(adapter->wiphy); adapter->wiphy = NULL; - vfree(adapter->chan_stats); + kfree(adapter->chan_stats); nxpwifi_free_cmd_buffers(adapter); } From 7769bb2457fcfa8216aeae84f32cb7084cab5637 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 20 Aug 2026 16:16:33 +0800 Subject: [PATCH 070/128] wifi: nxpwifi: fix the authentication frame length handling nxpwifi_cfg80211_authenticate() builds a 3-address management frame in a temporary buffer and hands it to nxpwifi_form_mgmt_frame(), which copies it into the skb and splices in address4. The frame length is tracked in a single u16 that conflates the two lengths, and it gets both wrong. Truncation. req->ie_len and req->auth_data_len are both size_t. The IE policy caps req->ie_len at IEEE80211_MAX_DATA_LEN, but NL80211_ATTR_AUTH_DATA only has a four-byte minimum length policy. Since nla_len is a u16, a single authentication data attribute can carry up to 65531 bytes, enough for the combined frame length to exceed U16_MAX before it is assigned to pkt_len. With auth_data_len == 65510 and no IEs the length wraps to 6, kzalloc(6) succeeds, and the memcpy() below then writes 65506 user-provided bytes past a 6-byte heap object. Geometry. NXPWIFI_MGMT_HEADER_LEN is 30, which is the 24-byte 3-address header plus the address4 that the firmware expects. The temporary buffer only holds a struct ieee80211_hdr_3addr, so six of the bytes accounted for are never used, and passing that length to nxpwifi_form_mgmt_frame() makes it append ETH_ALEN on top of a length that already included it. The skb is sized without those six bytes, so the last skb_put_data() overruns the tailroom by exactly ETH_ALEN. It usually goes unnoticed because SKB_DATA_ALIGN() rounding leaves slack. The driver's other caller of the helper, nxpwifi_cfg80211_mgmt_tx(), adds ETH_ALEN to the skb size before allocating and passes the 3-address length, which is what this path should do too. Keep the two lengths apart. frame_len is the 3-address frame the driver builds: it sizes the temporary buffer and is what the helper is told. pkt_len is what the firmware sees, frame_len plus address4: it sizes the skb and goes into tx_info. Computing frame_len in size_t and rejecting anything that cannot be represented once ETH_ALEN is added removes the wrap. The value handed to the firmware is unchanged, so this is not a behavioural change for frames that were already valid. A tighter 802.11 bound may make sense, but that depends on the firmware's frame format and is a separate decision. Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Signed-off-by: Linmao Li Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/cfg80211.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c index 788b83356981..3221ae167c58 100644 --- a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c +++ b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c @@ -3325,6 +3325,7 @@ nxpwifi_cfg80211_authenticate(struct wiphy *wiphy, struct nxpwifi_adapter *adapter = priv->adapter; struct sk_buff *skb; u16 pkt_len, auth_alg; + size_t frame_len; int ret; struct ieee80211_mgmt *mgmt; struct nxpwifi_txinfo *tx_info; @@ -3398,13 +3399,23 @@ nxpwifi_cfg80211_authenticate(struct wiphy *wiphy, nxpwifi_cancel_scan(adapter); - pkt_len = (u16)req->ie_len + req->auth_data_len + - NXPWIFI_MGMT_HEADER_LEN + NXPWIFI_AUTH_BODY_LEN; + frame_len = req->ie_len + req->auth_data_len + + sizeof(struct ieee80211_hdr_3addr) + NXPWIFI_AUTH_BODY_LEN; if (req->auth_data_len >= 4) - pkt_len -= 4; + frame_len -= 4; - mgmt = kzalloc(pkt_len, GFP_KERNEL); + /* nxpwifi_form_mgmt_frame() inserts address4, so the frame handed to + * the firmware is ETH_ALEN longer than the one built here. + */ + if (frame_len > U16_MAX - ETH_ALEN) { + nxpwifi_dbg(adapter, ERROR, + "auth frame too long: %zu bytes\n", frame_len); + return -EINVAL; + } + pkt_len = frame_len + ETH_ALEN; + + mgmt = kzalloc(frame_len, GFP_KERNEL); skb = dev_alloc_skb(NXPWIFI_MIN_DATA_HEADER_LEN + NXPWIFI_MGMT_FRAME_HEADER_SIZE + @@ -3450,7 +3461,7 @@ nxpwifi_cfg80211_authenticate(struct wiphy *wiphy, memcpy((u8 *)varptr, req->ie, req->ie_len); } - nxpwifi_form_mgmt_frame(skb, (const u8 *)mgmt, pkt_len); + nxpwifi_form_mgmt_frame(skb, (const u8 *)mgmt, frame_len); kfree(mgmt); priv->auth_flag = HOST_MLME_AUTH_PENDING; priv->auth_alg = auth_alg; From 7fa5fb9976c4532007a666624dc241021f74c8a9 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 20 Aug 2026 16:16:34 +0800 Subject: [PATCH 071/128] wifi: nxpwifi: handle authentication frame allocation failures nxpwifi_cfg80211_authenticate() allocates the buffer it builds the management frame in and never checks the result: mgmt = kzalloc(frame_len, GFP_KERNEL); skb = dev_alloc_skb(...); if (!skb) { ... return -ENOMEM; } ... memcpy(mgmt->da, req->bss->bssid, ETH_ALEN); On allocation failure the memcpy() a dozen lines later dereferences NULL. The same sequence leaks mgmt when dev_alloc_skb() fails: that path returns without freeing it, while the success path drops it after nxpwifi_form_mgmt_frame(). Bail out when the allocation fails, and free it before returning on the skb error path. Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Signed-off-by: Linmao Li Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/cfg80211.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c index 3221ae167c58..473e81304ca8 100644 --- a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c +++ b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c @@ -3416,6 +3416,8 @@ nxpwifi_cfg80211_authenticate(struct wiphy *wiphy, pkt_len = frame_len + ETH_ALEN; mgmt = kzalloc(frame_len, GFP_KERNEL); + if (!mgmt) + return -ENOMEM; skb = dev_alloc_skb(NXPWIFI_MIN_DATA_HEADER_LEN + NXPWIFI_MGMT_FRAME_HEADER_SIZE + @@ -3423,6 +3425,7 @@ nxpwifi_cfg80211_authenticate(struct wiphy *wiphy, if (!skb) { nxpwifi_dbg(adapter, ERROR, "allocate skb failed for management frame\n"); + kfree(mgmt); return -ENOMEM; } From 32d1a000e99612e010335760363936d85591fd69 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Wed, 26 Aug 2026 00:17:09 +0100 Subject: [PATCH 072/128] wifi: nxpwifi: fix inverted check in Tx BA stream entry deletion nxpwifi_is_tx_ba_stream_ptr_valid() returns true when the entry is still linked, and every caller passes an entry that is on the list, so the early return always fires and nothing is ever unlinked or freed. Entries leak on every teardown and, since nxpwifi_space_avail_for_new_ba_stream() counts them, Tx aggregation stops being negotiated once the stale count reaches the maximum. Changing the original dead && test to || to silence a NULL dereference report inverted the validity test along with it. Fixes: 00c786a7581e ("wifi: nxpwifi: fix multiple static analysis errors and warnings") Assisted-by: Claude:claude-opus-5 Signed-off-by: David Carlier Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/11n.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/nxp/nxpwifi/11n.c b/drivers/net/wireless/nxp/nxpwifi/11n.c index c2a54d781b42..25b2e430f3f9 100644 --- a/drivers/net/wireless/nxp/nxpwifi/11n.c +++ b/drivers/net/wireless/nxp/nxpwifi/11n.c @@ -451,7 +451,7 @@ void nxpwifi_11n_delete_tx_ba_stream_tbl_entry(struct nxpwifi_private *priv, struct nxpwifi_tx_ba_stream_tbl *tbl) { - if (!tbl || nxpwifi_is_tx_ba_stream_ptr_valid(priv, tbl)) + if (!tbl || !nxpwifi_is_tx_ba_stream_ptr_valid(priv, tbl)) return; nxpwifi_dbg(priv->adapter, INFO, From 5e99bdb94720331e2fd38c10c5b1afa1ce640835 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Wed, 26 Aug 2026 00:17:10 +0100 Subject: [PATCH 073/128] wifi: nxpwifi: do not delete Rx reorder entries under RCU The RCU guard is declared in the body of the per-TID loop, so it is still held across the teardown pass. nxpwifi_del_rx_reorder_entry() flushes the Rx workqueue and deletes the reorder timer synchronously, both of which sleep, so a station deauthenticating from the AP splats under CONFIG_DEBUG_ATOMIC_SLEEP. Scope the guard to the collection walk. The teardown does not need RCU: it serialises on priv->rx_reorder_tbl_lock[] and frees with kfree_rcu(). Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Assisted-by: Claude:claude-opus-5 Signed-off-by: David Carlier Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c b/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c index 65b628411543..87cf2ec85991 100644 --- a/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c +++ b/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c @@ -205,11 +205,12 @@ void nxpwifi_11n_del_rx_reorder_tbl_by_ta(struct nxpwifi_private *priv, u8 *ta) return; for (i = 0; i < MAX_NUM_TID; i++) { - guard(rcu)(); - list_for_each_entry_rcu(tbl, &priv->rx_reorder_tbl_ptr[i], list) { - if (!memcmp(tbl->ta, ta, ETH_ALEN)) { - INIT_LIST_HEAD(&tbl->tmp_list); - list_add_tail(&tbl->tmp_list, &to_delete); + scoped_guard(rcu) { + list_for_each_entry_rcu(tbl, &priv->rx_reorder_tbl_ptr[i], list) { + if (!memcmp(tbl->ta, ta, ETH_ALEN)) { + INIT_LIST_HEAD(&tbl->tmp_list); + list_add_tail(&tbl->tmp_list, &to_delete); + } } } From 05165f7b3b9ab6817fe216c24f90755b7c5e15f3 Mon Sep 17 00:00:00 2001 From: Yingying Tang Date: Thu, 11 Jun 2026 20:23:32 -0700 Subject: [PATCH 074/128] Revert "wifi: ath12k: add panic handler" ath12k_core_panic_handler() is invoked via atomic_notifier_call_chain(), which runs inside an RCU read-side critical section. The current code calls ath12k_pci_sw_reset() synchronously from this context, which eventually reaches mhi_device_get_sync() and schedule_timeout(), triggering a voluntary context switch within RCU. Call trace: rcu_note_context_switch+0x4c4/0x508 (P) __schedule+0xbc/0x1204 schedule+0x34/0x110 schedule_timeout+0x84/0x11c __mhi_device_get_sync+0x164/0x228 [mhi] mhi_device_get_sync+0x1c/0x3c [mhi] ath12k_wifi7_pci_bus_wake_up+0x20/0x2c [ath12k_wifi7] ath12k_pci_read32+0x58/0x350 [ath12k] ath12k_pci_clear_dbg_registers+0x28/0xb8 [ath12k] ath12k_pci_panic_handler+0x20/0x44 [ath12k] ath12k_core_panic_handler+0x28/0x3c [ath12k] notifier_call_chain+0x78/0x1c0 atomic_notifier_call_chain+0x3c/0x5c Revert change "wifi: ath12k: add panic handler" to avoid this issue. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3 Signed-off-by: Yingying Tang Tested-by: Joonhoe Kim <26rote@gmail.com> Reviewed-by: Rameshkumar Sundaram Fixes: 809055628bce ("wifi: ath12k: add panic handler") Link: https://patch.msgid.link/20260612032332.2278338-1-yingying.tang@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/core.c | 33 +------------------------- drivers/net/wireless/ath/ath12k/core.h | 3 --- drivers/net/wireless/ath/ath12k/hif.h | 9 ------- drivers/net/wireless/ath/ath12k/pci.c | 8 ------- 4 files changed, 1 insertion(+), 52 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c index 262a2045309b..b0107cde99ff 100644 --- a/drivers/net/wireless/ath/ath12k/core.c +++ b/drivers/net/wireless/ath/ath12k/core.c @@ -1776,29 +1776,6 @@ int ath12k_core_pre_init(struct ath12k_base *ab) return 0; } -static int ath12k_core_panic_handler(struct notifier_block *nb, - unsigned long action, void *data) -{ - struct ath12k_base *ab = container_of(nb, struct ath12k_base, - panic_nb); - - return ath12k_hif_panic_handler(ab); -} - -static int ath12k_core_panic_notifier_register(struct ath12k_base *ab) -{ - ab->panic_nb.notifier_call = ath12k_core_panic_handler; - - return atomic_notifier_chain_register(&panic_notifier_list, - &ab->panic_nb); -} - -static void ath12k_core_panic_notifier_unregister(struct ath12k_base *ab) -{ - atomic_notifier_chain_unregister(&panic_notifier_list, - &ab->panic_nb); -} - static inline bool ath12k_core_hw_group_create_ready(struct ath12k_hw_group *ag) { @@ -2214,18 +2191,13 @@ int ath12k_core_init(struct ath12k_base *ab) struct ath12k_hw_group *ag; int ret; - ret = ath12k_core_panic_notifier_register(ab); - if (ret) - ath12k_warn(ab, "failed to register panic handler: %d\n", ret); - mutex_lock(&ath12k_hw_group_mutex); ag = ath12k_core_hw_group_assign(ab); if (!ag) { mutex_unlock(&ath12k_hw_group_mutex); ath12k_warn(ab, "unable to get hw group\n"); - ret = -ENODEV; - goto err_unregister_notifier; + return -ENODEV; } mutex_unlock(&ath12k_hw_group_mutex); @@ -2250,8 +2222,6 @@ int ath12k_core_init(struct ath12k_base *ab) err_unassign_hw_group: ath12k_core_hw_group_unassign(ab); -err_unregister_notifier: - ath12k_core_panic_notifier_unregister(ab); return ret; } @@ -2260,7 +2230,6 @@ void ath12k_core_deinit(struct ath12k_base *ab) { ath12k_core_hw_group_destroy(ab->ag); ath12k_core_hw_group_unassign(ab); - ath12k_core_panic_notifier_unregister(ab); } void ath12k_core_free(struct ath12k_base *ab) diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index a98fc6e0699d..93fd227b4d19 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include @@ -1139,8 +1138,6 @@ struct ath12k_base { #endif /* CONFIG_ACPI */ - struct notifier_block panic_nb; - struct ath12k_hw_group *ag; struct ath12k_wsi_info wsi_info; enum ath12k_qmi_firmware_mode fw_mode; diff --git a/drivers/net/wireless/ath/ath12k/hif.h b/drivers/net/wireless/ath/ath12k/hif.h index 4de8667690e9..aecdb0c5f285 100644 --- a/drivers/net/wireless/ath/ath12k/hif.h +++ b/drivers/net/wireless/ath/ath12k/hif.h @@ -30,7 +30,6 @@ struct ath12k_hif_ops { void (*ce_irq_enable)(struct ath12k_base *ab); void (*ce_irq_disable)(struct ath12k_base *ab); void (*get_ce_msi_idx)(struct ath12k_base *ab, u32 ce_id, u32 *msi_idx); - int (*panic_handler)(struct ath12k_base *ab); void (*coredump_download)(struct ath12k_base *ab); }; @@ -149,14 +148,6 @@ static inline void ath12k_hif_power_down(struct ath12k_base *ab, bool is_suspend ab->hif.ops->power_down(ab, is_suspend); } -static inline int ath12k_hif_panic_handler(struct ath12k_base *ab) -{ - if (!ab->hif.ops->panic_handler) - return NOTIFY_DONE; - - return ab->hif.ops->panic_handler(ab); -} - static inline void ath12k_hif_coredump_download(struct ath12k_base *ab) { if (ab->hif.ops->coredump_download) diff --git a/drivers/net/wireless/ath/ath12k/pci.c b/drivers/net/wireless/ath/ath12k/pci.c index 6441927b5382..8978edb44cec 100644 --- a/drivers/net/wireless/ath/ath12k/pci.c +++ b/drivers/net/wireless/ath/ath12k/pci.c @@ -1484,13 +1484,6 @@ void ath12k_pci_power_down(struct ath12k_base *ab, bool is_suspend) ath12k_pci_sw_reset(ab_pci->ab, false); } -static int ath12k_pci_panic_handler(struct ath12k_base *ab) -{ - ath12k_pci_sw_reset(ab, false); - - return NOTIFY_OK; -} - static const struct ath12k_hif_ops ath12k_pci_hif_ops = { .start = ath12k_pci_start, .stop = ath12k_pci_stop, @@ -1508,7 +1501,6 @@ static const struct ath12k_hif_ops ath12k_pci_hif_ops = { .ce_irq_enable = ath12k_pci_hif_ce_irq_enable, .ce_irq_disable = ath12k_pci_hif_ce_irq_disable, .get_ce_msi_idx = ath12k_pci_get_ce_msi_idx, - .panic_handler = ath12k_pci_panic_handler, #ifdef CONFIG_ATH12K_COREDUMP .coredump_download = ath12k_pci_coredump_download, #endif From 99bda1ecbd56905f8bd934b9cdccd3bd251192b1 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 1 Oct 2026 19:11:38 +0000 Subject: [PATCH 075/128] flow_dissector: avoid u16 truncation of skb->len when computing thoff __skb_flow_dissect() computes key_control->thoff using: key_control->thoff = min_t(u16, nhoff, skb ? skb->len : hlen); min_t(u16, ...) casts both arguments to u16 before comparing them. Whenever skb->len (or hlen) modulo 65536 is smaller than nhoff, the truncated length wins and thoff is set to a bogus small value, even when the dissection succeeded. For example, an IPv4/TCP frame with skb->len == 65540 and nhoff == 34 gets thoff == 4, so callers such as skb_probe_transport_header() point the transport header inside the Ethernet header. Such skbs are not exotic: - At the time of commit d0c081b49137 ("flow_dissector: properly cap thoff field"), AF_PACKET with PACKET_VNET_HDR could already build GSO skbs larger than 64KB (MTU checks are skipped for GSO, and alloc_skb_with_frags() accepted up to MAX_SKB_FRAGS (17) order-0 pages on top of the linear part). - BIG TCP now makes skbs larger than 64KB common. - The following patch makes tun_get_user() dissect IFF_TAP frames before eth_type_trans() pulls the Ethernet header, so a GSO frame carrying a 65522..65535 byte L3 packet will be dissected with skb->len in [65536, 65549]. Compare as u32 instead. If the resulting offset cannot be represented in the u16 key_control->thoff, cap it to U16_MAX and report the dissection as failed rather than silently returning a wrong transport offset. thoff is still set on failure, as some callers (such as eth_get_headlen()) use it regardless of the return value. Fixes: d0c081b49137 ("flow_dissector: properly cap thoff field") Signed-off-by: Eric Dumazet Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20261001191140.2818991-2-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- net/core/flow_dissector.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/net/core/flow_dissector.c b/net/core/flow_dissector.c index 8aa4f9b4df81..27d8a01bc923 100644 --- a/net/core/flow_dissector.c +++ b/net/core/flow_dissector.c @@ -1071,6 +1071,7 @@ bool __skb_flow_dissect(const struct net *net, int mpls_lse = 0; int num_hdrs = 0; u8 ip_proto = 0; + u32 thoff; bool ret; if (!data) { @@ -1692,7 +1693,13 @@ out_good: ret = true; out: - key_control->thoff = min_t(u16, nhoff, skb ? skb->len : hlen); + thoff = min_t(u32, nhoff, skb ? skb->len : hlen); + if (unlikely(thoff > U16_MAX)) { + /* Cannot be represented in key_control->thoff. */ + thoff = U16_MAX; + ret = false; + } + key_control->thoff = thoff; key_basic->n_proto = proto; key_basic->ip_proto = ip_proto; From 44378d02c5aaae24b60a75fbbb539b4aa4db503d Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 1 Oct 2026 19:11:39 +0000 Subject: [PATCH 076/128] net: always dissect GSO packets in __virtio_net_hdr_to_skb() Commit 9e8db5913264 ("net: avoid false positives in untrusted gso validation") added a '&& skb->network_header' check before flow-dissecting GSO packets without VIRTIO_NET_HDR_F_NEEDS_CSUM in __virtio_net_hdr_to_skb(), because some callers (such as tun_get_user(), tun_xdp_one(), virtnet_receive_done(), and raw_verify_header()) called virtio_net_hdr_*_to_skb() before initializing skb->network_header and skb->dev. Because __alloc_skb() and __build_skb_around() zero-initialize skb->network_header to 0 (unlike mac_header and transport_header which are initialized to ~0U), those four callers always had skb->network_header == 0 and bypassed flow dissection in __virtio_net_hdr_to_skb(). More generally, skb->network_header is an offset from skb->head (where 0 is also a valid offset whenever skb_headroom(skb) is 0), not a boolean flag. Whenever the 'if (gso_type && skb->network_header)' branch was skipped, the fallback 'else if (gso_type)' only pulled nh_min_len + thlen (40 bytes for TCPv4) without dissecting the packet, without validating ip_proto or n_proto, and without setting skb->transport_header. If the packet has a malformed network header, it is not rejected and a subsequent skb_probe_transport_header() also fails, leaving skb->transport_header at ~0U (0xffff). Similarly, if an IPv4 packet carries IP options (ihl > 5) or an IPv6 packet carries extension headers, pulling only nh_min_len + thlen can leave the TCP header outside skb->head. In both cases, tcp_hdrlen(skb) in skb_gso_transport_seglen() reads out-of-bounds: BUG: KASAN: slab-out-of-bounds in skb_gso_transport_seglen Read of size 2 by task poc/133 skb_gso_transport_seglen (net/core/gso.c:155) skb_gso_validate_mac_len (net/core/gso.c:270) tbf_enqueue (net/sched/sch_tbf.c:260) dev_qdisc_enqueue (net/core/dev.c:4227) __dev_queue_xmit (net/core/dev.c:4884) In addition, checking virtio_net_hdr_match_proto() only inside 'if (!skb->protocol)' before flow dissection both skipped validation when skb->protocol was pre-set by the caller and rejected VLAN-tagged frames whose outer L2 protocol is ETH_P_8021Q or ETH_P_8021AD. Fix this by: 1. Initializing skb->dev and skb->network_header (plus skb->protocol for IFF_TUN) before virtio_net_hdr_*_to_skb() in tun_get_user(), tun_xdp_one(), virtnet_receive_done(), and raw_verify_header(). In tun_get_user(), drop the redundant skb_reset_mac_header(skb) in the IFF_TUN case since __virtio_net_hdr_to_skb() unconditionally resets mac_header. 2. Removing '&& skb->network_header' and the unvalidated 'else if (gso_type)' fallback in __virtio_net_hdr_to_skb() so all GSO packets without VIRTIO_NET_HDR_F_NEEDS_CSUM are flow-dissected, have their transport header pulled into linear data, and have skb->transport_header set. 3. Moving the virtio_net_hdr_match_proto() check to after skb_flow_dissect_flow_keys_basic(), validating keys.basic.n_proto against hdr_gso_type. Fixes: 9e8db5913264 ("net: avoid false positives in untrusted gso validation") Fixes: d5be7f632bad ("net: validate untrusted gso packets without csum offload") Fixes: 924a9bc362a5 ("net: check if protocol extracted by virtio_net_hdr_set_proto is correct") Reported-by: Weiming Shi Closes: https://lore.kernel.org/netdev/20260927163117.746432-2-bestswngs@gmail.com/ Signed-off-by: Eric Dumazet Reviewed-by: Willem de Bruijn Cc: Michael S. Tsirkin Link: https://patch.msgid.link/20261001191140.2818991-3-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- arch/um/drivers/vector_transports.c | 1 + drivers/net/tun.c | 23 +++++++---- drivers/net/virtio_net.c | 2 + include/linux/virtio_net.h | 64 ++++++++++++----------------- 4 files changed, 44 insertions(+), 46 deletions(-) diff --git a/arch/um/drivers/vector_transports.c b/arch/um/drivers/vector_transports.c index ddd127ee9678..e1fb2a76fdff 100644 --- a/arch/um/drivers/vector_transports.c +++ b/arch/um/drivers/vector_transports.c @@ -209,6 +209,7 @@ static int raw_verify_header( if ((vheader->flags & VIRTIO_NET_HDR_F_DATA_VALID) > 0) return 1; + skb_set_network_header(skb, ETH_HLEN); virtio_net_hdr_to_skb(skb, vheader, virtio_legacy_is_little_endian()); return 0; } diff --git a/drivers/net/tun.c b/drivers/net/tun.c index 5a302709a68a..242899f7fd07 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -1897,12 +1897,7 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, } } - if (tun_vnet_hdr_tnl_to_skb(tun->flags, features, skb, &hdr)) { - atomic_long_inc(&tun->rx_frame_errors); - err = -EINVAL; - goto free_skb; - } - + skb->dev = tun->dev; switch (tun->flags & TUN_TYPE_MASK) { case IFF_TUN: if (tun->flags & IFF_NO_PI) { @@ -1927,9 +1922,8 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, } } - skb_reset_mac_header(skb); + skb_reset_network_header(skb); skb->protocol = pi.proto; - skb->dev = tun->dev; break; case IFF_TAP: if (!pskb_may_pull(skb, ETH_HLEN)) { @@ -1937,10 +1931,19 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, drop_reason = SKB_DROP_REASON_HDR_TRUNC; goto drop; } - skb->protocol = eth_type_trans(skb, tun->dev); + skb_set_network_header(skb, ETH_HLEN); break; } + if (tun_vnet_hdr_tnl_to_skb(tun->flags, features, skb, &hdr)) { + atomic_long_inc(&tun->rx_frame_errors); + err = -EINVAL; + goto free_skb; + } + + if ((tun->flags & TUN_TYPE_MASK) == IFF_TAP) + skb->protocol = eth_type_trans(skb, tun->dev); + /* copy skb_ubuf_info for callback when skb has no error */ if (zerocopy) { skb_zcopy_init(skb, msg_control); @@ -2600,6 +2603,8 @@ build: features = tun_vnet_hdr_guest_features(READ_ONCE(tun->vnet_hdr_sz)); tnl_hdr = (struct virtio_net_hdr_v1_hash_tunnel *)gso; + skb->dev = tun->dev; + skb_set_network_header(skb, ETH_HLEN); if (tun_vnet_hdr_tnl_to_skb(tun->flags, features, skb, tnl_hdr)) { atomic_long_inc(&tun->rx_frame_errors); kfree_skb(skb); diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c index bf82ef9874ab..daab43ac92ce 100644 --- a/drivers/net/virtio_net.c +++ b/drivers/net/virtio_net.c @@ -2515,6 +2515,8 @@ static void virtnet_receive_done(struct virtnet_info *vi, struct receive_queue * goto frame_err; } + skb->dev = dev; + skb_set_network_header(skb, ETH_HLEN); if (virtio_net_hdr_tnl_to_skb(skb, &hdr->tnl_hdr, vi->rx_tnl, vi->rx_tnl_csum, virtio_is_little_endian(vi->vdev))) { diff --git a/include/linux/virtio_net.h b/include/linux/virtio_net.h index c381b916c1b5..d6466f96cdd0 100644 --- a/include/linux/virtio_net.h +++ b/include/linux/virtio_net.h @@ -111,48 +111,38 @@ static inline int __virtio_net_hdr_to_skb(struct sk_buff *skb, p_off = nh_min_len + thlen; if (!pskb_may_pull(skb, p_off)) return -EINVAL; - } else { + } else if (gso_type) { /* gso packets without NEEDS_CSUM do not set transport_offset. * probe and drop if does not match one of the above types. */ - if (gso_type && skb->network_header) { - struct flow_keys_basic keys; + struct flow_keys_basic keys; - if (!skb->protocol) { - __be16 protocol = dev_parse_header_protocol(skb); - - if (!protocol) - virtio_net_hdr_set_proto(skb, hdr); - else if (!virtio_net_hdr_match_proto(protocol, - hdr_gso_type)) - return -EINVAL; - else - skb->protocol = protocol; - } -retry: - if (!skb_flow_dissect_flow_keys_basic(NULL, skb, &keys, - NULL, 0, 0, 0, - 0)) { - /* UFO does not specify ipv4 or 6: try both */ - if (gso_type & SKB_GSO_UDP && - skb->protocol == htons(ETH_P_IP)) { - skb->protocol = htons(ETH_P_IPV6); - goto retry; - } - return -EINVAL; - } - - p_off = keys.control.thoff + thlen; - if (!pskb_may_pull(skb, p_off) || - keys.basic.ip_proto != ip_proto) - return -EINVAL; - - skb_set_transport_header(skb, keys.control.thoff); - } else if (gso_type) { - p_off = nh_min_len + thlen; - if (!pskb_may_pull(skb, p_off)) - return -EINVAL; + if (!skb->protocol) { + skb->protocol = dev_parse_header_protocol(skb); + if (!skb->protocol) + virtio_net_hdr_set_proto(skb, hdr); } +retry: + if (!skb_flow_dissect_flow_keys_basic(NULL, skb, &keys, + NULL, 0, 0, 0, + 0)) { + /* UFO does not specify ipv4 or 6: try both */ + if (gso_type & SKB_GSO_UDP && + skb->protocol == htons(ETH_P_IP)) { + skb->protocol = htons(ETH_P_IPV6); + goto retry; + } + return -EINVAL; + } + + p_off = keys.control.thoff + thlen; + if (!pskb_may_pull(skb, p_off) || + keys.basic.ip_proto != ip_proto || + !virtio_net_hdr_match_proto(keys.basic.n_proto, + hdr_gso_type)) + return -EINVAL; + + skb_set_transport_header(skb, keys.control.thoff); } if (hdr_gso_type != VIRTIO_NET_HDR_GSO_NONE) { From ee2d4c2d7cc18a2cd504044a75ea614e720248eb Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 1 Oct 2026 19:11:40 +0000 Subject: [PATCH 077/128] selftests: net: tun: add test for VLAN-tagged GSO without NEEDS_CSUM Add selftests in tun.c verifying that a VLAN-tagged (802.1Q) TCPv4 GSO packet without VIRTIO_NET_HDR_F_NEEDS_CSUM (both flags = 0 and flags = VIRTIO_NET_HDR_F_DATA_VALID) is accepted when written to a TAP device (/dev/net/tun with IFF_TAP | IFF_NO_PI | IFF_VNET_HDR). Also verify that the following are rejected with -EINVAL: - a mismatched GSO type (VIRTIO_NET_HDR_GSO_TCPV6 on a VLAN-tagged IPv4 packet). - a frame whose TCP header is truncated after 10 bytes. Pulling only sizeof(struct iphdr) + sizeof(struct tcphdr) bytes would accept it, so this requires the transport offset found by flow dissection. Finally, verify that a 65540-byte frame is accepted. Its skb->len is above U16_MAX while it is flow-dissected, before eth_type_trans() pulls the Ethernet header. Based on a reproducer by Michael S. Tsirkin . Signed-off-by: Eric Dumazet Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20261001191140.2818991-4-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/tun.c | 136 ++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/tools/testing/selftests/net/tun.c b/tools/testing/selftests/net/tun.c index abe488bac50b..afaa81c9bac5 100644 --- a/tools/testing/selftests/net/tun.c +++ b/tools/testing/selftests/net/tun.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -542,6 +543,141 @@ TEST_F(tun, reattach_close_delete) EXPECT_EQ(tun_delete(self->ifname), 0); } +FIXTURE(tun_vnet_gso) +{ + char ifname[IFNAMSIZ]; + int fd; +}; + +FIXTURE_SETUP(tun_vnet_gso) +{ + int flags = IFF_TAP | IFF_NO_PI | IFF_VNET_HDR; + + memset(self->ifname, 0, sizeof(self->ifname)); + self->fd = tun_open(self->ifname, flags, 0, 0, NULL); + ASSERT_GE(self->fd, 0); +} + +FIXTURE_TEARDOWN(tun_vnet_gso) +{ + if (self->fd >= 0) + close(self->fd); +} + +static int build_vlan_tcpv4_gso_packet(uint8_t *buf, int payload_len) +{ + uint16_t vlan_tag[2] = { htons(100), htons(ETH_P_IP) }; + uint8_t *cur = buf + sizeof(struct virtio_net_hdr); + struct virtio_net_hdr vh = { 0 }; + struct tcphdr tcph = { 0 }; + uint32_t sum; + + cur += build_eth(cur, ETH_P_8021Q, param_hwaddr_outer_src, + param_hwaddr_outer_dst); + + /* 802.1Q tag: VID=100, inner protocol=ETH_P_IP */ + memcpy(cur, vlan_tag, sizeof(vlan_tag)); + cur += sizeof(vlan_tag); + + cur += build_ipv4_header(cur, IPPROTO_TCP, + sizeof(tcph) + payload_len, + ¶m_ipaddr4_outer_src, + ¶m_ipaddr4_outer_dst); + + tcph.source = htons(12345); + tcph.dest = htons(80); + tcph.seq = htonl(1); + tcph.doff = sizeof(tcph) / 4; + tcph.ack = 1; + tcph.window = htons(65535); + memcpy(cur, &tcph, sizeof(tcph)); + memset(cur + sizeof(tcph), PKT_DATA, payload_len); + + sum = add_csum((const uint8_t *)¶m_ipaddr4_outer_src, + sizeof(param_ipaddr4_outer_src)); + sum += add_csum((const uint8_t *)¶m_ipaddr4_outer_dst, + sizeof(param_ipaddr4_outer_dst)); + sum += htons(IPPROTO_TCP) + htons(sizeof(tcph) + payload_len); + sum += add_csum(cur, sizeof(tcph) + payload_len); + tcph.check = finish_ip_csum(sum); + memcpy(cur, &tcph, sizeof(tcph)); + cur += sizeof(tcph) + payload_len; + + vh.gso_type = VIRTIO_NET_HDR_GSO_TCPV4; + vh.gso_size = 1400; + vh.hdr_len = (cur - buf) - sizeof(vh) - payload_len; + memcpy(buf, &vh, sizeof(vh)); + + return cur - buf; +} + +TEST_F(tun_vnet_gso, vlan_tcpv4_gso_no_csum) +{ + struct virtio_net_hdr vh; + uint8_t pkt[4096] = { 0 }; + int len, ret; + + len = build_vlan_tcpv4_gso_packet(pkt, 2800); + memcpy(&vh, pkt, sizeof(vh)); + + /* Valid VLAN-tagged TCPv4 GSO with flags = 0 (no NEEDS_CSUM) */ + vh.flags = 0; + memcpy(pkt, &vh, sizeof(vh)); + ret = write(self->fd, pkt, len); + ASSERT_EQ(ret, len); + + /* Valid VLAN-tagged TCPv4 GSO with flags = DATA_VALID */ + vh.flags = VIRTIO_NET_HDR_F_DATA_VALID; + memcpy(pkt, &vh, sizeof(vh)); + ret = write(self->fd, pkt, len); + ASSERT_EQ(ret, len); + + /* Mismatched GSO type (TCPV6 on VLAN-tagged IPv4 packet) */ + vh.flags = 0; + vh.gso_type = VIRTIO_NET_HDR_GSO_TCPV6; + memcpy(pkt, &vh, sizeof(vh)); + ret = write(self->fd, pkt, len); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, EINVAL); + + /* TCP header truncated after 10 bytes, without NEEDS_CSUM. + * Requires the transport header offset found by flow dissection: + * pulling only sizeof(struct iphdr) + sizeof(struct tcphdr) bytes + * from the mac header would accept this frame. + */ + vh.gso_type = VIRTIO_NET_HDR_GSO_TCPV4; + vh.hdr_len = ETH_HLEN + 4 + sizeof(struct iphdr) + 10; + memcpy(pkt, &vh, sizeof(vh)); + ret = write(self->fd, pkt, sizeof(vh) + vh.hdr_len); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, EINVAL); +} + +TEST_F(tun_vnet_gso, vlan_tcpv4_gso_no_csum_64k) +{ + /* Ethernet frame of 65540 bytes: skb->len is above U16_MAX when the + * frame is flow-dissected, before eth_type_trans() pulls ETH_HLEN. + */ + const int payload_len = 65540 - ETH_HLEN - 4 - + sizeof(struct iphdr) - sizeof(struct tcphdr); + struct virtio_net_hdr vh; + uint8_t *pkt; + int len, ret; + + pkt = calloc(1, sizeof(vh) + 65540); + ASSERT_NE(pkt, NULL); + + len = build_vlan_tcpv4_gso_packet(pkt, payload_len); + ASSERT_EQ(len, sizeof(vh) + 65540); + memcpy(&vh, pkt, sizeof(vh)); + + vh.flags = 0; + memcpy(pkt, &vh, sizeof(vh)); + ret = write(self->fd, pkt, len); + free(pkt); + ASSERT_EQ(ret, len); +} + FIXTURE(tun_vnet_udptnl) { char ifname[IFNAMSIZ]; From 8bc60db48e8cba35488bbd6e1f94aa990afa275a Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Fri, 2 Oct 2026 12:30:58 +0900 Subject: [PATCH 078/128] ip6_gre: remove obsolete ERSPAN PMTU update A tc tunnel_key action can attach a METADATA_IP_TUNNEL dst to an skb, and mirred can redirect it to a native ip6erspan device. The legacy PMTU update in ip6erspan_tunnel_xmit() treats that metadata dst as a route. Since it has no output device, dst_dev(dst)->mtu dereferences NULL. On v7.2 with KASAN, an initial UID/GID 65534 process with CapEff 0 used self-created user and network namespaces to trigger: KASAN: null-ptr-deref RIP: ip6erspan_tunnel_xmit+0x10fc/0x2cc0 Kernel panic - not syncing: Fatal exception in interrupt The update is obsolete. Commit fe1a4ca0a2b7 ("ip6_gre: process toobig in a better way") changed IPv6 GRE Too Big handling to update the underlay route and removed the equivalent block from __gre6_xmit(). ip6_tnl_xmit() then propagates the underlay PMTU to the overlay route. Remove the stale ERSPAN copy and its now-unused dst variable. This eliminates the metadata-dst dereference and keeps ERSPAN aligned with IPv6 GRE PMTU handling. Fixes: 5a963eb61b7c ("ip6_gre: Add ERSPAN native tunnel support") Cc: stable@vger.kernel.org Suggested-by: Ido Schimmel Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20261002033058.358137-1-4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv6/ip6_gre.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c index e61cb10b50dc..04f7c70b7320 100644 --- a/net/ipv6/ip6_gre.c +++ b/net/ipv6/ip6_gre.c @@ -923,7 +923,6 @@ static netdev_tx_t ip6erspan_tunnel_xmit(struct sk_buff *skb, { struct ip_tunnel_info *tun_info = NULL; struct ip6_tnl *t = netdev_priv(dev); - struct dst_entry *dst = skb_dst(skb); IP_TUNNEL_DECLARE_FLAGS(flags) = { }; bool truncate = false; int encap_limit = -1; @@ -1058,12 +1057,6 @@ static netdev_tx_t ip6erspan_tunnel_xmit(struct sk_buff *skb, gre_build_header(skb, 8, flags, proto, 0, htonl(atomic_fetch_inc(&t->o_seqno))); - /* TooBig packet may have updated dst->dev's mtu */ - if (!t->parms.collect_md && dst) { - mtu = READ_ONCE(dst_dev(dst)->mtu); - if (dst_mtu(dst) > mtu) - dst->ops->update_pmtu(dst, NULL, skb, mtu, false); - } err = ip6_tnl_xmit(skb, dev, dsfield, &fl6, encap_limit, &mtu, NEXTHDR_GRE); if (err != 0) { From a463f55c791b96cfd12df8b10ea737d45c80bb18 Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Fri, 2 Oct 2026 00:13:10 +0900 Subject: [PATCH 079/128] vsock/virtio: account only unread bytes in read_skb() After a partial stream receive, rx_bytes tracks the unread suffix while buf_used and peer credit still cover the whole packet. virtio_transport_read_skb() dequeues that packet but passes pkt_len for both counters, underflowing rx_bytes. On a connected CID_LOCAL stream with an SK_SKB verdict, a 40-byte read from a 100-byte packet made SIOCINQ report -40, an empty recv return ELOOP, and poll report the empty socket readable. After verdict detach, receiving 40 bytes wrapped the counter to zero and hid those queued bytes until one more byte arrived. The fixed run kept the counter balanced and exposed all queued data immediately. This was found during an LLM-assisted manual source audit of VSOCK receive accounting while re-evaluating virtio_transport_read_skb() after CVE-2024-50169. Subtract only skb->len minus the VSOCK offset from rx_bytes. Retain the full packet length for buf_used and peer credit. Fixes: 45ca7e9f0730 ("vsock/virtio: fix `rx_bytes` accounting for stream sockets") Cc: stable@vger.kernel.org Reviewed-by: Luigi Leonardi Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Link: https://patch.msgid.link/20261001151310.4101006-1-4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- net/vmw_vsock/virtio_transport_common.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c index f225f53ed4ba..1e762a480df4 100644 --- a/net/vmw_vsock/virtio_transport_common.c +++ b/net/vmw_vsock/virtio_transport_common.c @@ -1927,6 +1927,7 @@ int virtio_transport_read_skb(struct vsock_sock *vsk, skb_read_actor_t recv_acto struct sock *sk = sk_vsock(vsk); struct virtio_vsock_hdr *hdr; struct sk_buff *skb; + u32 bytes_read; u32 pkt_len; int off = 0; int err; @@ -1946,7 +1947,8 @@ int virtio_transport_read_skb(struct vsock_sock *vsk, skb_read_actor_t recv_acto vvs->msg_count--; pkt_len = le32_to_cpu(hdr->len); - virtio_transport_dec_rx_pkt(vvs, pkt_len, pkt_len); + bytes_read = skb->len - VIRTIO_VSOCK_SKB_CB(skb)->offset; + virtio_transport_dec_rx_pkt(vvs, bytes_read, pkt_len); spin_unlock_bh(&vvs->rx_lock); virtio_transport_send_credit_update(vsk); From 6619e01855e65ef35723685a557e3eb106f4ecf5 Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Fri, 2 Oct 2026 13:50:58 +0900 Subject: [PATCH 080/128] ipv4: stop PMTU walk when nexthop group shrinks Commit 7d3f3b4367f3 ("net: ipv4: Cache pmtu for all packet paths if multipath enabled") made __ip_rt_update_pmtu() update every path. For nexthop objects, fib_info_num_path() and fib_info_nhc() independently load nh->nh_grp. RCU protects each group's lifetime but does not make the loads observe the same group. If replacement shrinks the group after the loop accepts an index, fib_info_nhc() returns NULL and update_or_create_fnhe() dereferences it. A deterministic probe only widened the existing window between the real operations. On v7.2, an RTM_NEWNEXTHOP replacement published a one-member group after the PMTU reader accepted index 1 from a two-member group, producing: KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007] RIP: update_or_create_fnhe+0x45/0x15b0 Stop when the indexed path is absent. Groups are dense, so no later index exists in that snapshot. The same real-writer window completed 114,423 PMTU iterations without an oops with this check. A reproducer is available on request. Replacement requires CAP_NET_ADMIN in the network namespace. Where unprivileged user namespaces are permitted, a local user can obtain it in a new user and network namespace. Fixes: 7d3f3b4367f3 ("net: ipv4: Cache pmtu for all packet paths if multipath enabled") Cc: stable@vger.kernel.org Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/4fb87ed994dfc067178fc845cdd8bc9b5446b039.1790915964.git.4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv4/route.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/ipv4/route.c b/net/ipv4/route.c index 37674d76f90f..5f2197874beb 100644 --- a/net/ipv4/route.c +++ b/net/ipv4/route.c @@ -1082,6 +1082,8 @@ static void __ip_rt_update_pmtu(struct rtable *rt, struct flowi4 *fl4, u32 mtu) for (nhsel = 0; nhsel < fib_info_num_path(res.fi); nhsel++) { nhc = fib_info_nhc(res.fi, nhsel); + if (!nhc) + break; update_or_create_fnhe(nhc, fl4->daddr, 0, mtu, lock, jiffies + net->ipv4.ip_rt_mtu_expires); } From 4f4afc07dcb2edd4f9afa36595373f8cdb9b58c2 Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Fri, 2 Oct 2026 13:50:59 +0900 Subject: [PATCH 081/128] ipv4: stop exception dump when nexthop group shrinks Commit 4ce5dc9316de ("inet: switch inet_dump_fib() to RCU protection") allowed IPv4 FIB dumps to run concurrently with nexthop replacement. fib_dump_info_fnhe() uses fib_info_num_path() as the loop bound, then fib_info_nhc() independently reloads nh->nh_grp. If RTM_NEWNEXTHOP replaces a group with fewer paths between those loads, fib_info_nhc() returns NULL and the dump dereferences it through nhc_flags. Stop the walk when the indexed path is absent. Nexthop groups are dense, so the current snapshot has no later path. Fixes: 4ce5dc9316de ("inet: switch inet_dump_fib() to RCU protection") Reported-by: Ido Schimmel Link: https://lore.kernel.org/netdev/20261001170513.GA1657889@shredder/ Cc: stable@vger.kernel.org Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/5bb7bd97411eb9db454464bf449107395e760f26.1790915964.git.4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv4/route.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/ipv4/route.c b/net/ipv4/route.c index 5f2197874beb..268d8821f7e0 100644 --- a/net/ipv4/route.c +++ b/net/ipv4/route.c @@ -3170,6 +3170,9 @@ int fib_dump_info_fnhe(struct sk_buff *skb, struct netlink_callback *cb, struct fnhe_hash_bucket *bucket; int err; + if (!nhc) + break; + if (nhc->nhc_flags & RTNH_F_DEAD) continue; From d3daa33c7af92e38f2274bbd2576f58bfbaf89e6 Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Fri, 2 Oct 2026 13:51:00 +0900 Subject: [PATCH 082/128] ipv4: stop route notification sizing when nexthop group shrinks Commit 680aea08e78c ("net: ipv4: Emit notification when fib hardware flags are changed") added an RCU-only fib_nlmsg_size() call for asynchronous hardware flag notifications. fib_nlmsg_size() checks fib_info_num_path() before each iteration, then fib_info_nhc() independently reloads nh->nh_grp. If RTM_NEWNEXTHOP replaces a group with fewer paths between those loads, fib_info_nhc() returns NULL and fib_nexthop_nlmsg_size() dereferences it. Stop sizing when the indexed path is absent. Nexthop groups are dense, so the current snapshot has no later path. Fixes: 680aea08e78c ("net: ipv4: Emit notification when fib hardware flags are changed") Cc: stable@vger.kernel.org Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/1054fa46165bba1b473782fa41367804aa908e86.1790915964.git.4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv4/fib_semantics.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/ipv4/fib_semantics.c b/net/ipv4/fib_semantics.c index 5c9021ea3a79..47469b775808 100644 --- a/net/ipv4/fib_semantics.c +++ b/net/ipv4/fib_semantics.c @@ -542,6 +542,9 @@ size_t fib_nlmsg_size(struct fib_info *fi) struct fib_nh_common *nhc = fib_info_nhc(fi, i); size_t nhsize; + if (!nhc) + break; + nhsize = fib_nexthop_nlmsg_size(nhc, nhs != 1); if (nhs != 1) From ca5012757bf027df83d51593a07656cb8c80caee Mon Sep 17 00:00:00 2001 From: Quentin Freimanis Date: Sun, 4 Oct 2026 22:28:26 -0700 Subject: [PATCH 083/128] net: sparx5: start the TOD counters on non-PTP lan969x variants Currently the base non-PTP-capable lan969x variants don't forward any Ethernet frames: $ ip link set eth10 up $ ip addr add 10.0.0.54/24 dev eth10 $ ping 10.0.0.1 PING 10.0.0.1 (10.0.0.1): 56 data bytes --- 10.0.0.1 ping statistics --- 7 packets transmitted, 0 packets received, 100% packet loss $ cat /proc/interrupts | grep fdma 20: 0 GIC-0 120 Level sparx5-fdma $ ip -s link show dev eth10 RX: bytes packets errors dropped missed mcast 20828 102 0 0 0 72 TX: bytes packets errors dropped carrier collsns 0 0 0 0 0 0 Testing showed that starting the domain 0 TOD counter gets networking working again, but start all three for consistency with the PTP variants. Fixes: 207966787b71 ("net: sparx5: add feature support") Suggested-by: Daniel Machon Signed-off-by: Quentin Freimanis Reviewed-by: Daniel Machon Link: https://patch.msgid.link/20261005052826.14251-1-quentin@q-lab.dev Signed-off-by: Jakub Kicinski --- .../ethernet/microchip/sparx5/sparx5_ptp.c | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_ptp.c b/drivers/net/ethernet/microchip/sparx5/sparx5_ptp.c index 5953e04ef14e..2826e48af103 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_ptp.c +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_ptp.c @@ -622,18 +622,9 @@ int sparx5_ptp_init(struct sparx5 *sparx5) sparx5->ptp = 1; } - if (!sparx5->ptp) - return 0; - - for (i = 0; i < SPARX5_PHC_COUNT; ++i) { - err = sparx5_ptp_phc_init(sparx5, i, &sparx5_ptp_clock_info); - if (err) - return err; - } - - spin_lock_init(&sparx5->ptp_clock_lock); - spin_lock_init(&sparx5->ptp_ts_id_lock); - mutex_init(&sparx5->ptp_lock); + /* The base, non-PTP-capable lan969x variants need the + * first TOD counter running to forward frames. + */ /* Disable master counters */ spx5_wr(PTP_PTP_DOM_CFG_PTP_ENA_SET(0), sparx5, PTP_PTP_DOM_CFG); @@ -657,6 +648,19 @@ int sparx5_ptp_init(struct sparx5 *sparx5) /* Enable master counters */ spx5_wr(PTP_PTP_DOM_CFG_PTP_ENA_SET(0x7), sparx5, PTP_PTP_DOM_CFG); + if (!sparx5->ptp) + return 0; + + for (i = 0; i < SPARX5_PHC_COUNT; ++i) { + err = sparx5_ptp_phc_init(sparx5, i, &sparx5_ptp_clock_info); + if (err) + return err; + } + + spin_lock_init(&sparx5->ptp_clock_lock); + spin_lock_init(&sparx5->ptp_ts_id_lock); + mutex_init(&sparx5->ptp_lock); + for (i = 0; i < sparx5->data->consts->n_ports; i++) { port = sparx5->ports[i]; if (!port) From c5381ae1cd1481cc431c2bd79460472c6f1d3ce3 Mon Sep 17 00:00:00 2001 From: Haishuang Yan Date: Thu, 1 Oct 2026 12:22:32 +0800 Subject: [PATCH 084/128] devlink: fix devlink_rel reference leak when notify work is pending devlink_rel_nested_in_notify_work_schedule() takes a reference on the devlink_rel for the notify work and then queues the work, ignoring the return value of schedule_delayed_work(). If the work is already pending, nothing new is queued, the work runs only once and drops only one reference, so the extra one is leaked together with the devlink_rel and its index in devlink_rels. This is easy to hit. devl_register() of a nested instance queues the work, and if the instance is unregistered before the work has run, devlink_rel_put() queues it again while it is still pending. The work also keeps rescheduling itself for as long as the parent devlink lock cannot be taken, which widens the window. Registering and unregistering a nested devlink instance 100 times while holding the parent lock leaks all 100 devlink_rel objects. The reschedule path in devlink_rel_nested_in_notify_work() has the same problem: if the work was queued again while it was running, the reference it holds is never dropped. Drop the reference in both places when the work was already pending. The pending work holds its own reference, so this can not be the last one. Fixes: c137743bce02 ("devlink: introduce object and nested devlink relationship infra") Signed-off-by: Haishuang Yan Reviewed-by: Simon Horman Link: https://patch.msgid.link/20261001042232.222987-1-yanhaishuang@cmss.chinamobile.com Signed-off-by: Jakub Kicinski --- net/devlink/core.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/net/devlink/core.c b/net/devlink/core.c index c53a42e17a58..bddbbbf000fe 100644 --- a/net/devlink/core.c +++ b/net/devlink/core.c @@ -112,13 +112,19 @@ rel_put: return; reschedule_work: - schedule_delayed_work(&rel->nested_in.notify_work, 1); + /* The work may have been queued again meanwhile, which took its own + * reference. Drop ours in that case. + */ + if (!schedule_delayed_work(&rel->nested_in.notify_work, 1)) + __devlink_rel_put(rel); } static void devlink_rel_nested_in_notify_work_schedule(struct devlink_rel *rel) { __devlink_rel_get(rel); - schedule_delayed_work(&rel->nested_in.notify_work, 0); + /* The pending work holds a reference already, drop the new one. */ + if (!schedule_delayed_work(&rel->nested_in.notify_work, 0)) + __devlink_rel_put(rel); } static struct devlink_rel *devlink_rel_alloc(void) From b7454f1940c5e6d92e753853c476a769e452f30a Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Fri, 2 Oct 2026 02:23:25 +0800 Subject: [PATCH 085/128] net: team: stop reusing skb after queue override A nonzero dev_queue_xmit() result does not return ownership of the skb. When an override port's qdisc drops it, the queue override helper used to try another port or the mode transmit op with the freed skb. Return the handoff decision separately from transmit success. Select only the first override port under RCU and stop after handing it the skb, regardless of the lower transmit result. Keep the existing success and drop accounting. BUG: KASAN: slab-use-after-free in sk_skb_reason_drop Write of size 4 by task poc/5040 sk_skb_reason_drop (net/core/skbuff.c:1240) dev_kfree_skb_any_reason (net/core/dev.c:3470) ab_transmit (drivers/net/team/team_mode_activebackup.c:49) team_xmit (drivers/net/team/team_core.c:1849) __dev_direct_xmit (net/core/dev.c:4934) Freed by task 5040: __tcf_kfree_skb_list (net/sched/sch_generic.c:43) __dev_queue_xmit (net/core/dev.c:4831) team_xmit (drivers/net/team/team_core.c:1846) Kernel panic - not syncing: KASAN: panic_on_warn set ... Cc: stable@vger.kernel.org Fixes: 8ff5105a2b9d ("team: add support for queue override by setting queue_id for port") Reported-by: Signed-off-by: Weiming Shi Link: https://patch.msgid.link/20261001182326.572045-2-bestswngs@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/team/team_core.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/net/team/team_core.c b/drivers/net/team/team_core.c index beffbe450612..ca75f08e3c59 100644 --- a/drivers/net/team/team_core.c +++ b/drivers/net/team/team_core.c @@ -820,7 +820,8 @@ static struct list_head *__team_get_qom_list(struct team *team, u16 queue_id) /* * note: already called with rcu_read_lock */ -static bool team_queue_override_transmit(struct team *team, struct sk_buff *skb) +static bool team_queue_override_transmit(struct team *team, struct sk_buff *skb, + bool *tx_success) { struct list_head *qom_list; struct team_port *port; @@ -828,11 +829,11 @@ static bool team_queue_override_transmit(struct team *team, struct sk_buff *skb) if (!team->queue_override_enabled || !skb->queue_mapping) return false; qom_list = __team_get_qom_list(team, skb->queue_mapping); - list_for_each_entry_rcu(port, qom_list, qom_list) { - if (!team_dev_queue_xmit(team, port, skb)) - return true; - } - return false; + port = list_first_or_null_rcu(qom_list, struct team_port, qom_list); + if (!port) + return false; + *tx_success = !team_dev_queue_xmit(team, port, skb); + return true; } static void __team_queue_override_port_del(struct team *team, @@ -1864,8 +1865,7 @@ static netdev_tx_t team_xmit(struct sk_buff *skb, struct net_device *dev) bool tx_success; unsigned int len = skb->len; - tx_success = team_queue_override_transmit(team, skb); - if (!tx_success) + if (!team_queue_override_transmit(team, skb, &tx_success)) tx_success = READ_ONCE(team->ops.transmit)(team, skb); if (tx_success) { struct team_pcpu_stats *pcpu_stats; From 489114c9ecae44e523361a4b3e610ee5ebd9189c Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Fri, 2 Oct 2026 02:23:26 +0800 Subject: [PATCH 086/128] net: team: free skb when broadcast has no txable port bc_transmit() sends the original skb through the last txable port. When no port is txable, it returns false without consuming the skb, but team_xmit() still returns NETDEV_TX_OK. AF_PACKET sends then retain the skb and its socket write-memory charge. A TX-enabled, link-down port keeps the broadcast transmit op installed. Carrier can stay up through a second link-up, TX-disabled port or be forced on by userspace. Free the original skb on the no-port path while leaving the existing drop accounting intact. Discovered by Sashiko during review of previous commit. Cc: stable@vger.kernel.org Fixes: 5fc889911a99 ("team: add broadcast mode") Signed-off-by: Weiming Shi Link: https://patch.msgid.link/20261001182326.572045-3-bestswngs@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/team/team_mode_broadcast.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/team/team_mode_broadcast.c b/drivers/net/team/team_mode_broadcast.c index 61d7d79f0c36..69dc25a5847a 100644 --- a/drivers/net/team/team_mode_broadcast.c +++ b/drivers/net/team/team_mode_broadcast.c @@ -37,6 +37,8 @@ static bool bc_transmit(struct team *team, struct sk_buff *skb) ret = !team_dev_queue_xmit(team, last, skb); if (!sum_ret) sum_ret = ret; + } else { + dev_kfree_skb_any(skb); } return sum_ret; } From b72e025ac556984766392001e74b7587e145bcd0 Mon Sep 17 00:00:00 2001 From: Victor Nogueira Date: Thu, 1 Oct 2026 12:32:00 -0300 Subject: [PATCH 087/128] net/sched: cls_route: reject change with no routing attribute route4_change on a filter change that supplies no routing attribute at all (no to/from/iif) reaches route4_set_parms with fold set, so the handle-mismatch check -- which only fires when creating -- is skipped. nhandle is built as the all-wildcard bucket (0x8000 | 0xFFFF << 16) and the filter is rekeyed to 0xFFFF8000, so the handle userspace already stored no longer addresses the filter. (The netlink reply carries the new handle; what goes stale is the handle the caller holds.) The rekey actually misclassifies the packets. The new handle's from_hash is the wildcard chain (route4_hash_wild), whose filters route4_classify applies without any id/iif comparison, and its bucket (256) is reached by every packet through the h < 256 restart. A filter that matched only a specific realm therefore becomes an unconditional catch-all that applies its classid and actions to all otherwise-unmatched traffic. Reject a change that would rekey the filter this way, with the routing-attributes-required error. A change that names at least one of to/from/iif, and an in-place replace that keeps the routing identity via a supplied attribute, are unaffected. A change of a filter that already sits at the wildcard handle is a metadata-only update and remains valid, so only a change that actually moves the handle is rejected. This is a follow-up to commit 41e85e54e564 ("net/sched: cls_route: Fix in-place replace"); this patch closes the no-routing-attribute variant that the parent series did not address. Conditions to recreate the bug: tc qdisc add dev lo clsact tc filter add dev lo ingress protocol ip pref 100 route from 1 to 1 tc filter change dev lo ingress protocol ip pref 100 handle 0x10001 \ route classid 1:2 tc filter show dev lo ingress # handle became 0xffff8000; iproute2's # stored 0x10001 no longer addresses it A change of a filter already at the wildcard handle, e.g. one created by "tc filter add dev lo ingress protocol ip pref 100 route classid 1:1" (iproute2 defaults the handle to 0xffff8000), stays a valid metadata-only update. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: Sashiko Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260907192133.2639067-1-victor@mojatatu.com Reviewed-by: Jamal Hadi Salim Signed-off-by: Victor Nogueira Reviewed-by: Simon Horman Link: https://patch.msgid.link/QDISC-0JB3.v1.20260929143022@mojatatu.com Signed-off-by: Jakub Kicinski --- net/sched/cls_route.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/sched/cls_route.c b/net/sched/cls_route.c index 0f211f030fd9..dca812a75269 100644 --- a/net/sched/cls_route.c +++ b/net/sched/cls_route.c @@ -434,6 +434,12 @@ static int route4_set_parms(struct net *net, struct tcf_proto *tp, if (handle && (!fold || nhandle == (handle & ~0x7F00))) nhandle |= handle & 0x7F00; + if (fold && !tb[TCA_ROUTE4_TO] && !tb[TCA_ROUTE4_FROM] && + !tb[TCA_ROUTE4_IIF] && nhandle != fold->handle) { + NL_SET_ERR_MSG(extack, "Routing attributes required"); + return -EINVAL; + } + if (handle && !fold && nhandle != handle) { NL_SET_ERR_MSG_FMT(extack, "Handle mismatch constructed: %x (expected: %x)", From 6fa2e7db99fb9ed67496c652fcfce5a31c6b73d0 Mon Sep 17 00:00:00 2001 From: Victor Nogueira Date: Thu, 1 Oct 2026 12:32:01 -0300 Subject: [PATCH 088/128] selftests: tc-testing: add cls_route no-routing-attribute change tests Add a boundary pair for route4 change without a routing attribute: - a classid-only change of a wildcard filter (handle 0xffff8000) is a metadata-only update and must succeed; - the same change on a non-wildcard handle (0x10001) would rekey the filter to the wildcard bucket, leaving the handle userspace stored stale, and must be rejected with -EINVAL. The change of a wildcard filter is the flow iproute2 uses by default ("tc filter add ... route classid X:Y" gives handle 0xffff8000). Reviewed-by: Jamal Hadi Salim Signed-off-by: Victor Nogueira Reviewed-by: Simon Horman Link: https://patch.msgid.link/QDISC-0JB3.v1.20260923100921@mojatatu.com.2 Signed-off-by: Jakub Kicinski --- .../tc-testing/tc-tests/filters/route.json | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tools/testing/selftests/tc-testing/tc-tests/filters/route.json b/tools/testing/selftests/tc-testing/tc-tests/filters/route.json index 2d5843aebd72..c3b6ff157d3e 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/filters/route.json +++ b/tools/testing/selftests/tc-testing/tc-tests/filters/route.json @@ -412,5 +412,51 @@ "teardown": [ "$TC qdisc del dev $DEV1 ingress" ] + }, + { + "id": "4000", + "name": "Change a wildcard route filter classid only (no routing attribute)", + "category": [ + "filter", + "route" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DEV1 ingress", + "$TC filter add dev $DEV1 parent ffff: protocol ip prio 100 route classid 1:1" + ], + "cmdUnderTest": "$TC filter change dev $DEV1 parent ffff: protocol ip prio 100 route classid 1:9", + "expExitCode": "0", + "verifyCmd": "$TC filter ls dev $DEV1 parent ffff:", + "matchPattern": "fh 0xffff8000 flowid 1:9", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DEV1 ingress" + ] + }, + { + "id": "907a", + "name": "Try to change a route filter with no routing attribute and a non-wildcard handle", + "category": [ + "filter", + "route" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DEV1 ingress", + "$TC filter add dev $DEV1 parent ffff: protocol ip prio 100 route from 1 to 1 classid 1:1" + ], + "cmdUnderTest": "$TC filter change dev $DEV1 parent ffff: protocol ip prio 100 handle 0x10001 route classid 1:2", + "expExitCode": "2", + "verifyCmd": "$TC filter ls dev $DEV1 parent ffff:", + "matchPattern": "fh 0x00010001 flowid 1:1 to 1 from 1", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DEV1 ingress" + ] } ] From 86b785b18f8f4a6b4a7392d1e2b29d6bb095011a Mon Sep 17 00:00:00 2001 From: Or Har-Toov Date: Sun, 4 Oct 2026 11:35:31 +0300 Subject: [PATCH 089/128] Revert "net/mlx5: E-Switch, preserve max tx speed on vport state modification" This reverts commit ad0ae7aefa7a ("net/mlx5: E-Switch, preserve max tx speed on vport state modification"). mlx5_modify_vport_admin_state() and mlx5_esw_adj_vport_modify() query the vport's current max_tx_speed before modifying its state, and write that value back, to avoid resetting it to 0 as a side effect of an unrelated admin-state change. That's unnecessary: max_tx_speed is optional in MODIFY_VPORT_STATE - FW skips writing it whenever it's 0, treating that as "not provided" rather than "reset to zero". Leaving it unset already preserves FW's current value, with no query needed. Fixes: ad0ae7aefa7a ("net/mlx5: E-Switch, preserve max tx speed on vport state modification") Signed-off-by: Or Har-Toov Reviewed-by: Mark Bloch Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20261004083531.216988-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../mellanox/mlx5/core/esw/adj_vport.c | 20 ---------------- .../net/ethernet/mellanox/mlx5/core/vport.c | 24 ------------------- 2 files changed, 44 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/adj_vport.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/adj_vport.c index 7950b82d8b8a..3624c680a861 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/adj_vport.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/adj_vport.c @@ -11,26 +11,6 @@ int mlx5_esw_adj_vport_modify(struct mlx5_core_dev *dev, u16 vport, lockdep_assert_held(&dev->priv.eswitch->state_lock); - if (MLX5_CAP_ESW(dev, esw_vport_state_max_tx_speed)) { - u8 op_mod = MLX5_VPORT_STATE_OP_MOD_ESW_VPORT; - struct mlx5_vport *esw_vport; - u32 speed = 0; - int err; - - err = mlx5_query_vport_max_tx_speed(dev, op_mod, vport, - true, &speed, NULL); - if (err) { - esw_vport = mlx5_eswitch_get_vport(dev->priv.eswitch, - vport); - speed = IS_ERR(esw_vport) ? 0 : - esw_vport->agg_max_tx_speed; - mlx5_core_dbg(dev, - "Failed to query vport %d max tx speed, err=%d, using cached %u\n", - vport, err, speed); - } - MLX5_SET(modify_vport_state_in, in, max_tx_speed, speed); - } - MLX5_SET(modify_vport_state_in, in, opcode, MLX5_CMD_OP_MODIFY_VPORT_STATE); MLX5_SET(modify_vport_state_in, in, op_mod, diff --git a/drivers/net/ethernet/mellanox/mlx5/core/vport.c b/drivers/net/ethernet/mellanox/mlx5/core/vport.c index 5774ec117428..78f3d15b599d 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/vport.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/vport.c @@ -93,30 +93,6 @@ int mlx5_modify_vport_admin_state(struct mlx5_core_dev *mdev, u8 opmod, lockdep_assert_held(&mdev->priv.eswitch->state_lock); #endif - if (MLX5_CAP_ESW(mdev, esw_vport_state_max_tx_speed) && - opmod == MLX5_VPORT_STATE_OP_MOD_ESW_VPORT && - vport != MLX5_VPORT_UPLINK) { - u32 speed = 0; - int err; - - err = mlx5_query_vport_max_tx_speed(mdev, opmod, vport, - other_vport, &speed, NULL); - if (err) { -#ifdef CONFIG_MLX5_ESWITCH - struct mlx5_vport *esw_vport; - - esw_vport = mlx5_eswitch_get_vport(mdev->priv.eswitch, - vport); - speed = IS_ERR(esw_vport) ? 0 : - esw_vport->agg_max_tx_speed; -#endif - mlx5_core_dbg(mdev, - "Failed to query vport %d max tx speed, err=%d, using cached %u\n", - vport, err, speed); - } - MLX5_SET(modify_vport_state_in, in, max_tx_speed, speed); - } - MLX5_SET(modify_vport_state_in, in, opcode, MLX5_CMD_OP_MODIFY_VPORT_STATE); MLX5_SET(modify_vport_state_in, in, op_mod, opmod); From 538b529509c7ab0d909cb65bb7264edca1788d30 Mon Sep 17 00:00:00 2001 From: Hui Peng Date: Wed, 30 Sep 2026 04:46:11 +0000 Subject: [PATCH 090/128] sctp: copy zeroed stats to user in sctp_getsockopt_pr_streamstatus() when !streamoute When an SCTP stream output extension structure (streamoute) has not yet been allocated, sctp_getsockopt_pr_streamstatus() sets the local stack variables params.sprstat_abandoned_unsent = 0 and params.sprstat_abandoned_sent = 0, but then immediately jumps to 'out:', bypassing copy_to_user(). As a result, when an uninitialized user buffer is passed to getsockopt(SCTP_PR_STREAM_STATUS), the kernel returns success (0) without copying the zeroed stats back to user space, leaving uninitialized data in the user buffer. Refactor the !streamoute check into an if-else chain so that when streamoute is NULL, params is zeroed and falls through to copy_to_user(), copying the zeroed stats to user space and setting retval = 0 once before returning. Tested in QEMU against Linux 7.3.0-rc3 using an uninitialized sctp_prstatus user struct passed to getsockopt(SCTP_PR_STREAM_STATUS) on a freshly created stream: on the unfixed kernel the user struct contains uninitialized stack garbage; whereas with this fix applied, getsockopt() copies zeroed stats into user space and returns 0. Cc: stable@vger.kernel.org Reviewed-by: Xin Long Signed-off-by: Hui Peng Fixes: f952be79cebd ("sctp: introduce struct sctp_stream_out_ext") Link: https://patch.msgid.link/20260930044611.204739-1-benquike@gmail.com Signed-off-by: Jakub Kicinski --- net/sctp/socket.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/net/sctp/socket.c b/net/sctp/socket.c index 4652fd90d9a6..e35692dd8d30 100644 --- a/net/sctp/socket.c +++ b/net/sctp/socket.c @@ -7569,11 +7569,7 @@ static int sctp_getsockopt_pr_streamstatus(struct sock *sk, int len, /* Not allocated yet, means all stats are 0 */ params.sprstat_abandoned_unsent = 0; params.sprstat_abandoned_sent = 0; - retval = 0; - goto out; - } - - if (policy == SCTP_PR_SCTP_ALL) { + } else if (policy == SCTP_PR_SCTP_ALL) { params.sprstat_abandoned_unsent = 0; params.sprstat_abandoned_sent = 0; for (policy = 0; policy <= SCTP_PR_INDEX(MAX); policy++) { From 0984ebc631792acf8fff105112ee74a6b8f1efcc Mon Sep 17 00:00:00 2001 From: Sabrina Dubroca Date: Mon, 5 Oct 2026 15:50:02 +0200 Subject: [PATCH 091/128] strparser: make sure __strp_recv isn't running before tearing down the parser The comment above strp_done() claims that if strp is stopped, strp_recv will no longer be called. That's only true if the caller has a mechanism (eg locking) to guarantee that strp_recv() calls that started before strp_stop() have completed by the time we call strp_done(). This adds a dummy lock_sock/release_sock pair to guarantee that any in-flight strp_recv() (which runs under either bh_lock_sock or lock_sock, depending if it's called from ->sk_data_ready or strp_work) has completed. Only espintcp can be affected by this race condition, but this patch makes sure no future user of strp can have the bug. This could crash on strp->sk ("general mode" of strp), but this mode has been here for 9 years and has never been used. It'll be gone soon, no point worrying about it. Switch the stopped/paused/etc bits to u8's to avoid races between strp_stop() and strp_pause/unpause(). A reproducer has been published and turns the possible UAF into an exploit [1]. Reported-by: Hyunwoo Kim Link: https://lore.kernel.org/all/aZLn2Faeg1FB7XOf@v4bel/ Link: https://lore.kernel.org/all/aZgpkyTDU3aXe_V0@v4bel/ Link: https://github.com/m0x41nos/RustyTux [1] Fixes: e27cca96cd68 ("xfrm: add espintcp (RFC 8229)") Cc: stable@vger.kernel.org Signed-off-by: Sabrina Dubroca Reviewed-by: Hyunwoo Kim Link: https://patch.msgid.link/425d9d926709b542ed5331110c19fee4aee1f28d.1791206940.git.sd@queasysnail.net Signed-off-by: Jakub Kicinski --- Documentation/networking/strparser.rst | 2 +- include/net/strparser.h | 10 +++++----- net/strparser/strparser.c | 6 +++++- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Documentation/networking/strparser.rst b/Documentation/networking/strparser.rst index 372106b61e65..d0804c88ad0a 100644 --- a/Documentation/networking/strparser.rst +++ b/Documentation/networking/strparser.rst @@ -77,7 +77,7 @@ Functions strp_done is called to release any resources held by the stream parser instance. This must be called after the stream processor - has been stopped. + has been stopped. The socket lock must not be held. :: diff --git a/include/net/strparser.h b/include/net/strparser.h index 0ed73e364faa..881b7ae34498 100644 --- a/include/net/strparser.h +++ b/include/net/strparser.h @@ -91,11 +91,11 @@ static inline struct strp_msg *strp_msg(struct sk_buff *skb) struct strparser { struct sock *sk; - u32 stopped : 1; - u32 paused : 1; - u32 aborted : 1; - u32 interrupted : 1; - u32 unrecov_intr : 1; + u8 stopped; + u8 paused; + u8 aborted; + u8 interrupted; + u8 unrecov_intr; struct sk_buff **skb_nextp; struct sk_buff *skb_head; diff --git a/net/strparser/strparser.c b/net/strparser/strparser.c index a23f4b4dfc67..d5b17d099a0e 100644 --- a/net/strparser/strparser.c +++ b/net/strparser/strparser.c @@ -505,12 +505,16 @@ void strp_unpause(struct strparser *strp) EXPORT_SYMBOL_GPL(strp_unpause); /* strp must already be stopped so that strp_recv will no longer be called. - * Note that strp_done is not called with the lower socket held. + * Note that strp_done must not be called with the lower socket held. */ void strp_done(struct strparser *strp) { WARN_ON(!strp->stopped); + lock_sock(strp->sk); + /* sync with pending strp_recv */ + release_sock(strp->sk); + cancel_delayed_work_sync(&strp->msg_timer_work); cancel_work_sync(&strp->work); From 5857e5a196b069766a39ceb754736326a43bc6bc Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Wed, 30 Sep 2026 16:06:34 -0700 Subject: [PATCH 092/128] net: bcmgenet: if UMAC was suspended in SW_RESET, restore it to SW_RESET When the revised suspend/resume sequence was introduced this led to an edge case where the TX is left disabled in the following sequence. 1. phy link is down, so UMAC is held in reset and then network interface is WoL enabled 2. Enter suspend, bcmgenet_wol_power_down_cfg() enables UMAC_RX since MAC is in SW_RESET 4. Enter resume, UMAC_RX is left enabled. Since we only enable UMAC_TX and UMAC_RX in SW_RESET. The UMAC_TX is never enabled again on link up. The issue was hit in production and I verified the sequence keeps the TX disabled in my standalone test. I tested on an internal development board with bcm77122, but should be reproducible on any genet HW that supports power management. Fixes: 254f3239dd07 ("net: bcmgenet: revise suspend/resume") Signed-off-by: Justin Chen Reviewed-by: Florian Fainelli Reviewed-by: Nicolai Buchwitz Link: https://patch.msgid.link/20260930230634.3132729-1-justin.chen@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/genet/bcmgenet_wol.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet_wol.c b/drivers/net/ethernet/broadcom/genet/bcmgenet_wol.c index 96d5d4f7f51f..984432952963 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet_wol.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet_wol.c @@ -253,6 +253,17 @@ int bcmgenet_wol_power_up_cfg(struct bcmgenet_priv *priv, reg = bcmgenet_umac_readl(priv, UMAC_CMD); reg &= ~CMD_CRC_FWD; bcmgenet_umac_writel(priv, reg, UMAC_CMD); + + /* + * Mirror wol_power_down_cfg(). If only UMAC_RX + * is enabled, then we must place the UMAC back + * into SW_RESET. + */ + reg = bcmgenet_umac_readl(priv, UMAC_CMD); + if ((reg & CMD_RX_EN) && !(reg & CMD_TX_EN)) { + reg |= CMD_SW_RESET; + bcmgenet_umac_writel(priv, reg, UMAC_CMD); + } spin_unlock_bh(&priv->reg_lock); /* Resume link status tracking */ From 23609bce9e1de525d1d0e73fc68c6e7971d0b49e Mon Sep 17 00:00:00 2001 From: Haishuang Yan Date: Wed, 30 Sep 2026 20:48:18 +0800 Subject: [PATCH 093/128] pfcp: make sure the SEID is linear before reading it pfcp_encap_recv() only makes sure that the UDP header and the 4 byte PFCP header are in the linear area. When the S flag is set, pfcp_session_recv() then reads the 8 byte SEID that follows, which is not covered by the pskb_may_pull() check. A short PFCP packet with the S flag set, or one whose session header lies in a fragment, therefore makes pfcp_session_recv() read beyond the end of the packet data, and whatever it finds there is stored in the tunnel metadata that flower later classifies on. Pull up to the end of the SEID before reading it, and drop packets that are too short to contain it. Reload the header pointer afterwards since pskb_may_pull() may reallocate the skb head. Use offsetofend() rather than sizeof(struct pfcphdr_session): the structure is not packed, so its size is 16 bytes because of the alignment of the __be64 member, while the header on the wire is only 12 bytes, and valid session messages would be dropped. Fixes: 6dd514f48110 ("pfcp: always set pfcp metadata") Signed-off-by: Haishuang Yan Link: https://patch.msgid.link/20260930124818.114224-3-yanhaishuang@cmss.chinamobile.com Signed-off-by: Jakub Kicinski --- drivers/net/pfcp.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/net/pfcp.c b/drivers/net/pfcp.c index 3ecd43cafdf0..51c67c914fad 100644 --- a/drivers/net/pfcp.c +++ b/drivers/net/pfcp.c @@ -65,6 +65,13 @@ static int pfcp_encap_recv(struct sock *sk, struct sk_buff *skb) goto drop; unparsed = pfcp_hdr(skb); + if (unparsed->flags & PFCP_SEID_FLAG) { + if (unlikely(!pskb_may_pull(skb, PFCP_HLEN + + offsetofend(struct pfcphdr_session, + seid)))) + goto drop; + unparsed = pfcp_hdr(skb); + } ip_tunnel_flags_zero(flags); tun_dst = udp_tun_rx_dst(skb, sk->sk_family, flags, 0, From f394cbd0980c95c9173f8551a5eb4c4f28d598eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Roux?= Date: Tue, 6 Oct 2026 10:37:51 +0200 Subject: [PATCH 094/128] wifi: mac80211: don't estimate airtime for unsupported rate widths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ieee80211_fill_rate_info() copies the driver's rate_info bandwidth into the rx_status as is, but ieee80211_get_rate_duration() only handles 20/40/80/160/320 MHz. When a driver reports another width, e.g. an HE/EHT RU allocation, it hits WARN_ON_ONCE() and returns 0 without setting *overhead. ieee80211_rate_expected_tx_airtime() then goes on using the uninitialized overhead, as Dan's static checker report points out. Seen on a Microsoft Surface Pro 11 (WCN7850, ath12k) while NetworkManager dumps station info: WARNING: net/mac80211/airtime.c:532 at ieee80211_get_rate_duration.isra.0+0xc0/0x180 [mac80211] ieee80211_get_rate_duration.isra.0+0xc0/0x180 [mac80211] sta_estimate_expected_throughput.isra.0+0x38/0x64 [mac80211] sta_set_sinfo+0x7fc/0x8e4 [mac80211] ieee80211_dump_station+0x4c/0x84 [mac80211] nl80211_dump_station+0x594/0x7e8 [cfg80211] Only accept widths that ieee80211_get_rate_duration() handles in ieee80211_fill_rate_info(). Other widths then give no estimate instead of a warning. Also return 0 from ieee80211_rate_expected_tx_airtime() when no duration could be computed, as ieee80211_calc_rx_airtime() already does. Tested on the Surface Pro 11 with next-20260929: Wi-Fi works and "expected throughput" is still reported (about 1049 Mbit/s for a 1297 Mbit/s HE 160 MHz tx rate). The warning itself is rare (twice in several days of use), so this does not prove it is gone, only that the change does not break the normal path. Fixes: 094dc1619cb0 ("wifi: mac80211: factor out part of ieee80211_calc_expected_tx_airtime") Reported-by: Dan Carpenter Closes: https://lore.kernel.org/all/aneGlCMVnqRhlf6p@stanley.mountain/ Assisted-by: LLM Signed-off-by: François Roux Link: https://patch.msgid.link/20261006083751.4015-1-franzelfranzel@gmail.com Signed-off-by: Johannes Berg --- net/mac80211/airtime.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/net/mac80211/airtime.c b/net/mac80211/airtime.c index 0c54cdbd753c..a4e1f33c0377 100644 --- a/net/mac80211/airtime.c +++ b/net/mac80211/airtime.c @@ -632,6 +632,22 @@ static bool ieee80211_fill_rate_info(struct ieee80211_hw *hw, if (!ri || !sband) return false; + /* + * ieee80211_get_rate_duration() only handles these widths. Drivers + * may also report e.g. HE/EHT RU allocations, which cannot be used + * to estimate airtime here. + */ + switch (ri->bw) { + case RATE_INFO_BW_20: + case RATE_INFO_BW_40: + case RATE_INFO_BW_80: + case RATE_INFO_BW_160: + case RATE_INFO_BW_320: + break; + default: + return false; + } + stat->bw = ri->bw; stat->nss = ri->nss; stat->rate_idx = ri->mcs; @@ -770,6 +786,8 @@ u32 ieee80211_rate_expected_tx_airtime(struct ieee80211_hw *hw, return ieee80211_calc_rx_airtime(hw, &stat, len) * 1024; duration = ieee80211_get_rate_duration(hw, &stat, &overhead); + if (!duration) + return 0; /* * Assume that HT/VHT transmission on any AC except VO will From 08a3f02fea636e97a7422d1512f76dacdb5b0887 Mon Sep 17 00:00:00 2001 From: Michele Dionisio Date: Thu, 24 Sep 2026 14:30:27 +0200 Subject: [PATCH 095/128] wifi: brcmfmac: fix P2P device removal race in brcmf_detach() When the driver is removed while the user space process that created the P2P device (e.g. wpa_supplicant) is still exiting, the P2P device interface is removed twice and the kernel crashes. The two paths are: - wpa_supplicant, on exit, sends NL80211_CMD_DEL_INTERFACE for the P2P device. nl80211_del_interface() holds RTNL and the wiphy mutex and calls brcmf_p2p_del_vif(), which disables discovery and waits up to 1.5 s for BRCMF_E_IF_DEL. Then it calls brcmf_remove_interface(), which unregisters the wdev and frees the vif and the ifp. - brcmf_detach() sets the bus down, so BRCMF_E_IF_DEL never arrives, and calls brcmf_remove_interface(ifp, false) for the same ifp. brcmf_p2p_ifp_removed() reads ifp->vif and then blocks on rtnl_lock(). When brcmf_p2p_del_vif() releases RTNL, it calls cfg80211_unregister_wdev() on the wdev that was already unregistered and freed. The second list_del_rcu() of wdev->list faults on LIST_POISON2: brcmfmac: brcmf_p2p_del_vif delete P2P vif brcmfmac: brcmf_p2p_deinit_discovery enter brcmfmac: brcmf_p2p_del_vif P2P: GO_NEG_PHASE status cleared brcmfmac: brcmf_detach Enter brcmfmac: brcmf_bus_change_state 1 -> 0 brcmfmac: brcmf_remove_interface Enter, bsscfgidx=1, ifidx=0 brcmfmac: brcmf_del_if Enter, bsscfgidx=1, ifidx=0 brcmfmac: brcmf_p2p_ifp_removed P2P: device interface removed [1.5 s later, brcmf_p2p_del_vif() timed out] brcmfmac: brcmf_remove_interface Enter, bsscfgidx=1, ifidx=0 brcmfmac: brcmf_del_if Enter, bsscfgidx=1, ifidx=0 brcmfmac: brcmf_p2p_ifp_removed P2P: device interface removed Unable to handle kernel paging request at virtual address dead000000000122 Internal error: Oops: 96000044 [#1] PREEMPT_RT SMP pc : _cfg80211_unregister_wdev+0x6c/0x264 [cfg80211] Call trace: _cfg80211_unregister_wdev+0x6c/0x264 [cfg80211] cfg80211_unregister_wdev+0x10/0x1c [cfg80211] brcmf_p2p_ifp_removed+0x84/0xb0 [brcmfmac] brcmf_remove_interface+0x1f4/0x254 [brcmfmac] brcmf_detach+0x88/0x180 [brcmfmac] brcmf_sdio_remove+0x90/0x7b0 [brcmfmac] brcmf_sdiod_remove+0x20/0xa0 [brcmfmac] brcmf_ops_sdio_remove+0xbc/0x12c [brcmfmac] sdio_bus_remove+0x38/0x144 device_release_driver_internal+0x1e8/0x2c0 device_release_driver+0x14/0x20 brcmf_sdio_bus_remove+0x2c/0x40 [brcmfmac] brcmf_fwvid_unregister_vendor+0xd8/0x170 [brcmfmac] __exit_compat+0x18/0x418 [brcmfmac_cyw] __arm64_sys_delete_module+0x1ac/0x244 The log was taken with debug=0x406 on an i.MX 8M Plus board with a CYW55513 (Sona IF513) on SDIO. The driver is the Ezurio backport of brcmfmac from v6.18.22 on a 5.4-rt kernel. The code involved is the same in mainline. To reproduce: 1. Start wpa_supplicant on wlan0 and let it associate. It creates the P2P device (iw dev shows "type P2P-device"). 2. Stop wpa_supplicant and remove the driver without waiting for wpa_supplicant to exit: kill $(pidof wpa_supplicant) rmmod brcmfmac_cyw brcmfmac The crash is reproducible on that board. For the interface without a netdev (the P2P device), take RTNL and the wiphy mutex in brcmf_detach() before reading iflist, and remove it with locked=true. If brcmf_p2p_del_vif() runs first, brcmf_detach() finds the slot empty. If brcmf_detach() runs first, nl80211 does not find the wdev any more. The interfaces with a netdev are removed as before, because brcmf_del_if() takes RTNL on its own for the primary interface. Fixes: 9831bcb987df ("brcmfmac: Deleting of p2p device is leaking memory.") Cc: stable@vger.kernel.org Assisted-by: claude-opus-5-5 Signed-off-by: Michele Dionisio Acked-by: Arend van Spriel Link: https://patch.msgid.link/20260924123027.4122909-2-michele.dionisio@gmail.com Signed-off-by: Johannes Berg --- .../broadcom/brcm80211/brcmfmac/core.c | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c index d2ae67985606..92ec269f43b2 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c @@ -1489,8 +1489,31 @@ void brcmf_detach(struct device *dev) brcmf_bus_change_state(bus_if, BRCMF_BUS_DOWN); /* make sure primary interface removed last */ for (i = BRCMF_MAX_IFS - 1; i > -1; i--) { - if (drvr->iflist[i]) - brcmf_remove_interface(drvr->iflist[i], false); + struct brcmf_if *ifp = drvr->iflist[i]; + + if (!ifp) + continue; + + if (ifp->ndev) { + brcmf_remove_interface(ifp, false); + continue; + } + + /* The P2P device interface has no netdev. Its removal can + * race with NL80211_CMD_DEL_INTERFACE issued by a user space + * process that is exiting (e.g. wpa_supplicant), which ends + * in brcmf_p2p_del_vif() under RTNL and the wiphy mutex. If + * both paths remove the interface, the wdev is unregistered + * twice and the vif is used after being freed. Take the same + * locks and re-read iflist, so only one path removes it. + */ + rtnl_lock(); + wiphy_lock(drvr->wiphy); + ifp = drvr->iflist[i]; + if (ifp) + brcmf_remove_interface(ifp, true); + wiphy_unlock(drvr->wiphy); + rtnl_unlock(); } brcmf_bus_stop(drvr->bus_if); From 9727d1c4e1c971fc42b049b645fdda6852758835 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 7 Oct 2026 22:42:08 +0200 Subject: [PATCH 096/128] Revert "wifi: libertas: reject short monitor TX frames" This reverts commit 13ff543e0b2c713aedeaadadde686686e949dc78. The 'goto free' just made things worse, causing a spinlock to be handled incorrectly. Revert this for now. Reported-by: Takashi Iwai Cc: stable@vger.kernel.org Fixes: 13ff543e0b2c ("wifi: libertas: reject short monitor TX frames") Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/libertas/tx.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/drivers/net/wireless/marvell/libertas/tx.c b/drivers/net/wireless/marvell/libertas/tx.c index 13d08022e414..27304a98787d 100644 --- a/drivers/net/wireless/marvell/libertas/tx.c +++ b/drivers/net/wireless/marvell/libertas/tx.c @@ -117,13 +117,6 @@ netdev_tx_t lbs_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) if (priv->wdev->iftype == NL80211_IFTYPE_MONITOR) { struct tx_radiotap_hdr *rtap_hdr = (void *)skb->data; - if (skb->len < sizeof(*rtap_hdr) + 4 + ETH_ALEN) { - lbs_deb_tx("tx err: short monitor frame %u\n", skb->len); - dev->stats.tx_dropped++; - dev->stats.tx_errors++; - goto free; - } - /* set txpd fields from the radiotap header */ txpd->tx_control = cpu_to_le32(convert_radiotap_rate_to_mv(rtap_hdr->rate)); From 9b6c20f788472d2042717a3c2f1bc60b2b77083e Mon Sep 17 00:00:00 2001 From: Fredrik Nyberg Date: Fri, 2 Oct 2026 14:28:57 +0200 Subject: [PATCH 097/128] net: usb: ax88179_178a: fix rx frame length for non-last packets Commit 4ce62d5b2f7a ("net: usb: ax88179_178a: stop lying about skb->truesize") replaced skb_clone() with a copy into a new skb for every packet of a bulk-in transfer except the last one. The copy skips the 2-byte IP alignment pseudo header but still copies pkt_len bytes, and pkt_len includes that header. Each of these frames is therefore delivered with two extra trailing bytes: the 0xeeee pseudo header of the next slot when the frame ends on an 8-byte boundary, or slot padding otherwise. The last packet is still trimmed to pkt_len - 2. IPv4 and IPv6 trim the extra bytes, so most traffic is unaffected. Anything that relies on the frame length is not. MACsec takes the ICV from the last 16 bytes and drops every such frame as InPktsNotValid. In our setup that was 0.2-1.2% of received MACsec frames, depending on how many frames shared a transfer, including service discovery multicast. Allocate and copy pkt_len - 2 bytes, like the last-packet path. Fixes: 4ce62d5b2f7a ("net: usb: ax88179_178a: stop lying about skb->truesize") Cc: stable@vger.kernel.org Signed-off-by: Fredrik Nyberg Reviewed-by: Simon Horman Link: https://patch.msgid.link/20261002-ax88179-rx-len-v1-1-f01a2a295079@volvocars.com Signed-off-by: Jakub Kicinski --- drivers/net/usb/ax88179_178a.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c index 81d8412ce8e2..cba1dc95d359 100644 --- a/drivers/net/usb/ax88179_178a.c +++ b/drivers/net/usb/ax88179_178a.c @@ -1457,11 +1457,11 @@ static int ax88179_rx_fixup(struct usbnet *dev, struct sk_buff *skb) return 1; } - ax_skb = netdev_alloc_skb_ip_align(dev->net, pkt_len); + ax_skb = netdev_alloc_skb_ip_align(dev->net, pkt_len - 2); if (!ax_skb) return 0; - skb_put(ax_skb, pkt_len); - memcpy(ax_skb->data, skb->data + 2, pkt_len); + skb_put(ax_skb, pkt_len - 2); + memcpy(ax_skb->data, skb->data + 2, pkt_len - 2); ax88179_rx_checksum(ax_skb, pkt_hdr); usbnet_skb_return(dev, ax_skb); From 8f5d59b9cfa69beab3b52cca621bb301c5af98aa Mon Sep 17 00:00:00 2001 From: Michael Chan Date: Mon, 5 Oct 2026 13:42:44 -0700 Subject: [PATCH 098/128] bnxt_en: Add bnxt_clear_bars() helper In bnxt_io_slot_reset(), we clear the 6 BAR registers. Add a helper function to do that. The helper will be used again in the next patch. Reviewed-by: Kalesh AP Reviewed-by: Somnath Kotur Reviewed-by: Joe Damato Signed-off-by: Pavan Chebbi Signed-off-by: Michael Chan Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20261005204246.3822563-2-michael.chan@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index d7728d0c5b6e..8ce8a82d3453 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -17111,6 +17111,14 @@ void bnxt_print_device_info(struct bnxt *bp) pcie_print_link_status(bp->pdev); } +static void bnxt_clear_bars(struct pci_dev *pdev) +{ + int off; + + for (off = PCI_BASE_ADDRESS_0; off <= PCI_BASE_ADDRESS_5; off += 4) + pci_write_config_dword(pdev, off, 0); +} + static int bnxt_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) { struct bnxt_hw_resc *hw_resc; @@ -17598,7 +17606,6 @@ static pci_ers_result_t bnxt_io_slot_reset(struct pci_dev *pdev) struct bnxt *bp = netdev_priv(netdev); int retry = 0; int err = 0; - int off; netdev_info(bp->dev, "PCI Slot Reset\n"); @@ -17627,11 +17634,8 @@ static pci_ers_result_t bnxt_io_slot_reset(struct pci_dev *pdev) * write the BARs to 0 to force restore, in case of fatal error. */ if (test_and_clear_bit(BNXT_STATE_PCI_CHANNEL_IO_FROZEN, - &bp->state)) { - for (off = PCI_BASE_ADDRESS_0; - off <= PCI_BASE_ADDRESS_5; off += 4) - pci_write_config_dword(bp->pdev, off, 0); - } + &bp->state)) + bnxt_clear_bars(pdev); pci_restore_state(pdev); bnxt_inv_fw_health_reg(bp); From 386f887c8290f0747dd0180e98ec7a82811bc506 Mon Sep 17 00:00:00 2001 From: Michael Chan Date: Mon, 5 Oct 2026 13:42:45 -0700 Subject: [PATCH 099/128] bnxt_en: Fix driver init in kdump kernel Fix and strengthen the FLR sequence when initializing in the kdump kernel. If the NIC is behind a PCIe switch in synthetic (smart) mode, the switch may need to see that the BARs have been initialized before it will pass mem read/write TLPs to the NIC. On a Dell system with a PEX89144 PCIe switch, echo c > /proc/sysrq-trigger will trigger fatal AER without this patch: bnxt_en 0000:67:00.0: enabling device (0000 -> 0002) [Hardware Error]: Hardware error from APEI Generic Hardware Error Source: 5 [Hardware Error]: event severity: recoverable [Hardware Error]: Error 0, type: fatal [Hardware Error]: section_type: PCIe error [Hardware Error]: port_type: 5, upstream switch port ... Add a new bnxt_kdump_reset() to do the expanded FLR sequence in the kdump kernel. We now disable bus master and memory, save the PCI state, do the FLR, clear the BARs, and restore the PCI state. The BARs have to be cleared to ensure that they get re-initialized and visible to the PCIe switch. Since it is the kdump kernel, we make every effort to continue in the best possible way even if pci_save_state() or pcie_flr() returns error. After FLR, we poll for an additional 5 seconds before aborting in case the device is not properly returning CRS. This is similar to the 5-second wait in bnxt_io_slot_reset(). Fixes: 8743db4a9acf ("bnxt_en: Issue PCIe FLR in kdump kernel to cleanup pending DMAs.") Reviewed-by: Kalesh AP Signed-off-by: Pavan Chebbi Signed-off-by: Michael Chan Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20261005204246.3822563-3-michael.chan@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 45 ++++++++++++++++++++--- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 8ce8a82d3453..9ea7e172787e 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -17119,6 +17119,43 @@ static void bnxt_clear_bars(struct pci_dev *pdev) pci_write_config_dword(pdev, off, 0); } +/* Clear any pending DMA transactions from crash kernel while loading driver in + * capture kernel. + */ +static int bnxt_kdump_reset(struct pci_dev *pdev) +{ + int rc, i; + u16 cmd; + + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + cmd &= ~(PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY); + pci_write_config_word(pdev, PCI_COMMAND, cmd); + + if (pci_save_state(pdev)) + dev_warn(&pdev->dev, "Failed to save PCI state, PCI restore may be incomplete\n"); + + rc = pcie_flr(pdev); + if (rc) + dev_warn(&pdev->dev, "pcie_flr() failed (rc: %d), trying to continue\n", + rc); + + /* In case device is not returning CRS, wait 5 seconds longer */ + for (i = 0; i < 50; i++) { + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + if (!PCI_POSSIBLE_ERROR(cmd)) + break; + msleep(100); + } + if (PCI_POSSIBLE_ERROR(cmd)) { + dev_err(&pdev->dev, "PCI config space inaccessible after FLR, aborting\n"); + return -ENODEV; + } + + bnxt_clear_bars(pdev); + pci_restore_state(pdev); + return 0; +} + static int bnxt_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) { struct bnxt_hw_resc *hw_resc; @@ -17134,12 +17171,10 @@ static int bnxt_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) return -ENODEV; } - /* Clear any pending DMA transactions from crash kernel - * while loading driver in capture kernel. - */ if (is_kdump_kernel()) { - pci_clear_master(pdev); - pcie_flr(pdev); + rc = bnxt_kdump_reset(pdev); + if (rc) + return rc; } max_irqs = bnxt_get_max_irq(pdev); From 2e52da27096d9dbc487f303bbc10d44ad837a8c4 Mon Sep 17 00:00:00 2001 From: Pavan Chebbi Date: Mon, 5 Oct 2026 13:42:46 -0700 Subject: [PATCH 100/128] bnxt_en: Re-write the BARs following any type of PCIe errors Currently the driver zeroes the BARs only when fatal PCIe errors are reported so that pci_restore_state() restores it. However firmware handles both fatal and non-fatal errors the same way when it sees the slot reset resulting from the PCI_ERS_RESULT_NEED_RESET return code from the driver. This means that we must re-write the BARs post recovery even during non-fatal errors. Otherwise we will see that every MMIO access returns all-ones and the firmware appears dead. Zero-out the BARs during PCIe error recovery regardless of type of PCIe error, and make the wait after the hot reset unconditional. Disable memory decode and bus mastering before rewriting the BARs so the device doesn't decode a half-updated address, bailing out if config space is still inaccessible. Defer pci_enable_device() until after the BAR rewrite and restore, so the device isn't re-enabled while its BARs are still being rewritten, then re-enable the device and re-assert bus mastering. Guard the same Command register cleanup on the re-enable failure path against an inaccessible device. Skip re-enabling the device in bnxt_io_slot_reset() if it is already enabled, so enable_cnt does not go unbalanced. A concurrent bnxt_fw_reset_task() can also be re-enabling the same device in its ENABLE_DEV state, so guard that call the same way. Fixes: f75d9a0aa967 ("bnxt_en: Re-write PCI BARs after PCI fatal error.") Reviewed-by: Kalesh AP Reviewed-by: Scott Branden Signed-off-by: Pavan Chebbi Signed-off-by: Michael Chan Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20261005204246.3822563-4-michael.chan@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 117 ++++++++++++---------- drivers/net/ethernet/broadcom/bnxt/bnxt.h | 1 - 2 files changed, 65 insertions(+), 53 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 9ea7e172787e..5bd817479d64 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -15547,7 +15547,7 @@ static void bnxt_fw_reset_task(struct work_struct *work) if (test_and_clear_bit(BNXT_STATE_FW_ACTIVATE_RESET, &bp->state) && !test_bit(BNXT_STATE_FW_ACTIVATE, &bp->state)) bnxt_dl_remote_reload(bp); - if (pci_enable_device(bp->pdev)) { + if (!pci_is_enabled(bp->pdev) && pci_enable_device(bp->pdev)) { netdev_err(bp->dev, "Cannot re-enable PCI device\n"); rc = -ENODEV; goto fw_reset_abort; @@ -17608,10 +17608,8 @@ static pci_ers_result_t bnxt_io_error_detected(struct pci_dev *pdev, * so we disable bus master to prevent any potential bad DMAs before * freeing kernel memory. */ - if (state == pci_channel_io_frozen) { - set_bit(BNXT_STATE_PCI_CHANNEL_IO_FROZEN, &bp->state); + if (state == pci_channel_io_frozen) bnxt_fw_fatal_close(bp); - } if (netif_running(netdev)) __bnxt_close_nic(bp, true, true); @@ -17641,64 +17639,79 @@ static pci_ers_result_t bnxt_io_slot_reset(struct pci_dev *pdev) struct bnxt *bp = netdev_priv(netdev); int retry = 0; int err = 0; + u16 cmd; netdev_info(bp->dev, "PCI Slot Reset\n"); - if (test_bit(BNXT_STATE_PCI_CHANNEL_IO_FROZEN, &bp->state)) { - /* After DPC, the chip should return CRS when the vendor ID - * config register is read until it is ready. On all chips, - * this is not happening reliably so add a 5-second delay as a - * workaround. - */ - msleep(5000); - } + /* After a PCIe hot reset, the chip should return CRS when the + * vendor ID config register is read until it is ready. On all + * chips, this is not happening reliably so add a 5-second delay + * as a workaround. + */ + msleep(5000); netdev_lock(netdev); - if (pci_enable_device(pdev)) { + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + if (PCI_POSSIBLE_ERROR(cmd)) { + dev_err(&pdev->dev, + "PCI config space inaccessible after reset\n"); + goto reset_exit; + } + + /* Upon PCIe error, our device internal logic that latches to + * BAR value is getting reset and will restore only upon + * rewriting the BARs. + * + * As pci_restore_state() does not re-write the BARs if the + * value is same as saved value earlier, driver needs to + * write the BARs to 0 to force restore. + */ + pci_clear_master(pdev); + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + cmd &= ~PCI_COMMAND_MEMORY; + pci_write_config_word(pdev, PCI_COMMAND, cmd); + + bnxt_clear_bars(pdev); + pci_restore_state(pdev); + + if (!pci_is_enabled(pdev) && pci_enable_device(pdev)) { dev_err(&pdev->dev, "Cannot re-enable PCI device after reset.\n"); - } else { - pci_set_master(pdev); - /* Upon fatal error, our device internal logic that latches to - * BAR value is getting reset and will restore only upon - * rewriting the BARs. - * - * As pci_restore_state() does not re-write the BARs if the - * value is same as saved value earlier, driver needs to - * write the BARs to 0 to force restore, in case of fatal error. - */ - if (test_and_clear_bit(BNXT_STATE_PCI_CHANNEL_IO_FROZEN, - &bp->state)) - bnxt_clear_bars(pdev); - pci_restore_state(pdev); - - bnxt_inv_fw_health_reg(bp); - bnxt_try_map_fw_health_reg(bp); - - /* In some PCIe AER scenarios, firmware may take up to - * 10 seconds to become ready in the worst case. - */ - do { - err = bnxt_try_recover_fw(bp); - if (!err) - break; - retry++; - } while (retry < BNXT_FW_SLOT_RESET_RETRY); - - if (err) { - dev_err(&pdev->dev, "Firmware not ready\n"); - goto reset_exit; + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + if (!PCI_POSSIBLE_ERROR(cmd)) { + cmd &= ~(PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY); + pci_write_config_word(pdev, PCI_COMMAND, cmd); } - - err = bnxt_hwrm_func_reset(bp); - if (!err) - result = PCI_ERS_RESULT_RECOVERED; - - /* IRQ will be initialized later in bnxt_io_resume */ - bnxt_ulp_irq_stop(bp); - bnxt_clear_int_mode(bp); + goto reset_exit; } + pci_set_master(pdev); + + bnxt_inv_fw_health_reg(bp); + bnxt_try_map_fw_health_reg(bp); + + /* In some PCIe AER scenarios, firmware may take up to + * 10 seconds to become ready in the worst case. + */ + do { + err = bnxt_try_recover_fw(bp); + if (!err) + break; + retry++; + } while (retry < BNXT_FW_SLOT_RESET_RETRY); + + if (err) { + dev_err(&pdev->dev, "Firmware not ready\n"); + goto reset_exit; + } + + err = bnxt_hwrm_func_reset(bp); + if (!err) + result = PCI_ERS_RESULT_RECOVERED; + + /* IRQ will be initialized later in bnxt_io_resume */ + bnxt_ulp_irq_stop(bp); + bnxt_clear_int_mode(bp); reset_exit: clear_bit(BNXT_STATE_IN_FW_RESET, &bp->state); diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.h b/drivers/net/ethernet/broadcom/bnxt/bnxt.h index c673b2ce4a0d..41d05efd6307 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.h +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.h @@ -2468,7 +2468,6 @@ struct bnxt { #define BNXT_STATE_ABORT_ERR 5 #define BNXT_STATE_FW_FATAL_COND 6 #define BNXT_STATE_DRV_REGISTERED 7 -#define BNXT_STATE_PCI_CHANNEL_IO_FROZEN 8 #define BNXT_STATE_NAPI_DISABLED 9 #define BNXT_STATE_FW_ACTIVATE 11 #define BNXT_STATE_RECOVER 12 From a51d233ccd4889aadb71dace863525cc2b00271e Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 7 Oct 2026 07:17:47 +0200 Subject: [PATCH 101/128] bnxt_en: fix DMA mapping length for padded small packets Stefan Fleischmann reported Intel IOMMU DMA Read faults on BCM57412 NetXtreme-E NICs when transmitting packets on VLAN/macvlan interfaces: DMAR: [DMA Read NO_PASID] Request device [18:00.0] fault addr 0xfc499000 [fault reason 0x06] PTE Read access is not set bnxt_en 0000:18:00.0 eno1np0: Abandoning msg {0xb4 0x41a} len: 0 due to firmware status: 0x2000001 ... NETDEV WATCHDOG: eno1np0 (bnxt_en): transmit queue 0 timed out The fault address (0xfc499000) is on an exact 4KB page boundary, pointing to a DMA read buffer overrun. In bnxt_start_xmit(), packets smaller than BNXT_MIN_PKT_SIZE (52 bytes), such as 42-byte untagged ARP frames, are padded: if (length < BNXT_MIN_PKT_SIZE) { pad = BNXT_MIN_PKT_SIZE - length; if (skb_pad(skb, pad)) goto tx_kick_pending; length = BNXT_MIN_PKT_SIZE; } mapping = dma_map_single(&pdev->dev, skb->data, len, DMA_TO_DEVICE); ... dma_unmap_len_set(tx_buf, len, len); However, 'len' was initialized earlier to skb_headlen(skb) (e.g. 42 bytes) and is left unadjusted after padding. Consequently, dma_map_single() and dma_unmap_len_set() map and track only 42 bytes. Later, the hardware TX buffer descriptor is programmed with the padded length: txbd->tx_bd_len_flags_type = cpu_to_le32(((len + pad) << TX_BD_LEN_SHIFT) | flags | TX_BD_FLAGS_PACKET_END); The NIC DMA engine is thus instructed to read 52 bytes from a region where only 42 bytes were DMA-mapped. If skb->data ends near the boundary of a 4KB page (within 'pad' bytes of the next page), the hardware DMA read overruns into the unmapped adjacent page, triggering an IOMMU fault. This issue was exposed after commit 447cbe95ebb9 ("vlan: fix skb_under_panic and races when toggling HW VLAN offload") because reserving extra VLAN headroom rounded LL_RESERVED_SPACE from 48 up to 64 bytes, shifting skb->data offsets and potentially causing small frames to land right against page boundaries. Fix this by using skb_put_padto(skb, BNXT_MIN_PKT_SIZE) in bnxt_start_xmit(), before skb_shinfo(skb)->nr_frags is sampled, because padding might linearize the skb. This ensures skb->len and skb_headlen(skb) reflect the padded size so that dma_map_single() maps the full buffer and the descriptor length is consistent. This also removes the temporary 'pad' variable and masking logic. The padding is done after the SW USO branch, otherwise bnxt_sw_udp_gso_xmit() would account the padding as UDP payload and send it in extra segments. Note that SW USO segments are still not padded: with IPv4, a segment carrying less than 10 bytes of UDP payload (small gso_size, or a short last segment) is sent as a frame shorter than BNXT_MIN_PKT_SIZE. This is a separate issue, left for a followup patch. Fixes: c0c050c58d84 ("bnxt_en: New Broadcom ethernet driver.") Cc: stable@vger.kernel.org Reported-by: Stefan Fleischmann Closes: https://lore.kernel.org/netdev/20261004122616.56714cbd@nargothrond/ Signed-off-by: Eric Dumazet Reviewed-by: Michael Chan Link: https://patch.msgid.link/20261007051747.455273-1-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 25 +++++++++++------------ 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 5bd817479d64..15a8349ccf7b 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -486,7 +486,7 @@ static netdev_tx_t bnxt_start_xmit(struct sk_buff *skb, struct net_device *dev) struct netdev_queue *txq; int i; dma_addr_t mapping; - unsigned int length, pad = 0; + unsigned int length; u32 len, free_size, vlan_tag_flags, cfa_action, flags; struct bnxt_ptp_cfg *ptp = bp->ptp_cfg; struct pci_dev *pdev = bp->pdev; @@ -536,6 +536,16 @@ static netdev_tx_t bnxt_start_xmit(struct sk_buff *skb, struct net_device *dev) return rc < 0 ? NETDEV_TX_BUSY : NETDEV_TX_OK; } + /* Pad after the SW USO branch: bnxt_sw_udp_gso_xmit() would + * otherwise account the padding as UDP payload. + * Must be done before skb_shinfo(skb)->nr_frags is sampled, + * because skb_put_padto() might linearize the skb. + */ + if (skb_put_padto(skb, BNXT_MIN_PKT_SIZE)) { + /* SKB already freed. */ + goto tx_kick_pending; + } + free_size = bnxt_tx_avail(bp, txr); if (unlikely(free_size < skb_shinfo(skb)->nr_frags + 2)) { /* We must have raced with NAPI cleanup */ @@ -672,14 +682,6 @@ static netdev_tx_t bnxt_start_xmit(struct sk_buff *skb, struct net_device *dev) } normal_tx: - if (length < BNXT_MIN_PKT_SIZE) { - pad = BNXT_MIN_PKT_SIZE - length; - if (skb_pad(skb, pad)) - /* SKB already freed. */ - goto tx_kick_pending; - length = BNXT_MIN_PKT_SIZE; - } - mapping = dma_map_single(&pdev->dev, skb->data, len, DMA_TO_DEVICE); if (unlikely(dma_mapping_error(&pdev->dev, mapping))) @@ -759,10 +761,7 @@ normal_tx: txbd->tx_bd_len_flags_type = cpu_to_le32(flags); } - flags &= ~TX_BD_LEN; - txbd->tx_bd_len_flags_type = - cpu_to_le32(((len + pad) << TX_BD_LEN_SHIFT) | flags | - TX_BD_FLAGS_PACKET_END); + txbd->tx_bd_len_flags_type |= cpu_to_le32(TX_BD_FLAGS_PACKET_END); netdev_tx_sent_queue(txq, skb->len); From f202578efc738b883cfa04889eb97328914265a7 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 7 Oct 2026 06:56:15 +0200 Subject: [PATCH 102/128] flow_dissector: avoid u16 truncation of hlen in bpf_flow_dissect() bpf_flow_dissect() sanitizes the offsets returned by the BPF program with clamp_t(u16, ..., hlen). If hlen is bigger than 65535, the upper bound is truncated before the comparison. For instance, with hlen == 65536, both nhoff and thoff are forced to zero, even if the program returned sane values. This is the same class of bug as the one fixed in commit 99bda1ecbd56 ("flow_dissector: avoid u16 truncation of skb->len when computing thoff"). That fix does not cover the BPF path, because __skb_flow_dissect() returns early when a BPF flow dissector handles the packet. struct bpf_flow_keys stores nhoff and thoff as u16, so cap hlen to U16_MAX before clamping. Found by sashiko while reviewing the commit above. Fixes: d58e468b1112 ("flow_dissector: implements flow dissector BPF hook") Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20261007045616.445596-1-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- net/core/flow_dissector.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/core/flow_dissector.c b/net/core/flow_dissector.c index 27d8a01bc923..1be6c739e3c7 100644 --- a/net/core/flow_dissector.c +++ b/net/core/flow_dissector.c @@ -1023,6 +1023,8 @@ u32 bpf_flow_dissect(struct bpf_prog *prog, struct bpf_flow_dissector *ctx, result = bpf_prog_run_pin_on_cpu(prog, ctx); + /* bpf_flow_keys offsets are u16: do not let @hlen be truncated. */ + hlen = min_t(int, hlen, U16_MAX); flow_keys->nhoff = clamp_t(u16, flow_keys->nhoff, nhoff, hlen); flow_keys->thoff = clamp_t(u16, flow_keys->thoff, flow_keys->nhoff, hlen); From 1d6500523b8d5112afe0118173ebc8c1ca23784e Mon Sep 17 00:00:00 2001 From: Tony Nguyen Date: Thu, 1 Oct 2026 15:24:39 -0700 Subject: [PATCH 103/128] e1000e: add system to disable K1 list Javier reports seeing packet loss issues related to the re-enablement of K1; disabling it makes the issues go away. Add the reported system to have K1 disabled by default. Reproduction steps (from the Link): Plug the cable and ping the default gateway on the LAN. No suspend/resume involved, machine freshly booted and on AC power. Nothing e1000e-related in dmesg apart from the link up/down messages; no "Hardware Unit Hang". Default (K1 enabled): $ ping -c 30 192.168.10.1 --- 192.168.10.1 ping statistics --- 30 packets transmitted, 18 received, 40% packet loss, time 29734ms rtt min/avg/max/mdev = 0.211/0.315/0.384/0.046 ms The lost packets are spread over the run (seq 6, 8, 9, 11, 13, 14, 16, 19, 20, 24, 25, 30), not a single burst. DHCP on this link also takes 1-2 minutes to get a lease, consistent with incoming packets being dropped. With K1 disabled at runtime: # ethtool --set-priv-flags enp0s31f6 disable-k1 on $ ethtool --show-priv-flags enp0s31f6 Private flags for enp0s31f6: s0ix-enabled: on disable-k1 : on $ ping -c 30 192.168.10.1 --- 192.168.10.1 ping statistics --- 30 packets transmitted, 30 received, 0% packet loss, time 29713ms rtt min/avg/max/mdev = 0.169/0.251/0.468/0.054 ms $ ping -c 30 192.168.10.1 --- 192.168.10.1 ping statistics --- 30 packets transmitted, 30 received, 0% packet loss, time 29677ms rtt min/avg/max/mdev = 0.101/0.222/0.360/0.064 ms I have only compile tested this, but Javier has tested that disabling K1 on his system (Lenovo ThinkPad P14s Gen 5 (Intel), I219-LM [8086:550a]) resolves his issues. Cc: stable@vger.kernel.org Fixes: 578294b8b60d ("e1000e: Reconfigure PLL clock gate timeout and re-enable K1 on Meteor Lake") Reported-by: Javier Herrera Link: https://lore.kernel.org/intel-wired-lan/CAKRuCQLWEcci9hPgNUmvq44Bd2H30T5_vP=exMjsknXMN5Yh9A@mail.gmail.com/ Reviewed-by: Aleksandr Loktionov Signed-off-by: Tony Nguyen Link: https://patch.msgid.link/20261001222443.3500206-7-anthony.l.nguyen@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/e1000e/netdev.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/net/ethernet/intel/e1000e/netdev.c b/drivers/net/ethernet/intel/e1000e/netdev.c index 844f31ab37ad..c282f65199e4 100644 --- a/drivers/net/ethernet/intel/e1000e/netdev.c +++ b/drivers/net/ethernet/intel/e1000e/netdev.c @@ -67,6 +67,13 @@ static const struct dmi_system_id disable_k1_list[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Dell Pro 16 Plus PB16250"), }, }, + { + .ident = "Lenovo ThinkPad P14s Gen 5", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad P14s Gen 5"), + }, + }, {} }; From c57b2e60d4bf29707c51b39ed3bacbcce2f3ba2e Mon Sep 17 00:00:00 2001 From: Haishuang Yan Date: Fri, 2 Oct 2026 23:30:39 +0800 Subject: [PATCH 104/128] bareudp: set the inner protocol to the protocol of the packet bareudp_xmit_skb() sets the inner protocol of the skb to the configured ethertype, and bareudp6_xmit_skb() does not set it at all. The inner protocol is used by skb_udp_tunnel_segment() to segment the inner packet when GSO has to be done in software, for instance when the lower device does not offload the checksum. With multiproto, an IPv4 device also carries IPv6, so the IPv6 header is parsed as an IPv4 one. With IPv6 underlay, the inner protocol is whatever the skb had before. In both cases segmentation fails and the packets are dropped. With veth tx checksum offload disabled, iperf3 TCP over bareudp gets about 10 Mbit/s with thousands of retransmits instead of about 700 Mbit/s for IPv6 over IPv4 and for both families over IPv6, while IPv4 over IPv4 works. At this point skb->protocol is the protocol of the inner packet, which bareudp_xmit() has already checked against the configuration, so use it on both paths. Fixes: 571912c69f0e ("net: UDP tunnel encapsulation module for tunnelling different protocols like MPLS, IP, NSH etc.") Fixes: 4b5f67232d95 ("net: Special handling for IP & MPLS.") Signed-off-by: Haishuang Yan Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20261002153039.462663-1-yanhaishuang@cmss.chinamobile.com Signed-off-by: Jakub Kicinski --- drivers/net/bareudp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/bareudp.c b/drivers/net/bareudp.c index c3b5ed52d877..b9fe54151e1a 100644 --- a/drivers/net/bareudp.c +++ b/drivers/net/bareudp.c @@ -356,7 +356,7 @@ static int bareudp_xmit_skb(struct sk_buff *skb, struct net_device *dev, if (err) goto free_dst; - skb_set_inner_protocol(skb, bareudp->ethertype); + skb_set_inner_protocol(skb, skb->protocol); udp_tunnel_xmit_skb(rt, sk, skb, saddr, info->key.u.ipv4.dst, tos, ttl, df, sport, bareudp->port, !net_eq(bareudp->net, dev_net(bareudp->dev)), @@ -424,6 +424,7 @@ static int bareudp6_xmit_skb(struct sk_buff *skb, struct net_device *dev, if (err) goto free_dst; + skb_set_inner_protocol(skb, skb->protocol); daddr = info->key.u.ipv6.dst; udp_tunnel6_xmit_skb(dst, sk, skb, dev, &saddr, &daddr, prio, ttl, From 6d25ffca055a77787c21a36b66c253f76239411b Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Tue, 6 Oct 2026 12:51:08 +0900 Subject: [PATCH 105/128] cipso: adjust cached option offsets when removing CIPSO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cipso_v4_skbuff_delattr() removes the CIPSO bytes but does not adjust cached offsets for options that follow them. For example, with a valid 10-byte CIPSO option followed by a seven-byte RR option, parsing records rr at offset 30. Removing CIPSO moves RR to offset 20, while the cached offset remains 30. Consumers such as ip_forward_options() and __ip_options_echo() then access the wrong bytes; the latter may interpret packet data as the option length and copy it into fixed-size option storage. Mirror cipso_v4_delopt() and subtract cipso_len from the srr, rr, ts and router_alert offsets when they follow CIPSO. cipso_len is the distance the first memmove() shifts those options. The later header move and network-header reset relocate the bytes and their offset base together. Fixes: 89aa3619d141 ("cipso: make cipso_v4_skbuff_delattr() fully remove the CIPSO options") Cc: stable@vger.kernel.org Reviewed-by: Ondrej Mosnáček Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Acked-by: Paul Moore Link: https://patch.msgid.link/20261006035108.3101440-1-4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv4/cipso_ipv4.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/ipv4/cipso_ipv4.c b/net/ipv4/cipso_ipv4.c index a05aa075de1a..1aacbbeffbc6 100644 --- a/net/ipv4/cipso_ipv4.c +++ b/net/ipv4/cipso_ipv4.c @@ -2287,6 +2287,14 @@ int cipso_v4_skbuff_delattr(struct sk_buff *skb) new_hdr_len - new_hdr_len_actual); opt->optlen -= hdr_len_delta; + if (opt->srr > opt->cipso) + opt->srr -= cipso_len; + if (opt->rr > opt->cipso) + opt->rr -= cipso_len; + if (opt->ts > opt->cipso) + opt->ts -= cipso_len; + if (opt->router_alert > opt->cipso) + opt->router_alert -= cipso_len; opt->cipso = 0; opt->is_changed = 1; if (hdr_len_delta != 0) { From 259c4a519100e9182f264f8009ee212b32774277 Mon Sep 17 00:00:00 2001 From: Hamza Mahfooz Date: Fri, 2 Oct 2026 21:36:47 -0400 Subject: [PATCH 106/128] net: mana: reserve RX buffer headroom to fix forwarding performance Commit 730ff06d3f5c ("net: mana: Use page pool fragments for RX buffers instead of full pages to improve memory efficiency.") started handing out RX buffers with zero headroom so that two buffers fit into one page at the default MTU. The MANA TX path, however, stores the per scatter-gather entry DMA mappings in `struct mana_skb_head` at skb->head, and mana_start_xmit() therefore calls skb_cow_head(skb, MANA_HEADROOM). The port advertises this requirement as ndev->needed_headroom = MANA_HEADROOM. As a result every packet that is received and then forwarded out of a MANA port fails the skb_cow() in ip_forward() and gets reallocated and copied by pskb_expand_head(). This is invisible to a plain RX or TX workload, but it puts a full skb reallocation plus memcpy on the hot path of every single forwarded packet, which is exactly what a router/NVA workload does. Restore the headroom. Note that reserving MANA_HEADROOM (232) is not enough: ip_forward() asks for LL_RESERVED_SPACE(dev), which rounds hard_header_len + needed_headroom up to HH_DATA_MOD and is 256 bytes on ethernet. Use LL_RESERVED_SPACE() directly so the value keeps tracking both constants. Also, since LL_RESERVED_SPACE() tracks MANA_HEADROOM, it grows with MAX_SKB_FRAGS and for MAX_SKB_FRAGS >= 19 it is greater than 256, so we have to account for that by using the headroom the RX queue actually uses (instead of assuming XDP_PACKET_HEADROOM) and turning MANA_XDP_MTU_MAX into MANA_XDP_MTU_MAX(ndev) (note that at the default CONFIG_MAX_SKB_FRAGS=17 they are equivalent). At the default MTU on a 4K page this means a buffer no longer fits twice into a page (SKB_DATA_ALIGN(1500 + MANA_RXBUF_PAD + 256) = 2112), so the frag-vs-single decision is now made by computing the real buffer size instead of comparing the MTU against PAGE_SIZE / 2. The page_pool fragment path is still used wherever at least two buffers genuinely fit, e.g. on 16K and 64K page sizes. Measured on an Azure VM with a MANA NIC acting as a forwarding NVA (UDP, 1400 byte payload, 4 streams, 8 Gbps offered, only the forwarding node's kernel differs), 8 runs each, median: forwarded pps throughput before 272,830 3.06 Gbps after 390,560 4.37 Gbps (+43%) perf on the forwarding node, same workload: memset_orig __pi_memcpy pskb_expand_head before 10.07% 3.96% present after 0.94% 0.64% gone Cc: stable@vger.kernel.org Fixes: 730ff06d3f5c ("net: mana: Use page pool fragments for RX buffers instead of full pages to improve memory efficiency.") Signed-off-by: Hamza Mahfooz Reviewed-by: Simon Horman Link: https://patch.msgid.link/20261003013647.2051416-1-hamzamahfooz@linux.microsoft.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/microsoft/mana/mana_bpf.c | 8 +-- drivers/net/ethernet/microsoft/mana/mana_en.c | 63 ++++++++++++++----- .../ethernet/microsoft/mana/mana_ethtool.c | 10 +-- include/net/mana/mana.h | 14 ++++- 4 files changed, 68 insertions(+), 27 deletions(-) diff --git a/drivers/net/ethernet/microsoft/mana/mana_bpf.c b/drivers/net/ethernet/microsoft/mana/mana_bpf.c index 5c9961ee9747..b27fd9b8c4f6 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_bpf.c +++ b/drivers/net/ethernet/microsoft/mana/mana_bpf.c @@ -91,7 +91,7 @@ u32 mana_run_xdp(struct net_device *ndev, struct mana_rxq *rxq, goto out; xdp_init_buff(xdp, PAGE_SIZE, &rxq->xdp_rxq); - xdp_prepare_buff(xdp, buf_va, XDP_PACKET_HEADROOM, pkt_len, true); + xdp_prepare_buff(xdp, buf_va, rxq->headroom, pkt_len, true); act = bpf_prog_run_xdp(prog, xdp); @@ -183,9 +183,9 @@ static int mana_xdp_set(struct net_device *ndev, struct bpf_prog *prog, if (!old_prog && !prog) return 0; - if (prog && ndev->mtu > MANA_XDP_MTU_MAX) { + if (prog && ndev->mtu > MANA_XDP_MTU_MAX(ndev)) { netdev_err(ndev, "XDP: mtu:%u too large, mtu_max:%lu\n", - ndev->mtu, MANA_XDP_MTU_MAX); + ndev->mtu, MANA_XDP_MTU_MAX(ndev)); NL_SET_ERR_MSG_MOD(extack, "XDP: mtu too large"); return -EOPNOTSUPP; @@ -238,7 +238,7 @@ static int mana_xdp_set(struct net_device *ndev, struct bpf_prog *prog, bpf_prog_put(old_prog); if (prog) - ndev->max_mtu = min_t(unsigned int, MANA_XDP_MTU_MAX, + ndev->max_mtu = min_t(unsigned int, MANA_XDP_MTU_MAX(ndev), gc->adapter_mtu - ETH_HLEN); else ndev->max_mtu = gc->adapter_mtu - ETH_HLEN; diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c index 591fb4191d90..a1fb06b23947 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_en.c +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c @@ -758,6 +758,44 @@ static void *mana_get_rxbuf_pre(struct mana_rxq *rxq, dma_addr_t *da) return va; } +/* Reserve enough headroom to satisfy the skb_cow() in ip_forward() and avoid + * reallocation: the TX path keeps the SGE DMA mappings in struct mana_skb_head + * at skb->head, so the port advertises ndev->needed_headroom = MANA_HEADROOM. + */ +static u32 mana_get_rxbuf_headroom(struct mana_port_context *apc) +{ + if (mana_xdp_get(apc)) + return mana_xdp_headroom(apc->ndev); + + return LL_RESERVED_SPACE(apc->ndev); +} + +static u32 mana_get_rxbuf_size(struct mana_port_context *apc, u32 mtu) +{ + u32 len = SKB_DATA_ALIGN(mtu + MANA_RXBUF_PAD + + mana_get_rxbuf_headroom(apc)); + + return ALIGN(len, MANA_RX_FRAG_ALIGNMENT); +} + +/* Returns true when one RX buffer per page is already required by XDP or by + * the buffer size implied by the MTU, i.e. regardless of the + * MANA_PRIV_FLAG_USE_FULL_PAGE_RXBUF private flag. + */ +bool mana_single_rxbuf_per_page_forced(struct mana_port_context *apc, u32 mtu) +{ + /* For xdp make sure only one packet fits per page. */ + if (mana_xdp_get(apc)) + return true; + + /* Only use the page_pool fragment path when at least two buffers, + * including the headroom each of them has to reserve, actually fit + * into one page. Otherwise the fragment path degenerates into one + * buffer per page while still paying the fragment accounting cost. + */ + return PAGE_SIZE / mana_get_rxbuf_size(apc, mtu) < 2; +} + static bool mana_use_single_rxbuf_per_page(struct mana_port_context *apc, u32 mtu) { @@ -770,32 +808,27 @@ mana_use_single_rxbuf_per_page(struct mana_port_context *apc, u32 mtu) if (apc->priv_flags & BIT(MANA_PRIV_FLAG_USE_FULL_PAGE_RXBUF)) return true; - /* For xdp and jumbo frames make sure only one packet fits per page. */ - if (mtu + MANA_RXBUF_PAD > PAGE_SIZE / 2 || mana_xdp_get(apc)) - return true; - - return false; + return mana_single_rxbuf_per_page_forced(apc, mtu); } -/* Get RX buffer's data size, alloc size, XDP headroom based on MTU */ +/* Get RX buffer's data size, alloc size, headroom and frag count based on MTU */ static void mana_get_rxbuf_cfg(struct mana_port_context *apc, int mtu, u32 *datasize, u32 *alloc_size, u32 *headroom, u32 *frag_count) { - u32 len, buf_size; + u32 buf_size; /* Calculate datasize first (consistent across all cases) */ *datasize = mtu + ETH_HLEN; + *headroom = mana_get_rxbuf_headroom(apc); + if (mana_use_single_rxbuf_per_page(apc, mtu)) { - if (mana_xdp_get(apc)) { - *headroom = XDP_PACKET_HEADROOM; + if (mana_xdp_get(apc)) *alloc_size = PAGE_SIZE; - } else { - *headroom = 0; /* no support for XDP */ + else *alloc_size = SKB_DATA_ALIGN(mtu + MANA_RXBUF_PAD + *headroom); - } *frag_count = 1; @@ -809,11 +842,7 @@ static void mana_get_rxbuf_cfg(struct mana_port_context *apc, } /* Standard MTU case - optimize for multiple packets per page */ - *headroom = 0; - - /* Calculate base buffer size needed */ - len = SKB_DATA_ALIGN(mtu + MANA_RXBUF_PAD + *headroom); - buf_size = ALIGN(len, MANA_RX_FRAG_ALIGNMENT); + buf_size = mana_get_rxbuf_size(apc, mtu); /* Calculate how many packets can fit in a page */ *frag_count = PAGE_SIZE / buf_size; diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c index ece7ff9cc409..33db79569d3b 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c +++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c @@ -815,12 +815,12 @@ static int mana_set_priv_flags(struct net_device *ndev, u32 priv_flags) if (!apc->port_is_up) return 0; - /* If XDP is attached or MTU is jumbo, single-buffer-per-page - * is already forced regardless of this flag. Skip the - * expensive detach/attach cycle since nothing changes. + /* If XDP is attached or the MTU already forces one buffer per + * page, single-buffer-per-page is used regardless of this + * flag. Skip the expensive detach/attach cycle since nothing + * changes. */ - if (ndev->mtu + MANA_RXBUF_PAD > PAGE_SIZE / 2 || - mana_xdp_get(apc)) + if (mana_single_rxbuf_per_page_forced(apc, ndev->mtu)) return 0; /* Block RDMA from grabbing the vport during detach/attach */ diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h index 83b7eff4646e..8c3dc9d0b299 100644 --- a/include/net/mana/mana.h +++ b/include/net/mana/mana.h @@ -377,7 +377,18 @@ struct mana_recv_buf_oob { #define MANA_RXBUF_PAD (SKB_DATA_ALIGN(sizeof(struct skb_shared_info)) \ + ETH_HLEN) -#define MANA_XDP_MTU_MAX (PAGE_SIZE - MANA_RXBUF_PAD - XDP_PACKET_HEADROOM) +/* Headroom an RX buffer has to reserve while XDP is attached: the XDP program + * needs XDP_PACKET_HEADROOM, and the TX path needs LL_RESERVED_SPACE() (see + * mana_get_rxbuf_headroom()). LL_RESERVED_SPACE() grows with MAX_SKB_FRAGS and + * exceeds XDP_PACKET_HEADROOM once CONFIG_MAX_SKB_FRAGS is 19 or more. + */ +static inline u32 mana_xdp_headroom(struct net_device *ndev) +{ + return max_t(u32, LL_RESERVED_SPACE(ndev), XDP_PACKET_HEADROOM); +} + +#define MANA_XDP_MTU_MAX(ndev) \ + (PAGE_SIZE - MANA_RXBUF_PAD - mana_xdp_headroom(ndev)) struct mana_rxq { struct gdma_queue *gdma_rq; @@ -691,6 +702,7 @@ int mana_query_link_cfg(struct mana_port_context *apc); int mana_set_bw_clamp(struct mana_port_context *apc, u32 speed, int enable_clamping); void mana_query_phy_stats(struct mana_port_context *apc); +bool mana_single_rxbuf_per_page_forced(struct mana_port_context *apc, u32 mtu); int mana_pre_alloc_rxbufs(struct mana_port_context *apc, int mtu, int num_queues); void mana_pre_dealloc_rxbufs(struct mana_port_context *apc); void mana_unmap_skb(struct sk_buff *skb, struct mana_port_context *apc); From 3f090039995f609a8e236f37980dee5712dded61 Mon Sep 17 00:00:00 2001 From: Tianyi Gao Date: Tue, 6 Oct 2026 18:32:36 +0100 Subject: [PATCH 107/128] veth: fix peer NETDEV_XDP_ACT_NDO_XMIT after GRO is toggled while down MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A veth device advertises NETDEV_XDP_ACT_NDO_XMIT only if its peer has an XDP program attached or GRO enabled, that is, only if the peer will have NAPI to receive the frames. veth_set_features() updates the peer's flag when GRO is toggled, but returns early if the device is down, and veth_open() only refreshes the flags of the device being opened. Toggling GRO while the device is down therefore leaves the peer's flag stale after the device comes up. If GRO was enabled while down, the device comes up with NAPI but the peer does not advertise NDO_XMIT, and devmap rejects redirects to the peer with -EOPNOTSUPP. If GRO was disabled while down, the device comes up without NAPI but the peer still advertises NDO_XMIT, so redirects are accepted and then dropped in veth_xdp_xmit() with -ENXIO. Commit 7a6102aa6df0 ("veth: Update XDP feature set when bringing up device") made veth_open() refresh the device's own flags. Refresh the peer's flags there too. The peer's flag depends on this device's XDP program and GRO setting, not on whether the peer is up, so it is correct to set it even if the peer is down. Fixes: 8267fc71abb2 ("veth: take into account peer device for NETDEV_XDP_ACT_NDO_XMIT xdp_features flag") Signed-off-by: Tianyi Gao Reviewed-by: Toke Høiland-Jørgensen Acked-by: Jesper Dangaard Brouer Acked-by: Lorenzo Bianconi Link: https://patch.msgid.link/20261006173241.65945-2-tianyi@cloudflare.com Signed-off-by: Jakub Kicinski --- drivers/net/veth.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/veth.c b/drivers/net/veth.c index 71227d0389aa..643b97dc5245 100644 --- a/drivers/net/veth.c +++ b/drivers/net/veth.c @@ -1415,6 +1415,7 @@ static int veth_open(struct net_device *dev) } veth_set_xdp_features(dev); + veth_set_xdp_features(peer); return 0; } From c017800af5c71ebb2e6209eebf5c60c19850ffe7 Mon Sep 17 00:00:00 2001 From: Tianyi Gao Date: Tue, 6 Oct 2026 18:32:37 +0100 Subject: [PATCH 108/128] selftests: net: veth: test peer ndo-xmit after GRO toggle while down Test that toggling GRO on a veth device while it is down updates the peer's ndo-xmit XDP feature once the device comes up, for both GRO on and GRO off. xdp-features is only visible through netlink, so read it with the ynl CLI, as double_udp_encap.sh does. Skip the checks if the CLI is not found, as in an installed kselftest tree. Signed-off-by: Tianyi Gao Link: https://patch.msgid.link/20261006173241.65945-3-tianyi@cloudflare.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/veth.sh | 46 +++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tools/testing/selftests/net/veth.sh b/tools/testing/selftests/net/veth.sh index 9709dd067c72..8b867e0675a7 100755 --- a/tools/testing/selftests/net/veth.sh +++ b/tools/testing/selftests/net/veth.sh @@ -9,6 +9,8 @@ readonly DST=1 readonly DST_NAT=100 readonly NS_SRC=$BASE$SRC readonly NS_DST=$BASE$DST +# shellcheck disable=SC2155 # prefer RO variable over return value from cmd +readonly YNL="$(dirname "$(readlink -f "$0")")/../../../net/ynl/pyynl/cli.py" # "baremetal" network used for raw UDP traffic readonly BM_NET_V4=192.168.1. @@ -74,6 +76,37 @@ chk_tso_flag() { __chk_flag "$1" $2 $3 tcp-segmentation-offload } +chk_xdp_feature() { + local msg="$1" + local target=$2 + local expected=$3 + local feature=$4 + local ifindex + local out + local st + local flag + + ifindex=`ip netns exec $BASE$target cat /sys/class/net/veth$target/ifindex` + out=`ip netns exec $BASE$target "$YNL" --family netdev --do dev-get \ + --output-json --json "{\"ifindex\": $ifindex}" 2>&1` + st=$? + + printf "%-60s" "$msg" + if [ $st -ne 0 ]; then + echo " fail - ynl cli error: $out" + ret=1 + return + fi + + flag=`echo "$out" | grep -c "\"$feature\""` + if [ "$flag" = "$expected" ]; then + echo " ok " + else + echo " fail - expected $expected found $flag" + ret=1 + fi +} + chk_channels() { local msg="$1" local target=$2 @@ -222,6 +255,7 @@ fi [ $CPUS -lt 2 ] && echo "Only one CPU available, some tests will be skipped" [ $STRESS -gt 0 -a $CPUS -lt 3 ] && echo " stress test will be skipped, too" +[ ! -f "$YNL" ] && echo "ynl cli not found, ndo-xmit tests will be skipped" create_ns chk_gro_flag "default - gro flag" $SRC off @@ -283,6 +317,18 @@ chk_gro_flag " - peer gro flag" $SRC off chk_tso_flag " - tso flag" $SRC on chk_tso_flag " - peer tso flag" $DST on ip -n $NS_DST link set dev veth$DST up +if [ -f "$YNL" ]; then + chk_xdp_feature " - peer ndo-xmit" $SRC 1 ndo-xmit + # make sure the peer flag is set before disabling gro while down + ip netns exec $NS_DST ethtool -K veth$DST gro off + ip netns exec $NS_DST ethtool -K veth$DST gro on + chk_xdp_feature " - peer ndo-xmit re-armed" $SRC 1 ndo-xmit + ip -n $NS_DST link set dev veth$DST down + ip netns exec $NS_DST ethtool -K veth$DST gro off + ip -n $NS_DST link set dev veth$DST up + chk_xdp_feature " - peer ndo-xmit cleared" $SRC 0 ndo-xmit + ip netns exec $NS_DST ethtool -K veth$DST gro on +fi ip netns exec $NS_SRC ethtool -K veth$SRC tx-udp-segmentation off ip netns exec $NS_DST ethtool -K veth$DST rx-udp-gro-forwarding on chk_gro " - aggregation with TSO off" 1 From b056ca4d3742d0769f91137bb1ce3215428398f7 Mon Sep 17 00:00:00 2001 From: Li RongQing Date: Tue, 6 Oct 2026 13:58:20 +0300 Subject: [PATCH 109/128] net/mlx5e: Order ICOSQ cc update after CQ doorbell mlx5e_poll_ico_cq() requires sq->cc to be updated only after mlx5_cqwq_update_db_record(), otherwise a CQ overrun may occur. The current implementation updates sq->cc before the CQ doorbell record, violating this ordering requirement. Update the CQ doorbell record first and use dma_wmb() before updating sq->cc. This ensures that the CQ space is released to the device before the corresponding ICOSQ consumer index is updated by software. Fixes: fd9b4be8002c ("net/mlx5e: RX, Support multiple outstanding UMR posts") Signed-off-by: Li RongQing Reviewed-by: Dragos Tatulea Signed-off-by: Tariq Toukan Reviewed-by: Daniel Machon Link: https://patch.msgid.link/20261006105820.257208-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en_rx.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c b/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c index 7bd0606a5253..903af3b3e779 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c @@ -908,10 +908,12 @@ int mlx5e_poll_ico_cq(struct mlx5e_cq *cq) } while (!last_wqe); } while ((++i < MLX5E_TX_CQ_POLL_BUDGET) && (cqe = mlx5_cqwq_get_cqe(&cq->wq))); - sq->cc = sqcc; - mlx5_cqwq_update_db_record(&cq->wq); + /* ensure cq space is freed before enabling more cqes */ + dma_wmb(); + + sq->cc = sqcc; return i; } From fc13ba44da197a186f2f4d77c5b7d73d0f99f053 Mon Sep 17 00:00:00 2001 From: Sebastien Royen Date: Wed, 7 Oct 2026 10:05:57 +0200 Subject: [PATCH 110/128] net: dsa: microchip: fix KSZ8765 fiber detection The KSZ8765 is similar to the KSZ8795 but has two fiber ports. KSZ8_PORT_STATUS_0 is used to detect fiber mode. It is currently defined as 0x08, which is the Global Control 6 MIB Control register and has bit 7 defined as Flush Counter. Set KSZ8_PORT_STATUS_0 to 0x18, which is the Port 1 Status 0 register where bit 7 is the Fiber Mode bit. This issue was discovered when upgrading an embedded device from kernel 5.10 to 6.16. The device was using a KSZ8765 device tree configuration and the corresponding hardware, but during boot the kernel incorrectly detected it as a KSZ8795. The relevant boot messages were: ksz-switch spi0.0: found switch: KSZ8795, rev 0 ksz-switch spi0.0: Device tree specifies chip KSZ8765 but found KSZ8795, please fix it! Cc: stable@vger.kernel.org Fixes: 91a98917a883 ("net: dsa: microchip: move switch chip_id detection to ksz_common") Reviewed-by: Nicolai Buchwitz Signed-off-by: Sebastien Royen Link: https://patch.msgid.link/20261007080557.15719-1-sebastien.royen@armadeus.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/microchip/ksz_common.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h index cbe98494578c..f6104f714a8d 100644 --- a/drivers/net/dsa/microchip/ksz_common.h +++ b/drivers/net/dsa/microchip/ksz_common.h @@ -853,7 +853,7 @@ static inline bool ksz_is_sgmii_port(struct ksz_device *dev, int port) #define KSZ88_FAMILY_ID 0x88 #define KSZ8895_FAMILY_ID 0x95 -#define KSZ8_PORT_STATUS_0 0x08 +#define KSZ8_PORT_STATUS_0 0x18 #define KSZ8_PORT_FIBER_MODE BIT(7) #define SW_CHIP_ID_M GENMASK(7, 4) From 7ea07afb230f1342ab0f9365edd0aae137f425d3 Mon Sep 17 00:00:00 2001 From: Petr Machata Date: Tue, 6 Oct 2026 16:21:28 +0200 Subject: [PATCH 111/128] mlxsw: spectrum_flower: Fix port range register leak in tmplt_create() mlxsw_sp_flower_tmplt_create() parses a flow_cls_offload template into a stack-local struct mlxsw_sp_acl_rule_info purely to compute rulei.values.elusage. Parsing can acquire port range registers via mlxsw_sp_flower_parse_ports_range(), but since this rulei never goes through mlxsw_sp_acl_rulei_destroy(), those registers were never released, including through chain template deletion. Factor out of mlxsw_sp_acl_rulei_destroy() the code to actually release the necessary resources and call from mlxsw_sp_flower_tmplt_create() to plug the leak. The issue was found during a review of Wentao Liang's patch referenced below. Fixes: fe22f7410527 ("mlxsw: spectrum_flower: Add ability to match on port ranges") Reported-by: Wentao Liang Closes: https://lore.kernel.org/netdev/20260917113236.2149095-1-vulab@iscas.ac.cn/ Reviewed-by: Ido Schimmel Signed-off-by: Petr Machata Reviewed-by: Jacob Keller Link: https://patch.msgid.link/95339cf970ce78e1a74aa86ffcc104ecf7c7762c.1791294384.git.petrm@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlxsw/spectrum.h | 2 ++ drivers/net/ethernet/mellanox/mlxsw/spectrum_acl.c | 10 ++++++++-- drivers/net/ethernet/mellanox/mlxsw/spectrum_flower.c | 9 +++++++-- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum.h b/drivers/net/ethernet/mellanox/mlxsw/spectrum.h index b03ff9e044f9..10af08ce72ef 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum.h +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum.h @@ -989,6 +989,8 @@ void mlxsw_sp_acl_ruleset_prio_get(struct mlxsw_sp_acl_ruleset *ruleset, struct mlxsw_sp_acl_rule_info * mlxsw_sp_acl_rulei_create(struct mlxsw_sp_acl *acl, struct mlxsw_afa_block *afa_block); +void mlxsw_sp_acl_rulei_free(struct mlxsw_sp *mlxsw_sp, + const struct mlxsw_sp_acl_rule_info *rulei); void mlxsw_sp_acl_rulei_destroy(struct mlxsw_sp *mlxsw_sp, struct mlxsw_sp_acl_rule_info *rulei); int mlxsw_sp_acl_rulei_commit(struct mlxsw_sp_acl_rule_info *rulei); diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl.c index cb232accb296..f7be4f3a1518 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl.c +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl.c @@ -340,8 +340,8 @@ err_afa_block_create: return ERR_PTR(err); } -void mlxsw_sp_acl_rulei_destroy(struct mlxsw_sp *mlxsw_sp, - struct mlxsw_sp_acl_rule_info *rulei) +void mlxsw_sp_acl_rulei_free(struct mlxsw_sp *mlxsw_sp, + const struct mlxsw_sp_acl_rule_info *rulei) { if (rulei->action_created) mlxsw_afa_block_destroy(rulei->act_block); @@ -351,6 +351,12 @@ void mlxsw_sp_acl_rulei_destroy(struct mlxsw_sp *mlxsw_sp, if (rulei->dst_port_range_reg_valid) mlxsw_sp_port_range_reg_put(mlxsw_sp, rulei->dst_port_range_reg_index); +} + +void mlxsw_sp_acl_rulei_destroy(struct mlxsw_sp *mlxsw_sp, + struct mlxsw_sp_acl_rule_info *rulei) +{ + mlxsw_sp_acl_rulei_free(mlxsw_sp, rulei); kfree(rulei); } diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_flower.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_flower.c index 353fd9ca89a6..5e4632cb792d 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_flower.c +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_flower.c @@ -862,14 +862,19 @@ int mlxsw_sp_flower_tmplt_create(struct mlxsw_sp *mlxsw_sp, memset(&rulei, 0, sizeof(rulei)); err = mlxsw_sp_flower_parse(mlxsw_sp, block, &rulei, f); if (err) - return err; + goto out; + ruleset = mlxsw_sp_acl_ruleset_get(mlxsw_sp, block, f->common.chain_index, MLXSW_SP_ACL_PROFILE_FLOWER, &rulei.values.elusage); + err = PTR_ERR_OR_ZERO(ruleset); + +out: + mlxsw_sp_acl_rulei_free(mlxsw_sp, &rulei); /* keep the reference to the ruleset */ - return PTR_ERR_OR_ZERO(ruleset); + return err; } void mlxsw_sp_flower_tmplt_destroy(struct mlxsw_sp *mlxsw_sp, From 71198b59df56a9a0115115091cc1aad2a4e3c74d Mon Sep 17 00:00:00 2001 From: Petr Machata Date: Tue, 6 Oct 2026 16:21:29 +0200 Subject: [PATCH 112/128] selftests: mlxsw: Test port range occupancy on template create Add a test that creates a tc chain template matching on both source and destination port ranges and verifies via devlink-resource occupancy that this does not leak port range registers, neither while the template exists nor after it is deleted. Reviewed-by: Ido Schimmel Signed-off-by: Petr Machata Reviewed-by: Jacob Keller Link: https://patch.msgid.link/e7b37b80adb7ac8b0ef20a22d7654a6656c1c545.1791294384.git.petrm@nvidia.com Signed-off-by: Jakub Kicinski --- .../drivers/net/mlxsw/port_range_occ.sh | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tools/testing/selftests/drivers/net/mlxsw/port_range_occ.sh b/tools/testing/selftests/drivers/net/mlxsw/port_range_occ.sh index b1f0781f6b25..c7eba3f15066 100755 --- a/tools/testing/selftests/drivers/net/mlxsw/port_range_occ.sh +++ b/tools/testing/selftests/drivers/net/mlxsw/port_range_occ.sh @@ -9,6 +9,7 @@ lib_dir=$(dirname $0)/../../../net/forwarding ALL_TESTS=" port_range_occ_test + port_range_occ_tmplt_test " NUM_NETIFS=2 source $lib_dir/lib.sh @@ -101,6 +102,31 @@ port_range_occ_test() log_test "port range occupancy" } +port_range_occ_tmplt_test() +{ + local occ + + RET=0 + occ=$(port_range_occ_get) + + # Addition of a chain template should not impact occupancy. + tc chain add dev "$swp1" ingress chain 1 protocol ip \ + flower ip_proto udp src_port 501-600 dst_port 701-800 + check_err $? "Could not add chain template" + + (( occ == $(port_range_occ_get) )) + check_err $? "Got occupancy $(port_range_occ_get), expected $occ" + + # And just for completeness sake, neither should deletion. + tc chain del dev "$swp1" ingress chain 1 + check_err $? "Could not delete chain template" + + (( occ == $(port_range_occ_get) )) + check_err $? "Got occupancy $(port_range_occ_get), expected $occ" + + log_test "port range occupancy after template create/destroy" +} + trap cleanup EXIT setup_prepare From 2b82e16d6084cbc651e3c902198f1945408ee1a5 Mon Sep 17 00:00:00 2001 From: Daniel Machon Date: Wed, 7 Oct 2026 17:03:15 +0200 Subject: [PATCH 113/128] net: sparx5: free the matchall entry on destroy sparx5_tc_matchall_replace() allocates a struct sparx5_mall_entry for every offloaded matchall filter and adds it to sparx5->mall_entries. sparx5_tc_matchall_destroy() removes the entry from the list, but never frees it, so the entry of every deleted mirror and goto matchall filter is leaked. Free the entry after unlinking it. The leak was discovered by an AI code review agent, and reproduced with kmemleak on a lan969x EV board (EV23X71A) by repeatedly adding and deleting matchall mirror and goto filters. With the fix, kmemleak no longer reports the leak. Cc: stable@vger.kernel.org Fixes: 1ede4acf045c ("net: sparx5: add bookkeeping code for matchall rules") Signed-off-by: Daniel Machon Link: https://patch.msgid.link/20261007-sparx5-matchall-kfree-net-v1-1-c8918685b337@microchip.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/microchip/sparx5/sparx5_tc_matchall.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_tc_matchall.c b/drivers/net/ethernet/microchip/sparx5/sparx5_tc_matchall.c index 702257979462..146d7f28e8f3 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_tc_matchall.c +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_tc_matchall.c @@ -160,6 +160,7 @@ static int sparx5_tc_matchall_destroy(struct net_device *ndev, } list_del(&entry->list); + kfree(entry); return err; } From ab9414ed70bd783e902a85c350620dee269bcb2b Mon Sep 17 00:00:00 2001 From: Josef Bacik Date: Wed, 7 Oct 2026 17:29:34 +0000 Subject: [PATCH 114/128] net: skbuff: don't leave stale bytes in skb_copy_and_csum_bits() When skb_copy_and_csum_bits() reaches unreadable frags it returns 0 after copying only the linear part, and the rest of the caller's buffer is left as it was. The callers copy into a buffer that is about to go out on the wire: an ICMP error quoting the offending packet, or a driver's TX bounce buffer in skb_copy_and_csum_dev(). Neither buffer is zeroed beforehand, so whatever was in memory there gets sent. Zero the part of the buffer we didn't fill. The checksum usually won't match the data any more, so the receiver will usually drop the packet, but either way it no longer carries anything it shouldn't. Only zero for a positive @len, a negative one from a broken caller must not turn into a huge memset(). Fixes: 65249feb6b3d ("net: add support for skbs with unreadable frags") Cc: stable@vger.kernel.org Signed-off-by: Josef Bacik Reviewed-by: Mina Almasry Link: https://patch.msgid.link/20261007-b4-skb-copy-csum-stale-bytes-v1-1-adbbde033fb3@toxicpanda.com Signed-off-by: Jakub Kicinski --- net/core/skbuff.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/core/skbuff.c b/net/core/skbuff.c index 4aea06d5167d..41beaf625421 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -3633,8 +3633,12 @@ __wsum skb_copy_and_csum_bits(const struct sk_buff *skb, int offset, pos = copy; } - if (!skb_frags_readable(skb)) + if (!skb_frags_readable(skb)) { + /* Don't hand the caller a buffer with stale bytes in it. */ + if (len > 0) + memset(to, 0, len); return 0; + } for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { int end; From 089e58805c452e52179482b1025a8e309a57f801 Mon Sep 17 00:00:00 2001 From: Josef Bacik Date: Wed, 7 Oct 2026 17:55:40 +0000 Subject: [PATCH 115/128] xen/netfront: drop RX packets with a short Ethernet header handle_incoming_queue() pulls pull_to bytes into the head before calling eth_type_trans(). pull_to is the length of the first RX slot, capped at RX_COPY_THRESHOLD, and that length comes from the backend. Nothing checks it against ETH_HLEN. If the first slot is shorter than ETH_HLEN and more slots follow, the head ends up shorter than an Ethernet header while skb->len is longer, and eth_type_trans() BUG()s in __skb_pull(). If the whole packet is shorter than ETH_HLEN, eth_type_trans() reads the header past the end of the data instead. Pull at least ETH_HLEN, and drop the packet if that fails, which also drops packets too short to hold an Ethernet header. This also checks the return value of the pull, which was ignored. Fixes: 0d160211965b ("xen: add virtual network device driver") Cc: stable@vger.kernel.org Signed-off-by: Josef Bacik Link: https://patch.msgid.link/20261007-b4-xen-netfront-short-head-v1-1-12d7113a7e4e@toxicpanda.com Signed-off-by: Jakub Kicinski --- drivers/net/xen-netfront.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/net/xen-netfront.c b/drivers/net/xen-netfront.c index 2ed673649c48..d269457e839e 100644 --- a/drivers/net/xen-netfront.c +++ b/drivers/net/xen-netfront.c @@ -1234,8 +1234,16 @@ static int handle_incoming_queue(struct netfront_queue *queue, while ((skb = __skb_dequeue(rxq)) != NULL) { int pull_to = NETFRONT_SKB_CB(skb)->pull_to; - if (pull_to > skb_headlen(skb)) - __pskb_pull_tail(skb, pull_to - skb_headlen(skb)); + /* pull_to comes from the first slot's length, which the + * backend controls. Make sure the head holds at least an + * Ethernet header for eth_type_trans(). + */ + if (!pskb_may_pull(skb, max(pull_to, ETH_HLEN))) { + kfree_skb(skb); + packets_dropped++; + queue->info->netdev->stats.rx_errors++; + continue; + } /* Ethernet work: Delayed to here as it peeks the header. */ skb->protocol = eth_type_trans(skb, queue->info->netdev); From 513e23857a3a64fa73a1deb36a385eecd4ad7690 Mon Sep 17 00:00:00 2001 From: Willem de Bruijn Date: Wed, 7 Oct 2026 13:57:30 -0400 Subject: [PATCH 116/128] net/packet: call packet_parse_headers after virtio_net_hdr_to_skb SOCK_RAW packet sockets incorrectly drop VLAN-tagged GSO packets without VIRTIO_NET_HDR_F_NEEDS_CSUM. packet_parse_headers() sets skb->protocol to VLAN but advances skb->network_header past the VLAN tags. virtio_net_hdr_to_skb() then flow dissects these packets, which parses the network header as a VLAN tag, and fails. Call packet_parse_headers() after virtio_net_hdr_to_skb() instead. This matches tun_get_user() and tap_get_user() and thus simplifies overall complexity. Commit 01fdecc0480d ("net: packet: fix wrong transport_header when sending VLAN-tagged frame") fixed the same issue for the transport header probe in packet_parse_headers(). That probe is now skipped if virtio_net_hdr_to_skb() already set the transport header, avoiding a redundant dissection. Implementation details: - Do not set skb->protocol to the RX wildcard ETH_P_ALL. SOCK_RAW then derives it from the link layer header, as before. SOCK_DGRAM now leaves it 0, or derives it from gso_type for GSO with NEEDS_CSUM. - Keep virtio_net_hdr_set_proto() last, so that the link layer protocol takes priority over gso_type. Reported-by: Junnan Zhang Closes: https://lore.kernel.org/netdev/20260821085722.24036-1-zhangjn_dev@163.com/ Fixes: dfed913e8b55 ("net/af_packet: add VLAN support for AF_PACKET SOCK_RAW GSO") Signed-off-by: Willem de Bruijn Acked-by: Michael S. Tsirkin Link: https://patch.msgid.link/20261007175816.3138556-1-willemdebruijn.kernel@gmail.com Signed-off-by: Jakub Kicinski --- net/packet/af_packet.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c index 6ff16eef24f4..52ae3d875e97 100644 --- a/net/packet/af_packet.c +++ b/net/packet/af_packet.c @@ -1943,8 +1943,7 @@ static void packet_parse_headers(struct sk_buff *skb, struct socket *sock) /* On TX skb->data is the L2 header; anchor it for all socket types. */ skb_reset_mac_header(skb); - if ((!skb->protocol || skb->protocol == htons(ETH_P_ALL)) && - sock->type == SOCK_RAW) + if (!skb->protocol && sock->type == SOCK_RAW) skb->protocol = dev_parse_header_protocol(skb); skb_probe_transport_header(skb); @@ -2614,7 +2613,8 @@ static int tpacket_fill_skb(struct packet_sock *po, struct sk_buff *skb, struct page *page; int err; - skb->protocol = proto; + if (proto != htons(ETH_P_ALL)) + skb->protocol = proto; skb->dev = dev; skb->priority = sockc->priority; skb->mark = sockc->mark; @@ -2678,8 +2678,6 @@ static int tpacket_fill_skb(struct packet_sock *po, struct sk_buff *skb, if (unlikely(!skb->len)) return -EINVAL; - packet_parse_headers(skb, sock); - return tp_len; } @@ -2919,9 +2917,13 @@ tpacket_error: tp_len = -EINVAL; goto tpacket_error; } - virtio_net_hdr_set_proto(skb, &vnet_hdr); } + packet_parse_headers(skb, po->sk.sk_socket); + + if (has_vnet_hdr) + virtio_net_hdr_set_proto(skb, &vnet_hdr); + uarg = kmalloc(sizeof(*uarg), GFP_KERNEL); if (unlikely(!uarg)) { if (likely(len_sum > 0)) @@ -3122,7 +3124,8 @@ static int packet_snd(struct socket *sock, struct msghdr *msg, size_t len) goto out_free; } - skb->protocol = proto; + if (proto != htons(ETH_P_ALL)) + skb->protocol = proto; skb->dev = dev; skb->priority = sockc.priority; skb->mark = sockc.mark; @@ -3131,16 +3134,18 @@ static int packet_snd(struct socket *sock, struct msghdr *msg, size_t len) if (unlikely(extra_len == 4)) skb->no_fcs = 1; - packet_parse_headers(skb, sock); - if (vnet_hdr_sz) { err = virtio_net_hdr_to_skb(skb, &vnet_hdr, vio_le()); if (err) goto out_free; len += vnet_hdr_sz; - virtio_net_hdr_set_proto(skb, &vnet_hdr); } + packet_parse_headers(skb, sock); + + if (vnet_hdr_sz) + virtio_net_hdr_set_proto(skb, &vnet_hdr); + err = packet_xmit(po, skb); if (unlikely(err != 0)) { From 93ccaf1c26e2133396173b76a071e59024daa622 Mon Sep 17 00:00:00 2001 From: Josef Bacik Date: Wed, 7 Oct 2026 17:57:32 +0000 Subject: [PATCH 117/128] xen/netfront: don't leak the skb when xennet_fill_frags() fails When a response chain has more slots than fit in the skb's frags, xennet_fill_frags() returns an error and xennet_poll() jumps to its error path. That path moves what's left on tmpq to errq to be freed, but the skb being filled was already dequeued from tmpq, so it's never freed. Each chain that overflows leaks the skb and the pages attached to it as frags, and the backend decides how many slots it sends. Put the skb back on tmpq before taking the error path, like the xennet_set_skb_gso() failure just above it does. Fixes: ad4f15dc2c70 ("xen/netfront: don't bug in case of too many frags") Cc: stable@vger.kernel.org Signed-off-by: Josef Bacik Reviewed-by: Juergen Gross Link: https://patch.msgid.link/20261007-b4-xen-netfront-fill-frags-leak-v1-1-a8a01ff9cd52@toxicpanda.com Signed-off-by: Jakub Kicinski --- drivers/net/xen-netfront.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/xen-netfront.c b/drivers/net/xen-netfront.c index d269457e839e..fdcb91042f29 100644 --- a/drivers/net/xen-netfront.c +++ b/drivers/net/xen-netfront.c @@ -1346,8 +1346,10 @@ err: skb->data_len = rx->status; skb->len += rx->status; - if (unlikely(xennet_fill_frags(queue, skb, &tmpq))) + if (unlikely(xennet_fill_frags(queue, skb, &tmpq))) { + __skb_queue_head(&tmpq, skb); goto err; + } if (rx->flags & XEN_NETRXF_csum_blank) skb->ip_summed = CHECKSUM_PARTIAL; From f8c8bd159a9bf626dd4921b7746ff481fce8c758 Mon Sep 17 00:00:00 2001 From: Vadim Fedorenko Date: Wed, 7 Oct 2026 20:33:59 +0000 Subject: [PATCH 118/128] ptp: ocp: fix PCIe delay estimation calculation The commit in fixes introduced a high cap for delayas U64_MAX value while ktime_t is actually s64. This is wrong cap as it becomes negative value and any comparison to a real delay will fail to update delay value. Use KTIME_MAX constant as correct max cap for PCIe delay. The issue was hit in production (a negative value is observed): # cat /sys/class/timecard/ocp0/ts_window_adjust -3 Fixes: aa05fe67bcd64 ("ptp: ocp: Improve PCIe delay estimation") Signed-off-by: Vadim Fedorenko Reviewed-by: Daniel Machon Link: https://patch.msgid.link/20261007203359.417270-1-vadim.fedorenko@linux.dev Signed-off-by: Jakub Kicinski --- drivers/ptp/ptp_ocp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c index 3d26ec1f7b9e..a8cba2f64b98 100644 --- a/drivers/ptp/ptp_ocp.c +++ b/drivers/ptp/ptp_ocp.c @@ -1825,7 +1825,7 @@ ptp_ocp_watchdog(struct timer_list *t) static void ptp_ocp_estimate_pci_timing(struct ptp_ocp *bp) { - ktime_t start, end, delay = U64_MAX; + ktime_t start, end, delay = KTIME_MAX; u32 ctrl; int i; From 6785011f8b16abf40b1e9d8b2e332232b3e68822 Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Wed, 7 Oct 2026 18:42:49 -0400 Subject: [PATCH 119/128] ipv4: validate checksum_start before completing checksum If a packet with bad checksum metadata gets into the ipv4 stack, skb_checksum_help can corrupt the network header and cause a bunch of mischief. This was discovered and reported by Paulos, and has been reporoduced by others independently since. We really shouldn't allow such packets in, but as a defence in depth measure, let's also check before we complete the checksum. A more complete validation at input is forthcoming, but needs more work. Cc: stable@vger.kernel.org Fixes: f43798c27684 ("tun: Allow GSO using virtio_net_hdr") Fixes: bfd5f4a3d605 ("packet: Add GSO/csum offload support.") Reported-by: Paulos Yibelo Closes: https://lore.kernel.org/netdev/20260922030310.8684-2-habte.yibelo@gmail.com/ Signed-off-by: Michael S. Tsirkin Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/0a012b4923e189c4c593ef4f471e5ff0edbe9030.1791412497.git.mst@redhat.com Signed-off-by: Jakub Kicinski --- include/net/ip.h | 7 +++++++ net/bridge/netfilter/nf_conntrack_bridge.c | 10 +++++++--- net/ipv4/ip_output.c | 10 +++++++--- net/netfilter/nfnetlink_queue.c | 7 +++++++ net/netfilter/xt_CHECKSUM.c | 6 +++++- net/xfrm/xfrm_output.c | 16 +++++++++++----- 6 files changed, 44 insertions(+), 12 deletions(-) diff --git a/include/net/ip.h b/include/net/ip.h index 6f602df72ee6..d07c2c573024 100644 --- a/include/net/ip.h +++ b/include/net/ip.h @@ -74,6 +74,13 @@ static inline unsigned int ip_hdrlen(const struct sk_buff *skb) return ip_hdr(skb)->ihl * 4; } +static inline int ip_check_csum_start(const struct sk_buff *skb) +{ + if (unlikely(skb->csum_start < skb->network_header + ip_hdrlen(skb))) + return -EINVAL; + return 0; +} + struct ipcm_cookie { struct sockcm_cookie sockc; __be32 addr; diff --git a/net/bridge/netfilter/nf_conntrack_bridge.c b/net/bridge/netfilter/nf_conntrack_bridge.c index 7ecb8a26bfa3..b5444335b86f 100644 --- a/net/bridge/netfilter/nf_conntrack_bridge.c +++ b/net/bridge/netfilter/nf_conntrack_bridge.c @@ -39,9 +39,13 @@ static int nf_br_ip_fragment(struct net *net, struct sock *sk, int err = 0; /* for offloaded checksums cleanup checksum before fragmentation */ - if (skb->ip_summed == CHECKSUM_PARTIAL && - (err = skb_checksum_help(skb))) - goto blackhole; + if (skb->ip_summed == CHECKSUM_PARTIAL) { + err = ip_check_csum_start(skb); + if (!err) + err = skb_checksum_help(skb); + if (err) + goto blackhole; + } iph = ip_hdr(skb); diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c index a24cc8ee11d3..eaa6fabda347 100644 --- a/net/ipv4/ip_output.c +++ b/net/ipv4/ip_output.c @@ -771,9 +771,13 @@ int ip_do_fragment(struct net *net, struct sock *sk, struct sk_buff *skb, int err = 0; /* for offloaded checksums cleanup checksum before fragmentation */ - if (skb->ip_summed == CHECKSUM_PARTIAL && - (err = skb_checksum_help(skb))) - goto fail; + if (skb->ip_summed == CHECKSUM_PARTIAL) { + err = ip_check_csum_start(skb); + if (!err) + err = skb_checksum_help(skb); + if (err) + goto fail; + } /* * Point into the IP datagram header. diff --git a/net/netfilter/nfnetlink_queue.c b/net/netfilter/nfnetlink_queue.c index a3bc00280051..477fc632657f 100644 --- a/net/netfilter/nfnetlink_queue.c +++ b/net/netfilter/nfnetlink_queue.c @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -674,6 +675,12 @@ nla_put_failure: static int nf_queue_checksum_help(struct sk_buff *entskb) { + if (entskb->protocol == htons(ETH_P_IP)) { + if (!pskb_network_may_pull(entskb, sizeof(struct iphdr)) || + ip_check_csum_start(entskb)) + return -EINVAL; + } + if (skb_csum_is_sctp(entskb)) return skb_crc32c_csum_help(entskb); diff --git a/net/netfilter/xt_CHECKSUM.c b/net/netfilter/xt_CHECKSUM.c index 9d99f5a3d176..1fb0f8118404 100644 --- a/net/netfilter/xt_CHECKSUM.c +++ b/net/netfilter/xt_CHECKSUM.c @@ -15,6 +15,7 @@ #include #include +#include MODULE_LICENSE("GPL"); MODULE_AUTHOR("Michael S. Tsirkin "); @@ -25,8 +26,11 @@ MODULE_ALIAS("ip6t_CHECKSUM"); static unsigned int checksum_tg(struct sk_buff *skb, const struct xt_action_param *par) { - if (skb->ip_summed == CHECKSUM_PARTIAL && !skb_is_gso(skb)) + if (skb->ip_summed == CHECKSUM_PARTIAL && !skb_is_gso(skb)) { + if (xt_family(par) == NFPROTO_IPV4 && ip_check_csum_start(skb)) + return NF_DROP; skb_checksum_help(skb); + } return XT_CONTINUE; } diff --git a/net/xfrm/xfrm_output.c b/net/xfrm/xfrm_output.c index e305ba32e356..f1f612cd7b43 100644 --- a/net/xfrm/xfrm_output.c +++ b/net/xfrm/xfrm_output.c @@ -823,16 +823,22 @@ int xfrm_output(struct sock *sk, struct sk_buff *skb) } if (skb->ip_summed == CHECKSUM_PARTIAL) { - err = skb_checksum_help(skb); - if (err) { - XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR); - kfree_skb(skb); - return err; + if (skb->protocol == htons(ETH_P_IP)) { + err = ip_check_csum_start(skb); + if (err) + goto error; } + err = skb_checksum_help(skb); + if (err) + goto error; } out: return xfrm_output2(net, sk, skb); +error: + XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR); + kfree_skb(skb); + return err; } EXPORT_SYMBOL_GPL(xfrm_output); From e02a5b6588161673ef75a4bc5cdfb62bd493374d Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Thu, 8 Oct 2026 14:29:22 +0900 Subject: [PATCH 120/128] ipv4: do not warn on route notification size race Commit 680aea08e78c ("net: ipv4: Emit notification when fib hardware flags are changed") added asynchronous route notifications for hardware flag changes. fib_alias_hw_flags_set() sizes the skb with fib_nlmsg_size() and later fills it with fib_dump_info() while holding only RCU. With nexthop compatibility mode enabled, a concurrent replacement can grow the group between these independent snapshots. fib_dump_info() can then legitimately return -EMSGSIZE, so the warning does not prove a sizing bug. Remove the warning. The existing error path still frees the skb and reports the error to listeners. Fixes: 680aea08e78c ("net: ipv4: Emit notification when fib hardware flags are changed") Reported-by: Ido Schimmel Link: https://lore.kernel.org/netdev/20261004082043.GB92032@shredder/ Suggested-by: Ido Schimmel Cc: stable@vger.kernel.org Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Link: https://patch.msgid.link/b7e203fc5867651cb67ef84aa03e082f0f76008e.1791423190.git.4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv4/fib_trie.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/net/ipv4/fib_trie.c b/net/ipv4/fib_trie.c index acb1e4385914..248514dce0cd 100644 --- a/net/ipv4/fib_trie.c +++ b/net/ipv4/fib_trie.c @@ -1079,8 +1079,6 @@ void fib_alias_hw_flags_set(struct net *net, const struct fib_rt_info *fri) err = fib_dump_info(skb, 0, 0, RTM_NEWROUTE, fri, 0); if (err < 0) { - /* -EMSGSIZE implies BUG in fib_nlmsg_size() */ - WARN_ON(err == -EMSGSIZE); kfree_skb(skb); goto errout; } From c97426ec649d99596d8969e1a052c16b14b7b485 Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Thu, 8 Oct 2026 14:29:23 +0900 Subject: [PATCH 121/128] ipv6: do not warn on route notification size race fib6_info_hw_flags_set() sizes the skb with rt6_nlmsg_size() and later fills it with rt6_fill_node(). With nexthop compatibility mode enabled, a concurrent replacement can grow the group between these independent snapshots. rt6_fill_node() can then legitimately return -EMSGSIZE, so the warning does not prove a sizing bug. Remove the warning. The existing error path still frees the skb and reports the error to listeners. Fixes: 907eea486888 ("net: ipv6: Emit notification when fib hardware flags are changed") Reported-by: Ido Schimmel Link: https://lore.kernel.org/netdev/20261007114003.GA1011260@shredder/ Suggested-by: Ido Schimmel Cc: stable@vger.kernel.org Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Link: https://patch.msgid.link/4012604727e5ba80b69ccd4d08796511397ddaef.1791423190.git.4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv6/route.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 153ce16628c1..a5e955e56e79 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -6509,8 +6509,6 @@ void fib6_info_hw_flags_set(struct net *net, struct fib6_info *f6i, err = rt6_fill_node(net, skb, f6i, NULL, NULL, NULL, 0, RTM_NEWROUTE, 0, 0, 0, RT_DEL_REASON_UNSPEC); if (err < 0) { - /* -EMSGSIZE implies BUG in rt6_nlmsg_size() */ - WARN_ON(err == -EMSGSIZE); kfree_skb(skb); goto errout; } From 64a6b36b0e1855288080bc5f89769f27d6dc03c8 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Thu, 8 Oct 2026 16:44:44 +0800 Subject: [PATCH 122/128] net/smc: protect clcsock lifetime in smc_getname smc_getname() dereferences smc->clcsock without holding clcsock_release_lock. Link-group termination can release the CLC socket through smc_close_active_abort() while the SMC socket is still open, for example after shutdown(SHUT_WR). A getsockname() caller can load smc->clcsock, then the termination worker can clear the pointer and call sock_release() before the caller accesses clcsock->ops or invokes getname(). This causes a use-after-free; if the worker clears the pointer before the load, it causes a NULL dereference. The syscall's file reference keeps the SMC socket alive but does not prevent asynchronous release of its CLC socket. KASAN reported the following with test-only timing instrumentation: BUG: KASAN: slab-use-after-free in smc_getname+0x19e/0x1b0 Read of size 8 at addr ffff888109abb4e0 by task poc/103 Call Trace: smc_getname+0x19e/0x1b0 do_getsockname+0xe5/0x170 __sys_getsockname+0x8c/0x100 Allocated by task 95: sock_alloc_inode+0x1e/0x280 sock_alloc+0x3d/0x240 __sock_create+0x7e/0x430 smc_create+0x121/0x240 Freed by task 0: kmem_cache_free+0xcc/0x340 rcu_core+0x50a/0x1850 Last potentially related work creation: evict+0x446/0x6c0 smc_clcsock_release+0xa8/0xd0 smc_close_active_abort+0x26a/0x3a0 __smc_lgr_terminate.part.0+0x137/0x2e0 Hold clcsock_release_lock across the pointer check and the getname() callback to serialize with smc_clcsock_release(). Return -EBADF if the CLC socket has already been released, preserving the existing peer state check and the callback's return value otherwise. Fixes: b03faa1fafc8 ("net/smc: postpone release of clcsock") Cc: stable@vger.kernel.org Signed-off-by: Chengfeng Ye Reviewed-by: Mahanta Jambigi Reviewed-by: Dust Li Link: https://patch.msgid.link/20261008084444.787449-1-nicoyip.dev@gmail.com Signed-off-by: Jakub Kicinski --- net/smc/af_smc.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/net/smc/af_smc.c b/net/smc/af_smc.c index e9f93b3ab435..8b3ee70f8433 100644 --- a/net/smc/af_smc.c +++ b/net/smc/af_smc.c @@ -2784,6 +2784,7 @@ int smc_getname(struct socket *sock, struct sockaddr *addr, int peer) { struct smc_sock *smc; + int rc = -EBADF; if (peer && (sock->sk->sk_state != SMC_ACTIVE) && (sock->sk->sk_state != SMC_APPCLOSEWAIT1)) @@ -2791,7 +2792,11 @@ int smc_getname(struct socket *sock, struct sockaddr *addr, smc = smc_sk(sock->sk); - return smc->clcsock->ops->getname(smc->clcsock, addr, peer); + mutex_lock(&smc->clcsock_release_lock); + if (smc->clcsock) + rc = smc->clcsock->ops->getname(smc->clcsock, addr, peer); + mutex_unlock(&smc->clcsock_release_lock); + return rc; } int smc_sendmsg(struct socket *sock, struct msghdr *msg, size_t len) From 3c6a4b11330fe424557538a98fca9f0a4c5bf313 Mon Sep 17 00:00:00 2001 From: Fernando Fernandez Mancera Date: Thu, 8 Oct 2026 12:41:35 +0200 Subject: [PATCH 123/128] net: openvswitch: validate transport header presence in set_ipv6_addr When executing IPv6 address rewrite actions on IPv6 fragments, set_ipv6_addr() calls update_ipv6_checksum(). If parse_ipv6hdr() processes a non-first IPv6 fragment, it sets key->ip.proto to NEXTHDR_FRAGMENT and returns early without calling skb_set_transport_header(). update_ipv6_checksum() unconditionally evaluates skb_transport_offset() on entry before checking l4_proto. Because skb->transport_header is uninitialized, this triggers a warning under CONFIG_DEBUG_NET=y although it is completely harmless. Fix this by returning early in update_ipv6_checksum() if l4_proto is NEXTHDR_FRAGMENT. This avoids reading the uninitialized transport offset for fragments while preserving the debug warning for any other protocol where the transport header is unexpectedly missing. See the syzbot trace: !skb_transport_header_was_set(skb) WARNING: ./include/linux/skbuff.h:3075 at skb_transport_header include/linux/skbuff.h:3075 [inline], CPU#1: syz-executor463/5635 WARNING: ./include/linux/skbuff.h:3075 at skb_transport_offset include/linux/skbuff.h:3250 [inline], CPU#1: syz-executor463/5635 WARNING: ./include/linux/skbuff.h:3075 at update_ipv6_checksum net/openvswitch/actions.c:361 [inline], CPU#1: syz-executor463/5635 WARNING: ./include/linux/skbuff.h:3075 at set_ipv6_addr+0x462/0x660 net/openvswitch/actions.c:399, CPU#1: syz-executor463/5635 [...] RIP: 0010:skb_transport_header include/linux/skbuff.h:3075 [inline] RIP: 0010:skb_transport_offset include/linux/skbuff.h:3250 [inline] RIP: 0010:update_ipv6_checksum net/openvswitch/actions.c:361 [inline] RIP: 0010:set_ipv6_addr+0x462/0x660 net/openvswitch/actions.c:399 [...] Call Trace: set_ipv6 net/openvswitch/actions.c:531 [inline] do_execute_actions+0x557e/0x8600 net/openvswitch/actions.c:1366 ovs_execute_actions+0xde/0x520 net/openvswitch/actions.c:1592 ovs_packet_cmd_execute+0xb4f/0xf10 net/openvswitch/datapath.c:705 genl_family_rcv_msg_doit+0x233/0x340 net/netlink/genetlink.c:1114 genl_rcv_msg+0x614/0x7a0 net/netlink/genetlink.c:1209 netlink_rcv_skb+0x226/0x4a0 net/netlink/af_netlink.c:2572 genl_rcv+0x28/0x40 net/netlink/genetlink.c:1218 netlink_unicast+0x7bd/0x940 net/netlink/af_netlink.c:1361 netlink_sendmsg+0x813/0xb40 net/netlink/af_netlink.c:1916 sock_sendmsg_nosec+0x14e/0x190 net/socket.c:800 Reported-by: syzbot+4cc63fcfb3845e149969@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=4cc63fcfb3845e149969 Fixes: 3fdbd1ce11e5 ("openvswitch: add ipv6 'set' action") Signed-off-by: Fernando Fernandez Mancera Reviewed-by: Ilya Maximets Link: https://patch.msgid.link/20261008104135.4722-1-fmancera@suse.de Signed-off-by: Jakub Kicinski --- net/openvswitch/actions.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/net/openvswitch/actions.c b/net/openvswitch/actions.c index dc5ff859f114..27152bca8a8c 100644 --- a/net/openvswitch/actions.c +++ b/net/openvswitch/actions.c @@ -358,7 +358,12 @@ static void set_ip_addr(struct sk_buff *skb, struct iphdr *nh, static void update_ipv6_checksum(struct sk_buff *skb, u8 l4_proto, __be32 addr[4], const __be32 new_addr[4]) { - int transport_len = skb->len - skb_transport_offset(skb); + int transport_len; + + if (l4_proto == NEXTHDR_FRAGMENT) + return; + + transport_len = skb->len - skb_transport_offset(skb); if (l4_proto == NEXTHDR_TCP) { if (likely(transport_len >= sizeof(struct tcphdr))) From 65ab9de4bdd4c05d8277cfc20ae0b9f5aff60897 Mon Sep 17 00:00:00 2001 From: Ramses de Norre Date: Thu, 8 Oct 2026 14:59:15 +0200 Subject: [PATCH 124/128] wireguard: queueing: preserve tstamp_type when encapsulating packet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sending traffic through a wireguard tunnel on a host using the fq qdisc fills the log with: fq: likely mono tstamp with tstamp_type 0 An skb carries a timestamp in skb->tstamp and, separately, a skb->tstamp_type field recording which clock that timestamp came from. The two have to agree. When wireguard encapsulates a packet it calls wg_reset_packet(), which clears the fields that must not leak from the inner packet into the tunnel packet. It does so in two steps: skb_scrub_packet(skb, true); memset(&skb->headers, 0, sizeof(skb->headers)); skb_scrub_packet() deliberately keeps skb->tstamp when it holds a monotonic timestamp: that value is the time the packet is scheduled to be sent, and the qdisc still needs it. The memset then zeroes skb->tstamp_type, because that field sits inside the headers group while skb->tstamp does not. The packet therefore leaves wireguard carrying a monotonic timestamp labelled as a realtime one. Nothing noticed until commit c4f796c4f16b ("net_sched: sch_fq: convert skb->tstamp if not monotonic"): fq used to assume every timestamp was monotonic. It now consults tstamp_type, spots the mismatch, warns, and falls back to treating the value as monotonic. Pacing still ends up correct, so the log spam is the actual problem. Save tstamp_type before the memset and restore it when encapsulating, next to the hash fields that are already carried over this way. When decapsulating it stays zeroed, which is right: an incoming packet's timestamp is a realtime receive timestamp. Fixes: d98d58a00261 ("net: Set skb->mono_delivery_time and clear it after sch_handle_ingress()") Signed-off-by: Ramses de Norre Reviewed-by: Toke Høiland-Jørgensen Reviewed-by: Eric Dumazet Cc: stable@vger.kernel.org Signed-off-by: Jason A. Donenfeld Link: https://patch.msgid.link/20261008130124.724119-3-Jason@zx2c4.com Signed-off-by: Jakub Kicinski --- drivers/net/wireguard/queueing.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/wireguard/queueing.h b/drivers/net/wireguard/queueing.h index 79b6d70de236..663b19b78395 100644 --- a/drivers/net/wireguard/queueing.h +++ b/drivers/net/wireguard/queueing.h @@ -78,12 +78,14 @@ static inline void wg_reset_packet(struct sk_buff *skb, bool encapsulating) u8 l4_hash = skb->l4_hash; u8 sw_hash = skb->sw_hash; u32 hash = skb->hash; + u8 tstamp_type = skb->tstamp_type; skb_scrub_packet(skb, true); memset(&skb->headers, 0, sizeof(skb->headers)); if (encapsulating) { skb->l4_hash = l4_hash; skb->sw_hash = sw_hash; skb->hash = hash; + skb->tstamp_type = tstamp_type; } skb->queue_mapping = 0; skb->nohdr = 0; From d0305f8d90029556260c6824b61c493c424eba9e Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Thu, 8 Oct 2026 14:59:16 +0200 Subject: [PATCH 125/128] wireguard: noise: reject response consumption after intermediate initiation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two threads begin processing the identical response message, received twice. The first thread, A, runs. While it's running, the second one, B, gets partway through, and during that slow calculation, or even while blocking on down_write(), A completes and then also a handshake initiation that's already been queued up runs in thread C, which itself takes that same down_write(). The handshake initiation creation succeeds, and sets the state back to waiting-for-response, and calls up_write(), at which point thread B resumes, because either its finished its calculations or was finally allowed to acquire down_write(). Thread B then copies the state back to the peer, and begins a new session, using that state, which is the same session as the one made in thread A. Thread A Thread B Thread C down_read() sA = handshake->state memcpy(cA, handshake->crypto) up_read() if (sA != 1) goto fail slow_crypto(cA) down_read() sB = handshake->state memcpy(cB, handshake->crypto) up_read() if (sB != 1) goto fail slow_crypto(cB) down_write() if (sA != handshake->state) goto fail memcpy(handshake->crypto, cA) handshake->state = 2 up_write() down_write() if (handshake->state != 2) goto fail derive_session(handshake->crypto) up_write() down_write() slow_crypto(handshake->crypto) handshake->state = 1 up_write() down_write() if (sB != handshake->state) goto fail memcpy(handshake->crypto, cB) handshake->state = 2 up_write() down_write() if (handshake->state != 2) goto fail derive_session(handshake->crypto) up_write() This seems basically impossible to hit in a meaningful way in practice, but ensure that it absolutely cannot happen by comparing the ephemeral private key that's on the stack with the latest one that the peer's handshake state has. Cc: stable@vger.kernel.org Fixes: e7096c131e51 ("net: WireGuard secure network tunnel") Reported-by: Jérémy Jean Signed-off-by: Jason A. Donenfeld Link: https://patch.msgid.link/20261008130124.724119-4-Jason@zx2c4.com Signed-off-by: Jakub Kicinski --- drivers/net/wireguard/noise.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireguard/noise.c b/drivers/net/wireguard/noise.c index 9c0a09bf6c95..ec2513d6dfab 100644 --- a/drivers/net/wireguard/noise.c +++ b/drivers/net/wireguard/noise.c @@ -784,10 +784,11 @@ wg_noise_handshake_consume_response(struct message_handshake_response *src, /* Success! Copy everything to peer */ down_write(&handshake->lock); - /* It's important to check that the state is still the same, while we - * have an exclusive lock. + /* Check that the state is the same and that this is still the + * initiation we started with, while we have an exclusive lock. */ - if (handshake->state != state) { + if (handshake->state != state || + crypto_memneq(handshake->ephemeral_private, ephemeral_private, NOISE_PUBLIC_KEY_LEN)) { up_write(&handshake->lock); goto fail; } From 7c24a4e87e219072e5e106f6791146cbf3b056b3 Mon Sep 17 00:00:00 2001 From: Ilia Gavrilov Date: Thu, 8 Oct 2026 14:41:17 +0000 Subject: [PATCH 126/128] vsock: Fix memory leak in vmci_transport_recv_dgram_cb() During the closure of a datagram socket, the vmci_transport_recv_dgram_cb() function may be called, which will add the packets to the socket's backlog; then, after the receive queue is cleared, the __release_sock() function will move the packet back from the socket's backlog to the receive queue, which will lead to a memory leak. sock_close __sock_release __vsock_release // take ownership by user-space lock_sock_nested sock_set_flag(sk, SOCK_DEAD) vmci_transport_release vmci_dispatch_dgs vmci_datagram_invoke_guest_handler vmci_transport_recv_dgram_cb sk_receive_skb if (!sock_owned_by_user(sk)) [...] (!) else if sk_add_backlog() [...] (!) skb_queue_purge(&sk->sk_receive_queue) release_sock __release_sock // move all packets from the backlog // to the receive queue (!) sk_backlog_rcv vsock_queue_rcv_skb [...] __sock_queue_rcv_skb if (!sock_flag(sk, SOCK_DEAD)) // Since the socket is "dead", the function will not // be called sk->sk_data_ready(sk) sock_release_ownership(sk); sock_put Add a receive queue cleanup in the socket destructor to fix this. syzkaller report: unreferenced object 0xffff8880117bfb80 (size 240): comm "irq/56-vmw_vmci", pid 205, jiffies 4295064448 (age 73.992s) hex dump (first 32 bytes): 78 dc 26 1e 80 88 ff ff 78 dc 26 1e 80 88 ff ff x.&.....x.&..... 00 00 00 00 00 00 00 00 c0 da 26 1e 80 88 ff ff ..........&..... backtrace: [] __alloc_skb+0x287/0x330 net/core/skbuff.c:505 [] vmci_transport_recv_dgram_cb+0xbd/0x210 [vmw_vsock_vmci_transport] [] vmci_datagram_invoke_guest_handler+0x366/0x450 [vmw_vmci] [] vmci_dispatch_dgs+0x235/0x490 [vmw_vmci] [] vmci_interrupt+0x55/0x290 [vmw_vmci] [] irq_thread_fn+0x8b/0x1a0 kernel/irq/manage.c:1205 [] irq_thread+0x28e/0x530 kernel/irq/manage.c:1314 [] kthread+0x2e6/0x3a0 kernel/kthread.c:376 [] ret_from_fork+0x22/0x30 arch/x86/entry/entry_64.S:295 Found by InfoTeCS on behalf of Linux Verification Center (linuxtesting.org) with Syzkaller. Fixes: d021c344051a ("VSOCK: Introduce VM Sockets") Reviewed-by: Stefano Garzarella Signed-off-by: Ilia Gavrilov Link: https://patch.msgid.link/20261008144046.90853-1-Ilia.Gavrilov@infotecs.ru Signed-off-by: Jakub Kicinski --- net/vmw_vsock/af_vsock.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c index 9b71479a2b29..533c733618df 100644 --- a/net/vmw_vsock/af_vsock.c +++ b/net/vmw_vsock/af_vsock.c @@ -1048,6 +1048,8 @@ static void vsock_sk_destruct(struct sock *sk) { struct vsock_sock *vsk = vsock_sk(sk); + /* Socket buffers may remain in the queue in VMCI datagram mode */ + __skb_queue_purge(&sk->sk_receive_queue); /* Flush MSG_ZEROCOPY leftovers. */ __skb_queue_purge(&sk->sk_error_queue); From 6b48ed85ae0ca37c7403ef5d512be75975844f2f Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Tue, 6 Oct 2026 07:41:32 +0200 Subject: [PATCH 127/128] net: macb: check TX ring before modifying skb macb_pad_and_fcs() replaces or extends the skb before the ring space check. On NETDEV_TX_BUSY the stack requeues an skb that is already freed or grown. Check the ring first, using the padded length for the descriptor count. Nonlinear skbs always take the copy path so the count can assume a linear skb. Fixes: 653e92a9175e ("net: macb: add support for padding and fcs computation") Signed-off-by: Nicolai Buchwitz Link: https://patch.msgid.link/20261006-nb-macb-shared-skb-net-v1-1-a80641479041@tipi-net.de Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cadence/macb_main.c | 76 ++++++++++++++---------- 1 file changed, 46 insertions(+), 30 deletions(-) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index 8e5c034dc3a4..6082e63009a5 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -2421,8 +2421,15 @@ static inline int macb_clear_csum(struct sk_buff *skb) return 0; } +static bool macb_needs_sw_fcs(struct sk_buff *skb, struct net_device *netdev) +{ + return netdev->features & NETIF_F_HW_CSUM && + skb->ip_summed != CHECKSUM_PARTIAL && + !skb_shinfo(skb)->gso_size && !ptp_one_step_sync(skb); +} + /* Returns a negative errno, or the FCS bytes appended (0 or ETH_FCS_LEN). */ -static int macb_pad_and_fcs(struct sk_buff **skb, struct net_device *netdev) +static int macb_pad_and_fcs(struct sk_buff **skb, bool add_fcs) { bool cloned = skb_cloned(*skb) || skb_header_cloned(*skb) || skb_is_nonlinear(*skb); @@ -2431,18 +2438,15 @@ static int macb_pad_and_fcs(struct sk_buff **skb, struct net_device *netdev) struct sk_buff *nskb; u32 fcs; - if (!(netdev->features & NETIF_F_HW_CSUM) || - !((*skb)->ip_summed != CHECKSUM_PARTIAL) || - skb_shinfo(*skb)->gso_size || ptp_one_step_sync(*skb)) + if (!add_fcs) return 0; if (padlen <= 0) { - /* FCS could be appeded to tailroom. */ - if (tailroom >= ETH_FCS_LEN) + /* FCS could be appended to tailroom. */ + if (!skb_is_nonlinear(*skb) && tailroom >= ETH_FCS_LEN) goto add_fcs; - /* No room for FCS, need to reallocate skb. */ - else - padlen = ETH_FCS_LEN; + /* Reallocate with room for the FCS. */ + padlen = ETH_FCS_LEN; } else { /* Add room for FCS. */ padlen += ETH_FCS_LEN; @@ -2481,27 +2485,15 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, unsigned int desc_cnt, nr_frags, frag_size, f; struct macb_queue *queue = &bp->queues[q]; netdev_tx_t ret = NETDEV_TX_OK; - unsigned int hdrlen; + unsigned int hdrlen, tx_len; + bool add_fcs, is_lso; unsigned long flags; int fcs_len; - bool is_lso; - - if (macb_clear_csum(skb)) { - dev_kfree_skb_any(skb); - return ret; - } - - fcs_len = macb_pad_and_fcs(&skb, netdev); - if (fcs_len < 0) { - dev_kfree_skb_any(skb); - return ret; - } - - if (macb_dma_ptp(bp) && - (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP)) - skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; + add_fcs = macb_needs_sw_fcs(skb, netdev); is_lso = (skb_shinfo(skb)->gso_size != 0); + tx_len = add_fcs ? max_t(unsigned int, skb->len, ETH_ZLEN) + + ETH_FCS_LEN : skb->len; if (is_lso) { /* length of headers */ @@ -2515,8 +2507,11 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, /* if this is required, would need to copy to single buffer */ return NETDEV_TX_BUSY; } - } else + } else if (add_fcs) { + hdrlen = umin(tx_len, bp->max_tx_length); + } else { hdrlen = umin(skb_headlen(skb), bp->max_tx_length); + } #if defined(DEBUG) && defined(VERBOSE_DEBUG) netdev_vdbg(bp->netdev, @@ -2531,12 +2526,18 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, * socket buffer: skb fragments of jumbo frames may need to be * split into many buffer descriptors. */ - if (is_lso && (skb_headlen(skb) > hdrlen)) + if (add_fcs) { + /* macb_pad_and_fcs() linearizes the skb before adding the FCS. */ + desc_cnt = DIV_ROUND_UP(tx_len, bp->max_tx_length); + nr_frags = 0; + } else if (is_lso && (skb_headlen(skb) > hdrlen)) { /* extra header descriptor if also payload in first buffer */ desc_cnt = DIV_ROUND_UP((skb_headlen(skb) - hdrlen), bp->max_tx_length) + 1; - else + nr_frags = skb_shinfo(skb)->nr_frags; + } else { desc_cnt = DIV_ROUND_UP(skb_headlen(skb), bp->max_tx_length); - nr_frags = skb_shinfo(skb)->nr_frags; + nr_frags = skb_shinfo(skb)->nr_frags; + } for (f = 0; f < nr_frags; f++) { frag_size = skb_frag_size(&skb_shinfo(skb)->frags[f]); desc_cnt += DIV_ROUND_UP(frag_size, bp->max_tx_length); @@ -2554,6 +2555,21 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, goto unlock; } + if (macb_clear_csum(skb)) { + dev_kfree_skb_any(skb); + goto unlock; + } + + fcs_len = macb_pad_and_fcs(&skb, add_fcs); + if (fcs_len < 0) { + dev_kfree_skb_any(skb); + goto unlock; + } + + if (macb_dma_ptp(bp) && + (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP)) + skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; + /* Map socket buffer for DMA transfer */ if (macb_tx_map(bp, queue, skb, hdrlen, fcs_len)) { dev_kfree_skb_any(skb); From 9151d6c42799105e109c8b54dbaad91ce0e17c47 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Tue, 6 Oct 2026 07:41:33 +0200 Subject: [PATCH 128/128] net: macb: copy shared skbs before appending the FCS macb_pad_and_fcs() appends the FCS in place when the skb has tailroom. A shared skb, as pktgen sends in clone_skb mode, grows by one FCS per transmit. BQL then completes more bytes than were queued and dql_completed() hits its BUG_ON. On a Raspberry Pi CM5 (RP1 GEM) pktgen with clone_skb 1000 burst 32 at 60 bytes kills the box within seconds. Copy shared skbs before appending the FCS. Clearing IFF_TX_SKB_SHARING would also fix it but makes pktgen refuse clone_skb on macb. Fixes: 653e92a9175e ("net: macb: add support for padding and fcs computation") Signed-off-by: Nicolai Buchwitz Link: https://patch.msgid.link/20261006-nb-macb-shared-skb-net-v1-2-a80641479041@tipi-net.de Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cadence/macb_main.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index 6082e63009a5..261a7e87520a 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -2435,6 +2435,7 @@ static int macb_pad_and_fcs(struct sk_buff **skb, bool add_fcs) skb_is_nonlinear(*skb); int padlen = ETH_ZLEN - (*skb)->len; int tailroom = skb_tailroom(*skb); + bool shared = skb_shared(*skb); struct sk_buff *nskb; u32 fcs; @@ -2443,7 +2444,8 @@ static int macb_pad_and_fcs(struct sk_buff **skb, bool add_fcs) if (padlen <= 0) { /* FCS could be appended to tailroom. */ - if (!skb_is_nonlinear(*skb) && tailroom >= ETH_FCS_LEN) + if (!shared && !skb_is_nonlinear(*skb) && + tailroom >= ETH_FCS_LEN) goto add_fcs; /* Reallocate with room for the FCS. */ padlen = ETH_FCS_LEN; @@ -2452,7 +2454,7 @@ static int macb_pad_and_fcs(struct sk_buff **skb, bool add_fcs) padlen += ETH_FCS_LEN; } - if (cloned || tailroom < padlen) { + if (shared || cloned || tailroom < padlen) { nskb = skb_copy_expand(*skb, 0, padlen, GFP_ATOMIC); if (!nskb) return -ENOMEM;