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
This commit is contained in:
Jinwei Chen
2019-11-18 15:04:52 +08:00
committed by nshrivas
parent a1a4fe1417
commit 490e7fd6b4

View File

@@ -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, * dp_vdev_reset_peer() - Update peer related member in vdev
struct dp_peer *peer, as peer is going to free
struct dp_vdev *vdev) * @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; struct dp_peer *bss_peer = NULL;
uint8_t *m_addr = NULL;
if (!vdev) { if (!vdev) {
QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_ERROR, dp_err("vdev is NULL");
"vdev is NULL");
} else { } else {
if (vdev->vap_bss_peer == peer) if (vdev->vap_bss_peer == peer)
vdev->vap_bss_peer = NULL; 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) { if (vdev && vdev->vap_bss_peer) {
bss_peer = vdev->vap_bss_peer; bss_peer = vdev->vap_bss_peer;
DP_UPDATE_STATS(vdev, 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 * Peer AST list hast to be empty here
*/ */
@@ -5943,8 +5967,11 @@ void dp_peer_unref_delete(struct dp_peer *peer)
int found = 0; int found = 0;
uint16_t peer_id; uint16_t peer_id;
uint16_t vdev_id; uint16_t vdev_id;
bool delete_vdev; bool vdev_delete = false;
struct cdp_peer_cookie peer_cookie; 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 * 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 */ /* cleanup the peer data */
dp_peer_cleanup(vdev, peer, false); 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); 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)) { * Invoke soc.ol_ops->peer_unref_delete out of
/* * peer_ref_mutex in case deadlock issue.
* capture vdev delete pending flag's status */
* while holding peer_ref_mutex lock dp_peer_release_mem(soc, pdev, peer,
*/ vdev_opmode,
delete_vdev = vdev->delete.pending; vdev_mac_addr);
/* /*
* Now that there are no references to the peer, we can * Delete the vdev if it's waiting all peer deleted
* release the peer reference lock. * and it's chance now.
*/ */
qdf_spin_unlock_bh(&soc->peer_ref_mutex); if (vdev_delete)
/* dp_delete_pending_vdev(pdev, vdev, vdev_id);
* 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);
}
} else { } else {
qdf_spin_unlock_bh(&soc->peer_ref_mutex); qdf_spin_unlock_bh(&soc->peer_ref_mutex);