]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/netinet/ip_icmp.c
xnu-4903.221.2.tar.gz
[apple/xnu.git] / bsd / netinet / ip_icmp.c
index 3d7ecafe3516d30b575e4d22bd966e7965a03fd4..260449c30778ede4811e24cffb219073746e864a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000-2016 Apple Inc. All rights reserved.
+ * Copyright (c) 2000-2018 Apple Inc. All rights reserved.
  *
  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
  * 
@@ -207,67 +207,101 @@ icmp_error(
        u_int32_t dest,
        u_int32_t nextmtu)
 {
-       struct ip *oip, *nip;
-       struct icmp *icp;
-       struct mbuf *m;
-       u_int32_t oiphlen, icmplen, icmpelen, nlen;
-
+       struct ip *oip = NULL;
+       struct ip *nip = NULL;
+       struct icmp *icp = NULL;
+       struct mbuf *m = NULL;
+       u_int32_t oiphlen = 0;
+       u_int32_t icmplen = 0;
+       u_int32_t icmpelen = 0;
+       u_int32_t nlen = 0;
+
+       VERIFY((u_int)type <= ICMP_MAXTYPE);
        /* Expect 32-bit aligned data pointer on strict-align platforms */
        MBUF_STRICT_DATA_ALIGNMENT_CHECK_32(n);
 
+       if (type != ICMP_REDIRECT)
+               icmpstat.icps_error++;
+       /*
+        * Don't send error:
+        *   if not the first fragment of message
+        *   if original packet was a multicast or broadcast packet
+        *   if the old packet protocol was ICMP
+        *   error message, only known informational types.
+        */
+       if (n->m_flags & (M_BCAST|M_MCAST))
+               goto freeit;
+
+       /*
+        * Drop if IP header plus ICMP_MINLEN bytes are not contiguous
+        * in first mbuf.
+        */
+       if (n->m_len < sizeof(struct ip) + ICMP_MINLEN)
+               goto freeit;
+
        oip = mtod(n, struct ip *);
        oiphlen = IP_VHL_HL(oip->ip_vhl) << 2;
+       if (n->m_len < oiphlen + ICMP_MINLEN)
+               goto freeit;
 
 #if (DEBUG | DEVELOPMENT)
        if (icmpprintfs > 1)
                printf("icmp_error(0x%llx, %x, %d)\n",
                    (uint64_t)VM_KERNEL_ADDRPERM(oip), type, code);
 #endif
-       if (type != ICMP_REDIRECT)
-               icmpstat.icps_error++;
-       /*
-        * Don't send error if not the first fragment of message.
-        * Don't error if the old packet protocol was ICMP
-        * error message, only known informational types.
-        */
+
        if (oip->ip_off & ~(IP_MF|IP_DF))
                goto freeit;
 
        if (oip->ip_p == IPPROTO_ICMP && type != ICMP_REDIRECT &&
-         n->m_len >= oiphlen + ICMP_MINLEN &&
-         !ICMP_INFOTYPE(((struct icmp *)(void *)((caddr_t)oip + oiphlen))->
-         icmp_type)) {
+           n->m_len >= oiphlen + ICMP_MINLEN &&
+           !ICMP_INFOTYPE(((struct icmp *)(void *)((caddr_t)oip + oiphlen))->
+               icmp_type)) {
                icmpstat.icps_oldicmp++;
                goto freeit;
        }
-       /*
-        * Don't send error in response to a multicast or
-        * broadcast packet
-        */
-       if (n->m_flags & (M_BCAST|M_MCAST))
-               goto freeit;
 
        /*
         * Calculate the length to quote from original packet and prevent
         * the ICMP mbuf from overflowing.
+        * Unfortunatly this is non-trivial since ip_forward()
+        * sends us truncated packets.
         */
        nlen = m_length(n);
        if (oip->ip_p == IPPROTO_TCP) {
-               struct tcphdr *th;
-               u_int16_t tcphlen;
+               struct tcphdr *th = NULL;
+               u_int16_t tcphlen = 0;
 
+               /*
+                * If the packet got truncated and TCP header
+                * is not contained in the packet, send out
+                * standard reply with only IP header as payload
+                */
                if (oiphlen + sizeof(struct tcphdr) > n->m_len &&
                    n->m_next == NULL)
                        goto stdreply;
+
+               /*
+                * Otherwise, pull up to get IP and TCP headers
+                * together
+                */
                if (n->m_len < (oiphlen + sizeof(struct tcphdr)) &&
                    (n = m_pullup(n, (oiphlen + sizeof(struct tcphdr)))) == NULL)
                        goto freeit;
 
+               /*
+                * Reinit pointers derived from mbuf data pointer
+                * as things might have moved around with m_pullup
+                */
+               oip = mtod(n, struct ip *);
                th = (struct tcphdr *)(void *)((caddr_t)oip + oiphlen);
+
                if (th != ((struct tcphdr *)P2ROUNDDOWN(th,
                    sizeof(u_int32_t))))
                        goto freeit;
                tcphlen = th->th_off << 2;
+
+               /* Sanity checks */
                if (tcphlen < sizeof(struct tcphdr))
                        goto freeit;
                if (oip->ip_len < (oiphlen + tcphlen))
@@ -278,17 +312,27 @@ icmp_error(
                    (n = m_pullup(n, (oiphlen + tcphlen))) == NULL)
                        goto freeit;
 
+               /*
+                * Reinit pointers derived from mbuf data pointer
+                * as things might have moved around with m_pullup
+                */
+               oip = mtod(n, struct ip *);
+               th = (struct tcphdr *)(void *)((caddr_t)oip + oiphlen);
+
                icmpelen = max(tcphlen, min(icmp_datalen,
                    (oip->ip_len - oiphlen)));
        } else
 stdreply:      icmpelen = max(ICMP_MINLEN, min(icmp_datalen,
                    (oip->ip_len - oiphlen)));
 
-       icmplen = min(oiphlen + icmpelen, min(nlen, oip->ip_len));
+       icmplen = min(oiphlen + icmpelen, nlen);
        if (icmplen < sizeof(struct ip))
                goto freeit;
+
        /*
         * First, formulate icmp message
+        * Allocate enough space for the IP header, ICMP header
+        * and the payload (part of the original message to be sent back).
         */
        if (MHLEN > (sizeof(struct ip) + ICMP_MINLEN + icmplen))
                m = m_gethdr(M_DONTWAIT, MT_HEADER);    /* MAC-OK */
@@ -298,24 +342,20 @@ stdreply: icmpelen = max(ICMP_MINLEN, min(icmp_datalen,
        if (m == NULL)
                goto freeit;
 
-       if (n->m_flags & M_SKIP_FIREWALL) {
-               /*
-                * set M_SKIP_FIREWALL to skip firewall check, since
-                * we're called from firewall
-                */
-               m->m_flags |= M_SKIP_FIREWALL;
-       }
-
 #if CONFIG_MACF_NET
        mac_mbuf_label_associate_netlayer(n, m);
 #endif
-       m->m_len = icmplen + ICMP_MINLEN; /* for ICMP header and data */
-       MH_ALIGN(m, m->m_len);
+       /*
+        * Further refine the payload length to the space
+        * remaining in mbuf after including the IP header and ICMP
+        * header.
+        */
+       icmplen = min(icmplen, M_TRAILINGSPACE(m) -
+           sizeof(struct ip) - ICMP_MINLEN);
+       m_align(m, ICMP_MINLEN + icmplen);
+       m->m_len = ICMP_MINLEN + icmplen; /* for ICMP header and data */
+
        icp = mtod(m, struct icmp *);
-       if ((u_int)type > ICMP_MAXTYPE) {
-               m_freem(m);
-               goto freeit;
-       }
        icmpstat.icps_outhist[type]++;
        icp->icmp_type = type;
        if (type == ICMP_REDIRECT)
@@ -336,6 +376,11 @@ stdreply:  icmpelen = max(ICMP_MINLEN, min(icmp_datalen,
        }
 
        icp->icmp_code = code;
+
+       /*
+        * Copy icmplen worth of content from original
+        * mbuf (n) to the new packet after ICMP header.
+        */
        m_copydata(n, 0, icmplen, (caddr_t)&icp->icmp_ip);
        nip = &icp->icmp_ip;
 
@@ -347,13 +392,12 @@ stdreply: icmpelen = max(ICMP_MINLEN, min(icmp_datalen,
        HTONS(nip->ip_off);
 #endif
        /*
-        * Now, copy old ip header (without options)
-        * in front of icmp message.
-        */
-       if (m->m_data - sizeof(struct ip) < m->m_pktdat) {
-               m_freem(m);
-               goto freeit;
-       }
+        * Set up ICMP message mbuf and copy old IP header (without options
+        * in front of ICMP message.
+        * If the original mbuf was meant to bypass the firewall, the error
+        * reply should bypass as well.
+         */
+       m->m_flags |= n->m_flags & M_SKIP_FIREWALL;
        m->m_data -= sizeof(struct ip);
        m->m_len += sizeof(struct ip);
        m->m_pkthdr.len = m->m_len;
@@ -366,7 +410,6 @@ stdreply:   icmpelen = max(ICMP_MINLEN, min(icmp_datalen,
        nip->ip_tos = 0;
        nip->ip_off = 0;
        icmp_reflect(m);
-
 freeit:
        m_freem(n);
 }
@@ -538,9 +581,17 @@ icmp_input(struct mbuf *m, int hlen)
                 * notification to TCP layer.
                 */
                ctlfunc = ip_protox[icp->icmp_ip.ip_p]->pr_ctlinput;
-               if (ctlfunc)
+
+               if (ctlfunc) {
+                       LCK_MTX_ASSERT(inet_domain_mutex, LCK_MTX_ASSERT_OWNED);
+
+                       lck_mtx_unlock(inet_domain_mutex);
+
                        (*ctlfunc)(code, (struct sockaddr *)&icmpsrc,
                                   (void *)&icp->icmp_ip, m->m_pkthdr.rcvif);
+
+                       lck_mtx_lock(inet_domain_mutex);
+               }
                break;
 
        badcode:
@@ -911,9 +962,13 @@ icmp_send(struct mbuf *m, struct mbuf *opts)
        int hlen;
        struct icmp *icp;
        struct route ro;
-       struct ip_out_args ipoa = { IFSCOPE_NONE, { 0 },
-           IPOAF_SELECT_SRCIF | IPOAF_BOUND_SRCADDR, 0,
-           SO_TC_UNSPEC, _NET_SERVICE_TYPE_UNSPEC };
+       struct ip_out_args ipoa;
+
+       bzero(&ipoa, sizeof(ipoa));
+       ipoa.ipoa_boundif = IFSCOPE_NONE;
+       ipoa.ipoa_flags = IPOAF_SELECT_SRCIF | IPOAF_BOUND_SRCADDR;
+       ipoa.ipoa_sotc = SO_TC_UNSPEC;
+       ipoa.ipoa_netsvctype = _NET_SERVICE_TYPE_UNSPEC;
 
        if (!(m->m_pkthdr.pkt_flags & PKTF_LOOP) && m->m_pkthdr.rcvif != NULL) {
                ipoa.ipoa_boundif = m->m_pkthdr.rcvif->if_index;