Define constants and add support to send ICMPv6 Parameter Problem
errors as specified in draft-ietf-6man-icmp-limits-02.

The following parameter problem errors are sent when processing
Hop-by-Hop or Destination Options:

   * ICMPV6_TOOBIG_OPTION
      - Sent if the length of an option exceeds the extent of the
        extension header.
      - Sent if a packet exceeding the padding limit is received
        (more than seven consecutive bytes of padding).

   * ICMPV6_TOOMANY_OPTIONS
      - Sent if a packet is received and HBH option count exceeds
        ipv6.sysctl.max_hbh_opts_cnt or DO option count exceeds
        ipv6.sysctl.max_dst_opts_cnt.

   * ICMPV6_EXTHDR_TOOBIG
      - Sent if length of HBH EH exceeds ipv6.sysctl.max_hbh_opts_len
        or length of DO EH exceeds ipv6.sysctl.max_dst_opts_len.
      - Sent if the length of an extension header exceeds the
        extent of the packet.

   * ICMPV6_HDR_FIELD
      - Sent if a data byte in PADN is non-zero

Signed-off-by: Tom Herbert <t...@quantonium.net>
---
 include/uapi/linux/icmpv6.h |  6 ++++++
 net/ipv6/exthdrs.c          | 50 +++++++++++++++++++++++++++++++++++++--------
 2 files changed, 48 insertions(+), 8 deletions(-)

diff --git a/include/uapi/linux/icmpv6.h b/include/uapi/linux/icmpv6.h
index 2622b5a..966279b 100644
--- a/include/uapi/linux/icmpv6.h
+++ b/include/uapi/linux/icmpv6.h
@@ -124,6 +124,7 @@ struct icmp6hdr {
 #define ICMPV6_PORT_UNREACH            4
 #define ICMPV6_POLICY_FAIL             5
 #define ICMPV6_REJECT_ROUTE            6
+#define ICMPV6_SRCRT_ERR               7
 
 /*
  *     Codes for Time Exceeded
@@ -137,6 +138,11 @@ struct icmp6hdr {
 #define ICMPV6_HDR_FIELD               0
 #define ICMPV6_UNK_NEXTHDR             1
 #define ICMPV6_UNK_OPTION              2
+#define ICMPV6_FIRST_FRAG_INCOMP       3
+#define ICMPV6_EXTHDR_TOOBIG           4
+#define ICMPV6_EXTHDR_CHAINLONG                5
+#define ICMPV6_TOOMANY_OPTIONS         6
+#define ICMPV6_TOOBIG_OPTION           7
 
 /*
  *     constants for (set|get)sockopt
diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c
index 20291c2..05061f4 100644
--- a/net/ipv6/exthdrs.c
+++ b/net/ipv6/exthdrs.c
@@ -131,8 +131,11 @@ static bool ip6_parse_tlv(const struct tlvtype_proc *procs,
                max_count = -max_count;
        }
 
-       if (skb_transport_offset(skb) + len > skb_headlen(skb))
+       if (skb_transport_offset(skb) + len > skb_headlen(skb)) {
+               icmpv6_send(skb, ICMPV6_PARAMPROB,
+                           ICMPV6_EXTHDR_TOOBIG, skb_transport_offset(skb));
                goto bad;
+       }
 
        off += 2;
        len -= 2;
@@ -145,8 +148,11 @@ static bool ip6_parse_tlv(const struct tlvtype_proc *procs,
                case IPV6_TLV_PAD1:
                        optlen = 1;
                        padlen++;
-                       if (padlen > 7)
+                       if (padlen > 7) {
+                               icmpv6_send(skb, ICMPV6_PARAMPROB,
+                                           ICMPV6_TOOBIG_OPTION, off);
                                goto bad;
+                       }
                        break;
 
                case IPV6_TLV_PADN:
@@ -156,25 +162,37 @@ static bool ip6_parse_tlv(const struct tlvtype_proc 
*procs,
                         * See also RFC 4942, Section 2.1.9.5.
                         */
                        padlen += optlen;
-                       if (padlen > 7)
+                       if (padlen > 7) {
+                               icmpv6_send(skb, ICMPV6_PARAMPROB,
+                                           ICMPV6_TOOBIG_OPTION, off);
                                goto bad;
+                       }
                        /* RFC 4942 recommends receiving hosts to
                         * actively check PadN payload to contain
                         * only zeroes.
                         */
                        for (i = 2; i < optlen; i++) {
-                               if (nh[off + i] != 0)
+                               if (nh[off + i] != 0) {
+                                       icmpv6_send(skb, ICMPV6_PARAMPROB,
+                                                   ICMPV6_HDR_FIELD, off + i);
                                        goto bad;
+                               }
                        }
                        break;
 
                default: /* Other TLV code so scan list */
-                       if (optlen > len)
+                       if (optlen > len) {
+                               icmpv6_send(skb, ICMPV6_PARAMPROB,
+                                           ICMPV6_TOOBIG_OPTION, off);
                                goto bad;
+                       }
 
                        tlv_count++;
-                       if (tlv_count > max_count)
+                       if (tlv_count > max_count) {
+                               icmpv6_send(skb, ICMPV6_PARAMPROB,
+                                           ICMPV6_TOOMANY_OPTIONS, off);
                                goto bad;
+                       }
 
                        for (curr = procs; curr->type >= 0; curr++) {
                                if (curr->type == nh[off]) {
@@ -200,6 +218,8 @@ static bool ip6_parse_tlv(const struct tlvtype_proc *procs,
        if (len == 0)
                return true;
 bad:
+       __IP6_INC_STATS(dev_net(skb->dev), __in6_dev_get(skb->dev),
+                       IPSTATS_MIB_INHDRERRORS);
        kfree_skb(skb);
        return false;
 }
@@ -300,8 +320,15 @@ static int ipv6_destopt_rcv(struct sk_buff *skb)
        }
 
        extlen = (skb_transport_header(skb)[1] + 1) << 3;
-       if (extlen > net->ipv6.sysctl.max_dst_opts_len)
+       if (extlen > net->ipv6.sysctl.max_dst_opts_len) {
+               icmpv6_send(skb, ICMPV6_PARAMPROB,
+                           ICMPV6_EXTHDR_TOOBIG,
+                           skb_network_header_len(skb) +
+                               net->ipv6.sysctl.max_dst_opts_len);
+               __IP6_INC_STATS(dev_net(dst->dev), idev,
+                               IPSTATS_MIB_INHDRERRORS);
                goto fail_and_free;
+       }
 
        opt->lastopt = opt->dst1 = skb_network_header_len(skb);
 #if IS_ENABLED(CONFIG_IPV6_MIP6)
@@ -843,8 +870,15 @@ int ipv6_parse_hopopts(struct sk_buff *skb)
        }
 
        extlen = (skb_transport_header(skb)[1] + 1) << 3;
-       if (extlen > net->ipv6.sysctl.max_hbh_opts_len)
+       if (extlen > net->ipv6.sysctl.max_hbh_opts_len) {
+               __IP6_INC_STATS(net, __in6_dev_get(skb->dev),
+                               IPSTATS_MIB_INHDRERRORS);
+               icmpv6_send(skb, ICMPV6_PARAMPROB,
+                           ICMPV6_EXTHDR_TOOBIG,
+                       skb_network_header_len(skb) +
+                               net->ipv6.sysctl.max_hbh_opts_len);
                goto fail_and_free;
+       }
 
        opt->flags |= IP6SKB_HOPBYHOP;
        if (ip6_parse_tlv(tlvprochopopt_lst, skb,
-- 
2.7.4

Reply via email to