From: Jakub Kicinski <kuba@kernel.org>
To: pablo@netfilter.org
Cc: Jakub Kicinski <kuba@kernel.org>,
netfilter-devel@vger.kernel.org, davem@davemloft.net,
netdev@vger.kernel.org, pabeni@redhat.com, edumazet@google.com,
fw@strlen.de, horms@kernel.org
Subject: Re: [PATCH net 06/10] netfilter: ipset: add and use mtype_del_cidr_all helper
Date: Tue, 4 Aug 2026 17:15:33 -0700 [thread overview]
Message-ID: <20260805001533.3978816-1-kuba@kernel.org> (raw)
In-Reply-To: <20260731151806.849724-7-pablo@netfilter.org>
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
netfilter: ipset: add and use mtype_del_cidr_all helper
This adds a mtype_del_cidr_all() helper that wraps the per-net-dimension
loop calling mtype_del_cidr(), and converts the three open-coded loops in
mtype_gc_do(), mtype_add() and mtype_del() to use it. No functional
change is intended.
> diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
> index ef586b486f519..f00c82acd7f05 100644
> --- a/net/netfilter/ipset/ip_set_hash_gen.h
> +++ b/net/netfilter/ipset/ip_set_hash_gen.h
[ ... ]
> @@ -410,6 +412,17 @@ mtype_del_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n)
> }
> #endif
>
> +static void
> +mtype_del_cidr_all(struct ip_set *set, struct htype *h, const struct mtype_elem *data)
> +{
> +#ifdef IP_SET_HASH_WITH_NETS
> + int k;
> +
> + for (k = 0; k < IPSET_NET_COUNT; k++)
> + mtype_del_cidr(set, h, DCIDR_GET(data->cidr, k), k);
> +#endif
> +}
[Severity: Critical]
This helper makes mtype_del_cidr() the single delete-side writer of
h->rnets[], so it may be worth asking about the locking of that array.
The problem does not originate in this patch but in the earlier commit of
this series, "netfilter: ipset: rework cidr bookkeeping", which turned the
fixed-size h->nets[] into an RCU-published, reallocated struct
net_prefixes; this patch routes all three delete sites into one of the
racing writers.
mtype_add_cidr() and mtype_del_cidr() both serialize on set->lock:
spin_lock_bh(&set->lock);
nets = __ipset_dereference(h->rnets[n]);
...
rcu_assign_pointer(h->rnets[n], tmp);
kfree_rcu(nets, rcu);
unlock:
spin_unlock_bh(&set->lock);
mtype_flush() does the same read-allocate-publish-free sequence with no
set->lock held:
for (i = 0; i < IPSET_NET_COUNT; i++) {
nets = ipset_dereference_nfnl(h->rnets[i]);
tmp = kzalloc_obj(*tmp, GFP_ATOMIC);
if (!tmp) {
u8 j;
for (j = 0; j < nets->len; j++)
nets->nets[j].count = 0;
} else {
rcu_assign_pointer(h->rnets[i], tmp);
kfree_rcu(nets, rcu);
}
}
ip_set_flush_set() does call ip_set_lock(set), but for the hash variants
(.region_lock = true) that is a no-op:
net/netfilter/ipset/ip_set_core.c:
static inline void
ip_set_lock(struct ip_set *set)
{
if (!set->variant->region_lock)
spin_lock_bh(&set->lock);
}
The hregion locks flush does take are all released before the rnets loop
runs, and mtype_add_cidr()/mtype_del_cidr() do not take hregion locks.
Can this interleaving happen?
CPU0 (ipset flush, process context, nfnl mutex only)
nets_old = ipset_dereference_nfnl(h->rnets[i]);
CPU1 (SET target kadt in softirq -> mtype_add() -> mtype_add_cidr(),
or gc worker -> mtype_gc_do() -> mtype_del_cidr_all())
spin_lock_bh(&set->lock);
nets_old = __ipset_dereference(h->rnets[n]);
rcu_assign_pointer(h->rnets[n], tmp1);
kfree_rcu(nets_old, rcu);
spin_unlock_bh(&set->lock);
CPU0
rcu_assign_pointer(h->rnets[i], tmp0); /* tmp1 leaked */
kfree_rcu(nets_old, rcu); /* rcu_head reused */
Does the second kfree_rcu() on an object whose rcu_head is already queued
corrupt the callback list and double free the net_prefixes? And does the
tmp0 publish also drop the accounting update CPU1 just made in tmp1?
On the flush allocation-failure path, flush writes nets->nets[j].count = 0
in place while mtype_del_cidr() may be doing nets->nets[found].count-- and
mtype_add_cidr() nets->nets[i].count++ on the same object. Can that tear
the counts and the __counted_by(len) contents?
[ ... ]
> @@ -1004,11 +1010,7 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
> j = 0;
> data = ahash_data(n, j, set->dsize);
> if (!deleted) {
> -#ifdef IP_SET_HASH_WITH_NETS
> - for (i = 0; i < IPSET_NET_COUNT; i++)
> - mtype_del_cidr(set, h,
> - DCIDR_GET(data->cidr, i), i);
> -#endif
> + mtype_del_cidr_all(set, h, data);
> ip_set_ext_destroy(set, data);
> t->hregion[r].elements--;
> }
[Severity: High]
Related to the decrement side this patch factors out: the matching
increment in mtype_add() ignores the new error return of
mtype_add_cidr(). As with the previous comment, this comes from
"netfilter: ipset: rework cidr bookkeeping" earlier in the series, which
changed mtype_add_cidr() from a void in-place update into an int that
allocates a replacement net_prefixes with GFP_ATOMIC and can return
-ENOMEM.
The insert path in mtype_add() still reads:
#ifdef IP_SET_HASH_WITH_NETS
for (i = 0; i < IPSET_NET_COUNT; i++)
mtype_add_cidr(set, h, DCIDR_GET(d->cidr, i), i);
#endif
memcpy(data, d, sizeof(struct mtype_elem));
while mtype_add_cidr() can fail:
tmp = kzalloc_flex(*tmp, nets, len, GFP_ATOMIC);
if (!tmp) {
ret = -ENOMEM;
goto unlock;
}
ret in mtype_add() stays 0, so ipset add reports success and the element
shows up in ipset list. But mtype_test_cidrs() only builds lookup keys
from prefixes recorded in h->rnets[]:
for (j = 0; j < nets0->len && !multi; j++) {
if (!nets0->nets[j].count)
continue;
...
key = HKEY(d, h->initval, t->htable_bits);
Can this leave an element that never matches a packet, even though
userspace was told the add succeeded?
There also seems to be a follow-on effect on unrelated members. Since
mtype_add_cidr() only allocates when the cidr is absent, a failure means
that cidr is not registered at all. If another element later registers
the same cidr (count = 1) and the unregistered element is then deleted,
mtype_del_cidr_all() -> mtype_del_cidr() finds that shared entry and does:
nets->nets[found].count--;
Does that drop the count to 0 and remove a prefix that live elements still
use, so those elements stop matching too?
And in the case where mtype_del_cidr()'s own kzalloc_flex() previously
failed and left a hole (a count == 0 entry that keeps its cidr), deleting
the unregistered element decrements a count of 0. Does that underflow the
u32 to 0xFFFFFFFF and pin the prefix in the lookup list permanently?
Would propagating the mtype_add_cidr() return value (and undoing the
already-successful increments) be preferable here?
next prev parent reply other threads:[~2026-08-05 0:15 UTC|newest]
Thread overview: 30+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-31 15:17 [PATCH net 00/10] Netfilter/IPVS fixes for net Pablo Neira Ayuso
2026-07-31 15:17 ` [PATCH net 01/10] ipvs: stop estimator after disabled calc phase Pablo Neira Ayuso
2026-08-05 23:50 ` patchwork-bot+netdevbpf
2026-07-31 15:17 ` [PATCH net 02/10] netfilter: ebt_nflog: pin the NFLOG backend Pablo Neira Ayuso
2026-08-05 0:15 ` Jakub Kicinski
2026-08-05 7:11 ` Florian Westphal
2026-08-05 7:25 ` Paolo Abeni
2026-08-05 17:18 ` Pablo Neira Ayuso
2026-08-05 19:12 ` Florian Westphal
2026-07-31 15:17 ` [PATCH net 03/10] netfilter: ipset: rework cidr bookkeeping Pablo Neira Ayuso
2026-08-05 0:15 ` Jakub Kicinski
2026-08-05 7:33 ` Florian Westphal
2026-08-05 17:19 ` Pablo Neira Ayuso
2026-08-05 19:17 ` Florian Westphal
2026-07-31 15:18 ` [PATCH net 04/10] netfilter: ipset: switch ext_size to atomic64_t Pablo Neira Ayuso
2026-07-31 15:18 ` [PATCH net 05/10] netfilter: ipset: add small wrappers for hash and bucket sizes Pablo Neira Ayuso
2026-07-31 15:18 ` [PATCH net 06/10] netfilter: ipset: add and use mtype_del_cidr_all helper Pablo Neira Ayuso
2026-08-05 0:15 ` Jakub Kicinski [this message]
2026-07-31 15:18 ` [PATCH net 07/10] netfilter: ipset: switch to rcu work Pablo Neira Ayuso
2026-07-31 15:18 ` [PATCH net 08/10] ipvs: avoid out-of-bounds write in ip_vs_nat_icmp Pablo Neira Ayuso
2026-08-05 0:15 ` Jakub Kicinski
2026-08-05 4:18 ` Julian Anastasov
2026-08-05 17:20 ` Pablo Neira Ayuso
2026-07-31 15:18 ` [PATCH net 09/10] ipvs: return the csum validation for forward hook Pablo Neira Ayuso
2026-07-31 15:18 ` [PATCH net 10/10] netfilter: nft_ct: move custom expectation support to helper Pablo Neira Ayuso
2026-08-05 0:15 ` Jakub Kicinski
2026-08-05 17:38 ` Pablo Neira Ayuso
2026-08-05 7:42 ` [PATCH net 00/10] Netfilter/IPVS fixes for net Florian Westphal
2026-08-05 17:39 ` Pablo Neira Ayuso
2026-08-05 23:21 ` Jakub Kicinski
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=20260805001533.3978816-1-kuba@kernel.org \
--to=kuba@kernel.org \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=fw@strlen.de \
--cc=horms@kernel.org \
--cc=netdev@vger.kernel.org \
--cc=netfilter-devel@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=pablo@netfilter.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox