Fix bugs around RDMA CM destroying state
cover Fix bugs around RDMA CM destroying state
From: Leon Romanovsky <[email protected]>
From Jason:
This small series simplifies some of the RDMA CM state transitions connected with DESTROYING states and in the process resolves a bug discovered by syzkaller.
Thanks
Jason Gunthorpe (4):
RDMA/cma: Simplify DEVICE_REMOVAL for internal_id RDMA/cma: Using the standard locking pattern when delivering the
removal event
RDMA/cma: Remove unneeded locking for req paths RDMA/cma: Execute rdma_cm destruction from a handler properly
drivers/infiniband/core/cma.c | 257 ++++++++++++++++------------------ 1 file changed, 123 insertions(+), 134 deletions(-)
--
2.26.2
PATCH 1/4 RDMA/cma: Simplify DEVICE_REMOVAL for internal_id
From: Jason Gunthorpe <[email protected]>
cma_process_remove() triggers an unconditional rdma_destroy_id() for internal_id's and skips the event deliver and transition through RDMA_CM_DEVICE_REMOVAL.
This is confusing and unnecessary. internal_id always has cma_listen_handler() as the handler, have it catch the RDMA_CM_DEVICE_REMOVAL event and directly consume it and signal removal.
This way the FSM sequence never skips the DEVICE_REMOVAL case and the logic in this hard to test area is simplified.
--- drivers/infiniband/core/cma.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-)
drivers/infiniband/core/cma.c +5 -1
@@ -2482,6 +2482,10 @@ static int cma_listen_handler(struct rdma_cm_id *id, {struct rdma_id_private *id_priv = id->context; +/* Listening IDs are always destroyed on removal */ +if (event->event ==RDMA_CM_EVENT_DEVICE_REMOVAL ) +return -1 ; + id->context = id_priv->id.context; id->event_handler = id_priv->id.event_handler;trace_cm_event_handler (id_priv, event);
@@ -4829,7 +4833,7 @@ static void cma_process_remove(struct cma_device *cma_dev)cma_id_get (id_priv);mutex_unlock (&lock); - ret = id_priv->internal_id ?1 :cma_remove_id_dev (id_priv); + ret =cma_remove_id_dev (id_priv);cma_id_put (id_priv);if (ret)rdma_destroy_id (&id_priv->id);
Signature · 1 line
2.26.2
PATCH 2/4 RDMA/cma: Using the standard locking pattern when delivering the removal event
In reply to Leon RomanovskyFrom: Jason Gunthorpe <[email protected]>
Whenever an event is delivered to the handler it should be done under the handler_mutex and upon any non-zero return from the handler it should trigger destruction of the cm_id.
cma_process_remove() skips some steps here, it is not necessarily wrong since the state change should prevent any races, but it is confusing and unnecessary.
Follow the standard pattern here, with the slight twist that the transition to RDMA_CM_DEVICE_REMOVAL includes a cma_cancel_operation().
--- drivers/infiniband/core/cma.c | 62 ++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 26 deletions(-)
drivers/infiniband/core/cma.c +36 -26
@@ -1925,6 +1925,8 @@ static int cma_cm_event_handler(struct rdma_id_private *id_priv, {int ret; +lockdep_assert_held (&id_priv->handler_mutex); +trace_cm_event_handler (id_priv, event); ret = id_priv->id.event_handler (&id_priv->id, event);trace_cm_event_done (id_priv, event, ret);
@@ -4793,50 +4795,58 @@ static int cma_add_one(struct ib_device *device)return ret; } -static int cma_remove_id_dev (struct rdma_id_private *id_priv) +static void cma_send_device_removal_put (struct rdma_id_private *id_priv) { -struct rdma_cm_event event = {}; +struct rdma_cm_event event = { .event =RDMA_CM_EVENT_DEVICE_REMOVAL };enum rdma_cm_state state; -int ret =0 ; - -/* Record that we want to remove the device */ - state =cma_exch (id_priv,RDMA_CM_DEVICE_REMOVAL ); -if (state ==RDMA_CM_DESTROYING ) -return 0 ; +unsigned long flags; -cma_cancel_operation (id_priv, state);mutex_lock (&id_priv->handler_mutex); +/* Record that we want to remove the device */ +spin_lock_irqsave (&id_priv->lock, flags); + state = id_priv->state; +if (state ==RDMA_CM_DESTROYING || state ==RDMA_CM_DEVICE_REMOVAL ) { +spin_unlock_irqrestore (&id_priv->lock, flags); +mutex_unlock (&id_priv->handler_mutex); +cma_id_put (id_priv); +return ; + } + id_priv->state =RDMA_CM_DEVICE_REMOVAL ; +spin_unlock_irqrestore (&id_priv->lock, flags); -/* Check for destruction from another callback. */ -if (!cma_comp (id_priv,RDMA_CM_DEVICE_REMOVAL )) - goto out; - - event.event =RDMA_CM_EVENT_DEVICE_REMOVAL ; - ret =cma_cm_event_handler (id_priv, &event); -out: +if (cma_cm_event_handler (id_priv, &event)) { +/* +* At this point the ULP promises it won't call +* rdma_destroy_id() concurrently +*/ +cma_id_put (id_priv); +mutex_unlock (&id_priv->handler_mutex); +rdma_destroy_id (&id_priv->id); +return ; + }mutex_unlock (&id_priv->handler_mutex); -return ret; + +/* +* If this races with destroy then the thread that first assigns state +* to a destroying does the cancel. +*/ +cma_cancel_operation (id_priv, state); +cma_id_put (id_priv); }static void cma_process_remove (struct cma_device *cma_dev) { -struct rdma_id_private *id_priv; -int ret; -mutex_lock (&lock);while (!list_empty (&cma_dev->id_list)) { - id_priv =list_entry (cma_dev->id_list.next, - struct rdma_id_private, list); +struct rdma_id_private *id_priv =list_first_entry ( + &cma_dev->id_list, struct rdma_id_private, list);list_del (&id_priv->listen_list);list_del_init (&id_priv->list);cma_id_get (id_priv);mutex_unlock (&lock); - ret =cma_remove_id_dev (id_priv); -cma_id_put (id_priv); -if (ret) -rdma_destroy_id (&id_priv->id); +cma_send_device_removal_put (id_priv);mutex_lock (&lock); }
Signature · 1 line
2.26.2
PATCH 3/4 RDMA/cma: Remove unneeded locking for req paths
In reply to Leon RomanovskyFrom: Jason Gunthorpe <[email protected]>
The REQ flows are concerned that once the handler is called on the new cm_id the ULP can choose to trigger a rdma_destroy_id() concurrently at any time.
However, this is not true, while the ULP can call rdma_destroy_id(), it immediately blocks on the handler_mutex which prevents anything harmful from running concurrently.
Remove the confusing extra locking and refcounts and make the handler_mutex protecting state during destroy more clear.
--- drivers/infiniband/core/cma.c | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-)
drivers/infiniband/core/cma.c +6 -25
@@ -1831,21 +1831,21 @@ static void cma_leave_mc_groups(struct rdma_id_private *id_priv)void rdma_destroy_id (struct rdma_cm_id *id) { -struct rdma_id_private *id_priv; +struct rdma_id_private *id_priv = + container_of(id, struct rdma_id_private, id);enum rdma_cm_state state; - id_priv = container_of(id, struct rdma_id_private, id); -trace_cm_id_destroy (id_priv); - state =cma_exch (id_priv,RDMA_CM_DESTROYING ); -cma_cancel_operation (id_priv, state); -/* * Wait for any active callback to finish. New callbacks will find * the id_priv state set to destroying and abort. */ mutex_lock (&id_priv->handler_mutex); +trace_cm_id_destroy (id_priv); + state =cma_exch (id_priv,RDMA_CM_DESTROYING );mutex_unlock (&id_priv->handler_mutex); +cma_cancel_operation (id_priv, state); +rdma_restrack_del (&id_priv->res);if (id_priv->cma_dev) {if (rdma_cap_ib_cm (id_priv->id.device,1 )) {
@@ -2205,19 +2205,9 @@ static int cma_ib_req_handler(struct ib_cm_id *cm_id, cm_id->context = conn_id; cm_id->cm_handler = cma_ib_handler; -/* -* Protect against the user destroying conn_id from another thread -* until we're done accessing it. -*/ -cma_id_get (conn_id); ret =cma_cm_event_handler (conn_id, &event);if (ret) goto err3; -/* -* Acquire mutex to prevent user executing rdma_destroy_id() -* while we're accessing the cm_id. -*/ -mutex_lock (&lock);if (cma_comp (conn_id,RDMA_CM_CONNECT ) && (conn_id->id.qp_type !=IB_QPT_UD )) {trace_cm_send_mra (cm_id->context);
@@ -2226,13 +2216,11 @@ static int cma_ib_req_handler(struct ib_cm_id *cm_id,mutex_unlock (&lock);mutex_unlock (&conn_id->handler_mutex);mutex_unlock (&listen_id->handler_mutex); -cma_id_put (conn_id);if (net_dev)dev_put (net_dev);return 0 ; err3: -cma_id_put (conn_id);/* Destroy the CM ID by returning a non-zero value. */ conn_id->cm_id.ib =NULL ; err2:
@@ -2409,11 +2397,6 @@ static int iw_conn_req_handler(struct iw_cm_id *cm_id,memcpy (cma_src_addr (conn_id), laddr,rdma_addr_size (laddr));memcpy (cma_dst_addr (conn_id), raddr,rdma_addr_size (raddr)); -/* -* Protect against the user destroying conn_id from another thread -* until we're done accessing it. -*/ -cma_id_get (conn_id); ret =cma_cm_event_handler (conn_id, &event);if (ret) {/* User wants to destroy the CM ID */
@@ -2421,13 +2404,11 @@ static int iw_conn_req_handler(struct iw_cm_id *cm_id,cma_exch (conn_id,RDMA_CM_DESTROYING );mutex_unlock (&conn_id->handler_mutex);mutex_unlock (&listen_id->handler_mutex); -cma_id_put (conn_id);rdma_destroy_id (&conn_id->id);return ret; }mutex_unlock (&conn_id->handler_mutex); -cma_id_put (conn_id); out:mutex_unlock (&listen_id->handler_mutex);
Signature · 1 line
2.26.2
PATCH 4/4 RDMA/cma: Execute rdma_cm destruction from a handler properly
In reply to Leon RomanovskyFrom: Jason Gunthorpe <[email protected]>
When a rdma_cm_id needs to be destroyed after a handler callback fails, part of the destruction pattern is open coded into each call site.
Unfortunately the blind assignment to state discards important information needed to do cma_cancel_operation(). This results in active operations being left running after rdma_destroy_id() completes, and the use-after-free bugs from KASAN.
Consolidate this entire pattern into destroy_id_handler_unlock() and manage the locking correctly. The state should be set to RDMA_CM_DESTROYING under the handler_lock to atomically ensure no futher handlers are called.
--- drivers/infiniband/core/cma.c | 174 ++++++++++++++++------------------ 1 file changed, 84 insertions(+), 90 deletions(-)
drivers/infiniband/core/cma.c +84 -90
@@ -428,19 +428,6 @@ static int cma_comp_exch(struct rdma_id_private *id_priv,return ret; } -static enum rdma_cm_state cma_exch (struct rdma_id_private *id_priv, -enum rdma_cm_state exch) -{ -unsigned long flags; -enum rdma_cm_state old; - -spin_lock_irqsave (&id_priv->lock, flags); - old = id_priv->state; - id_priv->state = exch; -spin_unlock_irqrestore (&id_priv->lock, flags); -return old; -} -static inline u8 cma_get_ip_ver (const struct cma_hdr *hdr) {return hdr->ip_version >>4 ;
@@ -1829,21 +1816,9 @@ static void cma_leave_mc_groups(struct rdma_id_private *id_priv) } } -void rdma_destroy_id (struct rdma_cm_id *id) +static void _destroy_id (struct rdma_id_private *id_priv, +enum rdma_cm_state state) { -struct rdma_id_private *id_priv = - container_of(id, struct rdma_id_private, id); -enum rdma_cm_state state; - -/* -* Wait for any active callback to finish. New callbacks will find -* the id_priv state set to destroying and abort. -*/ -mutex_lock (&id_priv->handler_mutex); -trace_cm_id_destroy (id_priv); - state =cma_exch (id_priv,RDMA_CM_DESTROYING ); -mutex_unlock (&id_priv->handler_mutex); -cma_cancel_operation (id_priv, state);rdma_restrack_del (&id_priv->res);
@@ -1874,6 +1849,42 @@ void rdma_destroy_id(struct rdma_cm_id *id)put_net (id_priv->id.route.addr.dev_addr.net);kfree (id_priv); } + +/* +* destroy an ID from within the handler_mutex. This ensures that no other +* handlers can start running concurrently. +*/ +static void destroy_id_handler_unlock (struct rdma_id_private *id_priv) + __releases(&idprv->handler_mutex) +{ +enum rdma_cm_state state; +unsigned long flags; + +trace_cm_id_destroy (id_priv); + +/* +* Setting the state to destroyed under the handler mutex provides a +* fence against calling handler callbacks. If this is invoked due to +* the failure of a handler callback then it guarentees that no future +* handlers will be called. +*/ +lockdep_assert_held (&id_priv->handler_mutex); +spin_lock_irqsave (&id_priv->lock, flags); + state = id_priv->state; + id_priv->state =RDMA_CM_DESTROYING ; +spin_unlock_irqrestore (&id_priv->lock, flags); +mutex_unlock (&id_priv->handler_mutex); +_destroy_id (id_priv, state); +} + +void rdma_destroy_id (struct rdma_cm_id *id) +{ +struct rdma_id_private *id_priv = + container_of(id, struct rdma_id_private, id); + +mutex_lock (&id_priv->handler_mutex); +destroy_id_handler_unlock (id_priv); +}EXPORT_SYMBOL (rdma_destroy_id);static int cma_rep_recv (struct rdma_id_private *id_priv)
@@ -1938,7 +1949,7 @@ static int cma_ib_handler(struct ib_cm_id *cm_id, {struct rdma_id_private *id_priv = cm_id->context;struct rdma_cm_event event = {}; -int ret =0 ; +int ret;mutex_lock (&id_priv->handler_mutex); if ((ib_event->event != IB_CM_TIMEWAIT_EXIT &&
@@ -2007,14 +2018,12 @@ static int cma_ib_handler(struct ib_cm_id *cm_id,if (ret) {/* Destroy the CM ID by returning a non-zero value. */ id_priv->cm_id.ib =NULL ; -cma_exch (id_priv,RDMA_CM_DESTROYING ); -mutex_unlock (&id_priv->handler_mutex); -rdma_destroy_id (&id_priv->id); +destroy_id_handler_unlock (id_priv);return ret; } out:mutex_unlock (&id_priv->handler_mutex); -return ret; +return 0 ; } static struct rdma_id_private *
@@ -2176,7 +2185,7 @@ static int cma_ib_req_handler(struct ib_cm_id *cm_id,mutex_lock (&listen_id->handler_mutex);if (listen_id->state !=RDMA_CM_LISTEN ) { ret = -ECONNABORTED ; - goto err1; + goto err_unlock; } offset =cma_user_data_offset (listen_id);
@@ -2193,43 +2202,38 @@ static int cma_ib_req_handler(struct ib_cm_id *cm_id, }if (!conn_id) { ret = -ENOMEM ; - goto err1; + goto err_unlock; }mutex_lock_nested (&conn_id->handler_mutex,SINGLE_DEPTH_NESTING ); ret =cma_ib_acquire_dev (conn_id, listen_id, &req); -if (ret) - goto err2; +if (ret) { +destroy_id_handler_unlock (conn_id); + goto err_unlock; + } conn_id->cm_id.ib = cm_id; cm_id->context = conn_id; cm_id->cm_handler = cma_ib_handler; ret =cma_cm_event_handler (conn_id, &event); -if (ret) - goto err3; +if (ret) { +/* Destroy the CM ID by returning a non-zero value. */ + conn_id->cm_id.ib =NULL ; +mutex_unlock (&listen_id->handler_mutex); +destroy_id_handler_unlock (conn_id); + goto net_dev_put; + } +if (cma_comp (conn_id,RDMA_CM_CONNECT ) && (conn_id->id.qp_type !=IB_QPT_UD )) {trace_cm_send_mra (cm_id->context);ib_send_cm_mra (cm_id,CMA_CM_MRA_SETTING ,NULL ,0 ); } -mutex_unlock (&lock);mutex_unlock (&conn_id->handler_mutex); -mutex_unlock (&listen_id->handler_mutex); -if (net_dev) -dev_put (net_dev); -return 0 ; -err3: -/* Destroy the CM ID by returning a non-zero value. */ - conn_id->cm_id.ib =NULL ; -err2: -cma_exch (conn_id,RDMA_CM_DESTROYING ); -mutex_unlock (&conn_id->handler_mutex); -err1: +err_unlock:mutex_unlock (&listen_id->handler_mutex); -if (conn_id) -rdma_destroy_id (&conn_id->id); net_dev_put:if (net_dev)
@@ -2329,9 +2333,7 @@ static int cma_iw_handler(struct iw_cm_id *iw_id, struct iw_cm_event *iw_event)if (ret) {/* Destroy the CM ID by returning a non-zero value. */ id_priv->cm_id.iw =NULL ; -cma_exch (id_priv,RDMA_CM_DESTROYING ); -mutex_unlock (&id_priv->handler_mutex); -rdma_destroy_id (&id_priv->id); +destroy_id_handler_unlock (id_priv);return ret; }
@@ -2378,16 +2380,16 @@ static int iw_conn_req_handler(struct iw_cm_id *cm_id, ret =rdma_translate_ip (laddr, &conn_id->id.route.addr.dev_addr);if (ret) { -mutex_unlock (&conn_id->handler_mutex); -rdma_destroy_id (new_cm_id); - goto out; +mutex_unlock (&listen_id->handler_mutex); +destroy_id_handler_unlock (conn_id); +return ret; } ret =cma_iw_acquire_dev (conn_id, listen_id);if (ret) { -mutex_unlock (&conn_id->handler_mutex); -rdma_destroy_id (new_cm_id); - goto out; +mutex_unlock (&listen_id->handler_mutex); +destroy_id_handler_unlock (conn_id); +return ret; } conn_id->cm_id.iw = cm_id;
@@ -2401,10 +2403,8 @@ static int iw_conn_req_handler(struct iw_cm_id *cm_id,if (ret) {/* User wants to destroy the CM ID */ conn_id->cm_id.iw =NULL ; -cma_exch (conn_id,RDMA_CM_DESTROYING ); -mutex_unlock (&conn_id->handler_mutex);mutex_unlock (&listen_id->handler_mutex); -rdma_destroy_id (&conn_id->id); +destroy_id_handler_unlock (conn_id);return ret; }
@@ -2644,21 +2644,21 @@ static void cma_work_handler(struct work_struct *_work) { struct cma_work *work = container_of(_work, struct cma_work, work);struct rdma_id_private *id_priv = work->id; -int destroy =0 ;mutex_lock (&id_priv->handler_mutex);if (!cma_comp_exch (id_priv, work->old_state, work->new_state)) - goto out; + goto out_unlock;if (cma_cm_event_handler (id_priv, &work->event)) { -cma_exch (id_priv,RDMA_CM_DESTROYING ); - destroy =1 ; +cma_id_put (id_priv); +destroy_id_handler_unlock (id_priv); + goto out_free; } -out: + +out_unlock:mutex_unlock (&id_priv->handler_mutex);cma_id_put (id_priv); -if (destroy) -rdma_destroy_id (&id_priv->id); +out_free:kfree (work); }
@@ -2666,23 +2666,22 @@ static void cma_ndev_work_handler(struct work_struct *_work) { struct cma_ndev_work *work = container_of(_work, struct cma_ndev_work, work);struct rdma_id_private *id_priv = work->id; -int destroy =0 ;mutex_lock (&id_priv->handler_mutex);if (id_priv->state ==RDMA_CM_DESTROYING || id_priv->state ==RDMA_CM_DEVICE_REMOVAL ) - goto out; + goto out_unlock;if (cma_cm_event_handler (id_priv, &work->event)) { -cma_exch (id_priv,RDMA_CM_DESTROYING ); - destroy =1 ; +cma_id_put (id_priv); +destroy_id_handler_unlock (id_priv); + goto out_free; } -out: +out_unlock:mutex_unlock (&id_priv->handler_mutex);cma_id_put (id_priv); -if (destroy) -rdma_destroy_id (&id_priv->id); +out_free:kfree (work); }
@@ -3158,9 +3157,7 @@ static void addr_handler(int status, struct sockaddr *src_addr, event.event =RDMA_CM_EVENT_ADDR_RESOLVED ;if (cma_cm_event_handler (id_priv, &event)) { -cma_exch (id_priv,RDMA_CM_DESTROYING ); -mutex_unlock (&id_priv->handler_mutex); -rdma_destroy_id (&id_priv->id); +destroy_id_handler_unlock (id_priv);return ; } out:
@@ -3777,7 +3774,7 @@ static int cma_sidr_rep_handler(struct ib_cm_id *cm_id,struct rdma_cm_event event = {};const struct ib_cm_sidr_rep_event_param *rep = &ib_event->param.sidr_rep_rcvd; -int ret =0 ; +int ret;mutex_lock (&id_priv->handler_mutex);if (id_priv->state !=RDMA_CM_CONNECT )
@@ -3827,14 +3824,12 @@ static int cma_sidr_rep_handler(struct ib_cm_id *cm_id,if (ret) {/* Destroy the CM ID by returning a non-zero value. */ id_priv->cm_id.ib =NULL ; -cma_exch (id_priv,RDMA_CM_DESTROYING ); -mutex_unlock (&id_priv->handler_mutex); -rdma_destroy_id (&id_priv->id); +destroy_id_handler_unlock (id_priv);return ret; } out:mutex_unlock (&id_priv->handler_mutex); -return ret; +return 0 ; } static int cma_resolve_ib_udp(struct rdma_id_private *id_priv,
@@ -4359,9 +4354,7 @@ static int cma_ib_mc_handler(int status, struct ib_sa_multicast *multicast)rdma_destroy_ah_attr (&event.param.ud.ah_attr);if (ret) { -cma_exch (id_priv,RDMA_CM_DESTROYING ); -mutex_unlock (&id_priv->handler_mutex); -rdma_destroy_id (&id_priv->id); +destroy_id_handler_unlock (id_priv);return 0 ; }
@@ -4802,7 +4795,8 @@ static void cma_send_device_removal_put(struct rdma_id_private *id_priv)*/ cma_id_put (id_priv);mutex_unlock (&id_priv->handler_mutex); -rdma_destroy_id (&id_priv->id); +trace_cm_id_destroy (id_priv); +_destroy_id (id_priv, state);return ; }mutex_unlock (&id_priv->handler_mutex);
Signature · 1 line
2.26.2
Quoted 4 lines · Leon Romanovsky
This small series simplifies some of the RDMA CM state transitions connected with DESTROYING states and in the process resolves a bug discovered by syzkaller.
Quoted 8 lines
Thanks
Jason Gunthorpe (4):
RDMA/cma: SimplifyDEVICE_REMOVALforinternal_idRDMA/cma: Using the standard locking pattern when delivering the
removal event
RDMA/cma: Remove unneeded locking for req paths RDMA/cma: Executerdma_cmdestruction from a handler properly
Applied to for-next
Jason