From: Zhengchuan Liang Hash resize rebuilds the table by copying live elements into a new table, while comment data is stored outside of the element body. Recreate the comment extension for resized entries so the new table does not share comment storage with the retired table. Once resize gives each table its own comment data again, the old table can return to destroying its extensions in the normal teardown paths. This keeps comment lifetime and accounting consistent across resize and the follow-up gc, dump, add, del and flush paths. Fixes: f66ee0410b1c ("netfilter: ipset: Fix "INFO: rcu detected stall in hash_xxx" reports") Cc: stable@kernel.org Reported-by: Yuan Tan Reported-by: Yifan Wu Reported-by: Juefei Pu Reported-by: Xin Liu Signed-off-by: Zhengchuan Liang Signed-off-by: Ren Wei --- include/linux/netfilter/ipset/ip_set.h | 1 + net/netfilter/ipset/ip_set_core.c | 36 ++++++++++++++++++++++++++ net/netfilter/ipset/ip_set_hash_gen.h | 15 ++++++----- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/include/linux/netfilter/ipset/ip_set.h b/include/linux/netfilter/ipset/ip_set.h index b98331572ad2..c3620899744c 100644 --- a/include/linux/netfilter/ipset/ip_set.h +++ b/include/linux/netfilter/ipset/ip_set.h @@ -336,6 +336,7 @@ extern size_t ip_set_elem_len(struct ip_set *set, struct nlattr *tb[], size_t len, size_t align); extern int ip_set_get_extensions(struct ip_set *set, struct nlattr *tb[], struct ip_set_ext *ext); +extern int ip_set_ext_copy(struct ip_set *set, void *dst, const void *src); extern int ip_set_put_extensions(struct sk_buff *skb, const struct ip_set *set, const void *e, bool active); extern bool ip_set_match_extensions(struct ip_set *set, diff --git a/net/netfilter/ipset/ip_set_core.c b/net/netfilter/ipset/ip_set_core.c index c5a26236a0bb..0f5994ffec96 100644 --- a/net/netfilter/ipset/ip_set_core.c +++ b/net/netfilter/ipset/ip_set_core.c @@ -367,6 +367,42 @@ ip_set_init_comment(struct ip_set *set, struct ip_set_comment *comment, } EXPORT_SYMBOL_GPL(ip_set_init_comment); +static int +ip_set_copy_comment(struct ip_set *set, struct ip_set_comment *dst, + const struct ip_set_comment *src) +{ + struct ip_set_comment_rcu *c, *newc; + size_t len; + + RCU_INIT_POINTER(dst->c, NULL); + + c = rcu_dereference_bh(src->c); + if (!c) + return 0; + + len = strlen(c->str); + newc = kmalloc(sizeof(*newc) + len + 1, GFP_ATOMIC); + if (unlikely(!newc)) + return -ENOMEM; + + memcpy(newc->str, c->str, len + 1); + set->ext_size += sizeof(*newc) + len + 1; + rcu_assign_pointer(dst->c, newc); + + return 0; +} + +int +ip_set_ext_copy(struct ip_set *set, void *dst, const void *src) +{ + if (SET_WITH_COMMENT(set)) + return ip_set_copy_comment(set, ext_comment(dst, set), + ext_comment(src, set)); + + return 0; +} +EXPORT_SYMBOL_GPL(ip_set_ext_copy); + /* Used only when dumping a set, protected by rcu_read_lock() */ static int ip_set_put_comment(struct sk_buff *skb, const struct ip_set_comment *comment) diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h index b79e5dd2af03..b937a478f5ac 100644 --- a/net/netfilter/ipset/ip_set_hash_gen.h +++ b/net/netfilter/ipset/ip_set_hash_gen.h @@ -584,7 +584,7 @@ mtype_gc(struct work_struct *work) if (atomic_dec_and_test(&t->uref) && atomic_read(&t->ref)) { pr_debug("Table destroy after resize by expire: %p\n", t); - mtype_ahash_destroy(set, t, false); + mtype_ahash_destroy(set, t, true); } queue_delayed_work(system_power_efficient_wq, &gc->dwork, next_run); @@ -743,6 +743,9 @@ mtype_resize(struct ip_set *set, bool retried) } d = ahash_data(m, m->pos, dsize); memcpy(d, data, dsize); + ret = ip_set_ext_copy(set, d, data); + if (ret < 0) + goto cleanup; set_bit(m->pos++, m->used); t->hregion[nr].elements++; #ifdef IP_SET_HASH_WITH_NETS @@ -778,7 +781,7 @@ mtype_resize(struct ip_set *set, bool retried) /* If there's nobody else using the table, destroy it */ if (atomic_dec_and_test(&orig->uref)) { pr_debug("Table destroy by resize %p\n", orig); - mtype_ahash_destroy(set, orig, false); + mtype_ahash_destroy(set, orig, true); } out: @@ -791,7 +794,7 @@ mtype_resize(struct ip_set *set, bool retried) rcu_read_unlock_bh(); atomic_set(&orig->ref, 0); atomic_dec(&orig->uref); - mtype_ahash_destroy(set, t, false); + mtype_ahash_destroy(set, t, true); if (ret == -EAGAIN) goto retry; goto out; @@ -1023,7 +1026,7 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext, out: if (atomic_dec_and_test(&t->uref) && atomic_read(&t->ref)) { pr_debug("Table destroy after resize by add: %p\n", t); - mtype_ahash_destroy(set, t, false); + mtype_ahash_destroy(set, t, true); } return ret; } @@ -1135,7 +1138,7 @@ mtype_del(struct ip_set *set, void *value, const struct ip_set_ext *ext, } if (atomic_dec_and_test(&t->uref) && atomic_read(&t->ref)) { pr_debug("Table destroy after resize by del: %p\n", t); - mtype_ahash_destroy(set, t, false); + mtype_ahash_destroy(set, t, true); } return ret; } @@ -1341,7 +1344,7 @@ mtype_uref(struct ip_set *set, struct netlink_callback *cb, bool start) if (atomic_dec_and_test(&t->uref) && atomic_read(&t->ref)) { pr_debug("Table destroy after resize " " by dump: %p\n", t); - mtype_ahash_destroy(set, t, false); + mtype_ahash_destroy(set, t, true); } cb->args[IPSET_CB_PRIVATE] = 0; } -- 2.39.5