]> git.baikalelectronics.ru Git - kernel.git/commitdiff
IB/ipoib: Fix double free of skb in case of multicast traffic in CM mode
authorValentine Fatiev <valentinef@mellanox.com>
Wed, 27 May 2020 13:47:05 +0000 (16:47 +0300)
committerJason Gunthorpe <jgg@mellanox.com>
Thu, 28 May 2020 00:14:09 +0000 (21:14 -0300)
When connected mode is set, and we have connected and datagram traffic in
parallel, ipoib might crash with double free of datagram skb.

The current mechanism assumes that the order in the completion queue is
the same as the order of sent packets for all QPs. Order is kept only for
specific QP, in case of mixed UD and CM traffic we have few QPs (one UD and
few CM's) in parallel.

The problem:
----------------------------------------------------------

Transmit queue:
-----------------
UD skb pointer kept in queue itself, CM skb kept in spearate queue and
uses transmit queue as a placeholder to count the number of total
transmitted packets.

0   1   2   3   4  5  6  7  8   9  10  11 12 13 .........127
------------------------------------------------------------
NL ud1 UD2 CM1 ud3 cm2 cm3 ud4 cm4 ud5 NL NL NL ...........
------------------------------------------------------------
    ^                                  ^
   tail                               head

Completion queue (problematic scenario) - the order not the same as in
the transmit queue:

  1  2  3  4  5  6  7  8  9
------------------------------------
 ud1 CM1 UD2 ud3 cm2 cm3 ud4 cm4 ud5
------------------------------------

1. CM1 'wc' processing
   - skb freed in cm separate ring.
   - tx_tail of transmit queue increased although UD2 is not freed.
     Now driver assumes UD2 index is already freed and it could be used for
     new transmitted skb.

0   1   2   3   4  5  6  7  8   9  10  11 12 13 .........127
------------------------------------------------------------
NL NL  UD2 CM1 ud3 cm2 cm3 ud4 cm4 ud5 NL NL NL ...........
------------------------------------------------------------
        ^   ^                       ^
      (Bad)tail                    head
(Bad - Could be used for new SKB)

In this case (due to heavy load) UD2 skb pointer could be replaced by new
transmitted packet UD_NEW, as the driver assumes its free.  At this point
we will have to process two 'wc' with same index but we have only one
pointer to free.

During second attempt to free the same skb we will have NULL pointer
exception.

2. UD2 'wc' processing
   - skb freed according the index we got from 'wc', but it was already
     overwritten by mistake. So actually the skb that was released is the
     skb of the new transmitted packet and not the original one.

3. UD_NEW 'wc' processing
   - attempt to free already freed skb. NUll pointer exception.

The fix:
-----------------------------------------------------------------------

The fix is to stop using the UD ring as a placeholder for CM packets, the
cyclic ring variables tx_head and tx_tail will manage the UD tx_ring, a
new cyclic variables global_tx_head and global_tx_tail are introduced for
managing and counting the overall outstanding sent packets, then the send
queue will be stopped and waken based on these variables only.

Note that no locking is needed since global_tx_head is updated in the xmit
flow and global_tx_tail is updated in the NAPI flow only.  A previous
attempt tried to use one variable to count the outstanding sent packets,
but it did not work since xmit and NAPI flows can run at the same time and
the counter will be updated wrongly. Thus, we use the same simple cyclic
head and tail scheme that we have today for the UD tx_ring.

Fixes: e9648bc89204 ("IB/ipoib: Get rid of the tx_outstanding variable in all modes")
Link: https://lore.kernel.org/r/20200527134705.480068-1-leon@kernel.org
Signed-off-by: Valentine Fatiev <valentinef@mellanox.com>
Signed-off-by: Alaa Hleihel <alaa@mellanox.com>
Signed-off-by: Leon Romanovsky <leonro@mellanox.com>
Acked-by: Doug Ledford <dledford@redhat.com>
Signed-off-by: Jason Gunthorpe <jgg@mellanox.com>
drivers/infiniband/ulp/ipoib/ipoib.h
drivers/infiniband/ulp/ipoib/ipoib_cm.c
drivers/infiniband/ulp/ipoib/ipoib_ib.c
drivers/infiniband/ulp/ipoib/ipoib_main.c

index e188a95984b5c3c38800dc8b061494be874758c8..9a3379c49541fbe51a08e49db7757cc170bdd1bf 100644 (file)
@@ -377,8 +377,12 @@ struct ipoib_dev_priv {
        struct ipoib_rx_buf *rx_ring;
 
        struct ipoib_tx_buf *tx_ring;
+       /* cyclic ring variables for managing tx_ring, for UD only */
        unsigned int         tx_head;
        unsigned int         tx_tail;
+       /* cyclic ring variables for counting overall outstanding send WRs */
+       unsigned int         global_tx_head;
+       unsigned int         global_tx_tail;
        struct ib_sge        tx_sge[MAX_SKB_FRAGS + 1];
        struct ib_ud_wr      tx_wr;
        struct ib_wc         send_wc[MAX_SEND_CQE];
index c59e00a0881f19e6efae40145332743de8407ca7..9bf0fa30df28c9f76480bfcdf2b20846fe409864 100644 (file)
@@ -756,7 +756,8 @@ void ipoib_cm_send(struct net_device *dev, struct sk_buff *skb, struct ipoib_cm_
                return;
        }
 
-       if ((priv->tx_head - priv->tx_tail) == ipoib_sendq_size - 1) {
+       if ((priv->global_tx_head - priv->global_tx_tail) ==
+           ipoib_sendq_size - 1) {
                ipoib_dbg(priv, "TX ring 0x%x full, stopping kernel net queue\n",
                          tx->qp->qp_num);
                netif_stop_queue(dev);
@@ -786,7 +787,7 @@ void ipoib_cm_send(struct net_device *dev, struct sk_buff *skb, struct ipoib_cm_
        } else {
                netif_trans_update(dev);
                ++tx->tx_head;
-               ++priv->tx_head;
+               ++priv->global_tx_head;
        }
 }
 
@@ -820,10 +821,11 @@ void ipoib_cm_handle_tx_wc(struct net_device *dev, struct ib_wc *wc)
        netif_tx_lock(dev);
 
        ++tx->tx_tail;
-       ++priv->tx_tail;
+       ++priv->global_tx_tail;
 
        if (unlikely(netif_queue_stopped(dev) &&
-                    (priv->tx_head - priv->tx_tail) <= ipoib_sendq_size >> 1 &&
+                    ((priv->global_tx_head - priv->global_tx_tail) <=
+                     ipoib_sendq_size >> 1) &&
                     test_bit(IPOIB_FLAG_ADMIN_UP, &priv->flags)))
                netif_wake_queue(dev);
 
@@ -1232,8 +1234,9 @@ timeout:
                dev_kfree_skb_any(tx_req->skb);
                netif_tx_lock_bh(p->dev);
                ++p->tx_tail;
-               ++priv->tx_tail;
-               if (unlikely(priv->tx_head - priv->tx_tail == ipoib_sendq_size >> 1) &&
+               ++priv->global_tx_tail;
+               if (unlikely((priv->global_tx_head - priv->global_tx_tail) <=
+                            ipoib_sendq_size >> 1) &&
                    netif_queue_stopped(p->dev) &&
                    test_bit(IPOIB_FLAG_ADMIN_UP, &priv->flags))
                        netif_wake_queue(p->dev);
index c332b47618160327966b493a7493e7006833d345..da3c5315bbb515649c7f265fe263f38cca5f9d6e 100644 (file)
@@ -407,9 +407,11 @@ static void ipoib_ib_handle_tx_wc(struct net_device *dev, struct ib_wc *wc)
        dev_kfree_skb_any(tx_req->skb);
 
        ++priv->tx_tail;
+       ++priv->global_tx_tail;
 
        if (unlikely(netif_queue_stopped(dev) &&
-                    ((priv->tx_head - priv->tx_tail) <= ipoib_sendq_size >> 1) &&
+                    ((priv->global_tx_head - priv->global_tx_tail) <=
+                     ipoib_sendq_size >> 1) &&
                     test_bit(IPOIB_FLAG_ADMIN_UP, &priv->flags)))
                netif_wake_queue(dev);
 
@@ -634,7 +636,8 @@ int ipoib_send(struct net_device *dev, struct sk_buff *skb,
        else
                priv->tx_wr.wr.send_flags &= ~IB_SEND_IP_CSUM;
        /* increase the tx_head after send success, but use it for queue state */
-       if (priv->tx_head - priv->tx_tail == ipoib_sendq_size - 1) {
+       if ((priv->global_tx_head - priv->global_tx_tail) ==
+           ipoib_sendq_size - 1) {
                ipoib_dbg(priv, "TX ring full, stopping kernel net queue\n");
                netif_stop_queue(dev);
        }
@@ -662,6 +665,7 @@ int ipoib_send(struct net_device *dev, struct sk_buff *skb,
 
                rc = priv->tx_head;
                ++priv->tx_head;
+               ++priv->global_tx_head;
        }
        return rc;
 }
@@ -807,6 +811,7 @@ int ipoib_ib_dev_stop_default(struct net_device *dev)
                                ipoib_dma_unmap_tx(priv, tx_req);
                                dev_kfree_skb_any(tx_req->skb);
                                ++priv->tx_tail;
+                               ++priv->global_tx_tail;
                        }
 
                        for (i = 0; i < ipoib_recvq_size; ++i) {
index 81b8227214f1cf11ee622d16d3876e0eae7ed4d9..ceec24d451858adaac8c8503c5ccccb79c6a373c 100644 (file)
@@ -1184,9 +1184,11 @@ static void ipoib_timeout(struct net_device *dev, unsigned int txqueue)
 
        ipoib_warn(priv, "transmit timeout: latency %d msecs\n",
                   jiffies_to_msecs(jiffies - dev_trans_start(dev)));
-       ipoib_warn(priv, "queue stopped %d, tx_head %u, tx_tail %u\n",
-                  netif_queue_stopped(dev),
-                  priv->tx_head, priv->tx_tail);
+       ipoib_warn(priv,
+                  "queue stopped %d, tx_head %u, tx_tail %u, global_tx_head %u, global_tx_tail %u\n",
+                  netif_queue_stopped(dev), priv->tx_head, priv->tx_tail,
+                  priv->global_tx_head, priv->global_tx_tail);
+
        /* XXX reset QP, etc. */
 }
 
@@ -1701,7 +1703,7 @@ static int ipoib_dev_init_default(struct net_device *dev)
                goto out_rx_ring_cleanup;
        }
 
-       /* priv->tx_head, tx_tail & tx_outstanding are already 0 */
+       /* priv->tx_head, tx_tail and global_tx_tail/head are already 0 */
 
        if (ipoib_transport_dev_init(dev, priv->ca)) {
                pr_warn("%s: ipoib_transport_dev_init failed\n",