All of lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Emil Tsalapatis" <emil@etsalapatis.com>
Cc: bpf@vger.kernel.org
Subject: Re: [PATCH bpf-next v4 1/3] selftests/bpf: libarena: Add rbtree data structure
Date: Fri, 05 Jun 2026 22:30:52 +0000	[thread overview]
Message-ID: <20260605223052.D18201F00893@smtp.kernel.org> (raw)
In-Reply-To: <20260605222020.5231-2-emil@etsalapatis.com>

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Failure to check `can_loop` termination leads to severe tree corruption, memory leaks, and NULL pointer dereferences.
--

commit 090a58da31563cb07c7562cdaaf67470a54e90c0
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 for querying and modifying the tree, 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..0e4aaec96b6fa
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/libarena/src/rbtree.bpf.c

[ ... ]

> +__weak
> +int rb_destroy(struct rbtree __arena *rbtree)
> +{
> +	int ret = 0;
> +
> +	arena_subprog_init();
> +
> +	if (unlikely(!rbtree))
> +		return -EINVAL;

[ ... ]

> +	while (rbtree->root && can_loop) {
> +		ret = rb_remove(rbtree, rbtree->root->key);
> +		if (ret)
> +			break;
> +	}
> +
> +out:
> +	arena_free(rbtree);

[Severity: High]
Does this code leak memory if the loop exits early due to the can_loop
limit being reached? If the loop aborts early, the rbtree container is freed
even though the remaining nodes have not been removed.

> +	return ret;
> +}

[ ... ]

> +static
> +struct rbnode __arena *rbnode_least_upper_bound(struct rbnode __arena *subtree, uint64_t key)
> +{
> +	struct rbnode __arena *node = subtree;
> +	int dir;
> +
> +	if (!subtree)
> +		return NULL;
> +
> +	while (can_loop) {
> +		dir = (key <= node->key) ? 0 : 1;
> +
> +		if (!node->child[dir])
> +			break;
> +
> +		node = node->child[dir];
> +	}
> +
> +	return node;

[Severity: High]
If this loop terminates early because can_loop evaluates to false, will it
return an internal tree node rather than a leaf node?

> +}

[ ... ]

> +static
> +int rb_node_insert(struct rbtree __arena *rbtree,
> +		   struct rbnode __arena *node)
> +{
> +	struct rbnode __arena *grandparent, *parent = rbtree->root;
> +	u64 key = node->key;
> +	struct rbnode __arena *uncle;
> +	int dir;
> +	int ret;

[ ... ]

> +	if (rbtree->insert != RB_DUPLICATE)
> +		parent = rbnode_find(parent, key);
> +	else
> +		parent = rbnode_least_upper_bound(parent, key);

[ ... ]

> +	node->parent = parent;
> +	/* Also works if key == parent->key. */
> +	if (key <= parent->key)
> +		parent->left = node;

[Severity: High]
If rbnode_find() or rbnode_least_upper_bound() returns an internal node
due to a can_loop timeout, does unconditionally overwriting parent->left or
parent->right here permanently detach and leak the existing subtree?

> +	else
> +		parent->right = node;

[ ... ]

> +static
> +int rb_node_remove(struct rbtree __arena *rbtree,
> +		   struct rbnode __arena *node)
> +{
> +	struct rbnode __arena *parent, *sibling, *close_nephew, *distant_nephew;
> +	bool free = (rbtree->alloc == RB_ALLOC);
> +	struct rbnode __arena *replace, *initial;

[ ... ]

> +	while (can_loop) {
> +
> +		/* Balancing reached the root, there can be no imbalance. */
> +		if (!parent)
> +			return 0;

[ ... ]

> +		sibling->is_red = true;
> +		node = parent;
> +		parent = node->parent;
> +		dir = rbnode_dir(node);
> +	}
> +
> +	if (node != initial) {
> +		dir = rbnode_dir(node);
> +		parent = node->parent;
> +		sibling = parent->child[1-dir];
> +	}
> +	/*
> +	 * Almost there. We know between the parent, sibling,
> +	 * and nephews only one or two of the nephews are red. If
> +	 * it is the close one, rotate it to the sibling position,
> +	 * paint it black, and paint the previous sibling red.
> +	 */
> +
> +	close_nephew = sibling->child[dir];
> +	distant_nephew = sibling->child[1 - dir];
> +
> +	/*
> +	 * If the distant red nephew is not red, rotate
> +	 * and repaint. We need the distant nephew
> +	 * to be red. We know the close nephew is red
> +	 * because at least one of them are, so the
> +	 * distant one is black if it exists.
> +	 */
> +	if (!distant_nephew || !distant_nephew->is_red) {
> +		rbnode_rotate(rbtree, sibling, 1 - dir);
> +		sibling->is_red = true;
> +		close_nephew->is_red = false;

[Severity: High]
Can this cause a NULL pointer dereference if the rebalancing loop above
exits early due to can_loop?

An early exit bypasses the guarantee that the sibling has red children,
which means both nephews, including close_nephew, might be NULL here.

> +		distant_nephew = sibling;
> +		sibling = close_nephew;
> +	}

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

  reply	other threads:[~2026-06-05 22:30 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-06-05 22:20 [PATCH bpf-next v4 0/3] selftests/bpf: libarena: Add initial data structures Emil Tsalapatis
2026-06-05 22:20 ` [PATCH bpf-next v4 1/3] selftests/bpf: libarena: Add rbtree data structure Emil Tsalapatis
2026-06-05 22:30   ` sashiko-bot [this message]
2026-06-05 23:01     ` Emil Tsalapatis
2026-06-05 22:51   ` bot+bpf-ci
2026-06-05 22:20 ` [PATCH bpf-next v4 2/3] selftests/bpf: libarena: Add spmc queue " Emil Tsalapatis
2026-06-05 22:51   ` bot+bpf-ci
2026-06-05 22:20 ` [PATCH bpf-next v4 3/3] selftests/bpf: libarena: parallel test harness and spmc parallel selftest Emil Tsalapatis
2026-06-05 22:28   ` sashiko-bot
2026-06-05 22:51   ` bot+bpf-ci
2026-06-06  3:40 ` [PATCH bpf-next v4 0/3] selftests/bpf: libarena: Add initial data structures patchwork-bot+netdevbpf

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=20260605223052.D18201F00893@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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.