Skip to content

Commit 439cd39

Browse files
sbrivio-rhummakynes
authored andcommitted
netfilter: ipset: list:set: Decrease refcount synchronously on deletion and replace
Commit 4504097 ("netfilter: ipset: Fix set:list type crash when flush/dump set in parallel") postponed decreasing set reference counters to the RCU callback. An 'ipset del' command can terminate before the RCU grace period is elapsed, and if sets are listed before then, the reference counter shown in userspace will be wrong: # ipset create h hash:ip; ipset create l list:set; ipset add l # ipset del l h; ipset list h Name: h Type: hash:ip Revision: 4 Header: family inet hashsize 1024 maxelem 65536 Size in memory: 88 References: 1 Number of entries: 0 Members: # sleep 1; ipset list h Name: h Type: hash:ip Revision: 4 Header: family inet hashsize 1024 maxelem 65536 Size in memory: 88 References: 0 Number of entries: 0 Members: Fix this by making the reference count update synchronous again. As a result, when sets are listed, ip_set_name_byindex() might now fetch a set whose reference count is already zero. Instead of relying on the reference count to protect against concurrent set renaming, grab ip_set_ref_lock as reader and copy the name, while holding the same lock in ip_set_rename() as writer instead. Reported-by: Li Shuang <[email protected]> Fixes: 4504097 ("netfilter: ipset: Fix set:list type crash when flush/dump set in parallel") Signed-off-by: Stefano Brivio <[email protected]> Signed-off-by: Jozsef Kadlecsik <[email protected]> Signed-off-by: Pablo Neira Ayuso <[email protected]>
1 parent 4269fea commit 439cd39

File tree

3 files changed

+23
-19
lines changed

3 files changed

+23
-19
lines changed

include/linux/netfilter/ipset/ip_set.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ enum {
314314
extern ip_set_id_t ip_set_get_byname(struct net *net,
315315
const char *name, struct ip_set **set);
316316
extern void ip_set_put_byindex(struct net *net, ip_set_id_t index);
317-
extern const char *ip_set_name_byindex(struct net *net, ip_set_id_t index);
317+
extern void ip_set_name_byindex(struct net *net, ip_set_id_t index, char *name);
318318
extern ip_set_id_t ip_set_nfnl_get_byindex(struct net *net, ip_set_id_t index);
319319
extern void ip_set_nfnl_put(struct net *net, ip_set_id_t index);
320320

net/netfilter/ipset/ip_set_core.c

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -693,21 +693,20 @@ ip_set_put_byindex(struct net *net, ip_set_id_t index)
693693
EXPORT_SYMBOL_GPL(ip_set_put_byindex);
694694

695695
/* Get the name of a set behind a set index.
696-
* We assume the set is referenced, so it does exist and
697-
* can't be destroyed. The set cannot be renamed due to
698-
* the referencing either.
699-
*
696+
* Set itself is protected by RCU, but its name isn't: to protect against
697+
* renaming, grab ip_set_ref_lock as reader (see ip_set_rename()) and copy the
698+
* name.
700699
*/
701-
const char *
702-
ip_set_name_byindex(struct net *net, ip_set_id_t index)
700+
void
701+
ip_set_name_byindex(struct net *net, ip_set_id_t index, char *name)
703702
{
704-
const struct ip_set *set = ip_set_rcu_get(net, index);
703+
struct ip_set *set = ip_set_rcu_get(net, index);
705704

706705
BUG_ON(!set);
707-
BUG_ON(set->ref == 0);
708706

709-
/* Referenced, so it's safe */
710-
return set->name;
707+
read_lock_bh(&ip_set_ref_lock);
708+
strncpy(name, set->name, IPSET_MAXNAMELEN);
709+
read_unlock_bh(&ip_set_ref_lock);
711710
}
712711
EXPORT_SYMBOL_GPL(ip_set_name_byindex);
713712

@@ -1153,7 +1152,7 @@ static int ip_set_rename(struct net *net, struct sock *ctnl,
11531152
if (!set)
11541153
return -ENOENT;
11551154

1156-
read_lock_bh(&ip_set_ref_lock);
1155+
write_lock_bh(&ip_set_ref_lock);
11571156
if (set->ref != 0) {
11581157
ret = -IPSET_ERR_REFERENCED;
11591158
goto out;
@@ -1170,7 +1169,7 @@ static int ip_set_rename(struct net *net, struct sock *ctnl,
11701169
strncpy(set->name, name2, IPSET_MAXNAMELEN);
11711170

11721171
out:
1173-
read_unlock_bh(&ip_set_ref_lock);
1172+
write_unlock_bh(&ip_set_ref_lock);
11741173
return ret;
11751174
}
11761175

net/netfilter/ipset/ip_set_list_set.c

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -148,25 +148,29 @@ __list_set_del_rcu(struct rcu_head * rcu)
148148
{
149149
struct set_elem *e = container_of(rcu, struct set_elem, rcu);
150150
struct ip_set *set = e->set;
151-
struct list_set *map = set->data;
152151

153-
ip_set_put_byindex(map->net, e->id);
154152
ip_set_ext_destroy(set, e);
155153
kfree(e);
156154
}
157155

158156
static inline void
159157
list_set_del(struct ip_set *set, struct set_elem *e)
160158
{
159+
struct list_set *map = set->data;
160+
161161
set->elements--;
162162
list_del_rcu(&e->list);
163+
ip_set_put_byindex(map->net, e->id);
163164
call_rcu(&e->rcu, __list_set_del_rcu);
164165
}
165166

166167
static inline void
167-
list_set_replace(struct set_elem *e, struct set_elem *old)
168+
list_set_replace(struct ip_set *set, struct set_elem *e, struct set_elem *old)
168169
{
170+
struct list_set *map = set->data;
171+
169172
list_replace_rcu(&old->list, &e->list);
173+
ip_set_put_byindex(map->net, old->id);
170174
call_rcu(&old->rcu, __list_set_del_rcu);
171175
}
172176

@@ -298,7 +302,7 @@ list_set_uadd(struct ip_set *set, void *value, const struct ip_set_ext *ext,
298302
INIT_LIST_HEAD(&e->list);
299303
list_set_init_extensions(set, ext, e);
300304
if (n)
301-
list_set_replace(e, n);
305+
list_set_replace(set, e, n);
302306
else if (next)
303307
list_add_tail_rcu(&e->list, &next->list);
304308
else if (prev)
@@ -486,6 +490,7 @@ list_set_list(const struct ip_set *set,
486490
const struct list_set *map = set->data;
487491
struct nlattr *atd, *nested;
488492
u32 i = 0, first = cb->args[IPSET_CB_ARG0];
493+
char name[IPSET_MAXNAMELEN];
489494
struct set_elem *e;
490495
int ret = 0;
491496

@@ -504,8 +509,8 @@ list_set_list(const struct ip_set *set,
504509
nested = ipset_nest_start(skb, IPSET_ATTR_DATA);
505510
if (!nested)
506511
goto nla_put_failure;
507-
if (nla_put_string(skb, IPSET_ATTR_NAME,
508-
ip_set_name_byindex(map->net, e->id)))
512+
ip_set_name_byindex(map->net, e->id, name);
513+
if (nla_put_string(skb, IPSET_ATTR_NAME, name))
509514
goto nla_put_failure;
510515
if (ip_set_put_extensions(skb, set, e, true))
511516
goto nla_put_failure;

0 commit comments

Comments
 (0)