public inbox for stable@vger.kernel.org
 help / color / mirror / Atom feed
From: Natarajan KV <natarajankv91@gmail.com>
To: stable@vger.kernel.org
Cc: gregkh@linuxfoundation.org, pablo@netfilter.org,
	kadlec@netfilter.org, fw@strlen.de
Subject: [PATCH v2] netfilter: nft_set_pipapo: move clone allocation to insert/removal path
Date: Wed, 04 Mar 2026 07:08:12 -0800 (PST)	[thread overview]
Message-ID: <69a84adc.050a0220.1cea47.3011@mx.google.com> (raw)
In-Reply-To: <20260304133859.28372-1-natarajankv91@gmail.com>

Move pipapo_clone() from the commit/abort callbacks to the
insert and removal paths via pipapo_maybe_clone(), which creates
the working copy on demand and can propagate allocation failures.

Previously, pipapo_clone() was called from nft_pipapo_commit() and
nft_pipapo_abort() which return void, making it impossible to
report allocation failures. When pipapo_clone() failed during abort,
the stale clone persisted with dirty == true, causing subsequent
commits to promote a clone containing freed element references --
a use-after-free.

With this change:
 - pipapo_maybe_clone() allocates the clone lazily on first insert,
   deactivate, walk(UPDATE), or remove. Allocation failure returns
   NULL and propagates -ENOMEM to the caller.
 - nft_pipapo_commit() simply swaps clone to match and sets clone
   to NULL. No allocation needed.
 - nft_pipapo_abort() simply frees the clone and sets it to NULL.
   No allocation needed.
 - The dirty flag is removed; clone != NULL indicates pending changes.
 - nft_pipapo_init() no longer pre-allocates a clone.

