X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=net%2Fipv6%2Fnetfilter%2Fip6_tables.c;h=6a06de74a30f1bfab4480e7ba79bc3ed16c678d4;hb=6a77f38946aaee1cd85eeec6cf4229b204c15071;hp=0cef15b866f522be676df732312b288ef0d45670;hpb=c7b5ebbddf7bcd3651947760f423e3783bbe6573;p=linux-2.6.git diff --git a/net/ipv6/netfilter/ip6_tables.c b/net/ipv6/netfilter/ip6_tables.c index 0cef15b86..6a06de74a 100644 --- a/net/ipv6/netfilter/ip6_tables.c +++ b/net/ipv6/netfilter/ip6_tables.c @@ -158,14 +158,15 @@ ip6t_ext_hdr(u8 nexthdr) /* Returns whether matches rule or not. */ static inline int ip6_packet_match(const struct sk_buff *skb, - const struct ipv6hdr *ipv6, const char *indev, const char *outdev, const struct ip6t_ip6 *ip6info, - int isfrag) + unsigned int *protoff, + int *fragoff) { size_t i; unsigned long ret; + const struct ipv6hdr *ipv6 = skb->nh.ipv6h; #define FWINV(bool,invflg) ((bool) ^ !!(ip6info->invflags & invflg)) @@ -216,9 +217,10 @@ ip6_packet_match(const struct sk_buff *skb, /* look for the desired protocol header */ if((ip6info->flags & IP6T_F_PROTO)) { u_int8_t currenthdr = ipv6->nexthdr; - struct ipv6_opt_hdr *hdrptr; + struct ipv6_opt_hdr _hdr, *hp; u_int16_t ptr; /* Header offset in skb */ u_int16_t hdrlen; /* Header */ + u_int16_t _fragoff = 0, *fp = NULL; ptr = IPV6_HDR_LEN; @@ -232,25 +234,43 @@ ip6_packet_match(const struct sk_buff *skb, * we will change the return 0 to 1*/ if ((currenthdr == IPPROTO_NONE) || (currenthdr == IPPROTO_ESP)) - return 0; + break; - hdrptr = (struct ipv6_opt_hdr *)(skb->data + ptr); + hp = skb_header_pointer(skb, ptr, sizeof(_hdr), &_hdr); + BUG_ON(hp == NULL); /* Size calculation */ if (currenthdr == IPPROTO_FRAGMENT) { + fp = skb_header_pointer(skb, + ptr+offsetof(struct frag_hdr, + frag_off), + sizeof(_fragoff), + &_fragoff); + if (fp == NULL) + return 0; + + _fragoff = ntohs(*fp) & ~0x7; hdrlen = 8; } else if (currenthdr == IPPROTO_AH) - hdrlen = (hdrptr->hdrlen+2)<<2; + hdrlen = (hp->hdrlen+2)<<2; else - hdrlen = ipv6_optlen(hdrptr); + hdrlen = ipv6_optlen(hp); - currenthdr = hdrptr->nexthdr; + currenthdr = hp->nexthdr; ptr += hdrlen; /* ptr is too large */ if ( ptr > skb->len ) return 0; + if (_fragoff) { + if (ip6t_ext_hdr(currenthdr)) + return 0; + break; + } } + *protoff = ptr; + *fragoff = _fragoff; + /* currenthdr contains the protocol header */ dprintf("Packet protocol %hi ?= %s%hi.\n", @@ -292,9 +312,9 @@ ip6_checkentry(const struct ip6t_ip6 *ipv6) static unsigned int ip6t_error(struct sk_buff **pskb, - unsigned int hooknum, const struct net_device *in, const struct net_device *out, + unsigned int hooknum, const void *targinfo, void *userinfo) { @@ -310,13 +330,12 @@ int do_match(struct ip6t_entry_match *m, const struct net_device *in, const struct net_device *out, int offset, - const void *hdr, - u_int16_t datalen, + unsigned int protoff, int *hotdrop) { /* Stop iteration if it doesn't match */ if (!m->u.kernel.match->match(skb, in, out, m->data, - offset, hdr, datalen, hotdrop)) + offset, protoff, hotdrop)) return 1; else return 0; @@ -338,10 +357,8 @@ ip6t_do_table(struct sk_buff **pskb, void *userdata) { static const char nulldevname[IFNAMSIZ]; - u_int16_t offset = 0; - struct ipv6hdr *ipv6; - void *protohdr; - u_int16_t datalen; + int offset = 0; + unsigned int protoff = 0; int hotdrop = 0; /* Initializing verdict to NF_DROP keeps gcc happy. */ unsigned int verdict = NF_DROP; @@ -349,14 +366,7 @@ ip6t_do_table(struct sk_buff **pskb, void *table_base; struct ip6t_entry *e, *back; - /* FIXME: Push down to extensions --RR */ - if (skb_is_nonlinear(*pskb) && skb_linearize(*pskb, GFP_ATOMIC) != 0) - return NF_DROP; - /* Initialization */ - ipv6 = (*pskb)->nh.ipv6h; - protohdr = (u_int32_t *)((char *)ipv6 + IPV6_HDR_LEN); - datalen = (*pskb)->len - IPV6_HDR_LEN; indev = in ? in->name : nulldevname; outdev = out ? out->name : nulldevname; @@ -393,17 +403,19 @@ ip6t_do_table(struct sk_buff **pskb, IP_NF_ASSERT(e); IP_NF_ASSERT(back); (*pskb)->nfcache |= e->nfcache; - if (ip6_packet_match(*pskb, ipv6, indev, outdev, - &e->ipv6, offset)) { + if (ip6_packet_match(*pskb, indev, outdev, &e->ipv6, + &protoff, &offset)) { struct ip6t_entry_target *t; if (IP6T_MATCH_ITERATE(e, do_match, *pskb, in, out, - offset, protohdr, - datalen, &hotdrop) != 0) + offset, protoff, &hotdrop) != 0) goto no_match; - ADD_COUNTER(e->counters, ntohs(ipv6->payload_len) + IPV6_HDR_LEN, 1); + ADD_COUNTER(e->counters, + ntohs((*pskb)->nh.ipv6h->payload_len) + + IPV6_HDR_LEN, + 1); t = ip6t_get_target(e); IP_NF_ASSERT(t->u.kernel.target); @@ -443,8 +455,8 @@ ip6t_do_table(struct sk_buff **pskb, = 0xeeeeeeec; #endif verdict = t->u.kernel.target->target(pskb, - hook, in, out, + hook, t->data, userdata); @@ -459,11 +471,6 @@ ip6t_do_table(struct sk_buff **pskb, ((struct ip6t_entry *)table_base)->comefrom = 0x57acc001; #endif - /* Target might have changed stuff. */ - ipv6 = (*pskb)->nh.ipv6h; - protohdr = (u_int32_t *)((void *)ipv6 + IPV6_HDR_LEN); - datalen = (*pskb)->len - IPV6_HDR_LEN; - if (verdict == IP6T_CONTINUE) e = (void *)e + e->next_offset; else @@ -554,7 +561,7 @@ find_match_lock(const char *name, int *error, struct semaphore *mutex) return find_inlist_lock(&ip6t_match, name, "ip6t_", error, mutex); } -struct ip6t_target * +static struct ip6t_target * ip6t_find_target_lock(const char *name, int *error, struct semaphore *mutex) { return find_inlist_lock(&ip6t_target, name, "ip6t_", error, mutex); @@ -1211,11 +1218,12 @@ do_replace(void __user *user, unsigned int len) IP6T_ENTRY_ITERATE(oldinfo->entries, oldinfo->size, cleanup_entry,NULL); vfree(oldinfo); /* Silent error: too late now. */ - copy_to_user(tmp.counters, counters, - sizeof(struct ip6t_counters) * tmp.num_counters); + if (copy_to_user(tmp.counters, counters, + sizeof(struct ip6t_counters) * tmp.num_counters) != 0) + ret = -EFAULT; vfree(counters); up(&ip6t_mutex); - return 0; + return ret; put_module: module_put(t->me); @@ -1361,7 +1369,7 @@ do_ip6t_get_ctl(struct sock *sk, int cmd, void __user *user, int *len) sizeof(info.underflow)); info.num_entries = t->private->number; info.size = t->private->size; - strcpy(info.name, name); + memcpy(info.name, name, sizeof(info.name)); if (copy_to_user(user, &info, *len) != 0) ret = -EFAULT; @@ -1452,7 +1460,8 @@ ip6t_unregister_match(struct ip6t_match *match) up(&ip6t_mutex); } -int ip6t_register_table(struct ip6t_table *table) +int ip6t_register_table(struct ip6t_table *table, + const struct ip6t_replace *repl) { int ret; struct ip6t_table_info *newinfo; @@ -1460,17 +1469,17 @@ int ip6t_register_table(struct ip6t_table *table) = { 0, 0, 0, { 0 }, { 0 }, { } }; newinfo = vmalloc(sizeof(struct ip6t_table_info) - + SMP_ALIGN(table->table->size) * NR_CPUS); + + SMP_ALIGN(repl->size) * NR_CPUS); if (!newinfo) return -ENOMEM; - memcpy(newinfo->entries, table->table->entries, table->table->size); + memcpy(newinfo->entries, repl->entries, repl->size); ret = translate_table(table->name, table->valid_hooks, - newinfo, table->table->size, - table->table->num_entries, - table->table->hook_entry, - table->table->underflow); + newinfo, repl->size, + repl->num_entries, + repl->hook_entry, + repl->underflow); if (ret != 0) { vfree(newinfo); return ret; @@ -1499,7 +1508,7 @@ int ip6t_register_table(struct ip6t_table *table) /* save number of initial entries */ table->private->initial_entries = table->private->number; - table->lock = RW_LOCK_UNLOCKED; + rwlock_init(&table->lock); list_prepend(&ip6t_tables, table); unlock: @@ -1535,26 +1544,31 @@ port_match(u_int16_t min, u_int16_t max, u_int16_t port, int invert) static int tcp_find_option(u_int8_t option, - const struct tcphdr *tcp, - u_int16_t datalen, + const struct sk_buff *skb, + unsigned int tcpoff, + unsigned int optlen, int invert, int *hotdrop) { - unsigned int i = sizeof(struct tcphdr); - const u_int8_t *opt = (u_int8_t *)tcp; + /* tcp.doff is only 4 bits, ie. max 15 * 4 bytes */ + u_int8_t _opt[60 - sizeof(struct tcphdr)], *op; + unsigned int i; duprintf("tcp_match: finding option\n"); + if (!optlen) + return invert; /* If we don't have the whole header, drop packet. */ - if (tcp->doff * 4 < sizeof(struct tcphdr) || - tcp->doff * 4 > datalen) { + op = skb_header_pointer(skb, tcpoff + sizeof(struct tcphdr), optlen, + _opt); + if (op == NULL) { *hotdrop = 1; return 0; } - while (i < tcp->doff * 4) { - if (opt[i] == option) return !invert; - if (opt[i] < 2) i++; - else i += opt[i+1]?:1; + for (i = 0; i < optlen; ) { + if (op[i] == option) return !invert; + if (op[i] < 2) i++; + else i += op[i+1]?:1; } return invert; @@ -1566,27 +1580,31 @@ tcp_match(const struct sk_buff *skb, const struct net_device *out, const void *matchinfo, int offset, - const void *hdr, - u_int16_t datalen, + unsigned int protoff, int *hotdrop) { - const struct tcphdr *tcp; + struct tcphdr _tcph, *th; const struct ip6t_tcp *tcpinfo = matchinfo; - int tcpoff; - u8 nexthdr = skb->nh.ipv6h->nexthdr; - - /* To quote Alan: - Don't allow a fragment of TCP 8 bytes in. Nobody normal - causes this. Its a cracker trying to break in by doing a - flag overwrite to pass the direction checks. - */ + if (offset) { + /* To quote Alan: - if (offset == 1) { - duprintf("Dropping evil TCP offset=1 frag.\n"); - *hotdrop = 1; + Don't allow a fragment of TCP 8 bytes in. Nobody normal + causes this. Its a cracker trying to break in by doing a + flag overwrite to pass the direction checks. + */ + if (offset == 1) { + duprintf("Dropping evil TCP offset=1 frag.\n"); + *hotdrop = 1; + } + /* Must not be a fragment. */ return 0; - } else if (offset == 0 && datalen < sizeof(struct tcphdr)) { + } + +#define FWINVTCP(bool,invflg) ((bool) ^ !!(tcpinfo->invflags & invflg)) + + th = skb_header_pointer(skb, protoff, sizeof(_tcph), &_tcph); + if (th == NULL) { /* We've been asked to examine this packet, and we can't. Hence, no choice but to drop. */ duprintf("Dropping evil TCP offset=0 tinygram.\n"); @@ -1594,45 +1612,30 @@ tcp_match(const struct sk_buff *skb, return 0; } - tcpoff = (u8*)(skb->nh.ipv6h + 1) - skb->data; - tcpoff = ipv6_skip_exthdr(skb, tcpoff, &nexthdr, skb->len - tcpoff); - if (tcpoff < 0 || tcpoff > skb->len) { - duprintf("tcp_match: cannot skip exthdr. Dropping.\n"); - *hotdrop = 1; + if (!port_match(tcpinfo->spts[0], tcpinfo->spts[1], + ntohs(th->source), + !!(tcpinfo->invflags & IP6T_TCP_INV_SRCPT))) return 0; - } else if (nexthdr == IPPROTO_FRAGMENT) + if (!port_match(tcpinfo->dpts[0], tcpinfo->dpts[1], + ntohs(th->dest), + !!(tcpinfo->invflags & IP6T_TCP_INV_DSTPT))) return 0; - else if (nexthdr != IPPROTO_TCP || - skb->len - tcpoff < sizeof(struct tcphdr)) { - /* cannot be occured */ - duprintf("tcp_match: cannot get TCP header. Dropping.\n"); - *hotdrop = 1; + if (!FWINVTCP((((unsigned char *)th)[13] & tcpinfo->flg_mask) + == tcpinfo->flg_cmp, + IP6T_TCP_INV_FLAGS)) return 0; + if (tcpinfo->option) { + if (th->doff * 4 < sizeof(_tcph)) { + *hotdrop = 1; + return 0; + } + if (!tcp_find_option(tcpinfo->option, skb, protoff, + th->doff*4 - sizeof(*th), + tcpinfo->invflags & IP6T_TCP_INV_OPTION, + hotdrop)) + return 0; } - - tcp = (struct tcphdr *)(skb->data + tcpoff); - - /* FIXME: Try tcp doff >> packet len against various stacks --RR */ - -#define FWINVTCP(bool,invflg) ((bool) ^ !!(tcpinfo->invflags & invflg)) - - /* Must not be a fragment. */ - return !offset - && port_match(tcpinfo->spts[0], tcpinfo->spts[1], - ntohs(tcp->source), - !!(tcpinfo->invflags & IP6T_TCP_INV_SRCPT)) - && port_match(tcpinfo->dpts[0], tcpinfo->dpts[1], - ntohs(tcp->dest), - !!(tcpinfo->invflags & IP6T_TCP_INV_DSTPT)) - && FWINVTCP((((unsigned char *)tcp)[13] - & tcpinfo->flg_mask) - == tcpinfo->flg_cmp, - IP6T_TCP_INV_FLAGS) - && (!tcpinfo->option - || tcp_find_option(tcpinfo->option, tcp, datalen, - tcpinfo->invflags - & IP6T_TCP_INV_OPTION, - hotdrop)); + return 1; } /* Called when user tries to insert an entry of this type. */ @@ -1658,16 +1661,18 @@ udp_match(const struct sk_buff *skb, const struct net_device *out, const void *matchinfo, int offset, - const void *hdr, - u_int16_t datalen, + unsigned int protoff, int *hotdrop) { - const struct udphdr *udp; + struct udphdr _udph, *uh; const struct ip6t_udp *udpinfo = matchinfo; - int udpoff; - u8 nexthdr = skb->nh.ipv6h->nexthdr; - if (offset == 0 && datalen < sizeof(struct udphdr)) { + /* Must not be a fragment. */ + if (offset) + return 0; + + uh = skb_header_pointer(skb, protoff, sizeof(_udph), &_udph); + if (uh == NULL) { /* We've been asked to examine this packet, and we can't. Hence, no choice but to drop. */ duprintf("Dropping evil UDP tinygram.\n"); @@ -1675,30 +1680,11 @@ udp_match(const struct sk_buff *skb, return 0; } - udpoff = (u8*)(skb->nh.ipv6h + 1) - skb->data; - udpoff = ipv6_skip_exthdr(skb, udpoff, &nexthdr, skb->len - udpoff); - if (udpoff < 0 || udpoff > skb->len) { - duprintf("udp_match: cannot skip exthdr. Dropping.\n"); - *hotdrop = 1; - return 0; - } else if (nexthdr == IPPROTO_FRAGMENT) - return 0; - else if (nexthdr != IPPROTO_UDP || - skb->len - udpoff < sizeof(struct udphdr)) { - duprintf("udp_match: cannot get UDP header. Dropping.\n"); - *hotdrop = 1; - return 0; - } - - udp = (struct udphdr *)(skb->data + udpoff); - - /* Must not be a fragment. */ - return !offset - && port_match(udpinfo->spts[0], udpinfo->spts[1], - ntohs(udp->source), - !!(udpinfo->invflags & IP6T_UDP_INV_SRCPT)) + return port_match(udpinfo->spts[0], udpinfo->spts[1], + ntohs(uh->source), + !!(udpinfo->invflags & IP6T_UDP_INV_SRCPT)) && port_match(udpinfo->dpts[0], udpinfo->dpts[1], - ntohs(udp->dest), + ntohs(uh->dest), !!(udpinfo->invflags & IP6T_UDP_INV_DSTPT)); } @@ -1748,14 +1734,18 @@ icmp6_match(const struct sk_buff *skb, const struct net_device *out, const void *matchinfo, int offset, - const void *hdr, - u_int16_t datalen, + unsigned int protoff, int *hotdrop) { - const struct icmp6hdr *icmp = hdr; + struct icmp6hdr _icmp, *ic; const struct ip6t_icmp *icmpinfo = matchinfo; - if (offset == 0 && datalen < 2) { + /* Must not be a fragment. */ + if (offset) + return 0; + + ic = skb_header_pointer(skb, protoff, sizeof(_icmp), &_icmp); + if (ic == NULL) { /* We've been asked to examine this packet, and we can't. Hence, no choice but to drop. */ duprintf("Dropping evil ICMP tinygram.\n"); @@ -1763,13 +1753,11 @@ icmp6_match(const struct sk_buff *skb, return 0; } - /* Must not be a fragment. */ - return !offset - && icmp6_type_code_match(icmpinfo->type, - icmpinfo->code[0], - icmpinfo->code[1], - icmp->icmp6_type, icmp->icmp6_code, - !!(icmpinfo->invflags&IP6T_ICMP_INV)); + return icmp6_type_code_match(icmpinfo->type, + icmpinfo->code[0], + icmpinfo->code[1], + ic->icmp6_type, ic->icmp6_code, + !!(icmpinfo->invflags&IP6T_ICMP_INV)); } /* Called when user tries to insert an entry of this type. */ @@ -1972,7 +1960,6 @@ static void __exit fini(void) EXPORT_SYMBOL(ip6t_register_table); EXPORT_SYMBOL(ip6t_unregister_table); EXPORT_SYMBOL(ip6t_do_table); -EXPORT_SYMBOL(ip6t_find_target_lock); EXPORT_SYMBOL(ip6t_register_match); EXPORT_SYMBOL(ip6t_unregister_match); EXPORT_SYMBOL(ip6t_register_target);