patch-2.4.20 linux-2.4.20/net/ipv6/ndisc.c

Next file: linux-2.4.20/net/ipv6/netfilter/Config.in
Previous file: linux-2.4.20/net/ipv6/ip6_output.c
Back to the patch index
Back to the overall index

diff -urN linux-2.4.19/net/ipv6/ndisc.c linux-2.4.20/net/ipv6/ndisc.c
@@ -22,6 +22,7 @@
  *	Alexey Kuznetsov		:	state machine reworked
  *						and moved to net/core.
  *	Pekka Savola			:	RFC2461 validation
+ *	YOSHIFUJI Hideaki @USAGI	:	Verify ND options properly
  */
 
 /* Set to 3 to get tracing... */
@@ -154,6 +155,67 @@
 	return opt + space;
 }
 
+struct nd_opt_hdr *ndisc_next_option(struct nd_opt_hdr *cur,
+				     struct nd_opt_hdr *end)
+{
+	int type;
+	if (!cur || !end || cur >= end)
+		return NULL;
+	type = cur->nd_opt_type;
+	do {
+		cur = ((void *)cur) + (cur->nd_opt_len << 3);
+	} while(cur < end && cur->nd_opt_type != type);
+	return (cur <= end && cur->nd_opt_type == type ? cur : NULL);
+}
+
+struct ndisc_options *ndisc_parse_options(u8 *opt, int opt_len,
+					  struct ndisc_options *ndopts)
+{
+	struct nd_opt_hdr *nd_opt = (struct nd_opt_hdr *)opt;
+
+	if (!nd_opt || opt_len < 0 || !ndopts)
+		return NULL;
+	memset(ndopts, 0, sizeof(*ndopts));
+	while (opt_len) {
+		int l;
+		if (opt_len < sizeof(struct nd_opt_hdr))
+			return NULL;
+		l = nd_opt->nd_opt_len << 3;
+		if (opt_len < l || l == 0)
+			return NULL;
+		switch (nd_opt->nd_opt_type) {
+		case ND_OPT_SOURCE_LL_ADDR:
+		case ND_OPT_TARGET_LL_ADDR:
+		case ND_OPT_MTU:
+		case ND_OPT_REDIRECT_HDR:
+			if (ndopts->nd_opt_array[nd_opt->nd_opt_type]) {
+				ND_PRINTK2((KERN_WARNING
+					    "ndisc_parse_options(): duplicated ND6 option found: type=%d\n",
+					    nd_opt->nd_opt_type));
+			} else {
+				ndopts->nd_opt_array[nd_opt->nd_opt_type] = nd_opt;
+			}
+			break;
+		case ND_OPT_PREFIX_INFO:
+			ndopts->nd_opts_pi_end = nd_opt;
+			if (ndopts->nd_opt_array[nd_opt->nd_opt_type] == 0)
+				ndopts->nd_opt_array[nd_opt->nd_opt_type] = nd_opt;
+			break;
+		default:
+			/*
+			 * Unknown options must be silently ignored,
+			 * to accomodate future extension to the protocol.
+			 */
+			ND_PRINTK2(KERN_WARNING
+				   "ndisc_parse_options(): ignored unsupported option; type=%d, len=%d\n",
+				   nd_opt->nd_opt_type, nd_opt->nd_opt_len);
+		}
+		opt_len -= l;
+		nd_opt = ((void *)nd_opt) + l;
+	}
+	return ndopts;
+}
+
 int ndisc_mc_map(struct in6_addr *addr, char *buf, struct net_device *dev, int dir)
 {
 	switch (dev->type) {
@@ -484,27 +546,6 @@
 }
 		   
 
-static u8 * ndisc_find_option(u8 *opt, int opt_len, int len, int option)
-{
-	while (opt_len <= len) {
-		int l = opt[1]<<3;
-
-		if (opt[0] == option && l >= opt_len)
-			return opt + 2;
-
-		if (l == 0) {
-			if (net_ratelimit())
-			    printk(KERN_WARNING "ndisc: option has 0 len\n");
-			return NULL;
-		}
-
-		opt += l;
-		len -= l;
-	}
-	return NULL;
-}
-
-
 static void ndisc_error_report(struct neighbour *neigh, struct sk_buff *skb)
 {
 	/*
@@ -542,13 +583,6 @@
 	}
 }
 
-
-static void ndisc_update(struct neighbour *neigh, u8* opt, int len, int type)
-{
-	opt = ndisc_find_option(opt, neigh->dev->addr_len+2, len, type);
-	neigh_update(neigh, opt, NUD_STALE, 1, 1);
-}
-
 static void ndisc_router_discovery(struct sk_buff *skb)
 {
         struct ra_msg *ra_msg = (struct ra_msg *) skb->h.raw;
@@ -556,6 +590,7 @@
 	struct inet6_dev *in6_dev;
 	struct rt6_info *rt;
 	int lifetime;
+	struct ndisc_options ndopts;
 	int optlen;
 
 	__u8 * opt = (__u8 *)(ra_msg + 1);
@@ -587,6 +622,13 @@
 		return;
 	}
 
+	if (!ndisc_parse_options(opt, optlen, &ndopts)) {
+		if (net_ratelimit())
+			ND_PRINTK2(KERN_WARNING
+				   "ICMP6 RA: invalid ND option, ignored.\n");
+		return;
+	}
+
 	if (in6_dev->if_flags & IF_RS_SENT) {
 		/*
 		 *	flag that an RA was received after an RS was sent
@@ -670,63 +712,60 @@
 	 *	Process options.
 	 */
 
-        while (optlen > 0) {
-                int len = (opt[1] << 3);
+	if (rt && (neigh = rt->rt6i_nexthop) != NULL) {
+		u8 *lladdr = NULL;
+		int lladdrlen;
+		if (ndopts.nd_opts_src_lladdr) {
+			lladdr = (u8*)((ndopts.nd_opts_src_lladdr)+1);
+			lladdrlen = ndopts.nd_opts_src_lladdr->nd_opt_len << 3;
+			if (lladdrlen != NDISC_OPT_SPACE(skb->dev->addr_len)) {
+				if (net_ratelimit())
+					ND_PRINTK2(KERN_WARNING
+						   "ICMP6 RA: Invalid lladdr length.\n");
+				goto out;
+			}
+		}
+		neigh_update(neigh, lladdr, NUD_STALE, 1, 1);
+	}
 
-		if (len == 0) {
-			ND_PRINTK0("RA: opt has 0 len\n");
-			break;
+	if (ndopts.nd_opts_pi) {
+		struct nd_opt_hdr *p;
+		for (p = ndopts.nd_opts_pi;
+		     p;
+		     p = ndisc_next_option(p, ndopts.nd_opts_pi_end)) {
+			addrconf_prefix_rcv(skb->dev, (u8*)p, (p->nd_opt_len) << 3);
 		}
+	}
 
-                switch(*opt) {
-                case ND_OPT_SOURCE_LL_ADDR:
+	if (ndopts.nd_opts_mtu) {
+		u32 mtu;
 
-			if (rt == NULL)
-				break;
-			
-			if ((neigh = rt->rt6i_nexthop) != NULL &&
-			    skb->dev->addr_len + 2 >= len)
-				neigh_update(neigh, opt+2, NUD_STALE, 1, 1);
-			break;
+		memcpy(&mtu, ((u8*)(ndopts.nd_opts_mtu+1))+2, sizeof(mtu));
+		mtu = ntohl(mtu);
 
-                case ND_OPT_PREFIX_INFO:
-			addrconf_prefix_rcv(skb->dev, opt, len);
-                        break;
-
-                case ND_OPT_MTU:
-			{
-				int mtu;
-				
-				mtu = htonl(*(__u32 *)(opt+4));
-
-				if (mtu < IPV6_MIN_MTU || mtu > skb->dev->mtu) {
-					ND_PRINTK0("NDISC: router "
-						   "announcement with mtu = %d\n",
-						   mtu);
-					break;
-				}
+		if (mtu < IPV6_MIN_MTU || mtu > skb->dev->mtu) {
+			if (net_ratelimit()) {
+				ND_PRINTK0("NDISC: router announcement with mtu = %d\n",
+					   mtu);
+			}
+		}
 
-				if (in6_dev->cnf.mtu6 != mtu) {
-					in6_dev->cnf.mtu6 = mtu;
+		if (in6_dev->cnf.mtu6 != mtu) {
+			in6_dev->cnf.mtu6 = mtu;
 
-					if (rt)
-						rt->u.dst.pmtu = mtu;
+			if (rt)
+				rt->u.dst.pmtu = mtu;
 
-					rt6_mtu_change(skb->dev, mtu);
-				}
-			}
-                        break;
-
-		case ND_OPT_TARGET_LL_ADDR:
-		case ND_OPT_REDIRECT_HDR:
-			ND_PRINTK0("got illegal option with RA");
-			break;
-		default:
-			ND_PRINTK0("unkown option in RA\n");
-                };
-                optlen -= len;
-                opt += len;
-        }
+			rt6_mtu_change(skb->dev, mtu);
+		}
+	}
+			
+	if (ndopts.nd_opts_tgt_lladdr || ndopts.nd_opts_rh) {
+		if (net_ratelimit())
+			ND_PRINTK0(KERN_WARNING
+				   "ICMP6 RA: got illegal option with RA");
+	}
+out:
 	if (rt)
 		dst_release(&rt->u.dst);
 	in6_dev_put(in6_dev);
@@ -740,7 +779,10 @@
 	struct in6_addr *target;	/* new first hop to destination */
 	struct neighbour *neigh;
 	int on_link = 0;
+	struct ndisc_options ndopts;
 	int optlen;
+	u8 *lladdr = NULL;
+	int lladdrlen;
 
 	if (!(ipv6_addr_type(&skb->nh.ipv6h->saddr) & IPV6_ADDR_LINKLOCAL)) {
 		if (net_ratelimit())
@@ -788,6 +830,24 @@
 	 *	first-hop router for the specified ICMP Destination Address.
 	 */
 		
+	if (!ndisc_parse_options((u8*)(dest + 1), optlen, &ndopts)) {
+		if (net_ratelimit())
+			ND_PRINTK2(KERN_WARNING
+				   "ICMP6 Redirect: invalid ND options, rejected.\n");
+		in6_dev_put(in6_dev);
+		return;
+	}
+	if (ndopts.nd_opts_tgt_lladdr) {
+		lladdr = (u8*)(ndopts.nd_opts_tgt_lladdr + 1);
+		lladdrlen = ndopts.nd_opts_tgt_lladdr->nd_opt_len << 3;
+		if (lladdrlen != NDISC_OPT_SPACE(skb->dev->addr_len)) {
+			if (net_ratelimit())
+				ND_PRINTK2(KERN_WARNING
+					   "ICMP6 Redirect: invalid lladdr length.\n");
+			in6_dev_put(in6_dev);
+			return;
+		}
+	}
 	/* passed validation tests */
 
 	/*
@@ -796,7 +856,7 @@
 
 	neigh = __neigh_lookup(&nd_tbl, target, skb->dev, 1);
 	if (neigh) {
-		ndisc_update(neigh, (u8*)(dest + 1), optlen, ND_OPT_TARGET_LL_ADDR);
+		neigh_update(neigh, lladdr, NUD_STALE, 1, 1);
 		if (neigh->nud_state&NUD_VALID)
 			rt6_redirect(dest, &skb->nh.ipv6h->saddr, neigh, on_link);
 		else
@@ -922,31 +982,6 @@
 	ICMP6_INC_STATS(Icmp6OutMsgs);
 }
 
-static __inline__ struct neighbour *
-ndisc_recv_ns(struct in6_addr *saddr, struct sk_buff *skb)
-{
-	u8 *opt;
-
-	opt = skb->h.raw;
-	opt += sizeof(struct nd_msg);
-	opt = ndisc_find_option(opt, skb->dev->addr_len+2, skb->tail - opt, ND_OPT_SOURCE_LL_ADDR);
-
-	return neigh_event_ns(&nd_tbl, opt, saddr, skb->dev);
-}
-
-static __inline__ int ndisc_recv_na(struct neighbour *neigh, struct sk_buff *skb)
-{
-	u8 *opt;
-	struct nd_msg *msg = (struct nd_msg*) skb->h.raw;
-
-	opt = ndisc_find_option(msg->opt, skb->dev->addr_len+2,
-				skb->tail - msg->opt, ND_OPT_TARGET_LL_ADDR);
-
-	return neigh_update(neigh, opt,
-			    msg->icmph.icmp6_solicited ? NUD_REACHABLE : NUD_STALE,
-			    msg->icmph.icmp6_override, 1);
-}
-
 static void pndisc_redo(struct sk_buff *skb)
 {
 	ndisc_rcv(skb);
@@ -978,13 +1013,15 @@
 		return 0;
 	}
 
-	/* XXX: RFC2461 Validation of [all ndisc messages]:
-	 *	All included ndisc options MUST be of non-zero length
-	 *	(Some checking in ndisc_find_option)
-	 */
-
 	switch (msg->icmph.icmp6_type) {
 	case NDISC_NEIGHBOUR_SOLICITATION:
+	    {
+		struct nd_msg *msg = (struct nd_msg *)skb->h.raw;
+		u8 *lladdr = NULL;
+		int lladdrlen = 0;
+		u32 ndoptlen = skb->tail - msg->opt;
+		struct ndisc_options ndopts;
+
 		if (skb->len < sizeof(struct nd_msg)) {
 			if (net_ratelimit())
 				printk(KERN_WARNING "ICMP NS: packet too short\n");
@@ -997,6 +1034,22 @@
 			return 0;
 		}
 
+		if (!ndisc_parse_options(msg->opt, ndoptlen, &ndopts)) {
+			if (net_ratelimit())
+				printk(KERN_WARNING "ICMP NS: invalid ND option, ignored.\n");
+			return 0;
+		}
+
+		if (ndopts.nd_opts_src_lladdr) {
+			lladdr = (u8*)(ndopts.nd_opts_src_lladdr + 1);
+			lladdrlen = ndopts.nd_opts_src_lladdr->nd_opt_len << 3;
+			if (lladdrlen != NDISC_OPT_SPACE(skb->dev->addr_len)) {
+				if (net_ratelimit())
+					printk(KERN_WARNING "ICMP NS: bad lladdr length.\n");
+				return 0;
+			}
+		}
+
 		/* XXX: RFC2461 7.1.1:
 		 * 	If the IP source address is the unspecified address, there
 		 *	MUST NOT be source link-layer address option in the message.
@@ -1063,7 +1116,7 @@
 				 *	for the source adddress
 				 */
 
-				neigh = ndisc_recv_ns(saddr, skb);
+				neigh = neigh_event_ns(&nd_tbl, lladdr, saddr, skb->dev);
 
 				if (neigh || !dev->hard_header) {
 					ndisc_send_na(dev, neigh, saddr, &ifp->addr, 
@@ -1093,7 +1146,8 @@
 					else
 						nd_tbl.stats.rcv_probes_ucast++;
 
-					neigh = ndisc_recv_ns(saddr, skb);
+					
+					neigh = neigh_event_ns(&nd_tbl, lladdr, saddr, skb->dev);
 
 					if (neigh) {
 						ndisc_send_na(dev, neigh, saddr, &msg->target,
@@ -1113,8 +1167,16 @@
 			
 		}
 		return 0;
+	    }
 
 	case NDISC_NEIGHBOUR_ADVERTISEMENT:
+	    {
+		struct nd_msg *msg = (struct nd_msg *)skb->h.raw;
+		u8 *lladdr = NULL;
+		int lladdrlen = 0;
+		u32 ndoptlen = skb->tail - msg->opt;
+		struct ndisc_options ndopts;
+
 		if (skb->len < sizeof(struct nd_msg)) {
 			if (net_ratelimit())
 				printk(KERN_WARNING "ICMP NA: packet too short\n");
@@ -1133,6 +1195,20 @@
 			return 0;
 		}
 		
+		if (!ndisc_parse_options(msg->opt, ndoptlen, &ndopts)) {
+			if (net_ratelimit())
+				printk(KERN_WARNING "ICMP NS: invalid ND option, ignored.\n");
+			return 0;
+		}
+		if (ndopts.nd_opts_tgt_lladdr) {
+			lladdr = (u8*)(ndopts.nd_opts_tgt_lladdr + 1);
+			lladdrlen = ndopts.nd_opts_tgt_lladdr->nd_opt_len << 3;
+			if (lladdrlen != NDISC_OPT_SPACE(skb->dev->addr_len)) {
+				if (net_ratelimit())
+					printk(KERN_WARNING "NDISC NA: invalid lladdr length.\n");
+				return 0;
+			}
+		}
 		if ((ifp = ipv6_get_ifaddr(&msg->target, dev))) {
 			if (ifp->flags & IFA_F_TENTATIVE) {
 				addrconf_dad_failure(ifp);
@@ -1159,9 +1235,6 @@
 					struct rt6_info *rt;
 					rt = rt6_get_dflt_router(saddr, skb->dev);
 					if (rt) {
-						/* It is safe only because
-						   we aer in BH */
-						dst_release(&rt->u.dst);
 						ip6_del_rt(rt);
 					}
 				}
@@ -1170,10 +1243,13 @@
 					neigh->flags |= NTF_ROUTER;
 			}
 
-			ndisc_recv_na(neigh, skb);
+			neigh_update(neigh, lladdr,
+				     msg->icmph.icmp6_solicited ? NUD_REACHABLE : NUD_STALE,
+				     msg->icmph.icmp6_override, 1);
 			neigh_release(neigh);
 		}
 		break;
+	    }
 
 	case NDISC_ROUTER_ADVERTISEMENT:
 		ndisc_router_discovery(skb);

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