summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHideaki Yoshifuji <yoshfuji@linux-ipv6.org>2002-09-26 00:34:07 -0700
committerDavid S. Miller <davem@nuts.ninka.net>2002-09-26 00:34:07 -0700
commit464f9b3f5830aa3b74e6d1491193d29f1074151d (patch)
tree440c35e02c0e068ca8d5bc3a9d7806a4ffe2c963
parent5568a809583c69fab1cb22d0e9dcd3c93a118167 (diff)
[IPv6]: Verify ND options properly.
-rw-r--r--include/net/ndisc.h19
-rw-r--r--net/ipv6/ndisc.c300
2 files changed, 208 insertions, 111 deletions
diff --git a/include/net/ndisc.h b/include/net/ndisc.h
index 407268c78def..8748b657ee13 100644
--- a/include/net/ndisc.h
+++ b/include/net/ndisc.h
@@ -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);
diff --git a/net/ipv6/ndisc.c b/net/ipv6/ndisc.c
index 3f26f406029a..72288a01af49 100644
--- a/net/ipv6/ndisc.c
+++ b/net/ipv6/ndisc.c
@@ -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);