This commit was manufactured by cvs2svn to create tag
[linux-2.6.git] / net / ipv6 / netfilter / ip6_tables.c
index af2a1f5..4ef1644 100644 (file)
@@ -158,15 +158,14 @@ 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,
-                unsigned int *protoff,
-                int *fragoff)
+                int isfrag)
 {
        size_t i;
        unsigned long ret;
-       const struct ipv6hdr *ipv6 = skb->nh.ipv6h;
 
 #define FWINV(bool,invflg) ((bool) ^ !!(ip6info->invflags & invflg))
 
@@ -217,10 +216,9 @@ 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 _hdr, *hp;
+               struct ipv6_opt_hdr *hdrptr;
                u_int16_t ptr;          /* Header offset in skb */
                u_int16_t hdrlen;       /* Header */
-               u_int16_t _fragoff = 0, *fp = NULL;
 
                ptr = IPV6_HDR_LEN;
 
@@ -236,41 +234,23 @@ ip6_packet_match(const struct sk_buff *skb,
                                (currenthdr == IPPROTO_ESP))
                                return 0;
 
-                       hp = skb_header_pointer(skb, ptr, sizeof(_hdr), &_hdr);
-                       BUG_ON(hp == NULL);
+                       hdrptr = (struct ipv6_opt_hdr *)(skb->data + ptr);
 
                        /* 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 = (hp->hdrlen+2)<<2;
+                               hdrlen = (hdrptr->hdrlen+2)<<2;
                        else
-                               hdrlen = ipv6_optlen(hp);
+                               hdrlen = ipv6_optlen(hdrptr);
 
-                       currenthdr = hp->nexthdr;
+                       currenthdr = hdrptr->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",
@@ -312,9 +292,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)
 {
@@ -330,12 +310,13 @@ int do_match(struct ip6t_entry_match *m,
             const struct net_device *in,
             const struct net_device *out,
             int offset,
-            unsigned int protoff,
+            const void *hdr,
+            u_int16_t datalen,
             int *hotdrop)
 {
        /* Stop iteration if it doesn't match */
        if (!m->u.kernel.match->match(skb, in, out, m->data,
-                                     offset, protoff, hotdrop))
+                                     offset, hdr, datalen, hotdrop))
                return 1;
        else
                return 0;
