]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
RDMA/iwcm: Fix a use-after-free related to destroying CM IDs
authorBart Van Assche <bvanassche@acm.org>
Wed, 5 Jun 2024 14:51:01 +0000 (08:51 -0600)
committerLeon Romanovsky <leon@kernel.org>
Sun, 9 Jun 2024 08:25:10 +0000 (11:25 +0300)
iw_conn_req_handler() associates a new struct rdma_id_private (conn_id) with
an existing struct iw_cm_id (cm_id) as follows:

        conn_id->cm_id.iw = cm_id;
        cm_id->context = conn_id;
        cm_id->cm_handler = cma_iw_handler;

rdma_destroy_id() frees both the cm_id and the struct rdma_id_private. Make
sure that cm_work_handler() does not trigger a use-after-free by only
freeing of the struct rdma_id_private after all pending work has finished.

Cc: stable@vger.kernel.org
Fixes: 59c68ac31e15 ("iw_cm: free cm_id resources on the last deref")
Reviewed-by: Zhu Yanjun <yanjun.zhu@linux.dev>
Tested-by: Shin'ichiro Kawasaki <shinichiro.kawasaki@wdc.com>
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
Link: https://lore.kernel.org/r/20240605145117.397751-6-bvanassche@acm.org
Signed-off-by: Leon Romanovsky <leon@kernel.org>
drivers/infiniband/core/iwcm.c

index 4424f430fc0870e0e9c4b0693f0339107deb7ac8..1a6339f3a63fc9ad3d88c56ddbc5d75e38fee5d5 100644 (file)
@@ -368,8 +368,10 @@ EXPORT_SYMBOL(iw_cm_disconnect);
  *
  * Clean up all resources associated with the connection and release
  * the initial reference taken by iw_create_cm_id.
+ *
+ * Returns true if and only if the last cm_id_priv reference has been dropped.
  */
-static void destroy_cm_id(struct iw_cm_id *cm_id)
+static bool destroy_cm_id(struct iw_cm_id *cm_id)
 {
        struct iwcm_id_private *cm_id_priv;
        struct ib_qp *qp;
@@ -439,7 +441,7 @@ static void destroy_cm_id(struct iw_cm_id *cm_id)
                iwpm_remove_mapping(&cm_id->local_addr, RDMA_NL_IWCM);
        }
 
-       (void)iwcm_deref_id(cm_id_priv);
+       return iwcm_deref_id(cm_id_priv);
 }
 
 /*
@@ -450,7 +452,8 @@ static void destroy_cm_id(struct iw_cm_id *cm_id)
  */
 void iw_destroy_cm_id(struct iw_cm_id *cm_id)
 {
-       destroy_cm_id(cm_id);
+       if (!destroy_cm_id(cm_id))
+               flush_workqueue(iwcm_wq);
 }
 EXPORT_SYMBOL(iw_destroy_cm_id);
 
@@ -1031,7 +1034,7 @@ static void cm_work_handler(struct work_struct *_work)
                if (!test_bit(IWCM_F_DROP_EVENTS, &cm_id_priv->flags)) {
                        ret = process_event(cm_id_priv, &levent);
                        if (ret)
-                               destroy_cm_id(&cm_id_priv->id);
+                               WARN_ON_ONCE(destroy_cm_id(&cm_id_priv->id));
                } else
                        pr_debug("dropping event %d\n", levent.event);
                if (iwcm_deref_id(cm_id_priv))