RDMA/uverbs: Expand primary and alt AV port checks
authorJack Morgenstein <jackm@dev.mellanox.co.il>
Wed, 11 Jul 2018 08:23:52 +0000 (11:23 +0300)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 6 Aug 2018 14:20:51 +0000 (16:20 +0200)
commit addb8a6559f0f8b5a37582b7ca698358445a55bf upstream.

The commit cited below checked that the port numbers provided in the
primary and alt AVs are legal.

That is sufficient to prevent a kernel panic. However, it is not
sufficient for correct operation.

In Linux, AVs (both primary and alt) must be completely self-described.
We do not accept an AV from userspace without an embedded port number.
(This has been the case since kernel 3.14 commit dbf727de7440
("IB/core: Use GID table in AH creation and dmac resolution")).

For the primary AV, this embedded port number must match the port number
specified with IB_QP_PORT.

We also expect the port number embedded in the alt AV to match the
alt_port_num value passed by the userspace driver in the modify_qp command
base structure.

Add these checks to modify_qp.

Cc: <stable@vger.kernel.org> # 4.16
Fixes: 5d4c05c3ee36 ("RDMA/uverbs: Sanitize user entered port numbers prior to access it")
Signed-off-by: Jack Morgenstein <jackm@dev.mellanox.co.il>
Signed-off-by: Leon Romanovsky <leonro@mellanox.com>
Signed-off-by: Jason Gunthorpe <jgg@mellanox.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/infiniband/core/uverbs_cmd.c

index b8229d7b0ff52b8c993a0a3b69af218cd16a7b62..f836ed1dd300eb53a1e025bd29e293685482bea4 100644 (file)
@@ -1981,15 +1981,64 @@ static int modify_qp(struct ib_uverbs_file *file,
                goto release_qp;
        }
 
-       if ((cmd->base.attr_mask & IB_QP_AV) &&
-           !rdma_is_port_valid(qp->device, cmd->base.dest.port_num)) {
-               ret = -EINVAL;
-               goto release_qp;
+       if ((cmd->base.attr_mask & IB_QP_AV)) {
+               if (!rdma_is_port_valid(qp->device, cmd->base.dest.port_num)) {
+                       ret = -EINVAL;
+                       goto release_qp;
+               }
+
+               if (cmd->base.attr_mask & IB_QP_STATE &&
+                   cmd->base.qp_state == IB_QPS_RTR) {
+               /* We are in INIT->RTR TRANSITION (if we are not,
+                * this transition will be rejected in subsequent checks).
+                * In the INIT->RTR transition, we cannot have IB_QP_PORT set,
+                * but the IB_QP_STATE flag is required.
+                *
+                * Since kernel 3.14 (commit dbf727de7440), the uverbs driver,
+                * when IB_QP_AV is set, has required inclusion of a valid
+                * port number in the primary AV. (AVs are created and handled
+                * differently for infiniband and ethernet (RoCE) ports).
+                *
+                * Check the port number included in the primary AV against
+                * the port number in the qp struct, which was set (and saved)
+                * in the RST->INIT transition.
+                */
+                       if (cmd->base.dest.port_num != qp->real_qp->port) {
+                               ret = -EINVAL;
+                               goto release_qp;
+                       }
+               } else {
+               /* We are in SQD->SQD. (If we are not, this transition will
+                * be rejected later in the verbs layer checks).
+                * Check for both IB_QP_PORT and IB_QP_AV, these can be set
+                * together in the SQD->SQD transition.
+                *
+                * If only IP_QP_AV was set, add in IB_QP_PORT as well (the
+                * verbs layer driver does not track primary port changes
+                * resulting from path migration. Thus, in SQD, if the primary
+                * AV is modified, the primary port should also be modified).
+                *
+                * Note that in this transition, the IB_QP_STATE flag
+                * is not allowed.
+                */
+                       if (((cmd->base.attr_mask & (IB_QP_AV | IB_QP_PORT))
+                            == (IB_QP_AV | IB_QP_PORT)) &&
+                           cmd->base.port_num != cmd->base.dest.port_num) {
+                               ret = -EINVAL;
+                               goto release_qp;
+                       }
+                       if ((cmd->base.attr_mask & (IB_QP_AV | IB_QP_PORT))
+                           == IB_QP_AV) {
+                               cmd->base.attr_mask |= IB_QP_PORT;
+                               cmd->base.port_num = cmd->base.dest.port_num;
+                       }
+               }
        }
 
        if ((cmd->base.attr_mask & IB_QP_ALT_PATH) &&
            (!rdma_is_port_valid(qp->device, cmd->base.alt_port_num) ||
-           !rdma_is_port_valid(qp->device, cmd->base.alt_dest.port_num))) {
+           !rdma_is_port_valid(qp->device, cmd->base.alt_dest.port_num) ||
+           cmd->base.alt_port_num != cmd->base.alt_dest.port_num)) {
                ret = -EINVAL;
                goto release_qp;
        }