android_kernel_samsung_msm8976/net/ipv6/netfilter/ip6t_rt.c
Patrick McHardy 6d381634d2 [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: Patrick McHardy <kaber@trash.net>
Signed-off-by: David S. Miller <davem@davemloft.net>
2006-10-24 16:15:10 -07:00

243 lines
5.9 KiB
C

/* Kernel module to match ROUTING parameters. */
/* (C) 2001-2002 Andras Kis-Szabo <kisza@sch.bme.hu>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/ipv6.h>
#include <linux/types.h>
#include <net/checksum.h>
#include <net/ipv6.h>
#include <asm/byteorder.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
#include <linux/netfilter_ipv6/ip6t_rt.h>
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("IPv6 RT match");
MODULE_AUTHOR("Andras Kis-Szabo <kisza@sch.bme.hu>");
#if 0
#define DEBUGP printk
#else
#define DEBUGP(format, args...)
#endif
/* Returns 1 if the id is matched by the range, 0 otherwise */
static inline int
segsleft_match(u_int32_t min, u_int32_t max, u_int32_t id, int invert)
{
int r = 0;
DEBUGP("rt segsleft_match:%c 0x%x <= 0x%x <= 0x%x",
invert ? '!' : ' ', min, id, max);
r = (id >= min && id <= max) ^ invert;
DEBUGP(" result %s\n", r ? "PASS" : "FAILED");
return r;
}
static int
match(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const struct xt_match *match,
const void *matchinfo,
int offset,
unsigned int protoff,
int *hotdrop)
{
struct ipv6_rt_hdr _route, *rh;
const struct ip6t_rt *rtinfo = matchinfo;
unsigned int temp;
unsigned int ptr;
unsigned int hdrlen = 0;
unsigned int ret = 0;
struct in6_addr *ap, _addr;
int err;
err = ipv6_find_hdr(skb, &ptr, NEXTHDR_ROUTING, NULL);
if (err < 0) {
if (err != -ENOENT)
*hotdrop = 1;
return 0;
}
rh = skb_header_pointer(skb, ptr, sizeof(_route), &_route);
if (rh == NULL) {
*hotdrop = 1;
return 0;
}
hdrlen = ipv6_optlen(rh);
if (skb->len - ptr < hdrlen) {
/* Pcket smaller than its length field */
return 0;
}
DEBUGP("IPv6 RT LEN %u %u ", hdrlen, rh->hdrlen);
DEBUGP("TYPE %04X ", rh->type);
DEBUGP("SGS_LEFT %u %02X\n", rh->segments_left, rh->segments_left);
DEBUGP("IPv6 RT segsleft %02X ",
(segsleft_match(rtinfo->segsleft[0], rtinfo->segsleft[1],
rh->segments_left,
!!(rtinfo->invflags & IP6T_RT_INV_SGS))));
DEBUGP("type %02X %02X %02X ",
rtinfo->rt_type, rh->type,
(!(rtinfo->flags & IP6T_RT_TYP) ||
((rtinfo->rt_type == rh->type) ^
!!(rtinfo->invflags & IP6T_RT_INV_TYP))));
DEBUGP("len %02X %04X %02X ",
rtinfo->hdrlen, hdrlen,
(!(rtinfo->flags & IP6T_RT_LEN) ||
((rtinfo->hdrlen == hdrlen) ^
!!(rtinfo->invflags & IP6T_RT_INV_LEN))));
DEBUGP("res %02X %02X %02X ",
(rtinfo->flags & IP6T_RT_RES),
((struct rt0_hdr *)rh)->reserved,
!((rtinfo->flags & IP6T_RT_RES) &&
(((struct rt0_hdr *)rh)->reserved)));
ret = (rh != NULL)
&&
(segsleft_match(rtinfo->segsleft[0], rtinfo->segsleft[1],
rh->segments_left,
!!(rtinfo->invflags & IP6T_RT_INV_SGS)))
&&
(!(rtinfo->flags & IP6T_RT_LEN) ||
((rtinfo->hdrlen == hdrlen) ^
!!(rtinfo->invflags & IP6T_RT_INV_LEN)))
&&
(!(rtinfo->flags & IP6T_RT_TYP) ||
((rtinfo->rt_type == rh->type) ^
!!(rtinfo->invflags & IP6T_RT_INV_TYP)));
if (ret && (rtinfo->flags & IP6T_RT_RES)) {
u_int32_t *rp, _reserved;
rp = skb_header_pointer(skb,
ptr + offsetof(struct rt0_hdr,
reserved),
sizeof(_reserved),
&_reserved);
ret = (*rp == 0);
}
DEBUGP("#%d ", rtinfo->addrnr);
if (!(rtinfo->flags & IP6T_RT_FST)) {
return ret;
} else if (rtinfo->flags & IP6T_RT_FST_NSTRICT) {
DEBUGP("Not strict ");
if (rtinfo->addrnr > (unsigned int)((hdrlen - 8) / 16)) {
DEBUGP("There isn't enough space\n");
return 0;
} else {
unsigned int i = 0;
DEBUGP("#%d ", rtinfo->addrnr);
for (temp = 0;
temp < (unsigned int)((hdrlen - 8) / 16);
temp++) {
ap = skb_header_pointer(skb,
ptr
+ sizeof(struct rt0_hdr)
+ temp * sizeof(_addr),
sizeof(_addr),
&_addr);
BUG_ON(ap == NULL);
if (ipv6_addr_equal(ap, &rtinfo->addrs[i])) {
DEBUGP("i=%d temp=%d;\n", i, temp);
i++;
}
if (i == rtinfo->addrnr)
break;
}
DEBUGP("i=%d #%d\n", i, rtinfo->addrnr);
if (i == rtinfo->addrnr)
return ret;
else
return 0;
}
} else {
DEBUGP("Strict ");
if (rtinfo->addrnr > (unsigned int)((hdrlen - 8) / 16)) {
DEBUGP("There isn't enough space\n");
return 0;
} else {
DEBUGP("#%d ", rtinfo->addrnr);
for (temp = 0; temp < rtinfo->addrnr; temp++) {
ap = skb_header_pointer(skb,
ptr
+ sizeof(struct rt0_hdr)
+ temp * sizeof(_addr),
sizeof(_addr),
&_addr);
BUG_ON(ap == NULL);
if (!ipv6_addr_equal(ap, &rtinfo->addrs[temp]))
break;
}
DEBUGP("temp=%d #%d\n", temp, rtinfo->addrnr);
if ((temp == rtinfo->addrnr) &&
(temp == (unsigned int)((hdrlen - 8) / 16)))
return ret;
else
return 0;
}
}
return 0;
}
/* Called when user tries to insert an entry of this type. */
static int
checkentry(const char *tablename,
const void *entry,
const struct xt_match *match,
void *matchinfo,
unsigned int hook_mask)
{
const struct ip6t_rt *rtinfo = matchinfo;
if (rtinfo->invflags & ~IP6T_RT_INV_MASK) {
DEBUGP("ip6t_rt: unknown flags %X\n", rtinfo->invflags);
return 0;
}
if ((rtinfo->flags & (IP6T_RT_RES | IP6T_RT_FST_MASK)) &&
(!(rtinfo->flags & IP6T_RT_TYP) ||
(rtinfo->rt_type != 0) ||
(rtinfo->invflags & IP6T_RT_INV_TYP))) {
DEBUGP("`--rt-type 0' required before `--rt-0-*'");
return 0;
}
return 1;
}
static struct ip6t_match rt_match = {
.name = "rt",
.match = match,
.matchsize = sizeof(struct ip6t_rt),
.checkentry = checkentry,
.me = THIS_MODULE,
};
static int __init ip6t_rt_init(void)
{
return ip6t_register_match(&rt_match);
}
static void __exit ip6t_rt_fini(void)
{
ip6t_unregister_match(&rt_match);
}
module_init(ip6t_rt_init);
module_exit(ip6t_rt_fini);