@@ -357,8 +338,10 @@ ip6t_do_table(struct sk_buff **pskb,
              void *userdata)
 {
        static const char nulldevname[IFNAMSIZ];
-       int offset = 0;
-       unsigned int protoff = 0;
+       u_int16_t offset = 0;
+       struct ipv6hdr *ipv6;
+       void *protohdr;
+       u_int16_t datalen;
        int hotdrop = 0;
        /* Initializing verdict to NF_DROP keeps gcc happy. */
        unsigned int verdict = NF_DROP;
@@ -371,6 +354,9 @@ ip6t_do_table(struct sk_buff **pskb,
                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;
 
@@ -407,19 +393,17 @@ ip6t_do_table(struct sk_buff **pskb,
                IP_NF_ASSERT(e);
                IP_NF_ASSERT(back);
                (*pskb)->nfcache |= e->nfcache;
-               if (ip6_packet_match(*pskb, indev, outdev, &e->ipv6,
-                       &protoff, &offset)) {
+               if (ip6_packet_match(*pskb, ipv6, indev, outdev, 
+                       &e->ipv6, offset)) {
                        struct ip6t_entry_target *t;
 
                        if (IP6T_MATCH_ITERATE(e, do_match,
                                               *pskb, in, out,
-                                              offset, protoff, &hotdrop) != 0)
+                                              offset, protohdr,
+                                              datalen, &hotdrop) != 0)
                                goto no_match;
 
-                       ADD_COUNTER(e->counters,
-                                   ntohs((*pskb)->nh.ipv6h->payload_len)
-                                   + IPV6_HDR_LEN,
-                                   1);
+                       ADD_COUNTER(e->counters, ntohs(ipv6->payload_len) + IPV6_HDR_LEN, 1);
 
                        t = ip6t_get_target(e);
                        IP_NF_ASSERT(t->u.kernel.target);
@@ -459,8 +443,8 @@ ip6t_do_table(struct sk_buff **pskb,
                                        = 0xeeeeeeec;
 #endif
                                verdict = t->u.kernel.target->target(pskb,
-                                                                    in, out,
                                                                     hook,
+                                                                    in, out,
                                                                     t->data,
                                                                     userdata);
 
@@ -475,6 +459,11 @@ 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
@@ -1168,11 +1157,6 @@ do_replace(void __user *user, unsigned int len)
                ret = -EFAULT;
                goto free_newinfo;
        }
-       
-       if(tmp.num_counters >= (4 << 20)/sizeof(struct ip6t_counters)) {
-               ret = -ENOMEM;
-               goto free_newinfo;
-       }
 
        counters = vmalloc(tmp.num_counters * sizeof(struct ip6t_counters));
        if (!counters) {
@@ -1377,7 +1361,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;
-                       memcpy(info.name, name, sizeof(info.name));
+                       strcpy(info.name, name);
 
                        if (copy_to_user(user, &info, *len) != 0)
                                ret = -EFAULT;
@@ -1515,7 +1499,7 @@ int ip6t_register_table(struct ip6t_table *table)
        /* save number of initial entries */
        table->private->initial_entries = table->private->number;
 
-       rwlock_init(&table->lock);
+       table->lock = RW_LOCK_UNLOCKED;
        list_prepend(&ip6t_tables, table);
 
  unlock:
@@ -1551,31 +1535,26 @@ 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 sk_buff *skb,
-               unsigned int tcpoff,
-               unsigned int optlen,
+               const struct tcphdr *tcp,
+               u_int16_t datalen,
                int invert,
                int *hotdrop)
 {
-       /* tcp.doff is only 4 bits, ie. max 15 * 4 bytes */
-       u_int8_t _opt[60 - sizeof(struct tcphdr)], *op;
-       unsigned int i;
+       unsigned int i = sizeof(struct tcphdr);
+       const u_int8_t *opt = (u_int8_t *)tcp;
 
        duprintf("tcp_match: finding option\n");
-       if (!optlen)
-               return invert;
        /* If we don't have the whole header, drop packet. */
-       op = skb_header_pointer(skb, tcpoff + sizeof(struct tcphdr), optlen,
-                               _opt);
-       if (op == NULL) {
+       if (tcp->doff * 4 < sizeof(struct tcphdr) ||
+           tcp->doff * 4 > datalen) {
                *hotdrop = 1;
                return 0;
        }
 
-       for (i = 0; i < optlen; ) {
-               if (op[i] == option) return !invert;
-               if (op[i] < 2) i++;
-               else i += op[i+1]?:1;
+       while (i < tcp->doff * 4) {
+               if (opt[i] == option) return !invert;
+               if (opt[i] < 2) i++;
+               else i += opt[i+1]?:1;
        }
 
        return invert;
@@ -1587,31 +1566,27 @@ tcp_match(const struct sk_buff *skb,
          const struct net_device *out,
          const void *matchinfo,
          int offset,
-         unsigned int protoff,
+         const void *hdr,
+         u_int16_t datalen,
          int *hotdrop)
 {
-       struct tcphdr _tcph, *th;
+       const struct tcphdr *tcp;
        const struct ip6t_tcp *tcpinfo = matchinfo;
+       int tcpoff;
+       u8 nexthdr = skb->nh.ipv6h->nexthdr;
 
-       if (offset) {
-               /* To quote Alan:
+       /* 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 == 1) {
-                       duprintf("Dropping evil TCP offset=1 frag.\n");
-                       *hotdrop = 1;
-               }
-               /* Must not be a fragment. */
-               return 0;
-       }
-
-#define FWINVTCP(bool,invflg) ((bool) ^ !!(tcpinfo->invflags & invflg))
+          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.
+       */
 
-       th = skb_header_pointer(skb, protoff, sizeof(_tcph), &_tcph);
-       if (th == NULL) {
+       if (offset == 1) {
+               duprintf("Dropping evil TCP offset=1 frag.\n");
+               *hotdrop = 1;
+               return 0;
+       } else if (offset == 0 && datalen < sizeof(struct tcphdr)) {
                /* 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");
@@ -1619,30 +1594,45 @@ tcp_match(const struct sk_buff *skb,
                return 0;
        }
 
-       if (!port_match(tcpinfo->spts[0], tcpinfo->spts[1],
-                       ntohs(th->source),
-                       !!(tcpinfo->invflags & IP6T_TCP_INV_SRCPT)))
+       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;
                return 0;
-       if (!port_match(tcpinfo->dpts[0], tcpinfo->dpts[1],
-                       ntohs(th->dest),
-                       !!(tcpinfo->invflags & IP6T_TCP_INV_DSTPT)))
+       } else if (nexthdr == IPPROTO_FRAGMENT)
                return 0;
-       if (!FWINVTCP((((unsigned char *)th)[13] & tcpinfo->flg_mask)
-                     == tcpinfo->flg_cmp,
-                     IP6T_TCP_INV_FLAGS))
+       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;
                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;
        }
-       return 1;
+
+       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));
 }
 
 /* Called when user tries to insert an entry of this type. */
@@ -1668,18 +1658,16 @@ udp_match(const struct sk_buff *skb,
          const struct net_device *out,
          const void *matchinfo,
          int offset,
-         unsigned int protoff,
+         const void *hdr,
+         u_int16_t datalen,
          int *hotdrop)
 {
-       struct udphdr _udph, *uh;
+       const struct udphdr *udp;
        const struct ip6t_udp *udpinfo = matchinfo;
+       int udpoff;
+       u8 nexthdr = skb->nh.ipv6h->nexthdr;
 
-       /* Must not be a fragment. */
-       if (offset)
-               return 0;
-
-       uh = skb_header_pointer(skb, protoff, sizeof(_udph), &_udph);
-       if (uh == NULL) {
+       if (offset == 0 && datalen < sizeof(struct udphdr)) {
                /* We've been asked to examine this packet, and we
                   can't.  Hence, no choice but to drop. */
                duprintf("Dropping evil UDP tinygram.\n");
@@ -1687,11 +1675,30 @@ udp_match(const struct sk_buff *skb,
                return 0;
        }
 
-       return port_match(udpinfo->spts[0], udpinfo->spts[1],
-                         ntohs(uh->source),
-                         !!(udpinfo->invflags & IP6T_UDP_INV_SRCPT))
+       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))
                && port_match(udpinfo->dpts[0], udpinfo->dpts[1],
-                             ntohs(uh->dest),
+                             ntohs(udp->dest),
                              !!(udpinfo->invflags & IP6T_UDP_INV_DSTPT));
 }
 
@@ -1741,18 +1748,14 @@ icmp6_match(const struct sk_buff *skb,
           const struct net_device *out,
           const void *matchinfo,
           int offset,
-          unsigned int protoff,
+          const void *hdr,
+          u_int16_t datalen,
           int *hotdrop)
 {
-       struct icmp6hdr _icmp, *ic;
+       const struct icmp6hdr *icmp = hdr;
        const struct ip6t_icmp *icmpinfo = matchinfo;
 
-       /* Must not be a fragment. */
-       if (offset)
-               return 0;
-
-       ic = skb_header_pointer(skb, protoff, sizeof(_icmp), &_icmp);
-       if (ic == NULL) {
+       if (offset == 0 && datalen < 2) {
                /* We've been asked to examine this packet, and we
                   can't.  Hence, no choice but to drop. */
                duprintf("Dropping evil ICMP tinygram.\n");
@@ -1760,11 +1763,13 @@ icmp6_match(const struct sk_buff *skb,
                return 0;
        }
 
-       return icmp6_type_code_match(icmpinfo->type,
-                                    icmpinfo->code[0],
-                                    icmpinfo->code[1],
-                                    ic->icmp6_type, ic->icmp6_code,
-                                    !!(icmpinfo->invflags&IP6T_ICMP_INV));
+       /* 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));
 }
 
 /* Called when user tries to insert an entry of this type. */