All of lore.kernel.org
 help / color / mirror / Atom feed
From: Florian Westphal <fw@strlen.de>
To: <netfilter-devel@vger.kernel.org>
Cc: Jozsef Kadlecsik <kadlec@netfilter.org>, Florian Westphal <fw@strlen.de>
Subject: [PATCH nf-next v4 13/13] netfilter: ipset: improve lockdep coverage
Date: Fri,  4 Sep 2026 20:53:21 +0200	[thread overview]
Message-ID: <20260904185321.30313-14-fw@strlen.de> (raw)
In-Reply-To: <20260904185321.30313-1-fw@strlen.de>

Avoid always-true arguments to rcu_dereference_protected(), they defeat
lockdep.

Add a few lockdep assertions to ip_set_init_comment() callpaths to have
more confidence in the correctness of the "Called from uadd only" claim.
Uadd implies nfnl mutex is held, make that explicit.

ip_set_comment_free() is called from different contexts, some hold
set->lock spinlock, some do not hold a lock at all but are safe because
the set is being destroyed.

Jozsef suggests to add a "dead" flag, make it so.

Suggested-by: Jozsef Kadlecsik <kadlec@netfilter.org>
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Florian Westphal <fw@strlen.de>
---
 include/linux/netfilter/ipset/ip_set.h  |  5 +++++
 net/netfilter/ipset/ip_set_bitmap_gen.h |  4 ++++
 net/netfilter/ipset/ip_set_core.c       | 22 +++++++++++++++-------
 net/netfilter/ipset/ip_set_hash_gen.h   |  6 ++----
 net/netfilter/ipset/ip_set_list_set.c   | 20 ++++++++++++++++++--
 5 files changed, 44 insertions(+), 13 deletions(-)

