mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Pablo Neira Ayuso <pablo@netfilter.org>
To: joasantonio108@gmail.com
Cc: Florian Westphal <fw@strlen.de>, Phil Sutter <phil@nwl.cc>,
	"David S. Miller" <davem@davemloft.net>,
	Eric Dumazet <edumazet@kernel.org>,
	Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
	Simon Horman <horms@kernel.org>,
	netfilter-devel@vger.kernel.org, coreteam@netfilter.org,
	netdev@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH nf] netfilter: nft_set_rbtree: do not pair an expired start with another interval's end
Date: Sat, 31 Oct 2026 18:50:02 +0100	[thread overview]
Message-ID: <auYqSpBFtC0nTDDd@chamomile> (raw)
In-Reply-To: <auYpCzdrOsoWr84Z@chamomile>

On Sat, Oct 31, 2026 at 06:44:43PM +0100, Pablo Neira Ayuso wrote:
> On Sat, Oct 31, 2026 at 03:54:05PM +0000, Joas Antonio dos Santos via B4 Relay wrote:
> > From: Joas Antonio dos Santos <joasantonio108@gmail.com>
> > 
> > Garbage collection of an expired interval start element also removes its
> > end element. Both the insertion path, nft_rbtree_gc_elem(), and the
> > commit path, nft_rbtree_gc_scan(), find that end element by its position
> > in the tree, and both can pick the end element of a different, live
> > interval:
> >
> > - nft_rbtree_gc_elem() walks towards higher keys and skips end elements
> >   that are not active in both generations. If the end element of the
> >   expired interval is being deleted in the same transaction, the walk
> >   skips it, passes the start element of the next interval and takes that
> >   interval's end element instead.
> > 
> > - nft_rbtree_gc_scan() remembers the last end element it visited in
> >   rbe_end, but does not clear it when it visits a live start element. An
> >   expired start element without an end element (a start-only element, or
> >   an interval whose end element was already removed) then takes the end
> >   element of the live interval above it.
> > 
> > In both cases the live interval [S, E) loses E: the set no longer holds
> > the interval that userspace added, and deleting [S, E) later fails with
> > ENOENT.
> > 
> > In the insertion path, stop the walk at the first element that is active
> > in the current generation. Elements added by this transaction are still
> > skipped, as intended by commit 60c0c230c6f0 ("netfilter: nft_set_rbtree:
> > skip end interval element from gc"). If that element is a start element,
> > the expired interval has no end element. If it is an end element that
> > this transaction is removing, leave it to the transaction. In the commit
> > path, clear rbe_end when a live start element is visited.
> > 
> > Reproduced on current mainline under QEMU (arm64, KASAN), from an
> > unprivileged user in a new user and network namespace:
> > 
> > - insertion path: add [10,20) with a 50ms timeout and [30,40), wait,
> >   then in one batch delete the end element 20 and add [12,13).
> 
> I don't follow.
> 
> You add [10,20) with 50ms...
> 
> and [30,40) with... what timeout?
> 
> you wait how long?
> 
> You follow up with another batch an you only delete 20?
> 
> >   Before: a GET for 45 finds an element, the end element 40 is gone and
> >   deleting [30,40) fails with ENOENT. After: 45 is not found, 40 is
> >   present and the delete succeeds.
> > 
> > - commit path: add [30,40) and a start-only element 10 with a 50ms
> >   timeout to a set with a 100ms gc interval, wait, then add [200,201),
> >   whose insertion walk stops at the highest element, so only the
> >   commit-time scan reaches 10. Before: the end element 40 is gone and
> >   deleting [30,40) fails with ENOENT. After: both work.
> > 
> > Normal expiry, and a new interval whose end element shares the key of an
> > expired start element (the case handled by 60c0c230c6f0), behave the same
> > before and after. About 39000 random batches on interval sets with
> > timeouts, with and without slab/page allocation fault injection, showed
> > no KASAN, lockdep or WARN reports with the patch applied.

Hm, no KASAN, no splat, what's the issue, really?

> > The insertion path issue was found with a small nfnetlink fuzzer. Both
> > paths had been pointed out earlier by the netdev AI review of commit
> > 86b6471e690c ("netfilter: nft_set_rbtree: skip transaction elements
> > during GC"), see the Link below.
> > 
> > Fixes: f718863aca46 ("netfilter: nft_set_rbtree: fix overlap expiration walk")
> > Fixes: 8d8540c4f5e0 ("netfilter: nft_set_rbtree: add timeout support")
> > Link: https://lore.kernel.org/all/179063970372.3145.17414936987144600093@kernel.org/
> > Signed-off-by: Joas Antonio dos Santos <joasantonio108@gmail.com>
> > ---
> > Tool-use note: the fuzzer, the directed tests and the analysis were
> > prepared with help from an LLM-based assistant; I reviewed the change
> > and built and ran all tests myself. Test harness available on request.
> > 
> > This is a resend through the kernel.org web endpoint: my earlier
> > submission of this patch was blocked by my mail provider and never
> > reached the lists.
> > ---
> >  net/netfilter/nft_set_rbtree.c | 23 +++++++++++++++++++----
> >  1 file changed, 19 insertions(+), 4 deletions(-)
> > 
> > diff --git a/net/netfilter/nft_set_rbtree.c b/net/netfilter/nft_set_rbtree.c
> > index fbe1750f1..086823067 100644
> > --- a/net/netfilter/nft_set_rbtree.c
> > +++ b/net/netfilter/nft_set_rbtree.c
> > @@ -215,17 +215,25 @@ nft_rbtree_gc_elem(const struct nft_set *__set, struct nft_rbtree *priv,
> >  	struct nft_set *set = (struct nft_set *)__set;
> >  	struct rb_node *prev = rb_prev(&rbe->node);
> >  	struct net *net = read_pnet(&set->net);
> > +	u8 cur_genmask = nft_genmask_cur(net);
> >  	struct nft_rbtree_elem *rbe_prev;
> >  
> >  	/* search for end interval coming before this element.
> >  	 * end intervals don't carry a timeout extension, they
> >  	 * are coupled with the interval start element.
> > +	 *
> > +	 * Skip elements added in this transaction. The first element
> > +	 * that is active in the current generation is either the end
> > +	 * element of this interval or the start element of another
> > +	 * interval, in which case this interval has no end element.
> >  	 */
> >  	while (prev) {
> >  		rbe_prev = rb_entry(prev, struct nft_rbtree_elem, node);
> > -		if (nft_rbtree_interval_end(rbe_prev) &&
> > -		    nft_set_elem_active(&rbe_prev->ext, NFT_GENMASK_ANY))
> > +		if (nft_set_elem_active(&rbe_prev->ext, cur_genmask)) {
> > +			if (!nft_rbtree_interval_end(rbe_prev))
> > +				prev = NULL;
> >  			break;
> > +		}
> >  
> >  		prev = rb_prev(prev);
> >  	}
> > @@ -233,7 +241,11 @@ nft_rbtree_gc_elem(const struct nft_set *__set, struct nft_rbtree *priv,
> >  	rbe_prev = NULL;
> >  	if (prev) {
> >  		rbe_prev = rb_entry(prev, struct nft_rbtree_elem, node);
> > -		nft_rbtree_gc_elem_move(net, set, priv, rbe_prev);
> > +		/* end element is being removed by this transaction */
> > +		if (nft_set_elem_active(&rbe_prev->ext, NFT_GENMASK_ANY))
> > +			nft_rbtree_gc_elem_move(net, set, priv, rbe_prev);
> > +		else
> > +			rbe_prev = NULL;
> >  	}
> >  
> >  	nft_rbtree_gc_elem_move(net, set, priv, rbe);
> > @@ -911,8 +923,11 @@ static void nft_rbtree_gc_scan(struct nft_set *set)
> >  			rbe_end = rbe;
> >  			continue;
> >  		}
> > -		if (!__nft_set_elem_expired(&rbe->ext, tstamp))
> > +		if (!__nft_set_elem_expired(&rbe->ext, tstamp)) {
> > +			/* rbe_end belongs to this live interval */
> > +			rbe_end = NULL;
> >  			continue;
> > +		}
> >  
> >  		/* end element needs to be removed first, it has
> >  		 * no timeout extension.
> > 
> > ---
> > base-commit: c339f4010dea8b0d754f84ace70cd1b089878ef8
> > change-id: 20261031-b4-nft-rbtree-gc-1d9c72dba2bd
> > 
> > Best regards,
> > -- 
> > Joas Antonio dos Santos <joasantonio108@gmail.com>
> > 
> > 
> 

  reply	other threads:[~2026-10-31 17:50 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-31 15:54 Joas Antonio dos Santos via B4 Relay
2026-10-31 17:44 ` Pablo Neira Ayuso
2026-10-31 17:50   ` Pablo Neira Ayuso [this message]
2026-10-31 17:55     ` Joas Antonio

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=auYqSpBFtC0nTDDd@chamomile \
    --to=pablo@netfilter.org \
    --cc=coreteam@netfilter.org \
    --cc=davem@davemloft.net \
    --cc=edumazet@kernel.org \
    --cc=fw@strlen.de \
    --cc=horms@kernel.org \
    --cc=joasantonio108@gmail.com \
    --cc=kuba@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=netfilter-devel@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=phil@nwl.cc \
    /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

all inboxes | Powered by JetHome®