Re: [PATCH RFC nf-next 10/12] netfilter: ipset: use correct lockdep annotation in ipset_dereference

Jozsef Kadlecsik <[email protected]> Thu, 16 Jul 2026 15:56:45 +0200 (CEST)
Newsgroups gmane.comp.security.firewalls.netfilter.devel
Message-ID <[email protected]>
Hi Florian,

On Tue, 14 Jul 2026, Florian Westphal wrote:

> Avoid always-true arguments where possible, they defeat lockdep.
>
> ip_set_comment_free() is problematic: called from different contexts,
> some hold set->lock spinlock (safe), some do not hold a lock but have other
> means of mutual exclusion (e.g., entire set torn down).

Wouldn't something like the following be sufficient?

- Add a bool "deleted" element to struct ip_set.
- The set-specific destroy function would set it true before
   doing anything else.
- Then it'd be safe to use

#define ipset_dereference_locked(p, set)	\
 	rcu_dereference_protected(p, lockdep_is_held(&set->lock) || \
 				     set->deleted))

> Other callers need investigation: ip_set_comment_free() alters
> set->ext_size in a non-atomic way.  I don't see how this is safe except
> for "entire set is destroyed" case: parallel usage would be a bug.

Maybe we should convert ext_size to atomic64_t?

Best regards,
Jozsef
> Add a few lockdep assertions to ip_set_init_comment() callpaths to have
> more confidence in the correctness of the
> "Called from uadd only, protected by the set spinlock." comment at the
> start of ip_set_init_comment().
>
> Assisted-by: Claude:claude-opus-4-6
> Signed-off-by: Florian Westphal <[email protected]>
> ---
> include/linux/netfilter/ipset/ip_set.h | 3 +++
> net/netfilter/ipset/ip_set_core.c      | 3 +--
> net/netfilter/ipset/ip_set_hash_gen.h  | 6 ++----
> net/netfilter/ipset/ip_set_list_set.c  | 2 ++
> 4 files changed, 8 insertions(+), 6 deletions(-)
>
> diff --git a/include/linux/netfilter/ipset/ip_set.h b/include/linux/netfilter/ipset/ip_set.h
> index f9003ec21259..99bc997914f4 100644
> --- a/include/linux/netfilter/ipset/ip_set.h
> +++ b/include/linux/netfilter/ipset/ip_set.h
> @@ -282,6 +282,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_destroy_comment(struct ip_set *set, void *data)
> {
> diff --git a/net/netfilter/ipset/ip_set_core.c b/net/netfilter/ipset/ip_set_core.c
> index 6ece5cf305fe..a5f77f639d2a 100644
> --- a/net/netfilter/ipset/ip_set_core.c
> +++ b/net/netfilter/ipset/ip_set_core.c
> @@ -346,8 +346,7 @@ 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,
> -								 lockdep_is_held(&set->lock));
> +	struct ip_set_comment_rcu *c = ipset_dereference_locked(comment->c, set);
> 	size_t len = ext->comment ? strlen(ext->comment) : 0;
>
> 	if (unlikely(c)) {
> diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
> index a0f2cd481b82..e615de2e616b 100644
> --- a/net/netfilter/ipset/ip_set_hash_gen.h
> +++ b/net/netfilter/ipset/ip_set_hash_gen.h
> @@ -11,8 +11,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))
> @@ -271,7 +269,7 @@ mtype_add_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n)
> 	int i, j, found, len = 0, ret = 0;
>
> 	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++) {
> 		if (nets->nets[i].count)
> @@ -323,7 +321,7 @@ mtype_del_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n)
> 	int found;
>
> 	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++) {
> 		if (nets->nets[i].count)
> 			len++;
> diff --git a/net/netfilter/ipset/ip_set_list_set.c b/net/netfilter/ipset/ip_set_list_set.c
> index d7ddc57a4eca..27bc96458e13 100644
> --- a/net/netfilter/ipset/ip_set_list_set.c
> +++ b/net/netfilter/ipset/ip_set_list_set.c
> @@ -326,6 +326,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.54.0
>
>
>