patch-2.4.4 linux/net/ipv6/exthdrs.c

Next file: linux/net/ipv6/icmp.c
Previous file: linux/net/ipv6/datagram.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.4.3/linux/net/ipv6/exthdrs.c linux/net/ipv6/exthdrs.c
@@ -7,7 +7,7 @@
  *	Andi Kleen		<ak@muc.de>
  *	Alexey Kuznetsov	<kuznet@ms2.inr.ac.ru>
  *
- *	$Id: exthdrs.c,v 1.10 2000/01/09 02:19:55 davem Exp $
+ *	$Id: exthdrs.c,v 1.12 2001/01/22 02:36:37 davem Exp $
  *
  *	This program is free software; you can redistribute it and/or
  *      modify it under the terms of the GNU General Public License
@@ -15,6 +15,11 @@
  *      2 of the License, or (at your option) any later version.
  */
 
+/* Changes:
+ *	yoshfuji		: ensure not to overrun while parsing 
+ *				  tlv options.
+ */
+
 #include <linux/errno.h>
 #include <linux/types.h>
 #include <linux/socket.h>
@@ -41,15 +46,15 @@
 /*
  *	Parsing inbound headers.
  *
- *	Parsing function "func" returns pointer to the place,
+ *	Parsing function "func" returns offset wrt skb->nh of the place,
  *	where next nexthdr value is stored or NULL, if parsing
- *	failed. It should also update skb->h.
+ *	failed. It should also update skb->h tp point at the next header.
  */
 
 struct hdrtype_proc
 {
 	int	type;
-	u8*	(*func) (struct sk_buff **, u8 *ptr);
+	int	(*func) (struct sk_buff **, int offset);
 };
 
 /*
@@ -63,7 +68,7 @@
 struct tlvtype_proc
 {
 	int	type;
-	int	(*func) (struct sk_buff *, __u8 *ptr);
+	int	(*func) (struct sk_buff *, int offset);
 };
 
 /*********************
@@ -72,12 +77,12 @@
 
 /* An unknown option is detected, decide what to do */
 
