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
- Lines: 453
- Date:
Thu Nov 28 15:53:15 2002
- Orig file:
linux-2.4.19/net/ipv6/ndisc.c
- Orig date:
Fri Aug 2 17:39:46 2002
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)