diff --git a/include/linux/netfilter/ipset/ip_set.h b/include/linux/netfilter/ipset/ip_set.h
index b2ff881e2ec1..27270575d7ba 100644
--- a/include/linux/netfilter/ipset/ip_set.h
+++ b/include/linux/netfilter/ipset/ip_set.h
@@ -258,6 +258,8 @@ struct ip_set {
 	u8 extensions;
 	/* Create flags */
 	u8 flags;
+	/* set is being destroyed */
+	bool dead;
 	/* Default timeout value, if enabled */
 	u32 timeout;
 	/* Number of elements (vs timeout) */
@@ -272,6 +274,9 @@ struct ip_set {
 	void *data;
 };
 
+#define ipset_dereference_locked(p, set)		\
+	rcu_dereference_protected(p, lockdep_is_held(&set->lock))
+
 static inline void
 ip_set_ext_destroy(struct ip_set *set, void *data)
 {
diff --git a/net/netfilter/ipset/ip_set_bitmap_gen.h b/net/netfilter/ipset/ip_set_bitmap_gen.h
index 409a7d07fa8d..907273d6c37b 100644
--- a/net/netfilter/ipset/ip_set_bitmap_gen.h
+++ b/net/netfilter/ipset/ip_set_bitmap_gen.h
@@ -137,6 +137,8 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
 	void *x = get_ext(set, map, e->id);
 	int ret = mtype_do_add(e, map, flags, set->dsize);
 
+	lockdep_assert_held(&set->lock);
+
 	if (ret == IPSET_ADD_FAILED) {
 		if (SET_WITH_TIMEOUT(set) &&
 		    ip_set_timeout_expired(ext_timeout(x, set))) {
@@ -182,6 +184,8 @@ mtype_del(struct ip_set *set, void *value, const struct ip_set_ext *ext,
 	const struct mtype_adt_elem *e = value;
 	void *x = get_ext(set, map, e->id);
 
+	lockdep_assert_held(&set->lock);
+
 	if (mtype_do_del(e, map))
 		return -IPSET_ERR_EXIST;
 
diff --git a/net/netfilter/ipset/ip_set_core.c b/net/netfilter/ipset/ip_set_core.c
index 856e53271b38..9d1c765de86a 100644
--- a/net/netfilter/ipset/ip_set_core.c
+++ b/net/netfilter/ipset/ip_set_core.c
@@ -62,7 +62,7 @@ MODULE_ALIAS_NFNL_SUBSYS(NFNL_SUBSYS_IPSET);
 #define ip_set(inst, id)		\
 	ip_set_dereference(inst)[id]
 #define ip_set_ref_netlink(inst,id)	\
-	rcu_dereference_raw((inst)->ip_set_list)[id]
+	rcu_dereference((inst)->ip_set_list)[id]
 #define ip_set_dereference_nfnl(p)	\
 	rcu_dereference_check(p, lockdep_nfnl_is_held(NFNL_SUBSYS_IPSET))
 
@@ -323,14 +323,14 @@ ip_set_comment_uget(struct nlattr *tb)
 	return nla_data(tb);
 }
 
-/* Called from uadd only, protected by the set spinlock.
+/* Called from uadd only, protected by the nfnl subsys mutex.
  * The kadt functions don't use the comment extensions in any way.
  */
 void
 ip_set_init_comment(struct ip_set *set, struct ip_set_comment *comment,
 		    const struct ip_set_ext *ext)
 {
-	struct ip_set_comment_rcu *c = rcu_dereference_protected(comment->c, 1);
+	struct ip_set_comment_rcu *c = ip_set_dereference_nfnl(comment->c);
 	size_t len = ext->comment ? strlen(ext->comment) : 0;
 
 	if (unlikely(c)) {
@@ -373,7 +373,9 @@ ip_set_comment_free(struct ip_set *set, void *ptr)
 	struct ip_set_comment *comment = ptr;
 	struct ip_set_comment_rcu *c;
 
-	c = rcu_dereference_protected(comment->c, 1);
+	c = rcu_dereference_check(comment->c,
+				  lockdep_is_held(&set->lock) ||
+				  set->dead);
 	if (unlikely(!c))
 		return;
 	atomic64_sub(sizeof(*c) + strlen(c->str) + 1, &set->ext_size);
@@ -1014,6 +1016,12 @@ static int ip_set_none(struct sk_buff *skb, const struct nfnl_info *info,
 	return -EOPNOTSUPP;
 }
 
+static void ip_set_destroy_set(struct ip_set *set)
+{
+	set->dead = true;
+	set->variant->destroy(set);
+}
+
 static int ip_set_create(struct sk_buff *skb, const struct nfnl_info *info,
 			 const struct nlattr * const attr[])
 {
@@ -1129,7 +1137,7 @@ static int ip_set_create(struct sk_buff *skb, const struct nfnl_info *info,
 
 cleanup:
 	set->variant->cancel_gc(set);
-	set->variant->destroy(set);
+	ip_set_destroy_set(set);
 put_out:
 	module_put(set->type->me);
 out:
@@ -1149,7 +1157,7 @@ ip_set_setname_policy[IPSET_ATTR_CMD_MAX + 1] = {
 static void
 destroy_and_free_set(struct ip_set *set)
 {
-	set->variant->destroy(set);
+	ip_set_destroy_set(set);
 	module_put(set->type->me);
 	kfree(set);
 }
@@ -1189,7 +1197,7 @@ _destroy_all_sets(struct ip_set_net *inst)
 		set = ip_set(inst, i);
 		if (set) {
 			ip_set(inst, i) = NULL;
-			set->variant->destroy(set);
+			ip_set_destroy_set(set);
 			module_put(set->type->me);
 			kfree(set);
 		}
diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
index cf48b7c50ca1..e60602ecbdbd 100644
--- a/net/netfilter/ipset/ip_set_hash_gen.h
+++ b/net/netfilter/ipset/ip_set_hash_gen.h
@@ -12,8 +12,6 @@
 #include <linux/netfilter/nfnetlink.h>
 #include <linux/netfilter/ipset/ip_set.h>
 
-#define __ipset_dereference(p)		\
-	rcu_dereference_protected(p, 1)
 #define ipset_dereference_nfnl(p)	\
 	rcu_dereference_protected(p,	\
 		lockdep_nfnl_is_held(NFNL_SUBSYS_IPSET))
@@ -353,7 +351,7 @@ mtype_add_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n)
 	struct net_prefix np;
 
 	spin_lock_bh(&set->lock);
-	nets = __ipset_dereference(h->rnets[n]);
+	nets = ipset_dereference_locked(h->rnets[n], set);
 	/* Add in increasing prefix order, so larger cidr first */
 	for (i = 0, found = -1; i < nets->len; i++) {
 		np = READ_ONCE(nets->nets[i]);
@@ -426,7 +424,7 @@ mtype_del_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n)
 	BUILD_BUG_ON(sizeof(struct net_prefix) != sizeof(u32));
 
 	spin_lock_bh(&set->lock);
-	nets = __ipset_dereference(h->rnets[n]);
+	nets = ipset_dereference_locked(h->rnets[n], set);
 	for (i = 0, found = -1; i < nets->len; i++) {
 		np = READ_ONCE(nets->nets[i]);
 		if (np.count && np.cidr == cidr) {
diff --git a/net/netfilter/ipset/ip_set_list_set.c b/net/netfilter/ipset/ip_set_list_set.c
index 738f14a73684..ac749afe3801 100644
--- a/net/netfilter/ipset/ip_set_list_set.c
+++ b/net/netfilter/ipset/ip_set_list_set.c
@@ -139,15 +139,25 @@ list_set_kadt(struct ip_set *set, const struct sk_buff *skb,
 	return ret;
 }
 
-/* Userspace interfaces: we are protected by the nfnl mutex */
-
 static void
 __list_set_del_rcu(struct rcu_head * rcu)
 {
 	struct set_elem *e = container_of(rcu, struct set_elem, rcu);
 	struct ip_set *set = e->set;
 
+	/* element is no longer public, extensions can
+	 * be removed without lock.  This will trip the
+	 * rcu_dereference_check() call in ip_set_comment_free(),
+	 * so lock/unlock for debug kernels to avoid need to
+	 * add a 'dead' flag to the comment extension.
+	 */
+#ifdef CONFIG_PROVE_RCU
+	spin_lock_bh(&set->lock);
+#endif
 	ip_set_ext_destroy(set, e);
+#ifdef CONFIG_PROVE_RCU
+	spin_unlock_bh(&set->lock);
+#endif
 	kfree(e);
 }
 
@@ -223,6 +233,8 @@ static void
 list_set_init_extensions(struct ip_set *set, const struct ip_set_ext *ext,
 			 struct set_elem *e)
 {
+	lockdep_assert_held(&set->lock);
+
 	if (SET_WITH_COUNTER(set))
 		ip_set_init_counter(ext_counter(e, set), ext);
 	if (SET_WITH_COMMENT(set))
@@ -243,6 +255,8 @@ list_set_uadd(struct ip_set *set, void *value, const struct ip_set_ext *ext,
 	struct set_elem *e, *n, *prev, *next;
 	bool flag_exist = flags & IPSET_FLAG_EXIST;
 
+	lockdep_assert_held(&set->lock);
+
 	/* Find where to add the new entry */
 	n = prev = next = NULL;
 	list_for_each_entry_rcu(e, &map->members, list) {
@@ -327,6 +341,8 @@ list_set_udel(struct ip_set *set, void *value, const struct ip_set_ext *ext,
 	struct set_adt_elem *d = value;
 	struct set_elem *e, *n, *next, *prev = NULL;
 
+	lockdep_assert_held(&set->lock);
+
 	list_for_each_entry_safe(e, n, &map->members, list) {
 		if (SET_WITH_TIMEOUT(set) &&
 		    ip_set_timeout_expired(ext_timeout(e, set)))
-- 
2.55.0


      parent reply	other threads:[~2026-09-04 18:54 UTC|newest]

Thread overview: 28+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-04 18:53 [PATCH nf-next v4 00/13] ipset: replace internal hash table with rhashtable Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 01/13] rhashtable: add rhashtable_flush_and_free helper Florian Westphal
2026-09-04 19:29   ` Florian Westphal
2026-09-08  5:12   ` Herbert Xu
2026-09-08  5:30     ` Florian Westphal
2026-09-08  9:04       ` Herbert Xu
2026-09-08  9:56         ` Florian Westphal
2026-09-08 12:39           ` Herbert Xu
2026-09-08 13:25             ` Florian Westphal
2026-09-09  3:49               ` Herbert Xu
2026-09-09  4:17   ` Herbert Xu
2026-09-09 14:45     ` Florian Westphal
2026-09-10  9:11       ` Herbert Xu
2026-09-10 10:41         ` Florian Westphal
2026-09-11 11:56           ` Herbert Xu
2026-09-11 12:54             ` Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 02/13] netfilter: ipset: add rhashtable boilerplate stubs Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 03/13] netfilter: ipset: add rhltable " Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 04/13] netfilter: ipset: replace internal hash table with rhashtable Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 05/13] netfilter: ipset: re-add forceadd support Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 06/13] netfilter: ipset: also report mem size for cidr storage to userspace Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 07/13] netfilter: ipset: remove obsolete data_next stubs Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 08/13] netfilter: ipset: remove last region lock usage Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 09/13] netfilter: ipset: remove multi-flag Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 10/13] netfilter: ipset: remove resize completely Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 11/13] netfilter: ipset: remove trivial kvfree wrapper Florian Westphal
2026-09-04 18:53 ` [PATCH nf-next v4 12/13] netfilter: ipset: use plain rcu_read_lock Florian Westphal
2026-09-04 18:53 ` Florian Westphal [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260904185321.30313-14-fw@strlen.de \
    --to=fw@strlen.de \
    --cc=kadlec@netfilter.org \
    --cc=netfilter-devel@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.