]> git.neil.brown.name Git - history.git/commitdiff
[IPv6]: Verify ND options properly.
authorHideaki Yoshifuji <yoshfuji@linux-ipv6.org>
Thu, 26 Sep 2002 07:34:07 +0000 (00:34 -0700)
committerDavid S. Miller <davem@nuts.ninka.net>
Thu, 26 Sep 2002 07:34:07 +0000 (00:34 -0700)
include/net/ndisc.h
net/ipv6/ndisc.c

index 407268c78def142aa7d951b52ae2e9a0b4bac666..8748b657ee1320a41353744dba6b33060d90a41d 100644 (file)
@@ -51,6 +51,25 @@ struct ra_msg {
        __u32                   retrans_timer;
 };
 
+struct nd_opt_hdr {
+       __u8            nd_opt_type;
+       __u8            nd_opt_len;
+} __attribute__((__packed__));
+
+struct ndisc_options {
+       struct nd_opt_hdr *nd_opt_array[7];
+       struct nd_opt_hdr *nd_opt_piend;
+};
+
+#define nd_opts_src_lladdr     nd_opt_array[ND_OPT_SOURCE_LL_ADDR]
+#define nd_opts_tgt_lladdr     nd_opt_array[ND_OPT_TARGET_LL_ADDR]
+#define nd_opts_pi             nd_opt_array[ND_OPT_PREFIX_INFO]
+#define nd_opts_pi_end         nd_opt_piend
+#define nd_opts_rh             nd_opt_array[ND_OPT_REDIRECT_HDR]
+#define nd_opts_mtu            nd_opt_array[ND_OPT_MTU]
+
+extern struct nd_opt_hdr *ndisc_next_option(struct nd_opt_hdr *cur, struct nd_opt_hdr *end);
+extern struct ndisc_options *ndisc_parse_options(u8 *opt, int opt_len, struct ndisc_options *ndopts);
 
 extern int                     ndisc_init(struct net_proto_family *ops);
 
index 3f26f406029a20af7c78225689aa290da5b0cef5..72288a01af49da38cf9e77592f3c3fbdb2354c15 100644 (file)
@@ -159,6 +159,67 @@ static u8 *ndisc_fill_option(u8 *opt, int type, void *data, int data_len)
        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) {
@@ -489,27 +550,6 @@ void ndisc_send_rs(struct net_device *dev, struct in6_addr *saddr,
 }
                   
 
-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)
 {
        /*
@@ -547,13 +587,6 @@ static void ndisc_solicit(struct neighbour *neigh, struct sk_buff *skb)
        }
 }
 
-
-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;
@@ -561,6 +594,7 @@ static void ndisc_router_discovery(struct sk_buff *skb)
        struct inet6_dev *in6_dev;
        struct rt6_info *rt;
        int lifetime;
+       struct ndisc_options ndopts;
        int optlen;
 
        __u8 * opt = (__u8 *)(ra_msg + 1);
@@ -592,6 +626,13 @@ static void ndisc_router_discovery(struct sk_buff *skb)
                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
@@ -675,63 +716,60 @@ static void ndisc_router_discovery(struct sk_buff *skb)
         *      Process options.
         */
 
-        while (optlen > 0) {
-                int len = (opt[1] << 3);
-
-               if (len == 0) {
-                       ND_PRINTK0("RA: opt has 0 len\n");
-                       break;
+       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);
+       }
 
-                switch(*opt) {
-                case ND_OPT_SOURCE_LL_ADDR:
+       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);
+               }
+       }
 
-                       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;
+       if (ndopts.nd_opts_mtu) {
+               u32 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;
-                               }
+               memcpy(&mtu, ((u8*)(ndopts.nd_opts_mtu+1))+2, sizeof(mtu));
+               mtu = ntohl(mtu);
 
-                               if (in6_dev->cnf.mtu6 != mtu) {
-                                       in6_dev->cnf.mtu6 = mtu;
+               if (mtu < IPV6_MIN_MTU || mtu > skb->dev->mtu) {
+                       if (net_ratelimit()) {
+                               ND_PRINTK0("NDISC: router announcement with mtu = %d\n",
+                                          mtu);
+                       }
+               }
 
-                                       if (rt)
-                                               rt->u.dst.pmtu = mtu;
+               if (in6_dev->cnf.mtu6 != mtu) {
+                       in6_dev->cnf.mtu6 = mtu;
 
-                                       rt6_mtu_change(skb->dev, mtu);
-                               }
-                       }
-                        break;
+                       if (rt)
+                               rt->u.dst.pmtu = mtu;
 
-               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);
@@ -745,7 +783,10 @@ static void ndisc_redirect_rcv(struct sk_buff *skb)
        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())
@@ -793,6 +834,24 @@ static void ndisc_redirect_rcv(struct sk_buff *skb)
         *      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 */
 
        /*
@@ -801,7 +860,7 @@ static void ndisc_redirect_rcv(struct sk_buff *skb)
 
        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
@@ -927,31 +986,6 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh,
        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);
@@ -983,13 +1017,15 @@ int ndisc_rcv(struct sk_buff *skb)
                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");
@@ -1002,6 +1038,22 @@ int ndisc_rcv(struct sk_buff *skb)
                        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.
@@ -1068,7 +1120,7 @@ int ndisc_rcv(struct sk_buff *skb)
                                 *      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, 
@@ -1098,7 +1150,8 @@ int ndisc_rcv(struct sk_buff *skb)
                                        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,
@@ -1118,8 +1171,16 @@ int ndisc_rcv(struct sk_buff *skb)
                        
                }
                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");
@@ -1138,6 +1199,20 @@ int ndisc_rcv(struct sk_buff *skb)
                        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);
@@ -1175,10 +1250,13 @@ int ndisc_rcv(struct sk_buff *skb)
                                        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);