From: Roland Dreier <roland@xxxxxxxxxxxxxxx> There is a race (which userspace can trigger through ucma) that leads to use-after free in cma: rdma_bind_addr(id, bogus address) cma_comp_exch(RDMA_CM_IDLE, RDMA_CM_ADDR_BOUND) [succeed] copy address into id_priv struct fail to find device for address rdma_listen(id, any address); id_priv state isn't RDMA_CM_IDLE cma_comp_exch(RDMA_CM_ADDR_BOUND, RDMA_CM_LISTEN) [succeed] id->device not set, call cma_listen_on_all() add id_priv->list to listen_any_list return success cma_comp_exch(RDMA_CM_ADDR_BOUND, RDMA_CM_IDLE) [fail] return failure Now, when the id is destroyed, cma_release_dev() won't be called because id_priv->cma_dev isn't set. And cma_cancel_operation() won't call cma_cancel_listens() because cma_src_addr(id_priv) is the bogus address passed into rdma_bind_addr(). So neither of the paths that does list_del(&id_priv->list); will be followed, but the code will go ahead and kfree(id_priv) even though it is still linked into the listen_any_list. So we end up with use-after-free when listen_any_list is traversed. We can close this race by having rdma_bind_addr() put the CM ID into an intermediate "binding" state during the time that we are modifying the state but don't know whether the bind operation will succeed yet. Reported-and-tested-by: syzbot+db1c219466daac1083df@xxxxxxxxxxxxxxxxxxxxxxxxx Reported-by: Eric Biggers <ebiggers3@xxxxxxxxx> Signed-off-by: Roland Dreier <roland@xxxxxxxxxxxxxxx> --- drivers/infiniband/core/cma.c | 6 ++++-- drivers/infiniband/core/cma_priv.h | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/infiniband/core/cma.c b/drivers/infiniband/core/cma.c index a693fcd4c513..826b4ffbf259 100644 --- a/drivers/infiniband/core/cma.c +++ b/drivers/infiniband/core/cma.c @@ -3345,7 +3345,7 @@ int rdma_bind_addr(struct rdma_cm_id *id, struct sockaddr *addr) return -EAFNOSUPPORT; id_priv = container_of(id, struct rdma_id_private, id); - if (!cma_comp_exch(id_priv, RDMA_CM_IDLE, RDMA_CM_ADDR_BOUND)) + if (!cma_comp_exch(id_priv, RDMA_CM_IDLE, RDMA_CM_ADDR_BINDING)) return -EINVAL; ret = cma_check_linklocal(&id->route.addr.dev_addr, addr); @@ -3381,6 +3381,8 @@ int rdma_bind_addr(struct rdma_cm_id *id, struct sockaddr *addr) if (ret) goto err2; + cma_comp_exch(id_priv, RDMA_CM_ADDR_BINDING, RDMA_CM_ADDR_BOUND); + return 0; err2: if (id_priv->cma_dev) { @@ -3388,7 +3390,7 @@ int rdma_bind_addr(struct rdma_cm_id *id, struct sockaddr *addr) cma_release_dev(id_priv); } err1: - cma_comp_exch(id_priv, RDMA_CM_ADDR_BOUND, RDMA_CM_IDLE); + cma_comp_exch(id_priv, RDMA_CM_ADDR_BINDING, RDMA_CM_IDLE); return ret; } EXPORT_SYMBOL(rdma_bind_addr); diff --git a/drivers/infiniband/core/cma_priv.h b/drivers/infiniband/core/cma_priv.h index 194cfe78c447..8d0f8715dd51 100644 --- a/drivers/infiniband/core/cma_priv.h +++ b/drivers/infiniband/core/cma_priv.h @@ -44,6 +44,7 @@ enum rdma_cm_state { RDMA_CM_ROUTE_RESOLVED, RDMA_CM_CONNECT, RDMA_CM_DISCONNECT, + RDMA_CM_ADDR_BINDING, RDMA_CM_ADDR_BOUND, RDMA_CM_LISTEN, RDMA_CM_DEVICE_REMOVAL, -- 2.17.0 -- To unsubscribe from this list: send the line "unsubscribe linux-rdma" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html