Author: sephe
Date: Tue Sep  6 03:20:06 2016
New Revision: 305453
URL: https://svnweb.freebsd.org/changeset/base/305453

Log:
  hyperv/hn: Stringent RNDIS packet message length/offset check.
  
  While I'm here, use definition in net/rndis.h
  
  MFC after:    1 week
  Sponsored by: Microsoft
  Differential Revision:        https://reviews.freebsd.org/D7782

Modified:
  head/sys/dev/hyperv/netvsc/hv_rndis_filter.c
  head/sys/net/rndis.h

Modified: head/sys/dev/hyperv/netvsc/hv_rndis_filter.c
==============================================================================
--- head/sys/dev/hyperv/netvsc/hv_rndis_filter.c        Tue Sep  6 01:10:51 
2016        (r305452)
+++ head/sys/dev/hyperv/netvsc/hv_rndis_filter.c        Tue Sep  6 03:20:06 
2016        (r305453)
@@ -159,39 +159,22 @@ hv_rf_receive_indicate_status(struct hn_
 }
 
 static int
-hv_rf_find_recvinfo(const rndis_packet *rpkt, struct hn_recvinfo *info)
+hn_rndis_rxinfo(const void *info_data, int info_dlen, struct hn_recvinfo *info)
 {
-       const struct rndis_pktinfo *pi;
-       uint32_t mask = 0, len;
+       const struct rndis_pktinfo *pi = info_data;
+       uint32_t mask = 0;
 
-       info->vlan_info = HN_NDIS_VLAN_INFO_INVALID;
-       info->csum_info = HN_NDIS_RXCSUM_INFO_INVALID;
-       info->hash_info = HN_NDIS_HASH_INFO_INVALID;
-
-       if (rpkt->per_pkt_info_offset == 0)
-               return (0);
-       if (__predict_false(rpkt->per_pkt_info_offset &
-           (RNDIS_PKTINFO_ALIGN - 1)))
-               return (EINVAL);
-       if (__predict_false(rpkt->per_pkt_info_offset <
-           RNDIS_PACKET_MSG_OFFSET_MIN))
-               return (EINVAL);
-
-       pi = (const struct rndis_pktinfo *)
-           ((const uint8_t *)rpkt + rpkt->per_pkt_info_offset);
-       len = rpkt->per_pkt_info_length;
-
-       while (len != 0) {
+       while (info_dlen != 0) {
                const void *data;
                uint32_t dlen;
 
-               if (__predict_false(len < sizeof(*pi)))
+               if (__predict_false(info_dlen < sizeof(*pi)))
                        return (EINVAL);
-               if (__predict_false(len < pi->rm_size))
+               if (__predict_false(info_dlen < pi->rm_size))
                        return (EINVAL);
-               len -= pi->rm_size;
+               info_dlen -= pi->rm_size;
 
-               if (__predict_false(pi->rm_size & (RNDIS_PKTINFO_ALIGN - 1)))
+               if (__predict_false(pi->rm_size & RNDIS_PKTINFO_SIZE_ALIGNMASK))
                        return (EINVAL);
                if (__predict_false(pi->rm_size < pi->rm_pktinfooffset))
                        return (EINVAL);
@@ -249,43 +232,183 @@ next:
        return (0);
 }
 
+static __inline bool
+hn_rndis_check_overlap(int off, int len, int check_off, int check_len)
+{
+
+       if (off < check_off) {
+               if (__predict_true(off + len <= check_off))
+                       return (false);
+       } else if (off > check_off) {
+               if (__predict_true(check_off + check_len <= off))
+                       return (false);
+       }
+       return (true);
+}
+
 /*
  * RNDIS filter receive data
  */
 static void
 hv_rf_receive_data(struct hn_rx_ring *rxr, const void *data, int dlen)
 {
-       const rndis_msg *message = data;
-       const rndis_packet *rndis_pkt;
-       uint32_t data_offset;
+       const struct rndis_packet_msg *pkt;
        struct hn_recvinfo info;
-
-       rndis_pkt = &message->msg.packet;
+       int data_off, pktinfo_off, data_len, pktinfo_len;
 
        /*
-        * Fixme:  Handle multiple rndis pkt msgs that may be enclosed in this
-        * netvsc packet (ie tot_data_buf_len != message_length)
+        * Check length.
         */
+       if (__predict_false(dlen < sizeof(*pkt))) {
+               if_printf(rxr->hn_ifp, "invalid RNDIS packet msg\n");
+               return;
+       }
+       pkt = data;
 
-       /* Remove rndis header, then pass data packet up the stack */
-       data_offset = RNDIS_HEADER_SIZE + rndis_pkt->data_offset;
+       if (__predict_false(dlen < pkt->rm_len)) {
+               if_printf(rxr->hn_ifp, "truncated RNDIS packet msg, "
+                   "dlen %d, msglen %u\n", dlen, pkt->rm_len);
+               return;
+       }
+       if (__predict_false(pkt->rm_len <
+           pkt->rm_datalen + pkt->rm_oobdatalen + pkt->rm_pktinfolen)) {
+               if_printf(rxr->hn_ifp, "invalid RNDIS packet msglen, "
+                   "msglen %u, data %u, oob %u, pktinfo %u\n",
+                   pkt->rm_len, pkt->rm_datalen, pkt->rm_oobdatalen,
+                   pkt->rm_pktinfolen);
+               return;
+       }
+       if (__predict_false(pkt->rm_datalen == 0)) {
+               if_printf(rxr->hn_ifp, "invalid RNDIS packet msg, no data\n");
+               return;
+       }
 
-       dlen -= data_offset;
-       if (dlen < rndis_pkt->data_length) {
-               if_printf(rxr->hn_ifp,
-                   "total length %u is less than data length %u\n",
-                   dlen, rndis_pkt->data_length);
+       /*
+        * Check offests.
+        */
+#define IS_OFFSET_INVALID(ofs)                 \
+       ((ofs) < RNDIS_PACKET_MSG_OFFSET_MIN || \
+        ((ofs) & RNDIS_PACKET_MSG_OFFSET_ALIGNMASK))
+
+       /* XXX Hyper-V does not meet data offset alignment requirement */
+       if (__predict_false(pkt->rm_dataoffset < RNDIS_PACKET_MSG_OFFSET_MIN)) {
+               if_printf(rxr->hn_ifp, "invalid RNDIS packet msg, "
+                   "data offset %u\n", pkt->rm_dataoffset);
                return;
        }
+       if (__predict_false(pkt->rm_oobdataoffset > 0 &&
+           IS_OFFSET_INVALID(pkt->rm_oobdataoffset))) {
+               if_printf(rxr->hn_ifp, "invalid RNDIS packet msg, "
+                   "oob offset %u\n", pkt->rm_oobdataoffset);
+               return;
+       }
+       if (__predict_true(pkt->rm_pktinfooffset > 0) &&
+           __predict_false(IS_OFFSET_INVALID(pkt->rm_pktinfooffset))) {
+               if_printf(rxr->hn_ifp, "invalid RNDIS packet msg, "
+                   "pktinfo offset %u\n", pkt->rm_pktinfooffset);
+               return;
+       }
+
+#undef IS_OFFSET_INVALID
+
+       data_off = RNDIS_PACKET_MSG_OFFSET_ABS(pkt->rm_dataoffset);
+       data_len = pkt->rm_datalen;
+       pktinfo_off = RNDIS_PACKET_MSG_OFFSET_ABS(pkt->rm_pktinfooffset);
+       pktinfo_len = pkt->rm_pktinfolen;
+
+       /*
+        * Check OOB coverage.
+        */
+       if (__predict_false(pkt->rm_oobdatalen != 0)) {
+               int oob_off, oob_len;
+
+               if_printf(rxr->hn_ifp, "got oobdata\n");
+               oob_off = RNDIS_PACKET_MSG_OFFSET_ABS(pkt->rm_oobdataoffset);
+               oob_len = pkt->rm_oobdatalen;
+
+               if (__predict_false(oob_off + oob_len > pkt->rm_len)) {
+                       if_printf(rxr->hn_ifp, "invalid RNDIS packet msg, "
+                           "oob overflow, msglen %u, oob abs %d len %d\n",
+                           pkt->rm_len, oob_off, oob_len);
+                       return;
+               }
+
+               /*
+                * Check against data.
+                */
+               if (hn_rndis_check_overlap(oob_off, oob_len,
+                   data_off, data_len)) {
+                       if_printf(rxr->hn_ifp, "invalid RNDIS packet msg, "
+                           "oob overlaps data, oob abs %d len %d, "
+                           "data abs %d len %d\n",
+                           oob_off, oob_len, data_off, data_len);
+                       return;
+               }
+
+               /*
+                * Check against pktinfo.
+                */
+               if (pktinfo_len != 0 &&
+                   hn_rndis_check_overlap(oob_off, oob_len,
+                   pktinfo_off, pktinfo_len)) {
+                       if_printf(rxr->hn_ifp, "invalid RNDIS packet msg, "
+                           "oob overlaps pktinfo, oob abs %d len %d, "
+                           "pktinfo abs %d len %d\n",
+                           oob_off, oob_len, pktinfo_off, pktinfo_len);
+                       return;
+               }
+       }
 
-       dlen = rndis_pkt->data_length;
-       data = (const uint8_t *)data + data_offset;
+       /*
+        * Check per-packet-info coverage and find useful per-packet-info.
+        */
+       info.vlan_info = HN_NDIS_VLAN_INFO_INVALID;
+       info.csum_info = HN_NDIS_RXCSUM_INFO_INVALID;
+       info.hash_info = HN_NDIS_HASH_INFO_INVALID;
+       if (__predict_true(pktinfo_len != 0)) {
+               bool overlap;
+               int error;
+
+               if (__predict_false(pktinfo_off + pktinfo_len > pkt->rm_len)) {
+                       if_printf(rxr->hn_ifp, "invalid RNDIS packet msg, "
+                           "pktinfo overflow, msglen %u, "
+                           "pktinfo abs %d len %d\n",
+                           pkt->rm_len, pktinfo_off, pktinfo_len);
+                       return;
+               }
+
+               /*
+                * Check packet info coverage.
+                */
+               overlap = hn_rndis_check_overlap(pktinfo_off, pktinfo_len,
+                   data_off, data_len);
+               if (__predict_false(overlap)) {
+                       if_printf(rxr->hn_ifp, "invalid RNDIS packet msg, "
+                           "pktinfo overlap data, pktinfo abs %d len %d, "
+                           "data abs %d len %d\n",
+                           pktinfo_off, pktinfo_len, data_off, data_len);
+                       return;
+               }
+
+               /*
+                * Find useful per-packet-info.
+                */
+               error = hn_rndis_rxinfo(((const uint8_t *)pkt) + pktinfo_off,
+                   pktinfo_len, &info);
+               if (__predict_false(error)) {
+                       if_printf(rxr->hn_ifp, "invalid RNDIS packet msg "
+                           "pktinfo\n");
+                       return;
+               }
+       }
 
-       if (hv_rf_find_recvinfo(rndis_pkt, &info)) {
-               if_printf(rxr->hn_ifp, "recvinfo parsing failed\n");
+       if (__predict_false(data_off + data_len > pkt->rm_len)) {
+               if_printf(rxr->hn_ifp, "invalid RNDIS packet msg, "
+                   "data overflow, msglen %u, data abs %d len %d\n",
+                   pkt->rm_len, data_off, data_len);
                return;
        }
-       netvsc_recv(rxr, data, dlen, &info);
+       netvsc_recv(rxr, ((const uint8_t *)pkt) + data_off, data_len, &info);
 }
 
 /*
@@ -565,7 +688,7 @@ hn_rndis_query(struct hn_softc *sc, uint
         * Check output data length and offset.
         */
        /* ofs is the offset from the beginning of comp. */
-       ofs = RNDIS_QUERY_COMP_INFOBUFABS(comp->rm_infobufoffset);
+       ofs = RNDIS_QUERY_COMP_INFOBUFOFFSET_ABS(comp->rm_infobufoffset);
        if (ofs < sizeof(*comp) || ofs + comp->rm_infobuflen > comp_len) {
                if_printf(sc->hn_ifp, "RNDIS query invalid comp ib off/len, "
                    "%u/%u\n", comp->rm_infobufoffset, comp->rm_infobuflen);

Modified: head/sys/net/rndis.h
==============================================================================
--- head/sys/net/rndis.h        Tue Sep  6 01:10:51 2016        (r305452)
+++ head/sys/net/rndis.h        Tue Sep  6 03:20:06 2016        (r305453)
@@ -127,6 +127,14 @@ struct rndis_packet_msg {
        (sizeof(struct rndis_packet_msg) -      \
         __offsetof(struct rndis_packet_msg, rm_dataoffset))
 
+/* Offset from the beginning of rndis_packet_msg. */
+#define        RNDIS_PACKET_MSG_OFFSET_ABS(ofs)        \
+       ((ofs) + __offsetof(struct rndis_packet_msg, rm_dataoffset))
+
+#define        RNDIS_PACKET_MSG_OFFSET_ALIGN           4
+#define        RNDIS_PACKET_MSG_OFFSET_ALIGNMASK       \
+       (RNDIS_PACKET_MSG_OFFSET_ALIGN - 1)
+
 /* Per-packet-info for RNDIS data message */
 struct rndis_pktinfo {
        uint32_t rm_size;
@@ -137,7 +145,8 @@ struct rndis_pktinfo {
 
 #define        RNDIS_PKTINFO_OFFSET            \
        __offsetof(struct rndis_pktinfo, rm_data[0])
-#define        RNDIS_PKTINFO_ALIGN             4
+#define        RNDIS_PKTINFO_SIZE_ALIGN        4
+#define        RNDIS_PKTINFO_SIZE_ALIGNMASK    (RNDIS_PKTINFO_SIZE_ALIGN - 1)
 
 #define        NDIS_PKTINFO_TYPE_CSUM          0
 #define        NDIS_PKTINFO_TYPE_IPSEC         1
@@ -236,7 +245,8 @@ struct rndis_query_comp {
        uint32_t rm_infobufoffset;
 };
 
-#define        RNDIS_QUERY_COMP_INFOBUFABS(ofs)        \
+/* infobuf offset from the beginning of rndis_query_comp. */
+#define        RNDIS_QUERY_COMP_INFOBUFOFFSET_ABS(ofs) \
        ((ofs) + __offsetof(struct rndis_query_req, rm_rid))
 
 /* Send a set object request. */
_______________________________________________
svn-src-all@freebsd.org mailing list
https://lists.freebsd.org/mailman/listinfo/svn-src-all
To unsubscribe, send any mail to "svn-src-all-unsubscr...@freebsd.org"

Reply via email to