Эх сурвалжийг харах

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
Jinwei Chen 5 жил өмнө
parent
commit
490e7fd6b4
1 өөрчлөгдсөн 69 нэмэгдсэн , 36 устгасан
  1. 69 36
      dp/wifi3.0/dp_main.c

+ 69 - 36
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);