BPF List
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Emil Tsalapatis" <emil@etsalapatis.com>
Cc: bpf@vger.kernel.org
Subject: Re: [RESEND PATCH bpf-next 1/2] selftests/bpf: libarena: Add rbtree data structure
Date: Wed, 13 May 2026 00:42:16 +0000	[thread overview]
Message-ID: <20260513004217.49B44C2BCB0@smtp.kernel.org> (raw)
In-Reply-To: <20260511214100.9487-2-emil@etsalapatis.com>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Inserting duplicate keys into an `RB_DUPLICATE` rbtree overwrites existing subtrees, causing data loss and memory leaks.
- [High] The lock-free node freelist implementation is vulnerable to the ABA problem, leading to tree corruption.
--

commit 1c2293fc665383e2481f9d7ee15173fb46b0ea76
Author: Emil Tsalapatis <emil@etsalapatis.com>

selftests/bpf: libarena: Add rbtree data structure

This commit adds a native red-black tree data structure to libarena. It
supports multiple APIs (key-value based, node based) and uses the libarena
memory allocator to manage its data.

> diff --git a/tools/testing/selftests/bpf/libarena/src/rbtree.bpf.c b/tools/testing/selftests/bpf/libarena/src/rbtree.bpf.c
> new file mode 100644
> index 0000000000000..2ce2dfe5a2235
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/libarena/src/rbtree.bpf.c
> @@ -0,0 +1,1032 @@
[ ... ]
> +static inline rbnode_t *rb_node_alloc_common(rbtree_t *rbtree __arg_arena, u64 key, u64 value)
> +{
> +	rbnode_t *rbnode = NULL;
> +
> +	if (unlikely(!rbtree))
> +		return NULL;
> +
> +	/* Use the built-in node cache for the rbtree to avoid going to the allocator. */
> +	do  {
> +		rbnode = rbtree->freelist;
> +		if (!rbnode)
> +			break;
> +
> +	} while (cmpxchg(&rbtree->freelist, rbnode, rbnode->parent) != rbnode && can_loop);

Could this lock-free freelist implementation be vulnerable to the ABA problem
in concurrent environments?

If Thread A reads rbtree->freelist and evaluates rbnode->parent, and is then
delayed, Thread B could allocate rbnode, allocate its parent, and then free
rbnode back to the freelist. When Thread A resumes, the cmpxchg would
succeed because the freelist head is still rbnode, but it would erroneously
set the freelist head to the stale rbnode->parent. This could cause the
allocator to hand out an in-use tree node, leading to multiple concurrent
uses of the same memory and tree corruption.

[ ... ]

> +static
> +int rb_node_insert(rbtree_t *rbtree __arg_arena, rbnode_t *node __arg_arena)
> +{
> +	rbnode_t *grandparent, *parent = rbtree->root;
> +	enum rbnode_find_mode mode;
> +	u64 key = node->key;
> +	rbnode_t *uncle;
> +	int dir;
> +	int ret;
> +
> +	if (unlikely(!rbtree))
> +		return -EINVAL;
> +
> +	if (!parent) {
> +		rbtree->root = node;
> +		return 0;
> +	}
> +
> +	mode = RBN_FIND_DFL;
> +	/* Always find the first node with the key when allowing duplicates. */
> +	if (rbtree->insert == RB_DUPLICATE)
> +		mode = RBN_FIND_LUB;
> +	parent = rbnode_find(parent, key, mode);
> +
> +	if (key == parent->key && rbtree->insert != RB_DUPLICATE) {
> +		if (rbtree->insert == RB_UPDATE) {
> +			/*
> +			 * Replace the old node with the new one.
> +			 * Free up the old node.
> +			 */
> +			ret = rbnode_replace(rbtree, parent, node);
> +			if (ret)
> +				return ret;
> +
> +			if (rbtree->alloc == RB_ALLOC)
> +				rb_node_free(rbtree, parent);
> +
> +			return 0;
> +		}
> +
> +		/* Otherwise it's RB_DEFAULT. */
> +		return -EALREADY;
> +	}
> +
> +	node->parent = parent;
> +	/* Also works if key == parent->key. */
> +	if (key <= parent->key)
> +		parent->left = node;
> +	else
> +		parent->right = node;

Does this code overwrite existing subtrees when inserting duplicate keys
into an RB_DUPLICATE rbtree?

When rbnode_find() is called with RBN_FIND_LUB, it can break early when it
finds the target key whose child in the search direction has a different
key:

tools/testing/selftests/bpf/libarena/src/rbtree.bpf.c:rbnode_find() {
    ...
		/*
		 * For least upper bound lookups, ensure that we
		 * stop searching once we find the rightmost key.
		 */
		if (node->key == key && node->child[dir]->key != key)
			break;
    ...
}

This causes it to return an internal node where node->child[dir] is
explicitly not NULL. Back in rb_node_insert(), the new node is
unconditionally assigned to parent->left or parent->right. This blindly
overwrites the existing child pointer, permanently detaching and leaking
the previous subtree.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260511214100.9487-1-emil@etsalapatis.com?part=1

  parent reply	other threads:[~2026-05-13  0:42 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-11 21:40 [RESEND PATCH bpf-next 0/2] selftests/bpf: libarena: Add initial data structures Emil Tsalapatis
2026-05-11 21:40 ` [RESEND PATCH bpf-next 1/2] selftests/bpf: libarena: Add rbtree data structure Emil Tsalapatis
2026-05-11 22:24   ` bot+bpf-ci
2026-05-12 20:18     ` Emil Tsalapatis
2026-05-13  0:42   ` sashiko-bot [this message]
2026-05-11 21:41 ` [RESEND PATCH bpf-next 2/2] selftests/bpf: libarena: Add Lev-Chase queue " Emil Tsalapatis
2026-05-11 22:12   ` bot+bpf-ci
2026-05-12 20:20     ` Emil Tsalapatis
2026-05-13  2:23   ` sashiko-bot

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=20260513004217.49B44C2BCB0@smtp.kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=emil@etsalapatis.com \
    --cc=sashiko-reviews@lists.linux.dev \
    /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