Skip to content

Commit 4504097

Browse files
author
Jozsef Kadlecsik
committed
netfilter: ipset: Fix set:list type crash when flush/dump set in parallel
Flushing/listing entries was not RCU safe, so parallel flush/dump could lead to kernel crash. Bug reported by Deniz Eren. Fixes netfilter bugzilla id #1050. Signed-off-by: Jozsef Kadlecsik <[email protected]>
1 parent 5cc6ce9 commit 4504097

File tree

2 files changed

+28
-30
lines changed

2 files changed

+28
-30
lines changed

net/netfilter/ipset/ip_set_core.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,9 @@ static int ip_set_destroy(struct net *net, struct sock *ctnl,
985985
if (unlikely(protocol_failed(attr)))
986986
return -IPSET_ERR_PROTOCOL;
987987

988+
/* Must wait for flush to be really finished in list:set */
989+
rcu_barrier();
990+
988991
/* Commands are serialized and references are
989992
* protected by the ip_set_ref_lock.
990993
* External systems (i.e. xt_set) must call

net/netfilter/ipset/ip_set_list_set.c

Lines changed: 25 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ MODULE_ALIAS("ip_set_list:set");
3030
struct set_elem {
3131
struct rcu_head rcu;
3232
struct list_head list;
33+
struct ip_set *set; /* Sigh, in order to cleanup reference */
3334
ip_set_id_t id;
3435
} __aligned(__alignof__(u64));
3536

@@ -151,30 +152,29 @@ list_set_kadt(struct ip_set *set, const struct sk_buff *skb,
151152
/* Userspace interfaces: we are protected by the nfnl mutex */
152153

153154
static void
154-
__list_set_del(struct ip_set *set, struct set_elem *e)
155+
__list_set_del_rcu(struct rcu_head * rcu)
155156
{
157+
struct set_elem *e = container_of(rcu, struct set_elem, rcu);
158+
struct ip_set *set = e->set;
156159
struct list_set *map = set->data;
157160

158161
ip_set_put_byindex(map->net, e->id);
159-
/* We may call it, because we don't have a to be destroyed
160-
* extension which is used by the kernel.
161-
*/
162162
ip_set_ext_destroy(set, e);
163-
kfree_rcu(e, rcu);
163+
kfree(e);
164164
}
165165

166166
static inline void
167167
list_set_del(struct ip_set *set, struct set_elem *e)
168168
{
169169
list_del_rcu(&e->list);
170-
__list_set_del(set, e);
170+
call_rcu(&e->rcu, __list_set_del_rcu);
171171
}
172172

173173
static inline void
174-
list_set_replace(struct ip_set *set, struct set_elem *e, struct set_elem *old)
174+
list_set_replace(struct set_elem *e, struct set_elem *old)
175175
{
176176
list_replace_rcu(&old->list, &e->list);
177-
__list_set_del(set, old);
177+
call_rcu(&old->rcu, __list_set_del_rcu);
178178
}
179179

180180
static void
@@ -244,9 +244,6 @@ list_set_uadd(struct ip_set *set, void *value, const struct ip_set_ext *ext,
244244
struct set_elem *e, *n, *prev, *next;
245245
bool flag_exist = flags & IPSET_FLAG_EXIST;
246246

247-
if (SET_WITH_TIMEOUT(set))
248-
set_cleanup_entries(set);
249-
250247
/* Find where to add the new entry */
251248
n = prev = next = NULL;
252249
list_for_each_entry(e, &map->members, list) {
@@ -301,10 +298,11 @@ list_set_uadd(struct ip_set *set, void *value, const struct ip_set_ext *ext,
301298
if (!e)
302299
return -ENOMEM;
303300
e->id = d->id;
301+
e->set = set;
304302
INIT_LIST_HEAD(&e->list);
305303
list_set_init_extensions(set, ext, e);
306304
if (n)
307-
list_set_replace(set, e, n);
305+
list_set_replace(e, n);
308306
else if (next)
309307
list_add_tail_rcu(&e->list, &next->list);
310308
else if (prev)
@@ -431,6 +429,7 @@ list_set_destroy(struct ip_set *set)
431429

432430
if (SET_WITH_TIMEOUT(set))
433431
del_timer_sync(&map->gc);
432+
434433
list_for_each_entry_safe(e, n, &map->members, list) {
435434
list_del(&e->list);
436435
ip_set_put_byindex(map->net, e->id);
@@ -450,8 +449,10 @@ list_set_head(struct ip_set *set, struct sk_buff *skb)
450449
struct set_elem *e;
451450
u32 n = 0;
452451

453-
list_for_each_entry(e, &map->members, list)
452+
rcu_read_lock();
453+
list_for_each_entry_rcu(e, &map->members, list)
454454
n++;
455+
rcu_read_unlock();
455456

456457
nested = ipset_nest_start(skb, IPSET_ATTR_DATA);
457458
if (!nested)
@@ -483,33 +484,25 @@ list_set_list(const struct ip_set *set,
483484
atd = ipset_nest_start(skb, IPSET_ATTR_ADT);
484485
if (!atd)
485486
return -EMSGSIZE;
486-
list_for_each_entry(e, &map->members, list) {
487-
if (i == first)
488-
break;
489-
i++;
490-
}
491487

492488
rcu_read_lock();
493-
list_for_each_entry_from(e, &map->members, list) {
494-
i++;
495-
if (SET_WITH_TIMEOUT(set) &&
496-
ip_set_timeout_expired(ext_timeout(e, set)))
489+
list_for_each_entry_rcu(e, &map->members, list) {
490+
if (i < first ||
491+
(SET_WITH_TIMEOUT(set) &&
492+
ip_set_timeout_expired(ext_timeout(e, set)))) {
493+
i++;
497494
continue;
495+
}
498496
nested = ipset_nest_start(skb, IPSET_ATTR_DATA);
499-
if (!nested) {
500-
if (i == first) {
501-
nla_nest_cancel(skb, atd);
502-
ret = -EMSGSIZE;
503-
goto out;
504-
}
497+
if (!nested)
505498
goto nla_put_failure;
506-
}
507499
if (nla_put_string(skb, IPSET_ATTR_NAME,
508500
ip_set_name_byindex(map->net, e->id)))
509501
goto nla_put_failure;
510502
if (ip_set_put_extensions(skb, set, e, true))
511503
goto nla_put_failure;
512504
ipset_nest_end(skb, nested);
505+
i++;
513506
}
514507

515508
ipset_nest_end(skb, atd);
@@ -520,10 +513,12 @@ list_set_list(const struct ip_set *set,
520513
nla_put_failure:
521514
nla_nest_cancel(skb, nested);
522515
if (unlikely(i == first)) {
516+
nla_nest_cancel(skb, atd);
523517
cb->args[IPSET_CB_ARG0] = 0;
524518
ret = -EMSGSIZE;
519+
} else {
520+
cb->args[IPSET_CB_ARG0] = i;
525521
}
526-
cb->args[IPSET_CB_ARG0] = i - 1;
527522
ipset_nest_end(skb, atd);
528523
out:
529524
rcu_read_unlock();

0 commit comments

Comments
 (0)