-int ip6_tlvopt_unknown(struct sk_buff *skb, u8 *opt)
+int ip6_tlvopt_unknown(struct sk_buff *skb, int optoff)
 {
-	switch ((opt[0] & 0xC0) >> 6) {
+	switch ((skb->nh.raw[optoff] & 0xC0) >> 6) {
 	case 0: /* ignore */
 		return 1;
-		
+
 	case 1: /* drop packet */
 		break;
 
@@ -88,7 +93,7 @@
 		if (ipv6_addr_is_multicast(&skb->nh.ipv6h->daddr))
 			break;
 	case 2: /* send ICMP PARM PROB regardless and drop packet */
-		icmpv6_param_prob(skb, ICMPV6_UNK_OPTION, opt);
+		icmpv6_param_prob(skb, ICMPV6_UNK_OPTION, optoff);
 		return 0;
 	};
 
@@ -98,24 +103,21 @@
 
 /* Parse tlv encoded option header (hop-by-hop or destination) */
 
-static int ip6_parse_tlv(struct tlvtype_proc *procs, struct sk_buff *skb,
-			 __u8 *nhptr)
+static int ip6_parse_tlv(struct tlvtype_proc *procs, struct sk_buff *skb)
 {
 	struct tlvtype_proc *curr;
-	u8 *ptr = skb->h.raw;
-	int len = ((ptr[1]+1)<<3) - 2;
+	int off = skb->h.raw - skb->nh.raw;
+	int len = ((skb->h.raw[1]+1)<<3);
 
-	ptr += 2;
+	if ((skb->h.raw + len) - skb->data > skb_headlen(skb))
+		goto bad;
 
-	if (skb->tail - (ptr + len) < 0) {
-		kfree_skb(skb);
-		return 0;
-	}
+	len -= 2;
 
 	while (len > 0) {
-		int optlen = ptr[1]+2;
+		int optlen = skb->nh.raw[off+1]+2;
 
-		switch (ptr[0]) {
+		switch (skb->nh.raw[off]) {
 		case IPV6_TLV_PAD0:
 			optlen = 1;
 			break;
@@ -124,24 +126,30 @@
 			break;
 
 		default: /* Other TLV code so scan list */
+			if (optlen > len)
+				goto bad;
 			for (curr=procs; curr->type >= 0; curr++) {
-				if (curr->type == ptr[0]) {
-					if (curr->func(skb, ptr) == 0)
+				if (curr->type == skb->nh.raw[off]) {
+					/* type specific length/alignment 
+					   checks will be perfomed in the 
+					   func(). */
+					if (curr->func(skb, off) == 0)
 						return 0;
 					break;
 				}
 			}
 			if (curr->type < 0) {
-				if (ip6_tlvopt_unknown(skb, ptr) == 0)
+				if (ip6_tlvopt_unknown(skb, off) == 0)
 					return 0;
 			}
 			break;
 		}
-		ptr += optlen;
+		off += optlen;
 		len -= optlen;
 	}
 	if (len == 0)
 		return 1;
+bad:
 	kfree_skb(skb);
 	return 0;
 }
@@ -155,37 +163,42 @@
 	{-1,			NULL}
 };
 
-static u8 *ipv6_dest_opt(struct sk_buff **skb_ptr, u8 *nhptr)
+static int ipv6_dest_opt(struct sk_buff **skb_ptr, int nhoff)
 {
 	struct sk_buff *skb=*skb_ptr;
 	struct inet6_skb_parm *opt = (struct inet6_skb_parm *)skb->cb;
-	struct ipv6_destopt_hdr *hdr = (struct ipv6_destopt_hdr *) skb->h.raw;
 
-	opt->dst1 = (u8*)hdr - skb->nh.raw;
+	if (!pskb_may_pull(skb, (skb->h.raw-skb->data)+8) ||
+	    !pskb_may_pull(skb, (skb->h.raw-skb->data)+((skb->h.raw[1]+1)<<3))) {
+		kfree_skb(skb);
+		return -1;
+	}
+
+	opt->dst1 = skb->h.raw - skb->nh.raw;
 
-	if (ip6_parse_tlv(tlvprocdestopt_lst, skb, nhptr)) {
-		skb->h.raw += ((hdr->hdrlen+1)<<3);
-		return &hdr->nexthdr;
+	if (ip6_parse_tlv(tlvprocdestopt_lst, skb)) {
+		skb->h.raw += ((skb->h.raw[1]+1)<<3);
+		return opt->dst1;
 	}
 
-	return NULL;
+	return -1;
 }
 
 /********************************
   NONE header. No data in packet.
  ********************************/
 
-static u8 *ipv6_nodata(struct sk_buff **skb_ptr, u8 *nhptr)
+static int ipv6_nodata(struct sk_buff **skb_ptr, int nhoff)
 {
 	kfree_skb(*skb_ptr);
-	return NULL;
+	return -1;
 }
 
 /********************************
   Routing header.
  ********************************/
 
-static u8* ipv6_routing_header(struct sk_buff **skb_ptr, u8 *nhptr)
+static int ipv6_routing_header(struct sk_buff **skb_ptr, int nhoff)
 {
 	struct sk_buff *skb = *skb_ptr;
 	struct inet6_skb_parm *opt = (struct inet6_skb_parm *)skb->cb;
@@ -194,36 +207,38 @@
 	int addr_type;
 	int n, i;
 
-	struct ipv6_rt_hdr *hdr = (struct ipv6_rt_hdr *) skb->h.raw;
+	struct ipv6_rt_hdr *hdr;
 	struct rt0_hdr *rthdr;
 
-	if (((hdr->hdrlen+1)<<3) > skb->tail - skb->h.raw) {
+	if (!pskb_may_pull(skb, (skb->h.raw-skb->data)+8) ||
+	    !pskb_may_pull(skb, (skb->h.raw-skb->data)+((skb->h.raw[1]+1)<<3))) {
 		IP6_INC_STATS_BH(Ip6InHdrErrors);
 		kfree_skb(skb);
-		return NULL;
+		return -1;
+	}
+
+	hdr = (struct ipv6_rt_hdr *) skb->h.raw;
+
+	if ((ipv6_addr_type(&skb->nh.ipv6h->daddr)&IPV6_ADDR_MULTICAST) ||
+	    skb->pkt_type != PACKET_HOST) {
+		kfree_skb(skb);
+		return -1;
 	}
 
 looped_back:
 	if (hdr->segments_left == 0) {
-		opt->srcrt = (u8*)hdr - skb->nh.raw;
+		opt->srcrt = skb->h.raw - skb->nh.raw;
 		skb->h.raw += (hdr->hdrlen + 1) << 3;
 		opt->dst0 = opt->dst1;
 		opt->dst1 = 0;
-		return &hdr->nexthdr;		
+		return (&hdr->nexthdr) - skb->nh.raw;
 	}
 
-	if (hdr->type != IPV6_SRCRT_TYPE_0 || hdr->hdrlen & 0x01) {
-		u8 *pos = (u8*) hdr;
-
-		if (hdr->type != IPV6_SRCRT_TYPE_0)
-			pos += 2;
-		else
-			pos += 1;
-
-		icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, pos);
-		return NULL;	
+	if (hdr->type != IPV6_SRCRT_TYPE_0 || (hdr->hdrlen & 0x01)) {
+		icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, hdr->type != IPV6_SRCRT_TYPE_0 ? 2 : 1);
+		return -1;
 	}
-	
+
 	/*
 	 *	This is the routing header forwarding algorithm from
 	 *	RFC 1883, page 17.
@@ -232,8 +247,8 @@
 	n = hdr->hdrlen >> 1;
 
 	if (hdr->segments_left > n) {
-		icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, &hdr->segments_left);
-		return NULL;
+		icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, (&hdr->segments_left) - skb->nh.raw);
+		return -1;
 	}
 
 	/* We are about to mangle packet header. Be careful!
@@ -243,12 +258,15 @@
 		struct sk_buff *skb2 = skb_copy(skb, GFP_ATOMIC);
 		kfree_skb(skb);
 		if (skb2 == NULL)
-			return NULL;
+			return -1;
 		*skb_ptr = skb = skb2;
 		opt = (struct inet6_skb_parm *)skb2->cb;
 		hdr = (struct ipv6_rt_hdr *) skb2->h.raw;
 	}
 
+	if (skb->ip_summed == CHECKSUM_HW)
+		skb->ip_summed = CHECKSUM_NONE;
+
 	i = n - --hdr->segments_left;
 
 	rthdr = (struct rt0_hdr *) hdr;
@@ -257,9 +275,9 @@
 
 	addr_type = ipv6_addr_type(addr);
 
-	if (addr_type == IPV6_ADDR_MULTICAST) {
+	if (addr_type&IPV6_ADDR_MULTICAST) {
 		kfree_skb(skb);
-		return NULL;
+		return -1;
 	}
 
 	ipv6_addr_copy(&daddr, addr);
@@ -270,21 +288,21 @@
 	ip6_route_input(skb);
 	if (skb->dst->error) {
 		skb->dst->input(skb);
-		return NULL;
+		return -1;
 	}
 	if (skb->dst->dev->flags&IFF_LOOPBACK) {
 		if (skb->nh.ipv6h->hop_limit <= 1) {
 			icmpv6_send(skb, ICMPV6_TIME_EXCEED, ICMPV6_EXC_HOPLIMIT,
 				    0, skb->dev);
 			kfree_skb(skb);
-			return NULL;
+			return -1;
 		}
 		skb->nh.ipv6h->hop_limit--;
 		goto looped_back;
 	}
 
 	skb->dst->input(skb);
-	return NULL;
+	return -1;
 }
 
 /*
@@ -374,20 +392,30 @@
    and opt->hdrlen is even. Shit!		--ANK (980730)
  */
 
-static u8 *ipv6_auth_hdr(struct sk_buff **skb_ptr, u8 *nhptr)
+static int ipv6_auth_hdr(struct sk_buff **skb_ptr, int nhoff)
 {
 	struct sk_buff *skb=*skb_ptr;
 	struct inet6_skb_parm *opt = (struct inet6_skb_parm *)skb->cb;
-	struct ipv6_opt_hdr *hdr = (struct ipv6_opt_hdr *)skb->h.raw;
-	int len = (hdr->hdrlen+2)<<2;
+	int len;
+
+	if (!pskb_may_pull(skb, (skb->h.raw-skb->data)+8))
+		goto fail;
+
+	len = (skb->h.raw[1]+1)<<2;
 
 	if (len&7)
-		return NULL;
-	opt->auth = (u8*)hdr - skb->nh.raw;
-	if (skb->h.raw + len > skb->tail)
-		return NULL;
+		goto fail;
+
+	if (!pskb_may_pull(skb, (skb->h.raw-skb->data)+len))
+		goto fail;
+
+	opt->auth = skb->h.raw - skb->nh.raw;
 	skb->h.raw += len;
-	return &hdr->nexthdr;
+	return opt->auth;
+
+fail:
+	kfree_skb(skb);
+	return -1;
 }
 
 /* This list MUST NOT contain entry for NEXTHDR_HOP.
@@ -408,22 +436,22 @@
 	{-1,			NULL}
 };
 
-u8 *ipv6_parse_exthdrs(struct sk_buff **skb_in, u8 *nhptr)
+int ipv6_parse_exthdrs(struct sk_buff **skb_in, int nhoff)
 {
 	struct hdrtype_proc *hdrt;
-	u8 nexthdr = *nhptr;
+	u8 nexthdr = (*skb_in)->nh.raw[nhoff];
 
 restart:
 	for (hdrt=hdrproc_lst; hdrt->type >= 0; hdrt++) {
 		if (hdrt->type == nexthdr) {
-			if ((nhptr = hdrt->func(skb_in, nhptr)) != NULL) {
-				nexthdr = *nhptr;
+			if ((nhoff = hdrt->func(skb_in, nhoff)) >= 0) {
+				nexthdr = (*skb_in)->nh.raw[nhoff];
 				goto restart;
 			}
-			return NULL;
+			return -1;
 		}
 	}
-	return nhptr;
+	return nhoff;
 }
 
 
@@ -433,37 +461,37 @@
 
 /* Router Alert as of draft-ietf-ipngwg-ipv6router-alert-04 */
 
-static int ipv6_hop_ra(struct sk_buff *skb, u8 *ptr)
+static int ipv6_hop_ra(struct sk_buff *skb, int optoff)
 {
-	if (ptr[1] == 2) {
-		((struct inet6_skb_parm*)skb->cb)->ra = ptr - skb->nh.raw;
+	if (skb->nh.raw[optoff+1] == 2) {
+		((struct inet6_skb_parm*)skb->cb)->ra = optoff;
 		return 1;
 	}
 	if (net_ratelimit())
-		printk(KERN_DEBUG "ipv6_hop_ra: wrong RA length %d\n", ptr[1]);
+		printk(KERN_DEBUG "ipv6_hop_ra: wrong RA length %d\n", skb->nh.raw[optoff+1]);
 	kfree_skb(skb);
 	return 0;
 }
 
 /* Jumbo payload */
 
-static int ipv6_hop_jumbo(struct sk_buff *skb, u8 *ptr)
+static int ipv6_hop_jumbo(struct sk_buff *skb, int optoff)
 {
 	u32 pkt_len;
 
-	if (ptr[1] != 4 || ((ptr-skb->nh.raw)&3) != 2) {
+	if (skb->nh.raw[optoff+1] != 4 || (optoff&3) != 2) {
 		if (net_ratelimit())
-			printk(KERN_DEBUG "ipv6_hop_jumbo: wrong jumbo opt length/alignment %d\n", ptr[1]);
+			printk(KERN_DEBUG "ipv6_hop_jumbo: wrong jumbo opt length/alignment %d\n", skb->nh.raw[optoff+1]);
 		goto drop;
 	}
 
-	pkt_len = ntohl(*(u32*)(ptr+2));
+	pkt_len = ntohl(*(u32*)(skb->nh.raw+optoff+2));
 	if (pkt_len < 0x10000) {
-		icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, ptr+2);
+		icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, optoff+2);
 		return 0;
 	}
 	if (skb->nh.ipv6h->payload_len) {
-		icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, ptr);
+		icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, optoff);
 		return 0;
 	}
 
@@ -471,7 +499,11 @@
 		IP6_INC_STATS_BH(Ip6InTruncatedPkts);
 		goto drop;
 	}
-	skb_trim(skb, pkt_len + sizeof(struct ipv6hdr));
+	if (pkt_len + sizeof(struct ipv6hdr) < skb->len) {
+		__pskb_trim(skb, pkt_len + sizeof(struct ipv6hdr));
+		if (skb->ip_summed == CHECKSUM_HW)
+			skb->ip_summed = CHECKSUM_NONE;
+	}
 	return 1;
 
 drop:
@@ -485,12 +517,12 @@
 	{-1,			NULL}
 };
 
-u8 * ipv6_parse_hopopts(struct sk_buff *skb, u8 *nhptr)
+int ipv6_parse_hopopts(struct sk_buff *skb, int nhoff)
 {
 	((struct inet6_skb_parm*)skb->cb)->hop = sizeof(struct ipv6hdr);
-	if (ip6_parse_tlv(tlvprochopopt_lst, skb, nhptr))
-		return nhptr+((nhptr[1]+1)<<3);
-	return NULL;
+	if (ip6_parse_tlv(tlvprochopopt_lst, skb))
+		return sizeof(struct ipv6hdr);
+	return -1;
 }
 
 /*
@@ -684,7 +716,7 @@
  * find out if nexthdr is a well-known extension header or a protocol
  */
 
-static __inline__ int ipv6_ext_hdr(u8 nexthdr)
+int ipv6_ext_hdr(u8 nexthdr)
 {
 	/* 
 	 * find out if nexthdr is an extension header or a protocol
@@ -739,33 +771,36 @@
  * --ANK (980726)
  */
 
-u8 *ipv6_skip_exthdr(struct ipv6_opt_hdr *hdr, u8 *nexthdrp, int len)
+int ipv6_skip_exthdr(struct sk_buff *skb, int start, u8 *nexthdrp, int len)
 {
 	u8 nexthdr = *nexthdrp;
 
 	while (ipv6_ext_hdr(nexthdr)) {
-		int hdrlen; 
+		struct ipv6_opt_hdr hdr;
+		int hdrlen;
 
-		if (len < sizeof(struct ipv6_opt_hdr))
-			return NULL;
+		if (len < (int)sizeof(struct ipv6_opt_hdr))
+			return -1;
 		if (nexthdr == NEXTHDR_NONE)
-			return NULL;
+			return -1;
+		if (skb_copy_bits(skb, start, &hdr, sizeof(hdr)))
+			BUG();
 		if (nexthdr == NEXTHDR_FRAGMENT) {
-			struct frag_hdr *fhdr = (struct frag_hdr *) hdr;
+			struct frag_hdr *fhdr = (struct frag_hdr *) &hdr;
 			if (ntohs(fhdr->frag_off) & ~0x7)
 				break;
 			hdrlen = 8;
 		} else if (nexthdr == NEXTHDR_AUTH)
-			hdrlen = (hdr->hdrlen+2)<<2; 
+			hdrlen = (hdr.hdrlen+2)<<2; 
 		else
-			hdrlen = ipv6_optlen(hdr); 
+			hdrlen = ipv6_optlen(&hdr); 
 
-		nexthdr = hdr->nexthdr;
-		hdr = (struct ipv6_opt_hdr *) ((u8*)hdr + hdrlen);
+		nexthdr = hdr.nexthdr;
 		len -= hdrlen;
+		start += hdrlen;
 	}
 
 	*nexthdrp = nexthdr;
-	return (u8*)hdr;
+	return start;
 }
 

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)