diff --git a/sys/netinet6/in6.h b/sys/netinet6/in6.h --- a/sys/netinet6/in6.h +++ b/sys/netinet6/in6.h @@ -646,7 +646,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 */ /* diff --git a/sys/netinet6/in6_proto.c b/sys/netinet6/in6_proto.c --- a/sys/netinet6/in6_proto.c +++ b/sys/netinet6/in6_proto.c @@ -216,6 +216,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 @@ -365,6 +366,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 " diff --git a/sys/netinet6/ip6_input.c b/sys/netinet6/ip6_input.c --- a/sys/netinet6/ip6_input.c +++ b/sys/netinet6/ip6_input.c @@ -131,6 +131,13 @@ #include +#include +#include +#include +#include +#include +#include + ipproto_input_t *ip6_protox[IPPROTO_MAX] = { [0 ... IPPROTO_MAX - 1] = rip6_input }; ipproto_ctlinput_t *ip6_ctlprotox[IPPROTO_MAX] = { @@ -526,6 +533,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; @@ -919,6 +929,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 = ip6_protox[nxt](&m, &off, nxt); } @@ -1715,6 +1731,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 */ diff --git a/sys/netinet6/ip6_var.h b/sys/netinet6/ip6_var.h --- a/sys/netinet6/ip6_var.h +++ b/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? */ @@ -385,6 +387,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);