diff options
Diffstat (limited to 'net/ipv6/ip6_tunnel.c')
| -rw-r--r-- | net/ipv6/ip6_tunnel.c | 42 | 
1 files changed, 27 insertions, 15 deletions
| diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c index 8b186b56183a..75fac933c209 100644 --- a/net/ipv6/ip6_tunnel.c +++ b/net/ipv6/ip6_tunnel.c @@ -42,7 +42,7 @@  #include <linux/hash.h>  #include <linux/etherdevice.h> -#include <asm/uaccess.h> +#include <linux/uaccess.h>  #include <linux/atomic.h>  #include <net/icmp.h> @@ -400,18 +400,19 @@ ip6_tnl_dev_uninit(struct net_device *dev)  __u16 ip6_tnl_parse_tlv_enc_lim(struct sk_buff *skb, __u8 *raw)  { -	const struct ipv6hdr *ipv6h = (const struct ipv6hdr *) raw; -	__u8 nexthdr = ipv6h->nexthdr; -	__u16 off = sizeof(*ipv6h); +	const struct ipv6hdr *ipv6h = (const struct ipv6hdr *)raw; +	unsigned int nhoff = raw - skb->data; +	unsigned int off = nhoff + sizeof(*ipv6h); +	u8 next, nexthdr = ipv6h->nexthdr;  	while (ipv6_ext_hdr(nexthdr) && nexthdr != NEXTHDR_NONE) { -		__u16 optlen = 0;  		struct ipv6_opt_hdr *hdr; -		if (raw + off + sizeof(*hdr) > skb->data && -		    !pskb_may_pull(skb, raw - skb->data + off + sizeof (*hdr))) +		u16 optlen; + +		if (!pskb_may_pull(skb, off + sizeof(*hdr)))  			break; -		hdr = (struct ipv6_opt_hdr *) (raw + off); +		hdr = (struct ipv6_opt_hdr *)(skb->data + off);  		if (nexthdr == NEXTHDR_FRAGMENT) {  			struct frag_hdr *frag_hdr = (struct frag_hdr *) hdr;  			if (frag_hdr->frag_off) @@ -422,20 +423,29 @@ __u16 ip6_tnl_parse_tlv_enc_lim(struct sk_buff *skb, __u8 *raw)  		} else {  			optlen = ipv6_optlen(hdr);  		} +		/* cache hdr->nexthdr, since pskb_may_pull() might +		 * invalidate hdr +		 */ +		next = hdr->nexthdr;  		if (nexthdr == NEXTHDR_DEST) { -			__u16 i = off + 2; +			u16 i = 2; + +			/* Remember : hdr is no longer valid at this point. */ +			if (!pskb_may_pull(skb, off + optlen)) +				break; +  			while (1) {  				struct ipv6_tlv_tnl_enc_lim *tel;  				/* No more room for encapsulation limit */ -				if (i + sizeof (*tel) > off + optlen) +				if (i + sizeof(*tel) > optlen)  					break; -				tel = (struct ipv6_tlv_tnl_enc_lim *) &raw[i]; +				tel = (struct ipv6_tlv_tnl_enc_lim *)(skb->data + off + i);  				/* return index of option if found and valid */  				if (tel->type == IPV6_TLV_TNL_ENCAP_LIMIT &&  				    tel->length == 1) -					return i; +					return i + off - nhoff;  				/* else jump to next option */  				if (tel->type)  					i += tel->length + 2; @@ -443,7 +453,7 @@ __u16 ip6_tnl_parse_tlv_enc_lim(struct sk_buff *skb, __u8 *raw)  					i++;  			}  		} -		nexthdr = hdr->nexthdr; +		nexthdr = next;  		off += optlen;  	}  	return 0; @@ -1108,7 +1118,7 @@ route_lookup:  				     t->parms.name);  		goto tx_err_dst_release;  	} -	mtu = dst_mtu(dst) - psh_hlen; +	mtu = dst_mtu(dst) - psh_hlen - t->tun_hlen;  	if (encap_limit >= 0) {  		max_headroom += 8;  		mtu -= 8; @@ -1117,7 +1127,7 @@ route_lookup:  		mtu = IPV6_MIN_MTU;  	if (skb_dst(skb) && !t->parms.collect_md)  		skb_dst(skb)->ops->update_pmtu(skb_dst(skb), NULL, skb, mtu); -	if (skb->len > mtu && !skb_is_gso(skb)) { +	if (skb->len - t->tun_hlen > mtu && !skb_is_gso(skb)) {  		*pmtu = mtu;  		err = -EMSGSIZE;  		goto tx_err_dst_release; @@ -1303,6 +1313,8 @@ ip6ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev)  		fl6.flowlabel = key->label;  	} else {  		offset = ip6_tnl_parse_tlv_enc_lim(skb, skb_network_header(skb)); +		/* ip6_tnl_parse_tlv_enc_lim() might have reallocated skb->head */ +		ipv6h = ipv6_hdr(skb);  		if (offset > 0) {  			struct ipv6_tlv_tnl_enc_lim *tel; |