Index: sys/netinet/icmp6.h =================================================================== --- sys/netinet/icmp6.h +++ sys/netinet/icmp6.h @@ -156,6 +156,7 @@ #define ICMP6_PARAMPROB_HEADER 0 /* erroneous header field */ #define ICMP6_PARAMPROB_NEXTHEADER 1 /* unrecognized next header */ #define ICMP6_PARAMPROB_OPTION 2 /* unrecognized option */ +#define ICMP6_PARAMPROB_FRAGHDRCHAIN 3 /* first fragment has incomplete header chain */ #define ICMP6_INFOMSG_MASK 0x80 /* all informational messages */ @@ -587,6 +588,7 @@ uint64_t icp6errs_paramprob_header; uint64_t icp6errs_paramprob_nextheader; uint64_t icp6errs_paramprob_option; + uint64_t icp6errs_paramprob_fraghdrchain; uint64_t icp6errs_redirect; /* we regard redirect as an error here */ uint64_t icp6errs_unknown; }; @@ -626,6 +628,8 @@ #define icp6s_oparamprob_nextheader \ icp6s_outerrhist.icp6errs_paramprob_nextheader #define icp6s_oparamprob_option icp6s_outerrhist.icp6errs_paramprob_option +#define icp6s_oparamprob_fraghdrchain \ + icp6s_outerrhist.icp6errs_paramprob_fraghdrchain #define icp6s_oredirect icp6s_outerrhist.icp6errs_redirect #define icp6s_ounknown icp6s_outerrhist.icp6errs_unknown uint64_t icp6s_pmtuchg; /* path MTU changes */ Index: sys/netinet6/icmp6.c =================================================================== --- sys/netinet6/icmp6.c +++ sys/netinet6/icmp6.c @@ -210,6 +210,9 @@ case ICMP6_PARAMPROB_OPTION: ICMP6STAT_INC(icp6s_oparamprob_option); return; + case ICMP6_PARAMPROB_FRAGHDRCHAIN: + ICMP6STAT_INC(icp6s_oparamprob_fraghdrchain); + return; } break; case ND_REDIRECT: @@ -516,6 +519,7 @@ break; case ICMP6_PARAMPROB_HEADER: case ICMP6_PARAMPROB_OPTION: + case ICMP6_PARAMPROB_FRAGHDRCHAIN: code = PRC_PARAMPROB; break; default: Index: sys/netinet6/in6.h =================================================================== --- sys/netinet6/in6.h +++ sys/netinet6/in6.h @@ -641,7 +641,8 @@ * queue */ #define IPV6CTL_MAXFRAGSPERPACKET 53 /* Max fragments per packet */ #define IPV6CTL_MAXFRAGBUCKETSIZE 54 /* Max reassembly queues per bucket */ -#define IPV6CTL_MAXID 55 +#define IPV6CTL_UNKNOWN_UPPERLAYERHDR 55 /* Allow unknown upper layer headers */ +#define IPV6CTL_MAXID 56 #endif /* __BSD_VISIBLE */ /* Index: sys/netinet6/in6_proto.c =================================================================== --- sys/netinet6/in6_proto.c +++ sys/netinet6/in6_proto.c @@ -395,6 +395,7 @@ * walk list every 5 sec. */ VNET_DEFINE(int, ip6_mcast_pmtu) = 0; /* enable pMTU discovery for multicast? */ VNET_DEFINE(int, ip6_v6only) = 1; +VNET_DEFINE(int, ip6_allow_unknown_upperlayerhdr) = 1; VNET_DEFINE(time_t, ip6_log_time) = (time_t)0L; #ifdef IPSTEALTH @@ -538,6 +539,9 @@ SYSCTL_INT(_net_inet6_ip6, IPV6CTL_V6ONLY, v6only, CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ip6_v6only), 0, "Restrict AF_INET6 sockets to IPv6 addresses only"); +SYSCTL_INT(_net_inet6_ip6, IPV6CTL_UNKNOWN_UPPERLAYERHDR, allow_unknown_upperlayerhdr, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ip6_allow_unknown_upperlayerhdr), 0, + "Allow unknown upper layer protocols to skip header chain validation"); SYSCTL_INT(_net_inet6_ip6, IPV6CTL_AUTO_LINKLOCAL, auto_linklocal, CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ip6_auto_linklocal), 0, "Default value of per-interface flag for automatically adding an IPv6 " Index: sys/netinet6/ip6_input.c =================================================================== --- sys/netinet6/ip6_input.c +++ sys/netinet6/ip6_input.c @@ -124,6 +124,13 @@ #include +#include +#include +#include +#include +#include +#include + extern struct domain inet6domain; u_char ip6_protox[IPPROTO_MAX]; @@ -535,6 +542,9 @@ struct in6_ifaddr *ia; struct ifnet *rcvif; u_int32_t plen; +#ifdef INVARIANTS + u_int16_t length; +#endif u_int32_t rtalert = ~0; int off = sizeof(struct ip6_hdr), nest; int nxt, ours = 0; @@ -944,6 +954,12 @@ return; } #endif /* IPSEC */ +#ifdef INVARIANTS + length = sizeof(struct ip6_hdr) + ntohs(ip6->ip6_plen) - off; + if (ip6_upperlayerhdr(nxt)) + if (!ip6_valid_hdrchain(&m, off, nxt, length)) + goto bad; +#endif nxt = (*inet6sw[ip6_protox[nxt]].pr_input)(&m, &off, nxt); } @@ -1737,6 +1753,172 @@ } } +/* + * Check if proto is a supported upper layer protocol. + */ +int +ip6_upperlayerhdr(uint8_t proto) +{ + switch (proto) { + case IPPROTO_ICMPV6: + case IPPROTO_IPV6: + case IPPROTO_ESP: + case IPPROTO_SCTP: + case IPPROTO_TCP: + case IPPROTO_UDP: + case IPPROTO_UDPLITE: + case IPPROTO_DCCP: + case IPPROTO_IP: + case IPPROTO_GRE: + return 1; + default: + return 0; + } +} + +/* + * Search the header chain for the first upper layer protocol and return the + * offset of that header. m will be kept untainted. + */ +int +ip6_first_upperlayerhdr(const struct mbuf *m, int off, int proto, int *nxtp) +{ + int newoff; + int nxt; + int nest = 0; + + if (!nxtp) { + nxt = -1; + nxtp = &nxt; + } + while (nest++ < V_ip6_hdrnestlimit) { + if (ip6_upperlayerhdr(proto) == 1) + return off; + newoff = ip6_nexthdr(m, off, proto, nxtp); + if (newoff < 0) + return -1; + else if (newoff < off) + return -1; /* invalid */ + else if (newoff == off) + return -1; + + off = newoff; + proto = *nxtp; + } + return -1; +} + +/* + * Ensure that proto is an upper layer header and there is enough space for the + * upper layer header. + */ +int +ip6_valid_upperlayerhdr(struct mbuf **mp, int offset, uint8_t proto, uint16_t length) +{ + struct mbuf *m; + struct ipv6_hdr *ip6; + struct ip *ip4; + struct tcphdr *tp; + struct dccphdr *dccp; + uint16_t hdrlen; + + m = *mp; + ip6 = mtod(m, struct ipv6_hdr *); + + switch (proto) { + case IPPROTO_ICMPV6: + if (length < sizeof(struct icmp6_hdr)) + return 0; + return 1; + case IPPROTO_IPV6: + if (length < sizeof(struct ip6_hdr)) + return 0; + return 1; + case IPPROTO_ESP: + if (length < sizeof(uint32_t)) + return 0; + return 1; + case IPPROTO_SCTP: + if (length < sizeof(struct sctphdr)) + return 0; + return 1; + case IPPROTO_TCP: + if (length < sizeof(struct tcphdr)) + return 0; + /* Get the tcp header in the first mbuf. */ + if (m->m_len < offset + sizeof(struct tcphdr)) + if ((m = m_pullup(m, offset + sizeof(struct tcphdr))) == NULL) { + *mp = NULL; + return 0; + } + tp = (struct tcphdr *)((caddr_t)ip6 + offset); + hdrlen = tp->th_off << 2; + if (length < hdrlen) + return 0; + return 1; + case IPPROTO_UDP: + case IPPROTO_UDPLITE: + if (length < sizeof(struct udphdr)) + return 0; + return 1; + case IPPROTO_DCCP: + if (length < DCCP_SHORTHDR) + return 0; + /* Get the dccp short header in the first mbuf. */ + if (m->m_len < offset + DCCP_SHORTHDR) + if ((m = m_pullup(m, offset + DCCP_SHORTHDR)) == NULL) { + *mp = NULL; + return 0; + } + dccp = (struct dccphdr *)((caddr_t)ip6 + offset); + hdrlen = dccp->d_x; + if (hdrlen & DCCP_EXTHDR && length < DCCP_LONGHDR) + return 0; + return 1; + case IPPROTO_IP: + if (length < sizeof(struct ip)) + return 0; + /* Get the ip header in the first mbuf. */ + if (m->m_len < offset + sizeof(struct ip)) + if ((m = m_pullup(m, offset + sizeof(struct ip))) == NULL) { + *mp = NULL; + return 0; + } + ip4 = (struct ip *)((caddr_t)ip6 + offset); + hdrlen = ip4->ip_hl << 2; + if (length < hdrlen) + return 0; + return 1; + case IPPROTO_GRE: + if (length < sizeof(struct grehdr)) + return 0; + return 1; + default: + /* not a known upper layer protocol */ + return 0; + } +} + +uint8_t +ip6_valid_hdrchain(struct mbuf **mp, int offset, uint8_t proto, uint16_t length) +{ + int nxtp = proto; + struct mbuf *m; + m = *mp; + + offset = ip6_first_upperlayerhdr(m, offset, proto, &nxtp); + + if (offset == -1) { + if (V_ip6_allow_unknown_upperlayerhdr) + return 1; + + else + return 0; + } + + return ip6_valid_upperlayerhdr(mp, offset, proto, length - offset); +} + /* * System control for IP6 */ Index: sys/netinet6/ip6_var.h =================================================================== --- sys/netinet6/ip6_var.h +++ sys/netinet6/ip6_var.h @@ -277,6 +277,7 @@ * walk list every 5 sec. */ VNET_DECLARE(int, ip6_mcast_pmtu); /* enable pMTU discovery for multicast? */ VNET_DECLARE(int, ip6_v6only); +VNET_DECLARE(int, ip6_allow_unknown_upperlayerhdr); #define V_ip6_defhlim VNET(ip6_defhlim) #define V_ip6_defmcasthlim VNET(ip6_defmcasthlim) #define V_ip6_forwarding VNET(ip6_forwarding) @@ -284,6 +285,7 @@ #define V_ip6_rr_prune VNET(ip6_rr_prune) #define V_ip6_mcast_pmtu VNET(ip6_mcast_pmtu) #define V_ip6_v6only VNET(ip6_v6only) +#define V_ip6_allow_unknown_upperlayerhdr VNET(ip6_allow_unknown_upperlayerhdr) VNET_DECLARE(struct socket *, ip6_mrouter); /* multicast routing daemon */ VNET_DECLARE(int, ip6_sendredirects); /* send IP redirects when forwarding? */ @@ -390,6 +392,10 @@ int ip6_deletefraghdr(struct mbuf *, int, int); int ip6_fragment(struct ifnet *, struct mbuf *, int, u_char, int, uint32_t); +int ip6_upperlayerhdr(uint8_t); +int ip6_first_upperlayerhdr(const struct mbuf *, int , int , int *); +int ip6_valid_upperlayerhdr(struct mbuf **, int , uint8_t , uint16_t); +uint8_t ip6_valid_hdrchain(struct mbuf **, int , uint8_t , uint16_t); int route6_input(struct mbuf **, int *, int); Index: usr.bin/netstat/inet6.c =================================================================== --- usr.bin/netstat/inet6.c +++ usr.bin/netstat/inet6.c @@ -1035,6 +1035,8 @@ "{N:/unrecognized next header}\n"); p_5(icp6s_oparamprob_option, "\t\t{:bad-option/%ju} " "{N:/unrecognized option}\n"); + p_5(icp6s_oparamprob_fraghdrchain, "\t\t{:bad-header-chain/%ju} " + "{N:/incomplete upper layer first fragment}\n"); p_5(icp6s_oredirect, "\t\t{:redirects/%ju} " "{N:/redirect}\n"); p_5(icp6s_ounknown, "\t\t{:unknown/%ju} {N:unknown}\n");