Commit 464f9b3f authored by Hideaki Yoshifuji's avatar Hideaki Yoshifuji Committed by David S. Miller

[IPv6]: Verify ND options properly.

parent 5568a809
......@@ -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);
......
......@@ -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);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment