From 490e7fd6b4df79d8631e7cbbfcfdd648447b51c3 Mon Sep 17 00:00:00 2001 From: Jinwei Chen Date: Mon, 18 Nov 2019 15:04:52 +0800 Subject: [PATCH] qcacmn: fix dp_vdev use after free caused by racing condition crash scenario: a. dp_peer_unref_delete remove peer from vdev->peer_list. b. dp_vdev_detach_wifi3 skip setting vdev->delete.pending as vdev->peer_list is empty, dp_vdev is freed. c. dp_peer_unref_delete still try to access dp_vdev after first peer_ref_mutex released, invalid accessing happened. solution: a. Get vdev member like vdev->delete.pending flag within first peer_ref_mutex in dp_peer_unref_delete to avoid vdev freed. b. Separate dp_reset_and_release_peer_mem function into two function dp_vdev_reset_peer/dp_peer_release_mem, dp__vdev_reset_peer will be invoked within first peer_ref_mutex. after first peer_ref_mutex is released, invoke dp_peer_release_mem since dp_soc ->cdp_soc.ol_ops->peer_unref_delete should be outside of peer_ref_mutex in case deadlock issue reported from WIN. Change-Id: I90f3b139030c5ce399d85723ae4f67ce0faf4b28 CRs-Fixed: 2568256 --- dp/wifi3.0/dp_main.c | 105 ++++++++++++++++++++++++++++--------------- 1 file changed, 69 insertions(+), 36 deletions(-) diff --git a/dp/wifi3.0/dp_main.c b/dp/wifi3.0/dp_main.c index fe836aa586..ac7d574ae4 100644 --- a/dp/wifi3.0/dp_main.c +++ b/dp/wifi3.0/dp_main.c @@ -5852,31 +5852,55 @@ static void dp_peer_authorize(struct cdp_peer *peer_handle, uint32_t authorize) } } -static void dp_reset_and_release_peer_mem(struct dp_soc *soc, - struct dp_pdev *pdev, - struct dp_peer *peer, - struct dp_vdev *vdev) +/* + * dp_vdev_reset_peer() - Update peer related member in vdev + as peer is going to free + * @vdev: datapath vdev handle + * @peer: dataptah peer handle + * + * Return: None + */ +static void dp_vdev_reset_peer(struct dp_vdev *vdev, + struct dp_peer *peer) { struct dp_peer *bss_peer = NULL; - uint8_t *m_addr = NULL; if (!vdev) { - QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_ERROR, - "vdev is NULL"); + dp_err("vdev is NULL"); } else { if (vdev->vap_bss_peer == peer) vdev->vap_bss_peer = NULL; - m_addr = peer->mac_addr.raw; - if (soc->cdp_soc.ol_ops->peer_unref_delete) - soc->cdp_soc.ol_ops->peer_unref_delete(pdev->ctrl_pdev, - m_addr, vdev->mac_addr.raw, vdev->opmode, - peer->ctrl_peer, NULL); if (vdev && vdev->vap_bss_peer) { bss_peer = vdev->vap_bss_peer; DP_UPDATE_STATS(vdev, peer); } } +} + +/* + * dp_peer_release_mem() - free dp peer handle memory + * @soc: dataptah soc handle + * @pdev: datapath pdev handle + * @peer: datapath peer handle + * @vdev_opmode: Vdev operation mode + * @vdev_mac_addr: Vdev Mac address + * + * Return: None + */ +static void dp_peer_release_mem(struct dp_soc *soc, + struct dp_pdev *pdev, + struct dp_peer *peer, + enum wlan_op_mode vdev_opmode, + uint8_t *vdev_mac_addr) +{ + if (soc->cdp_soc.ol_ops->peer_unref_delete) + soc->cdp_soc.ol_ops->peer_unref_delete( + pdev->ctrl_pdev, + peer->mac_addr.raw, vdev_mac_addr, + vdev_opmode, peer->ctrl_peer, + NULL); + /* * Peer AST list hast to be empty here */ @@ -5943,8 +5967,11 @@ void dp_peer_unref_delete(struct dp_peer *peer) int found = 0; uint16_t peer_id; uint16_t vdev_id; - bool delete_vdev; + bool vdev_delete = false; struct cdp_peer_cookie peer_cookie; + enum wlan_op_mode vdev_opmode; + uint8_t vdev_mac_addr[QDF_MAC_ADDR_SIZE]; + /* * Hold the lock all the way from checking if the peer ref count @@ -6016,31 +6043,37 @@ void dp_peer_unref_delete(struct dp_peer *peer) /* cleanup the peer data */ dp_peer_cleanup(vdev, peer, false); + /* reset this peer related info in vdev */ + dp_vdev_reset_peer(vdev, peer); + /* save vdev related member in case vdev freed */ + vdev_opmode = vdev->opmode; + qdf_mem_copy(vdev_mac_addr, vdev->mac_addr.raw, + QDF_MAC_ADDR_SIZE); + /* + * check whether the parent vdev is pending for deleting + * and no peers left. + */ + if (vdev->delete.pending && TAILQ_EMPTY(&vdev->peer_list)) + vdev_delete = true; + /* + * Now that there are no references to the peer, we can + * release the peer reference lock. + */ qdf_spin_unlock_bh(&soc->peer_ref_mutex); - dp_reset_and_release_peer_mem(soc, pdev, peer, vdev); - qdf_spin_lock_bh(&soc->peer_ref_mutex); - /* check whether the parent vdev has no peers left */ - if (TAILQ_EMPTY(&vdev->peer_list)) { - /* - * capture vdev delete pending flag's status - * while holding peer_ref_mutex lock - */ - delete_vdev = vdev->delete.pending; - /* - * Now that there are no references to the peer, we can - * release the peer reference lock. - */ - qdf_spin_unlock_bh(&soc->peer_ref_mutex); - /* - * Check if the parent vdev was waiting for its peers - * to be deleted, in order for it to be deleted too. - */ - if (delete_vdev) - dp_delete_pending_vdev(pdev, vdev, vdev_id); - } else { - qdf_spin_unlock_bh(&soc->peer_ref_mutex); - } + /* + * Invoke soc.ol_ops->peer_unref_delete out of + * peer_ref_mutex in case deadlock issue. + */ + dp_peer_release_mem(soc, pdev, peer, + vdev_opmode, + vdev_mac_addr); + /* + * Delete the vdev if it's waiting all peer deleted + * and it's chance now. + */ + if (vdev_delete) + dp_delete_pending_vdev(pdev, vdev, vdev_id); } else { qdf_spin_unlock_bh(&soc->peer_ref_mutex);