Skip to content

Commit

Permalink
netfilter: ipset: Fix extension alignment
Browse files Browse the repository at this point in the history
The data extensions in ipset lacked the proper memory alignment and
thus could lead to kernel crash on several architectures. Therefore
the structures have been reorganized and alignment attributes added
where needed. The patch was tested on armv7h by Gerhard Wiesinger and
on x86_64, sparc64 by Jozsef Kadlecsik.

Reported-by: Gerhard Wiesinger <lists@wiesinger.com>
Tested-by: Gerhard Wiesinger <lists@wiesinger.com>
Tested-by: Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
Signed-off-by: Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
  • Loading branch information
Jozsef Kadlecsik committed Nov 7, 2015
1 parent 212cd08 commit 95ad1f4
Show file tree
Hide file tree
Showing 8 changed files with 65 additions and 80 deletions.
2 changes: 1 addition & 1 deletion include/linux/netfilter/ipset/ip_set.h
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ extern void ip_set_free(void *members);
extern int ip_set_get_ipaddr4(struct nlattr *nla, __be32 *ipaddr);
extern int ip_set_get_ipaddr6(struct nlattr *nla, union nf_inet_addr *ipaddr);
extern size_t ip_set_elem_len(struct ip_set *set, struct nlattr *tb[],
size_t len);
size_t len, size_t align);
extern int ip_set_get_extensions(struct ip_set *set, struct nlattr *tb[],
struct ip_set_ext *ext);

Expand Down
17 changes: 6 additions & 11 deletions net/netfilter/ipset/ip_set_bitmap_gen.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
#define mtype_gc IPSET_TOKEN(MTYPE, _gc)
#define mtype MTYPE

#define get_ext(set, map, id) ((map)->extensions + (set)->dsize * (id))
#define get_ext(set, map, id) ((map)->extensions + ((set)->dsize * (id)))

static void
mtype_gc_init(struct ip_set *set, void (*gc)(unsigned long ul_set))
Expand Down Expand Up @@ -67,12 +67,9 @@ mtype_destroy(struct ip_set *set)
del_timer_sync(&map->gc);

ip_set_free(map->members);
if (set->dsize) {
if (set->extensions & IPSET_EXT_DESTROY)
mtype_ext_cleanup(set);
ip_set_free(map->extensions);
}
kfree(map);
if (set->dsize && set->extensions & IPSET_EXT_DESTROY)
mtype_ext_cleanup(set);
ip_set_free(map);

set->data = NULL;
}
Expand All @@ -92,16 +89,14 @@ mtype_head(struct ip_set *set, struct sk_buff *skb)
{
const struct mtype *map = set->data;
struct nlattr *nested;
size_t memsize = sizeof(*map) + map->memsize;

nested = ipset_nest_start(skb, IPSET_ATTR_DATA);
if (!nested)
goto nla_put_failure;
if (mtype_do_head(skb, map) ||
nla_put_net32(skb, IPSET_ATTR_REFERENCES, htonl(set->ref - 1)) ||
nla_put_net32(skb, IPSET_ATTR_MEMSIZE,
htonl(sizeof(*map) +
map->memsize +
set->dsize * map->elements)))
nla_put_net32(skb, IPSET_ATTR_MEMSIZE, htonl(memsize)))
goto nla_put_failure;
if (unlikely(ip_set_put_flags(skb, set)))
goto nla_put_failure;
Expand Down
14 changes: 4 additions & 10 deletions net/netfilter/ipset/ip_set_bitmap_ip.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,15 @@ MODULE_ALIAS("ip_set_bitmap:ip");
/* Type structure */
struct bitmap_ip {
void *members; /* the set members */
void *extensions; /* data extensions */
u32 first_ip; /* host byte order, included in range */
u32 last_ip; /* host byte order, included in range */
u32 elements; /* number of max elements in the set */
u32 hosts; /* number of hosts in a subnet */
size_t memsize; /* members size */
u8 netmask; /* subnet netmask */
struct timer_list gc; /* garbage collection */
unsigned char extensions[0] /* data extensions */
__aligned(__alignof__(u64));
};

