Commit 6d381634 authored by Patrick McHardy's avatar Patrick McHardy Committed by David S. Miller

[NETFILTER]: Fix ip6_tables extension header bypass bug

As reported by Mark Dowd <Mark_Dowd@McAfee.com>, ip6_tables is susceptible
to a fragmentation attack causing false negatives on extension header matches.

When extension headers occur in the non-first fragment after the fragment
header (possibly with an incorrect nexthdr value in the fragment header)
a rule looking for this extension header will never match.

Drop fragments that are at offset 0 and don't contain the final protocol
header regardless of the ruleset, since this should not happen normally.
Since all extension headers are before the protocol header this makes sure
an extension header is either not present or in the first fragment, where
we can properly parse it.

With help from Yasuyuki KOZAKAI <yasuyuki.kozakai@toshiba.co.jp>.
Signed-off-by: default avatarPatrick McHardy <kaber@trash.net>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 51d8b1a6
...@@ -1440,6 +1440,9 @@ static void __exit ip6_tables_fini(void) ...@@ -1440,6 +1440,9 @@ static void __exit ip6_tables_fini(void)
* If target header is found, its offset is set in *offset and return protocol * If target header is found, its offset is set in *offset and return protocol
* number. Otherwise, return -1. * number. Otherwise, return -1.
* *
* If the first fragment doesn't contain the final protocol header or
* NEXTHDR_NONE it is considered invalid.
*
* Note that non-1st fragment is special case that "the protocol number * Note that non-1st fragment is special case that "the protocol number
* of last header" is "next header" field in Fragment header. In this case, * of last header" is "next header" field in Fragment header. In this case,
* *offset is meaningless and fragment offset is stored in *fragoff if fragoff * *offset is meaningless and fragment offset is stored in *fragoff if fragoff
...@@ -1463,12 +1466,12 @@ int ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset, ...@@ -1463,12 +1466,12 @@ int ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset,
if ((!ipv6_ext_hdr(nexthdr)) || nexthdr == NEXTHDR_NONE) { if ((!ipv6_ext_hdr(nexthdr)) || nexthdr == NEXTHDR_NONE) {
if (target < 0) if (target < 0)
break; break;
return -1; return -ENOENT;
} }
hp = skb_header_pointer(skb, start, sizeof(_hdr), &_hdr); hp = skb_header_pointer(skb, start, sizeof(_hdr), &_hdr);
if (hp == NULL) if (hp == NULL)
return -1; return -EBADMSG;
if (nexthdr == NEXTHDR_FRAGMENT) { if (nexthdr == NEXTHDR_FRAGMENT) {
unsigned short _frag_off, *fp; unsigned short _frag_off, *fp;
fp = skb_header_pointer(skb, fp = skb_header_pointer(skb,
...@@ -1477,7 +1480,7 @@ int ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset, ...@@ -1477,7 +1480,7 @@ int ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset,
sizeof(_frag_off), sizeof(_frag_off),
&_frag_off); &_frag_off);
if (fp == NULL) if (fp == NULL)
return -1; return -EBADMSG;
_frag_off = ntohs(*fp) & ~0x7; _frag_off = ntohs(*fp) & ~0x7;
if (_frag_off) { if (_frag_off) {
...@@ -1488,7 +1491,7 @@ int ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset, ...@@ -1488,7 +1491,7 @@ int ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset,
*fragoff = _frag_off; *fragoff = _frag_off;
return hp->nexthdr; return hp->nexthdr;
} }
return -1; return -ENOENT;
} }
hdrlen = 8; hdrlen = 8;
} else if (nexthdr == NEXTHDR_AUTH) } else if (nexthdr == NEXTHDR_AUTH)
......
...@@ -54,9 +54,14 @@ match(const struct sk_buff *skb, ...@@ -54,9 +54,14 @@ match(const struct sk_buff *skb,
const struct ip6t_ah *ahinfo = matchinfo; const struct ip6t_ah *ahinfo = matchinfo;
unsigned int ptr; unsigned int ptr;
unsigned int hdrlen = 0; unsigned int hdrlen = 0;
int err;
if (ipv6_find_hdr(skb, &ptr, NEXTHDR_AUTH, NULL) < 0) err = ipv6_find_hdr(skb, &ptr, NEXTHDR_AUTH, NULL);
if (err < 0) {
if (err != -ENOENT)
*hotdrop = 1;
return 0; return 0;
}
ah = skb_header_pointer(skb, ptr, sizeof(_ah), &_ah); ah = skb_header_pointer(skb, ptr, sizeof(_ah), &_ah);
if (ah == NULL) { if (ah == NULL) {
......
...@@ -52,9 +52,14 @@ match(const struct sk_buff *skb, ...@@ -52,9 +52,14 @@ match(const struct sk_buff *skb,
struct frag_hdr _frag, *fh; struct frag_hdr _frag, *fh;
const struct ip6t_frag *fraginfo = matchinfo; const struct ip6t_frag *fraginfo = matchinfo;
unsigned int ptr; unsigned int ptr;
int err;
if (ipv6_find_hdr(skb, &ptr, NEXTHDR_FRAGMENT, NULL) < 0) err = ipv6_find_hdr(skb, &ptr, NEXTHDR_FRAGMENT, NULL);
if (err < 0) {
if (err != -ENOENT)
*hotdrop = 1;
return 0; return 0;
}
fh = skb_header_pointer(skb, ptr, sizeof(_frag), &_frag); fh = skb_header_pointer(skb, ptr, sizeof(_frag), &_frag);
if (fh == NULL) { if (fh == NULL) {
......
...@@ -65,9 +65,14 @@ match(const struct sk_buff *skb, ...@@ -65,9 +65,14 @@ match(const struct sk_buff *skb,
u8 _opttype, *tp = NULL; u8 _opttype, *tp = NULL;
u8 _optlen, *lp = NULL; u8 _optlen, *lp = NULL;
unsigned int optlen; unsigned int optlen;
int err;
if (ipv6_find_hdr(skb, &ptr, match->data, NULL) < 0) err = ipv6_find_hdr(skb, &ptr, match->data, NULL);
if (err < 0) {
if (err != -ENOENT)
*hotdrop = 1;
return 0; return 0;
}
oh = skb_header_pointer(skb, ptr, sizeof(_optsh), &_optsh); oh = skb_header_pointer(skb, ptr, sizeof(_optsh), &_optsh);
if (oh == NULL) { if (oh == NULL) {
......
...@@ -58,9 +58,14 @@ match(const struct sk_buff *skb, ...@@ -58,9 +58,14 @@ match(const struct sk_buff *skb,
unsigned int hdrlen = 0; unsigned int hdrlen = 0;
unsigned int ret = 0; unsigned int ret = 0;
struct in6_addr *ap, _addr; struct in6_addr *ap, _addr;
int err;
if (ipv6_find_hdr(skb, &ptr, NEXTHDR_ROUTING, NULL) < 0) err = ipv6_find_hdr(skb, &ptr, NEXTHDR_ROUTING, NULL);
if (err < 0) {
if (err != -ENOENT)
*hotdrop = 1;
return 0; return 0;
}
rh = skb_header_pointer(skb, ptr, sizeof(_route), &_route); rh = skb_header_pointer(skb, ptr, sizeof(_route), &_route);
if (rh == NULL) { if (rh == NULL) {
......
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