[PATCH] RDMA/cma: Avoid using invalid state during rdma_bind_addr()

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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



[Index of Archives]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Photo]     [Yosemite News]     [Yosemite Photos]     [Linux Kernel]     [Linux SCSI]     [XFree86]

  Powered by Linux