/* ADT structure for generic function args */
Expand Down Expand Up @@ -224,13 +225,6 @@ init_map_ip(struct ip_set *set, struct bitmap_ip *map,
map->members = ip_set_alloc(map->memsize);
if (!map->members)
return false;
if (set->dsize) {
map->extensions = ip_set_alloc(set->dsize * elements);
if (!map->extensions) {
kfree(map->members);
return false;
}
}
map->first_ip = first_ip;
map->last_ip = last_ip;
map->elements = elements;
Expand Down Expand Up @@ -316,13 +310,13 @@ bitmap_ip_create(struct net *net, struct ip_set *set, struct nlattr *tb[],
pr_debug("hosts %u, elements %llu\n",
hosts, (unsigned long long)elements);

map = kzalloc(sizeof(*map), GFP_KERNEL);
set->dsize = ip_set_elem_len(set, tb, 0, 0);
map = ip_set_alloc(sizeof(*map) + elements * set->dsize);
if (!map)
return -ENOMEM;

map->memsize = bitmap_bytes(0, elements - 1);
set->variant = &bitmap_ip;
set->dsize = ip_set_elem_len(set, tb, 0);
if (!init_map_ip(set, map, first_ip, last_ip,
elements, hosts, netmask)) {
kfree(map);
Expand Down
64 changes: 29 additions & 35 deletions net/netfilter/ipset/ip_set_bitmap_ipmac.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,36 +47,38 @@ enum {
/* Type structure */
struct bitmap_ipmac {
void *members; /* the set members */
void *extensions; /* MAC + data extensions */
u32 first_ip; /* host byte order, included in range */
u32 last_ip; /* host byte order, included in range */
u32 elements; /* number of max elements in the set */
size_t memsize; /* members size */
struct timer_list gc; /* garbage collector */
unsigned char extensions[0] /* MAC + data extensions */
__aligned(__alignof__(u64));
};

/* ADT structure for generic function args */
struct bitmap_ipmac_adt_elem {
unsigned char ether[ETH_ALEN] __aligned(2);
u16 id;
unsigned char *ether;
u16 add_mac;
};

struct bitmap_ipmac_elem {
unsigned char ether[ETH_ALEN];
unsigned char filled;
} __attribute__ ((aligned));
} __aligned(__alignof__(u64));

static inline u32
ip_to_id(const struct bitmap_ipmac *m, u32 ip)
{
return ip - m->first_ip;
}

static inline struct bitmap_ipmac_elem *
get_elem(void *extensions, u16 id, size_t dsize)
{
return (struct bitmap_ipmac_elem *)(extensions + id * dsize);
}
#define get_elem(extensions, id, dsize) \
(struct bitmap_ipmac_elem *)(extensions + (id) * (dsize))

#define get_const_elem(extensions, id, dsize) \
(const struct bitmap_ipmac_elem *)(extensions + (id) * (dsize))

/* Common functions */

Expand All @@ -88,10 +90,9 @@ bitmap_ipmac_do_test(const struct bitmap_ipmac_adt_elem *e,

if (!test_bit(e->id, map->members))
return 0;
elem = get_elem(map->extensions, e->id, dsize);
if (elem->filled == MAC_FILLED)
return !e->ether ||
ether_addr_equal(e->ether, elem->ether);
elem = get_const_elem(map->extensions, e->id, dsize);
if (e->add_mac && elem->filled == MAC_FILLED)
return ether_addr_equal(e->ether, elem->ether);
/* Trigger kernel to fill out the ethernet address */
return -EAGAIN;
}
Expand All @@ -103,7 +104,7 @@ bitmap_ipmac_gc_test(u16 id, const struct bitmap_ipmac *map, size_t dsize)

if (!test_bit(id, map->members))
return 0;
elem = get_elem(map->extensions, id, dsize);
elem = get_const_elem(map->extensions, id, dsize);
/* Timer not started for the incomplete elements */
return elem->filled == MAC_FILLED;
}
Expand Down Expand Up @@ -133,7 +134,7 @@ bitmap_ipmac_add_timeout(unsigned long *timeout,
* and we can reuse it later when MAC is filled out,
* possibly by the kernel
*/
if (e->ether)
if (e->add_mac)
ip_set_timeout_set(timeout, t);
else
*timeout = t;
Expand All @@ -150,7 +151,7 @@ bitmap_ipmac_do_add(const struct bitmap_ipmac_adt_elem *e,
elem = get_elem(map->extensions, e->id, dsize);
if (test_bit(e->id, map->members)) {
if (elem->filled == MAC_FILLED) {
if (e->ether &&
if (e->add_mac &&
(flags & IPSET_FLAG_EXIST) &&
!ether_addr_equal(e->ether, elem->ether)) {
/* memcpy isn't atomic */
Expand All @@ -159,7 +160,7 @@ bitmap_ipmac_do_add(const struct bitmap_ipmac_adt_elem *e,
ether_addr_copy(elem->ether, e->ether);
}
return IPSET_ADD_FAILED;
} else if (!e->ether)
} else if (!e->add_mac)
/* Already added without ethernet address */
return IPSET_ADD_FAILED;
/* Fill the MAC address and trigger the timer activation */
Expand All @@ -168,7 +169,7 @@ bitmap_ipmac_do_add(const struct bitmap_ipmac_adt_elem *e,
ether_addr_copy(elem->ether, e->ether);
elem->filled = MAC_FILLED;
return IPSET_ADD_START_STORED_TIMEOUT;
} else if (e->ether) {
} else if (e->add_mac) {
/* We can store MAC too */
ether_addr_copy(elem->ether, e->ether);
elem->filled = MAC_FILLED;
Expand All @@ -191,7 +192,7 @@ bitmap_ipmac_do_list(struct sk_buff *skb, const struct bitmap_ipmac *map,
u32 id, size_t dsize)
{
const struct bitmap_ipmac_elem *elem =
get_elem(map->extensions, id, dsize);
get_const_elem(map->extensions, id, dsize);

return nla_put_ipaddr4(skb, IPSET_ATTR_IP,
htonl(map->first_ip + id)) ||
Expand All @@ -213,7 +214,7 @@ bitmap_ipmac_kadt(struct ip_set *set, const struct sk_buff *skb,
{
struct bitmap_ipmac *map = set->data;
ipset_adtfn adtfn = set->variant->adt[adt];
struct bitmap_ipmac_adt_elem e = { .id = 0 };
struct bitmap_ipmac_adt_elem e = { .id = 0, .add_mac = 1 };
struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set);
u32 ip;

Expand All @@ -231,7 +232,7 @@ bitmap_ipmac_kadt(struct ip_set *set, const struct sk_buff *skb,
return -EINVAL;

e.id = ip_to_id(map, ip);
e.ether = eth_hdr(skb)->h_source;
memcpy(e.ether, eth_hdr(skb)->h_source, ETH_ALEN);

return adtfn(set, &e, &ext, &opt->ext, opt->cmdflags);
}
Expand Down Expand Up @@ -265,11 +266,10 @@ bitmap_ipmac_uadt(struct ip_set *set, struct nlattr *tb[],
return -IPSET_ERR_BITMAP_RANGE;

e.id = ip_to_id(map, ip);
if (tb[IPSET_ATTR_ETHER])
e.ether = nla_data(tb[IPSET_ATTR_ETHER]);
else
e.ether = NULL;

if (tb[IPSET_ATTR_ETHER]) {
memcpy(e.ether, nla_data(tb[IPSET_ATTR_ETHER]), ETH_ALEN);
e.add_mac = 1;
}
ret = adtfn(set, &e, &ext, &ext, flags);

return ip_set_eexist(ret, flags) ? 0 : ret;
Expand Down Expand Up @@ -300,13 +300,6 @@ init_map_ipmac(struct ip_set *set, struct bitmap_ipmac *map,
map->members = ip_set_alloc(map->memsize);
if (!map->members)
return false;
if (set->dsize) {
map->extensions = ip_set_alloc(set->dsize * elements);
if (!map->extensions) {
kfree(map->members);
return false;
}
}
map->first_ip = first_ip;
map->last_ip = last_ip;
map->elements = elements;
Expand Down Expand Up @@ -361,14 +354,15 @@ bitmap_ipmac_create(struct net *net, struct ip_set *set, struct nlattr *tb[],
if (elements > IPSET_BITMAP_MAX_RANGE + 1)
return -IPSET_ERR_BITMAP_RANGE_SIZE;

map = kzalloc(sizeof(*map), GFP_KERNEL);
set->dsize = ip_set_elem_len(set, tb,
sizeof(struct bitmap_ipmac_elem),
__alignof__(struct bitmap_ipmac_elem));
map = ip_set_alloc(sizeof(*map) + elements * set->dsize);
if (!map)
return -ENOMEM;

map->memsize = bitmap_bytes(0, elements - 1);
set->variant = &bitmap_ipmac;
set->dsize = ip_set_elem_len(set, tb,
sizeof(struct bitmap_ipmac_elem));
if (!init_map_ipmac(set, map, first_ip, last_ip, elements)) {
kfree(map);
return -ENOMEM;
Expand Down
18 changes: 7 additions & 11 deletions net/netfilter/ipset/ip_set_bitmap_port.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,13 @@ MODULE_ALIAS("ip_set_bitmap:port");
/* Type structure */
struct bitmap_port {
void *members; /* the set members */
void *extensions; /* data extensions */
u16 first_port; /* host byte order, included in range */
u16 last_port; /* host byte order, included in range */
u32 elements; /* number of max elements in the set */
size_t memsize; /* members size */
struct timer_list gc; /* garbage collection */
unsigned char extensions[0] /* data extensions */
__aligned(__alignof__(u64));
};

/* ADT structure for generic function args */
Expand Down Expand Up @@ -209,13 +210,6 @@ init_map_port(struct ip_set *set, struct bitmap_port *map,
map->members = ip_set_alloc(map->memsize);
if (!map->members)
return false;
if (set->dsize) {
map->extensions = ip_set_alloc(set->dsize * map->elements);
if (!map->extensions) {
kfree(map->members);
return false;
}
}
map->first_port = first_port;
map->last_port = last_port;
set->timeout = IPSET_NO_TIMEOUT;
Expand All @@ -232,6 +226,7 @@ bitmap_port_create(struct net *net, struct ip_set *set, struct nlattr *tb[],
{
struct bitmap_port *map;
u16 first_port, last_port;
u32 elements;

if (unlikely(!ip_set_attr_netorder(tb, IPSET_ATTR_PORT) ||
!ip_set_attr_netorder(tb, IPSET_ATTR_PORT_TO) ||
Expand All @@ -248,14 +243,15 @@ bitmap_port_create(struct net *net, struct ip_set *set, struct nlattr *tb[],
last_port = tmp;
}

map = kzalloc(sizeof(*map), GFP_KERNEL);
elements = last_port - first_port + 1;
set->dsize = ip_set_elem_len(set, tb, 0, 0);
map = ip_set_alloc(sizeof(*map) + elements * set->dsize);
if (!map)
return -ENOMEM;

map->elements = last_port - first_port + 1;
map->elements = elements;
map->memsize = bitmap_bytes(0, map->elements);
set->variant = &bitmap_port;
set->dsize = ip_set_elem_len(set, tb, 0);
if (!init_map_port(set, map, first_port, last_port)) {
kfree(map);
return -ENOMEM;
Expand Down
14 changes: 8 additions & 6 deletions net/netfilter/ipset/ip_set_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -364,25 +364,27 @@ add_extension(enum ip_set_ext_id id, u32 flags, struct nlattr *tb[])
}

size_t
ip_set_elem_len(struct ip_set *set, struct nlattr *tb[], size_t len)
ip_set_elem_len(struct ip_set *set, struct nlattr *tb[], size_t len,
size_t align)
{
enum ip_set_ext_id id;
size_t offset = len;
u32 cadt_flags = 0;

if (tb[IPSET_ATTR_CADT_FLAGS])
cadt_flags = ip_set_get_h32(tb[IPSET_ATTR_CADT_FLAGS]);
if (cadt_flags & IPSET_FLAG_WITH_FORCEADD)
set->flags |= IPSET_CREATE_FLAG_FORCEADD;
if (!align)
align = 1;
for (id = 0; id < IPSET_EXT_ID_MAX; id++) {
if (!add_extension(id, cadt_flags, tb))
continue;
offset = ALIGN(offset, ip_set_extensions[id].align);
set->offset[id] = offset;
len = ALIGN(len, ip_set_extensions[id].align);
set->offset[id] = len;
set->extensions |= ip_set_extensions[id].type;
offset += ip_set_extensions[id].len;
len += ip_set_extensions[id].len;
}
return offset;
return ALIGN(len, align);
}
EXPORT_SYMBOL_GPL(ip_set_elem_len);

Expand Down
Loading

0 comments on commit 95ad1f4

Please sign in to comment.