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