This is a backport adaptation of the mainline on-demand clone refactor
series from commit a590f4760922 ("netfilter: nft_set_pipapo: move
prove_locking helper around") through commit 532aec7e878b
("netfilter: nft_set_pipapo: remove dirty flag") to the 6.6.x
stable tree, preserving the 6.6.x function signatures and API
conventions.

Fixes: 3c4287f62044 ("nf_tables: Add set type for arbitrary concatenation of ranges")
Cc: stable@vger.kernel.org
Signed-off-by: Natarajan KV <natarajankv91@gmail.com>
---
 net/netfilter/nft_set_pipapo.c | 119 ++++++++++++++++++---------------
 net/netfilter/nft_set_pipapo.h |   2 -
 2 files changed, 66 insertions(+), 55 deletions(-)

diff --git a/net/netfilter/nft_set_pipapo.c b/net/netfilter/nft_set_pipapo.c
index 4274831b6e67..26fff610d9a3 100644
--- a/net/netfilter/nft_set_pipapo.c
+++ b/net/netfilter/nft_set_pipapo.c
@@ -512,20 +512,15 @@ bool nft_pipapo_lookup(const struct net *net, const struct nft_set *set,
  *
  * Return: pointer to &struct nft_pipapo_elem on match, error pointer otherwise.
  */
-static struct nft_pipapo_elem *pipapo_get(const struct net *net,
-					  const struct nft_set *set,
+static struct nft_pipapo_elem *pipapo_get(const struct nft_pipapo_match *m,
 					  const u8 *data, u8 genmask,
 					  u64 tstamp)
 {
 	struct nft_pipapo_elem *ret = ERR_PTR(-ENOENT);
-	struct nft_pipapo *priv = nft_set_priv(set);
 	unsigned long *res_map, *fill_map = NULL;
-	const struct nft_pipapo_match *m;
 	const struct nft_pipapo_field *f;
 	int i;
 
-	m = priv->clone;
-
 	res_map = kmalloc_array(m->bsize_max, sizeof(*res_map), GFP_ATOMIC);
 	if (!res_map) {
 		ret = ERR_PTR(-ENOMEM);
@@ -606,7 +601,18 @@ static struct nft_pipapo_elem *pipapo_get(const struct net *net,
 static void *nft_pipapo_get(const struct net *net, const struct nft_set *set,
 			    const struct nft_set_elem *elem, unsigned int flags)
 {
-	return pipapo_get(net, set, (const u8 *)elem->key.val.data,
+	struct nft_pipapo *priv = nft_set_priv(set);
+	const struct nft_pipapo_match *m;
+
+	m = priv->clone;
+	if (!m) {
+		struct nftables_pernet *nft_net;
+
+		nft_net = nft_pernet(read_pnet(&set->net));
+		m = rcu_dereference_check(priv->match,
+					  lockdep_is_held(&nft_net->commit_mutex));
+	}
+	return pipapo_get(m, (const u8 *)elem->key.val.data,
 			 nft_genmask_cur(net), get_jiffies_64());
 }
 
@@ -1181,6 +1187,8 @@ static int pipapo_realloc_scratch(struct nft_pipapo_match *clone,
 	return 0;
 }
 
+static struct nft_pipapo_match *pipapo_maybe_clone(const struct nft_set *set);
+
 /**
  * nft_pipapo_insert() - Validate and insert ranged elements
  * @net:	Network namespace
@@ -1198,20 +1206,22 @@ static int nft_pipapo_insert(const struct net *net, const struct nft_set *set,
 	union nft_pipapo_map_bucket rulemap[NFT_PIPAPO_MAX_FIELDS];
 	const u8 *start = (const u8 *)elem->key.val.data, *end;
 	struct nft_pipapo_elem *e = elem->priv, *dup;
-	struct nft_pipapo *priv = nft_set_priv(set);
-	struct nft_pipapo_match *m = priv->clone;
+	struct nft_pipapo_match *m = pipapo_maybe_clone(set);
 	u8 genmask = nft_genmask_next(net);
 	u64 tstamp = nft_net_tstamp(net);
 	struct nft_pipapo_field *f;
 	const u8 *start_p, *end_p;
 	int i, bsize_max, err = 0;
 
+	if (!m)
+		return -ENOMEM;
+
 	if (nft_set_ext_exists(ext, NFT_SET_EXT_KEY_END))
 		end = (const u8 *)nft_set_ext_key_end(ext)->data;
 	else
 		end = start;
 
-	dup = pipapo_get(net, set, start, genmask, tstamp);
+	dup = pipapo_get(m, start, genmask, tstamp);
 	if (!IS_ERR(dup)) {
 		/* Check if we already have the same exact entry */
 		const struct nft_data *dup_key, *dup_end;
@@ -1233,7 +1243,7 @@ static int nft_pipapo_insert(const struct net *net, const struct nft_set *set,
 
 	if (PTR_ERR(dup) == -ENOENT) {
 		/* Look for partially overlapping entries */
-		dup = pipapo_get(net, set, end, nft_genmask_next(net), tstamp);
+		dup = pipapo_get(m, end, nft_genmask_next(net), tstamp);
 	}
 
 	if (PTR_ERR(dup) != -ENOENT) {
@@ -1259,8 +1269,6 @@ static int nft_pipapo_insert(const struct net *net, const struct nft_set *set,
 	}
 
 	/* Insert */
-	priv->dirty = true;
-
 	bsize_max = m->bsize_max;
 
 	nft_pipapo_for_each_field(f, i, m) {
@@ -1620,8 +1628,6 @@ static void pipapo_gc(struct nft_set *set, struct nft_pipapo_match *m)
 		 * NFT_SET_ELEM_DEAD_BIT.
 		 */
 		if (__nft_set_elem_expired(&e->ext, tstamp)) {
-			priv->dirty = true;
-
 			gc = nft_trans_gc_queue_sync(gc, GFP_ATOMIC);
 			if (!gc)
 				return;
@@ -1699,26 +1705,20 @@ static void pipapo_reclaim_match(struct rcu_head *rcu)
 static void nft_pipapo_commit(struct nft_set *set)
 {
 	struct nft_pipapo *priv = nft_set_priv(set);
-	struct nft_pipapo_match *new_clone, *old;
-
-	if (time_after_eq(jiffies, priv->last_gc + nft_set_gc_interval(set)))
-		pipapo_gc(set, priv->clone);
-
-	if (!priv->dirty)
-		return;
+	struct nft_pipapo_match *old;
 
-	new_clone = pipapo_clone(priv->clone);
-	if (IS_ERR(new_clone))
+	if (!priv->clone)
 		return;
 
-	priv->dirty = false;
+	if (time_after_eq(jiffies, priv->last_gc + nft_set_gc_interval(set)))
+		pipapo_gc(set, priv->clone);
 
 	old = rcu_access_pointer(priv->match);
 	rcu_assign_pointer(priv->match, priv->clone);
+	priv->clone = NULL;
+
 	if (old)
 		call_rcu(&old->rcu, pipapo_reclaim_match);
-
-	priv->clone = new_clone;
 }
 
 static bool nft_pipapo_transaction_mutex_held(const struct nft_set *set)
@@ -1732,24 +1732,38 @@ static bool nft_pipapo_transaction_mutex_held(const struct nft_set *set)
 #endif
 }
 
-static void nft_pipapo_abort(const struct nft_set *set)
+static struct nft_pipapo_match *pipapo_clone(struct nft_pipapo_match *old);
+
+/**
+ * pipapo_maybe_clone() - Build clone for pending data changes, if not existing
+ * @set:	nftables API set representation
+ *
+ * Return: newly created or existing clone, if any. NULL on allocation failure.
+ */
+static struct nft_pipapo_match *pipapo_maybe_clone(const struct nft_set *set)
 {
 	struct nft_pipapo *priv = nft_set_priv(set);
-	struct nft_pipapo_match *new_clone, *m;
+	struct nft_pipapo_match *m;
 
-	if (!priv->dirty)
-		return;
+	if (priv->clone)
+		return priv->clone;
+
+	m = rcu_dereference_protected(priv->match,
+				      nft_pipapo_transaction_mutex_held(set));
+	priv->clone = pipapo_clone(m);
 
-	m = rcu_dereference_protected(priv->match, nft_pipapo_transaction_mutex_held(set));
+	return priv->clone;
+}
 
-	new_clone = pipapo_clone(m);
-	if (IS_ERR(new_clone))
-		return;
+static void nft_pipapo_abort(const struct nft_set *set)
+{
+	struct nft_pipapo *priv = nft_set_priv(set);
 
-	priv->dirty = false;
+	if (!priv->clone)
+		return;
 
 	pipapo_free_match(priv->clone);
-	priv->clone = new_clone;
+	priv->clone = NULL;
 }
 
 /**
@@ -1788,9 +1802,13 @@ static void nft_pipapo_activate(const struct net *net,
 static void *pipapo_deactivate(const struct net *net, const struct nft_set *set,
 			       const u8 *data, const struct nft_set_ext *ext)
 {
+	struct nft_pipapo_match *m = pipapo_maybe_clone(set);
 	struct nft_pipapo_elem *e;
 
-	e = pipapo_get(net, set, data, nft_genmask_next(net), nft_net_tstamp(net));
+	if (!m)
+		return NULL;
+
+	e = pipapo_get(m, data, nft_genmask_next(net), nft_net_tstamp(net));
 	if (IS_ERR(e))
 		return NULL;
 
@@ -1973,8 +1991,7 @@ static bool pipapo_match_field(struct nft_pipapo_field *f,
 static void nft_pipapo_remove(const struct net *net, const struct nft_set *set,
 			      const struct nft_set_elem *elem)
 {
-	struct nft_pipapo *priv = nft_set_priv(set);
-	struct nft_pipapo_match *m = priv->clone;
+	struct nft_pipapo_match *m = pipapo_maybe_clone(set);
 	struct nft_pipapo_elem *e = elem->priv;
 	int rules_f0, first_rule = 0;
 	const u8 *data;
@@ -2014,7 +2031,6 @@ static void nft_pipapo_remove(const struct net *net, const struct nft_set *set,
 			match_end += NFT_PIPAPO_GROUPS_PADDED_SIZE(f);
 
 			if (last && f->mt[rulemap[i].to].e == e) {
-				priv->dirty = true;
 				pipapo_drop(m, rulemap);
 				return;
 			}
@@ -2048,10 +2064,15 @@ static void nft_pipapo_walk(const struct nft_ctx *ctx, struct nft_set *set,
 		     iter->type != NFT_ITER_UPDATE);
 
 	rcu_read_lock();
-	if (iter->type == NFT_ITER_READ)
+	if (iter->type == NFT_ITER_READ) {
 		m = rcu_dereference(priv->match);
-	else
-		m = priv->clone;
+	} else {
+		m = pipapo_maybe_clone(set);
+		if (!m) {
+			iter->err = -ENOMEM;
+			goto out;
+		}
+	}
 
 	if (unlikely(!m))
 		goto out;
@@ -2181,20 +2202,12 @@ static int nft_pipapo_init(const struct nft_set *set,
 		f->mt = NULL;
 	}
 
-	/* Create an initial clone of matching data for next insertion */
-	priv->clone = pipapo_clone(m);
-	if (IS_ERR(priv->clone)) {
-		err = PTR_ERR(priv->clone);
-		goto out_free;
-	}
-
-	priv->dirty = false;
+	priv->clone = NULL;
 
 	rcu_assign_pointer(priv->match, m);
 
 	return 0;
 
-out_free:
 	free_percpu(m->scratch);
 out_scratch:
 	kfree(m);
diff --git a/net/netfilter/nft_set_pipapo.h b/net/netfilter/nft_set_pipapo.h
index aad9130cc763..8442aaecbe7d 100644
--- a/net/netfilter/nft_set_pipapo.h
+++ b/net/netfilter/nft_set_pipapo.h
@@ -163,14 +163,12 @@ struct nft_pipapo_match {
  * @match:	Currently in-use matching data
  * @clone:	Copy where pending insertions and deletions are kept
  * @width:	Total bytes to be matched for one packet, including padding
- * @dirty:	Working copy has pending insertions or deletions
  * @last_gc:	Timestamp of last garbage collection run, jiffies
  */
 struct nft_pipapo {
 	struct nft_pipapo_match __rcu *match;
 	struct nft_pipapo_match *clone;
 	int width;
-	bool dirty;
 	unsigned long last_gc;
 };
 
-- 
2.34.1


  parent reply	other threads:[~2026-03-04 15:08 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-03-04 13:38 [PATCH] netfilter: nft_set_pipapo: clear dirty flag on abort/commit clone failure Natarajan KV
2026-03-04 13:47 ` Greg KH
2026-03-04 13:50 ` Florian Westphal
2026-03-04 15:08 ` Natarajan KV [this message]
2026-03-04 15:12   ` [PATCH v2] netfilter: nft_set_pipapo: move clone allocation to insert/removal path Greg KH
2026-03-04 16:54     ` [PATCH v3 6.6.y 0/8] " Natarajan KV
2026-03-04 16:54       ` [PATCH v3 6.6.y 1/8] netfilter: nft_set_pipapo: move prove_locking helper around Natarajan KV
2026-03-04 16:54       ` [PATCH v3 6.6.y 2/8] netfilter: nft_set_pipapo: make pipapo_clone helper return NULL Natarajan KV
2026-03-04 16:55       ` [PATCH v3 6.6.y 3/8] netfilter: nft_set_pipapo: prepare destroy function for on-demand clone Natarajan KV
2026-03-04 16:55       ` [PATCH v3 6.6.y 4/8] netfilter: nft_set_pipapo: prepare walk " Natarajan KV
2026-03-04 16:55       ` [PATCH v3 6.6.y 5/8] netfilter: nft_set_pipapo: merge deactivate helper into caller Natarajan KV
2026-03-04 16:55       ` [PATCH v3 6.6.y 6/8] netfilter: nft_set_pipapo: prepare pipapo_get helper for on-demand clone Natarajan KV
2026-03-04 16:55       ` [PATCH v3 6.6.y 7/8] netfilter: nft_set_pipapo: move cloning of match info to insert/removal path Natarajan KV
2026-03-04 16:55       ` [PATCH v3 6.6.y 8/8] netfilter: nft_set_pipapo: remove dirty flag Natarajan KV
2026-03-04 21:30       ` [PATCH v3 6.6.y 0/8] netfilter: nft_set_pipapo: move clone allocation to insert/removal path Pablo Neira Ayuso

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=69a84adc.050a0220.1cea47.3011@mx.google.com \
    --to=natarajankv91@gmail.com \
    --cc=fw@strlen.de \
    --cc=gregkh@linuxfoundation.org \
    --cc=kadlec@netfilter.org \
    --cc=pablo@netfilter.org \
    --cc=stable@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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox