Bug 208390 - CVE-2006-4572 IPv6/IP6Tables Vulnerabilities
Summary: CVE-2006-4572 IPv6/IP6Tables Vulnerabilities
Alias: None
Product: Red Hat Enterprise Linux 4
Classification: Red Hat
Component: kernel
Version: 4.0
Hardware: All
OS: Linux
Target Milestone: ---
: ---
Assignee: Red Hat Kernel Manager
QA Contact: Brian Brock
Whiteboard: impact=important,source=kernelsec,rep...
Keywords: Security
Depends On:
TreeView+ depends on / blocked
Reported: 2006-09-28 11:01 UTC by Marcel Holtmann
Modified: 2007-11-30 22:07 UTC (History)
1 user (show)

Clone Of:
Last Closed: 2006-10-02 12:49:30 UTC

Attachments (Terms of Use)

Description Marcel Holtmann 2006-09-28 11:01:07 UTC
Reported by Mark Dowd from McAfee:

1. Upper Layer Protocol Mismatch

A problem exists within the netfilter code that is responsible for matching
upper layer protocols with rules that are installed by ip6tables (using the
protocol "-p" switch) on the system. When a packet is fragmented, the following
code in ip6_packet_match() is executed to evaluate whether the incoming packet
is a match or not:


    /* look for the desired protocol header */
    if((ip6info->flags & IP6T_F_PROTO)) {
        int protohdr;
        unsigned short _frag_off;

        protohdr = ipv6_find_hdr(skb, protoff, -1, &_frag_off);
        if (protohdr < 0)
            return 0;

        *fragoff = _frag_off;

        dprintf("Packet protocol %hi ?= %s%hi.\n",
                ip6info->invflags & IP6T_INV_PROTO ? "!":"",

        if (ip6info->proto == protohdr) {
            if(ip6info->invflags & IP6T_INV_PROTO) {
                return 0;
            return 1;

        /* We need match for the '-p all', too! */


This code uses ipv6_find_header() to encapsulate the following logic for
fragmented packets:

a. If the fragment is a 0-offset fragment, cycle through extension headers to
find the upper layer protocol for this packet. If one is not found, return -1.
b. If the fragment is not a 0-offset fragment and the fragment header's nexthdr
value is not an extension header, return it as the upper layer protocol.
c. If the fragment is not a 0-offset fragment and the fragment header's nexthdr
value is an extension header, return -1.

Using these rules, it is possible to create a fragmented packet that incorrectly
evades a match. To illustrate this, consider a scenario where the following rule
is installed:

ip6tables -A INPUT -p udp -j DROP

This rule should drop all UDP packets. This rule can be evaded by sending the
following pair of packets:

[ IP Header (nxthdr=fraghdr) ] [ fraghdr (offset=0, nxthdr=destopts)  ] [
destopts (nxthdr=routeopts) ]
[ IP Header (nxthdr=fraghdr) ] [ fraghdr (offset=X, nxthdr=routeopts) ] [
routeopts (nexthdr=UDP)     ] [ UDP Data ]

The first fragment will not match the DROP udp rule because it doesn't contain a
UDP header, and the second one will not match the rule either because the
fragment offset is non-zero and the nexthdr in the fragment extension is not UDP.

Kernel Versions tested:
    (Others supporting IPv6/IPTables are also suspected to be vulnerable)

2. Extension Header match bypass

The attack described in the previous scenario can also be used to evade rules
that are attempting to block certain extension headers from appearing in the
input packets. This is also due to the way ipv6_find_header() is invoked and the
same rules are essentially followed, except that when a non-zero offset fragment
is encountered the target match will always fail. Consider the following rule:

ip6tables -A INPUT -m rt -j DROP

The following packet pair will evade this rule:

[ IP Header (nxthdr=fraghdr) ] [ fraghdr (offset=0, nxthdr=destopts) ] [
destopts (nxthdr=destopts) ]
[ IP Header (nxthdr=fraghdr) ] [ fraghdr (offset=X, nxthdr=destopts) ] [
destopts (nxthdr=routeopts)] [ routeopts ] ...

Because the option appears in a non 0-offset fragment, the rule fails to match
this packet and it will be successfully delivered

(Same Kernel Versions Tested)

Comment 1 Marcel Holtmann 2006-09-28 12:47:48 UTC
The function ipv6_find_hdr() was first introduced with 2.6.14-rc2 and later
updated with 2.6.16-rc1. The code in question before 2.6.14 and so also in the
RHEL4 kernel looks like this:

        /* look for the desired protocol header */
        if((ip6info->flags & IP6T_F_PROTO)) {
                u_int8_t currenthdr = ipv6->nexthdr;
                struct ipv6_opt_hdr *hdrptr;
                u_int16_t ptr;          /* Header offset in skb */
                u_int16_t hdrlen;       /* Header */

                ptr = IPV6_HDR_LEN;

                while (ip6t_ext_hdr(currenthdr)) {
                        /* Is there enough space for the next ext header? */
                        if (skb->len - ptr < IPV6_OPTHDR_LEN)
                                return 0;

                        /* NONE or ESP: there isn't protocol part */
                        /* If we want to count these packets in '-p all',
                         * we will change the return 0 to 1*/
                        if ((currenthdr == IPPROTO_NONE) || 
                                (currenthdr == IPPROTO_ESP))
                                return 0;

                        hdrptr = (struct ipv6_opt_hdr *)(skb->data + ptr);

                        /* Size calculation */
                        if (currenthdr == IPPROTO_FRAGMENT) {
                                hdrlen = 8;
                        } else if (currenthdr == IPPROTO_AH)
                                hdrlen = (hdrptr->hdrlen+2)<<2;
                                hdrlen = ipv6_optlen(hdrptr);

                        currenthdr = hdrptr->nexthdr;
                        ptr += hdrlen;
                        /* ptr is too large */
                        if ( ptr > skb->len ) 
                                return 0;

                /* currenthdr contains the protocol header */

                dprintf("Packet protocol %hi ?= %s%hi.\n",
                                ip6info->invflags & IP6T_INV_PROTO ? "!":"",

                if (ip6info->proto == currenthdr) {
                        if(ip6info->invflags & IP6T_INV_PROTO) {
                                return 0;
                        return 1;

                /* We need match for the '-p all', too! */
                if ((ip6info->proto != 0) &&
                        !(ip6info->invflags & IP6T_INV_PROTO))
                        return 0;

Comment 2 Marcel Holtmann 2006-10-02 12:49:30 UTC
The attack talks about circumventing the possibly installed DROP rule and make
the function return a non-match. RHEL4 doesn't seem to be affected to the
fragment rule workaround.

This is an old issue that comes up every now and then, it gets down to whether
to consider malformed packets as candidates to match a certain rule. Is it right
to consider a packet with malformed extension headers as udp packet? If you want
to drop everything related to UDP then definitely yes, if you only want to pass
UDP traffic you'd probably expect the opposite and wouldn't want malformed stuff
to get through.

Note You need to log in before you can comment on or make changes to this bug.