caif-hsi: Added sanity check for length of CAIF frames
authorDaniel Martensson <daniel.martensson@stericsson.com>
Thu, 13 Oct 2011 11:29:28 +0000 (11:29 +0000)
committerDavid S. Miller <davem@davemloft.net>
Wed, 19 Oct 2011 07:25:42 +0000 (03:25 -0400)
Added sanity check for length of CAIF frames, and tear down of
CAIF link-layer device upon protocol error.

Signed-off-by: Sjur Brændeland <sjur.brandeland@stericsson.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/caif/caif_hsi.c
include/net/caif/caif_hsi.h

index 1e1f0a372ffaa38ec85f1e6e1c94b63550a0b866..e9e7cbf01a5f56207d38b40079d26f9f5545ccac 100644 (file)
@@ -18,6 +18,7 @@
 #include <linux/sched.h>
 #include <linux/if_arp.h>
 #include <linux/timer.h>
+#include <linux/rtnetlink.h>
 #include <net/caif/caif_layer.h>
 #include <net/caif/caif_hsi.h>
 
@@ -348,8 +349,7 @@ static void cfhsi_tx_done_cb(struct cfhsi_drv *drv)
        cfhsi_tx_done(cfhsi);
 }
 
-static int cfhsi_rx_desc(struct cfhsi_desc *desc, struct cfhsi *cfhsi,
-                               bool *dump)
+static int cfhsi_rx_desc(struct cfhsi_desc *desc, struct cfhsi *cfhsi)
 {
        int xfer_sz = 0;
        int nfrms = 0;
@@ -360,8 +360,7 @@ static int cfhsi_rx_desc(struct cfhsi_desc *desc, struct cfhsi *cfhsi,
                        (desc->offset > CFHSI_MAX_EMB_FRM_SZ)) {
                dev_err(&cfhsi->ndev->dev, "%s: Invalid descriptor.\n",
                        __func__);
-               *dump = true;
-               return 0;
+               return -EPROTO;
        }
 
        /* Check for embedded CAIF frame. */
@@ -379,6 +378,12 @@ static int cfhsi_rx_desc(struct cfhsi_desc *desc, struct cfhsi *cfhsi,
                len |= ((*(pfrm+1)) << 8) & 0xFF00;
                len += 2;       /* Add FCS fields. */
 
+               /* Sanity check length of CAIF frame. */
+               if (unlikely(len > CFHSI_MAX_CAIF_FRAME_SZ)) {
+                       dev_err(&cfhsi->ndev->dev, "%s: Invalid length.\n",
+                               __func__);
+                       return -EPROTO;
+               }
 
                /* Allocate SKB (OK even in IRQ context). */
                skb = alloc_skb(len + 1, GFP_ATOMIC);
@@ -423,18 +428,16 @@ static int cfhsi_rx_desc(struct cfhsi_desc *desc, struct cfhsi *cfhsi,
        if (desc->header & CFHSI_PIGGY_DESC)
                xfer_sz += CFHSI_DESC_SZ;
 
-       if (xfer_sz % 4) {
+       if ((xfer_sz % 4) || (xfer_sz > (CFHSI_BUF_SZ_RX - CFHSI_DESC_SZ))) {
                dev_err(&cfhsi->ndev->dev,
                                "%s: Invalid payload len: %d, ignored.\n",
                        __func__, xfer_sz);
-               xfer_sz = 0;
-               *dump = true;
+               return -EPROTO;
        }
        return xfer_sz;
 }
 
-static int cfhsi_rx_pld(struct cfhsi_desc *desc, struct cfhsi *cfhsi,
-                               bool *dump)
+static int cfhsi_rx_pld(struct cfhsi_desc *desc, struct cfhsi *cfhsi)
 {
        int rx_sz = 0;
        int nfrms = 0;
@@ -446,8 +449,7 @@ static int cfhsi_rx_pld(struct cfhsi_desc *desc, struct cfhsi *cfhsi,
                        (desc->offset > CFHSI_MAX_EMB_FRM_SZ))) {
                dev_err(&cfhsi->ndev->dev, "%s: Invalid descriptor.\n",
                        __func__);
-               *dump = true;
-               return -EINVAL;
+               return -EPROTO;
        }
 
        /* Set frame pointer to start of payload. */
@@ -469,13 +471,6 @@ static int cfhsi_rx_pld(struct cfhsi_desc *desc, struct cfhsi *cfhsi,
                u8 *pcffrm = NULL;
                int len = 0;
 
-               if (WARN_ON(desc->cffrm_len[nfrms] > CFHSI_MAX_PAYLOAD_SZ)) {
-                       dev_err(&cfhsi->ndev->dev, "%s: Invalid payload.\n",
-                               __func__);
-                       *dump = true;
-                       return -EINVAL;
-               }
-
                /* CAIF frame starts after head padding. */
                pcffrm = pfrm + *pfrm + 1;
 
@@ -484,6 +479,13 @@ static int cfhsi_rx_pld(struct cfhsi_desc *desc, struct cfhsi *cfhsi,
                len |= ((*(pcffrm + 1)) << 8) & 0xFF00;
                len += 2;       /* Add FCS fields. */
 
+               /* Sanity check length of CAIF frames. */
+               if (unlikely(len > CFHSI_MAX_CAIF_FRAME_SZ)) {
+                       dev_err(&cfhsi->ndev->dev, "%s: Invalid length.\n",
+                               __func__);
+                       return -EPROTO;
+               }
+
                /* Allocate SKB (OK even in IRQ context). */
                skb = alloc_skb(len + 1, GFP_ATOMIC);
                if (!skb) {
@@ -528,7 +530,6 @@ static void cfhsi_rx_done(struct cfhsi *cfhsi)
        int res;
        int desc_pld_len = 0;
        struct cfhsi_desc *desc = NULL;
-       bool dump = false;
 
        desc = (struct cfhsi_desc *)cfhsi->rx_buf;
 
@@ -544,16 +545,20 @@ static void cfhsi_rx_done(struct cfhsi *cfhsi)
        spin_unlock_bh(&cfhsi->lock);
 
        if (cfhsi->rx_state.state == CFHSI_RX_STATE_DESC) {
-               desc_pld_len = cfhsi_rx_desc(desc, cfhsi, &dump);
+               desc_pld_len = cfhsi_rx_desc(desc, cfhsi);
                if (desc_pld_len == -ENOMEM)
                        goto restart;
+               if (desc_pld_len == -EPROTO)
+                       goto out_of_sync;
        } else {
                int pld_len;
 
                if (!cfhsi->rx_state.piggy_desc) {
-                       pld_len = cfhsi_rx_pld(desc, cfhsi, &dump);
+                       pld_len = cfhsi_rx_pld(desc, cfhsi);
                        if (pld_len == -ENOMEM)
                                goto restart;
+                       if (pld_len == -EPROTO)
+                               goto out_of_sync;
                        cfhsi->rx_state.pld_len = pld_len;
                } else {
                        pld_len = cfhsi->rx_state.pld_len;
@@ -567,7 +572,7 @@ static void cfhsi_rx_done(struct cfhsi *cfhsi)
                        cfhsi->rx_state.piggy_desc = true;
 
                        /* Extract piggy-backed descriptor. */
-                       desc_pld_len = cfhsi_rx_desc(piggy_desc, cfhsi, &dump);
+                       desc_pld_len = cfhsi_rx_desc(piggy_desc, cfhsi);
                        if (desc_pld_len == -ENOMEM)
                                goto restart;
 
@@ -577,15 +582,10 @@ static void cfhsi_rx_done(struct cfhsi *cfhsi)
                         */
                        memcpy((u8 *)desc, (u8 *)piggy_desc,
                                        CFHSI_DESC_SHORT_SZ);
-               }
-       }
 
-       if (unlikely(dump)) {
-               size_t rx_offset = cfhsi->rx_ptr - cfhsi->rx_buf;
-               dev_err(&cfhsi->ndev->dev, "%s: RX offset: %u.\n",
-                       __func__, (unsigned) rx_offset);
-               print_hex_dump_bytes("--> ", DUMP_PREFIX_NONE,
-                               cfhsi->rx_buf, cfhsi->rx_len + rx_offset);
+                       if (desc_pld_len == -EPROTO)
+                               goto out_of_sync;
+               }
        }
 
        memset(&cfhsi->rx_state, 0, sizeof(cfhsi->rx_state));
@@ -622,6 +622,13 @@ restart:
                BUG();
        }
        mod_timer(&cfhsi->rx_slowpath_timer, jiffies + 1);
+       return;
+
+out_of_sync:
+       dev_err(&cfhsi->ndev->dev, "%s: Out of sync.\n", __func__);
+       print_hex_dump_bytes("--> ", DUMP_PREFIX_NONE,
+                       cfhsi->rx_buf, CFHSI_DESC_SZ);
+       schedule_work(&cfhsi->out_of_sync_work);
 }
 
 static void cfhsi_rx_slowpath(unsigned long arg)
@@ -804,6 +811,17 @@ static void cfhsi_wake_down(struct work_struct *work)
 
 }
 
+static void cfhsi_out_of_sync(struct work_struct *work)
+{
+       struct cfhsi *cfhsi = NULL;
+
+       cfhsi = container_of(work, struct cfhsi, out_of_sync_work);
+
+       rtnl_lock();
+       dev_close(cfhsi->ndev);
+       rtnl_unlock();
+}
+
 static void cfhsi_wake_up_cb(struct cfhsi_drv *drv)
 {
        struct cfhsi *cfhsi = NULL;
@@ -1023,6 +1041,7 @@ int cfhsi_probe(struct platform_device *pdev)
        /* Initialize the work queues. */
        INIT_WORK(&cfhsi->wake_up_work, cfhsi_wake_up);
        INIT_WORK(&cfhsi->wake_down_work, cfhsi_wake_down);
+       INIT_WORK(&cfhsi->out_of_sync_work, cfhsi_out_of_sync);
 
        /* Clear all bit fields. */
        clear_bit(CFHSI_WAKE_UP_ACK, &cfhsi->bits);
index 9b69d153c94afc73afd69de4ddc4f1c42606d1a9..3356769afaee628a8885faaacc49734f9b6a816e 100644 (file)
@@ -52,8 +52,9 @@ struct cfhsi_desc {
 /*
  * Maximum bytes transferred in one transfer.
  */
-/* TODO: 4096 is temporary... */
-#define CFHSI_MAX_PAYLOAD_SZ (CFHSI_MAX_PKTS * 4096)
+#define CFHSI_MAX_CAIF_FRAME_SZ 4096
+
+#define CFHSI_MAX_PAYLOAD_SZ (CFHSI_MAX_PKTS * CFHSI_MAX_CAIF_FRAME_SZ)
 
 /* Size of the complete HSI TX buffer. */
 #define CFHSI_BUF_SZ_TX (CFHSI_DESC_SZ + CFHSI_MAX_PAYLOAD_SZ)
@@ -143,6 +144,7 @@ struct cfhsi {
        struct list_head list;
        struct work_struct wake_up_work;
        struct work_struct wake_down_work;
+       struct work_struct out_of_sync_work;
        struct workqueue_struct *wq;
        wait_queue_head_t wake_up_wait;
        wait_queue_head_t wake_down_wait;