From: Emil Tsalapatis <emil@etsalapatis.com>
To: bpf@vger.kernel.org
Cc: ast@kernel.org, andrii@kernel.org, memxor@gmail.com,
daniel@iogearbox.net, eddyz87@gmail.com,
nickolay.lysenko@gmail.com,
Emil Tsalapatis <emil@etsalapatis.com>
Subject: [PATCH bpf-next 2/5] bpf: Track availability information for ranges in range tree
Date: Wed, 2 Sep 2026 03:02:36 -0400 [thread overview]
Message-ID: <20260902070239.16968-3-emil@etsalapatis.com> (raw)
In-Reply-To: <20260902070239.16968-1-emil@etsalapatis.com>
Arena address ranges are currently encoded in a range tree: Ranges
present in the tree are free and available for allocation, while
absent ranges are allocated. However, this opens up the arena code to
subtle races between page table updates, range tree updates, and
concurrent allocations that can cause permanent inconsistencies.
Avoiding such races involves distinguishing between memory ranges that
are free and ready to be allocated, and ranges that are being freed but
should not be reused yet. Such tracking is cleanly possible through the
arena's range tree. The range tree is only consumed by arena and has no
additional future consumers, so it can be tailored towards tracking more
state. Alternatives such as deferring range freeing require more
asynchrony and per-range state tracking in the main arena code, and can
introduce additional race conditions.
Expand the tree to track whether a range present in the tree is
available for allocation. For now, all ranges are available: We add
the option to add back a range in an unavailable state, and to move
already present ranges from unavailable to available. We do not
implement other transitions, since they are not required to support
BPF arenas.
The patch adds two operations: Adding a range as unavailable, and
turning a range from unavailable to available. Unavailable ranges are
not mergable, and will be imminently be turned available by the ongoing
arena free() operation that created them. Turning ranges from
unavailable to available is a simple flag change on the range with an
optional merge with adjacent available ranges.
Signed-off-by: Emil Tsalapatis <emil@etsalapatis.com>
---
kernel/bpf/arena.c | 12 +--
kernel/bpf/range_tree.c | 180 +++++++++++++++++++++++++++++++++-------
kernel/bpf/range_tree.h | 4 +-
3 files changed, 161 insertions(+), 35 deletions(-)
diff --git a/kernel/bpf/arena.c b/kernel/bpf/arena.c
index 7b6847200b43..f49b52fa8586 100644
--- a/kernel/bpf/arena.c
+++ b/kernel/bpf/arena.c
@@ -317,7 +317,7 @@ static struct bpf_map *arena_map_alloc(union bpf_attr *attr)
goto err_free_arena;
range_tree_init(&arena->rt);
- err = range_tree_set(&arena->rt, 0, attr->max_entries);
+ err = range_tree_set_avail(&arena->rt, 0, attr->max_entries);
if (err)
goto err_free_scratch;
mutex_init(&arena->lock);
@@ -520,13 +520,13 @@ static vm_fault_t arena_vm_fault(struct vm_fault *vmf)
/* Account into memcg of the process that created bpf_arena */
ret = bpf_map_alloc_pages(map, NUMA_NO_NODE, 1, &page);
if (ret) {
- range_tree_set(&arena->rt, vmf->pgoff, 1);
+ range_tree_set_avail(&arena->rt, vmf->pgoff, 1);
goto out_sigsegv_memcg;
}
ret = apply_to_page_range(&init_mm, kaddr, PAGE_SIZE, apply_range_set_cb, &data);
if (ret) {
- range_tree_set(&arena->rt, vmf->pgoff, 1);
+ range_tree_set_avail(&arena->rt, vmf->pgoff, 1);
free_pages_nolock(page, 0);
goto out_sigsegv_memcg;
}
@@ -766,7 +766,7 @@ static long arena_alloc_pages(struct bpf_arena *arena, long uaddr, long page_cnt
bpf_map_memcg_exit(old_memcg, new_memcg);
return clear_lo32(arena->user_vm_start) + uaddr32;
out:
- range_tree_set(&arena->rt, pgoff + mapped, page_cnt - mapped);
+ range_tree_set_avail(&arena->rt, pgoff + mapped, page_cnt - mapped);
raw_res_spin_unlock_irqrestore(&arena->spinlock, flags);
if (mapped) {
flush_vmap_cache(kern_vm_start + uaddr32, mapped << PAGE_SHIFT);
@@ -881,7 +881,7 @@ static void arena_free_pages(struct bpf_arena *arena, long uaddr, long page_cnt,
if (ret)
goto defer;
- range_tree_set(&arena->rt, pgoff, page_cnt);
+ range_tree_set_avail(&arena->rt, pgoff, page_cnt);
init_llist_head(&free_pages);
cdata.arena = arena;
@@ -1008,7 +1008,7 @@ static void arena_free_worker(struct work_struct *work)
apply_to_existing_page_range(&init_mm, kaddr, page_cnt << PAGE_SHIFT,
apply_range_clear_cb, &cdata);
- range_tree_set(&arena->rt, pgoff, page_cnt);
+ range_tree_set_avail(&arena->rt, pgoff, page_cnt);
}
raw_res_spin_unlock_irqrestore(&arena->spinlock, flags);
diff --git a/kernel/bpf/range_tree.c b/kernel/bpf/range_tree.c
index 6f6ba718887c..515e72f054f5 100644
--- a/kernel/bpf/range_tree.c
+++ b/kernel/bpf/range_tree.c
@@ -39,8 +39,15 @@ struct range_node {
u32 rn_start;
u32 rn_last; /* inclusive */
u32 __rn_subtree_last;
+ bool available; /* range is available for allocating. */
};
+/* Is the range available for merging? */
+static inline bool range_available(struct range_node *rn)
+{
+ return rn && rn->available;
+}
+
static struct range_node *rb_to_range_node(struct rb_node *rb)
{
return rb_entry(rb, struct range_node, rb_range_size);
@@ -55,20 +62,30 @@ static u32 rn_size(struct range_node *rn)
static inline struct range_node *__find_range(struct range_tree *rt, u32 len)
{
struct rb_node *rb = rt->range_size_root.rb_root.rb_node;
- struct range_node *best = NULL;
+ struct rb_node *best = NULL;
+ struct range_node *rn;
while (rb) {
- struct range_node *rn = rb_to_range_node(rb);
+ rn = rb_to_range_node(rb);
if (len <= rn_size(rn)) {
- best = rn;
+ best = rb;
rb = rb->rb_right;
} else {
rb = rb->rb_left;
}
}
- return best;
+ /* Filter unavailable ranges. */
+ while (best) {
+ rn = rb_to_range_node(best);
+ if (range_available(rn))
+ return rn;
+
+ best = rb_prev(best);
+ }
+
+ return NULL;
}
s64 range_tree_find(struct range_tree *rt, u32 len)
@@ -135,10 +152,19 @@ range_it_iter_first(struct range_tree *rt, u32 start, u32 last)
/* Clear the range in this range tree */
int range_tree_clear(struct range_tree *rt, u32 start, u32 len)
{
+ u32 first = start;
u32 last = start + len - 1;
struct range_node *new_rn;
struct range_node *rn;
+ /* Scan for unavailable ranges and try again if so. */
+ while ((rn = range_it_iter_first(rt, first, last))) {
+ if (!range_available(rn))
+ return -EAGAIN;
+
+ first = rn->rn_last + 1;
+ }
+
while ((rn = range_it_iter_first(rt, start, last))) {
if (rn->rn_start < start && rn->rn_last > last) {
u32 old_last = rn->rn_last;
@@ -153,6 +179,7 @@ int range_tree_clear(struct range_tree *rt, u32 start, u32 len)
NUMA_NO_NODE);
if (!new_rn)
return -ENOMEM;
+ new_rn->available = rn->available;
new_rn->rn_start = last + 1;
new_rn->rn_last = old_last;
range_it_insert(new_rn, rt);
@@ -182,7 +209,8 @@ int is_range_tree_set(struct range_tree *rt, u32 start, u32 len)
u32 last = start + len - 1;
struct range_node *rn;
- while ((rn = range_it_iter_first(rt, start, last))) {
+ for (rn = range_it_iter_first(rt, start, last); rn;
+ rn = __range_it_iter_next(rn, start, last)) {
/* Make sure the range covers the start */
if (rn->rn_start > start)
return -ESRCH;
@@ -198,23 +226,12 @@ int is_range_tree_set(struct range_tree *rt, u32 start, u32 len)
return -ESRCH;
}
-/* Set the range in this range tree */
-int range_tree_set(struct range_tree *rt, u32 start, u32 len)
+/* Do we have adjacent ranges (and do not overlap with them)? */
+static int range_get_adjacent(struct range_tree *rt, u32 start, u32 last,
+ struct range_node **leftp, struct range_node **rightp)
{
- u32 last = start + len - 1;
struct range_node *right;
struct range_node *left;
- int err;
-
- /* Is this whole range already set ? */
- left = range_it_iter_first(rt, start, last);
- if (left && left->rn_start <= start && left->rn_last >= last)
- return 0;
-
- /* Clear out everything in the range we want to set. */
- err = range_tree_clear(rt, start, len);
- if (err)
- return err;
/* Do we have a left-adjacent range ? */
left = range_it_iter_first(rt, start - 1, start - 1);
@@ -226,34 +243,141 @@ int range_tree_set(struct range_tree *rt, u32 start, u32 len)
if (right && right->rn_start != last + 1)
return -EFAULT;
- if (left && right) {
+ *leftp = left;
+ *rightp = right;
+
+ return 0;
+}
+
+/*
+ * Merge with adjacent available ranges if possible. The new [start, last]
+ * has already been confirmed to be adjacent with left/right by the caller.
+ */
+static int range_tree_merge(struct range_tree *rt, u32 start, u32 last,
+ struct range_node *left, struct range_node *right)
+{
+ if (range_available(left) && range_available(right)) {
/* Combine left and right adjacent ranges */
range_it_remove(left, rt);
range_it_remove(right, rt);
left->rn_last = right->rn_last;
range_it_insert(left, rt);
kfree_nolock(right);
- } else if (left) {
+ } else if (range_available(left)) {
/* Combine with the left range */
range_it_remove(left, rt);
left->rn_last = last;
range_it_insert(left, rt);
- } else if (right) {
+ } else if (range_available(right)) {
/* Combine with the right range */
range_it_remove(right, rt);
right->rn_start = start;
range_it_insert(right, rt);
} else {
- left = kmalloc_nolock(sizeof(struct range_node), __GFP_ACCOUNT, NUMA_NO_NODE);
- if (!left)
- return -ENOMEM;
- left->rn_start = start;
- left->rn_last = last;
- range_it_insert(left, rt);
+ /* No merge available. */
+ return -ENOENT;
+ }
+
+ return 0;
+}
+
+/* Make a range available, possibly merging. */
+int range_tree_make_avail(struct range_tree *rt, u32 start, u32 len)
+{
+ u32 last = start + len - 1;
+ struct range_node *rn;
+ struct range_node *right;
+ struct range_node *left;
+ int err;
+
+ /*
+ * Confirm the range exists is unavailable,
+ * and fits the requested range exactly.
+ */
+ rn = range_it_iter_first(rt, start, last);
+ if (!rn || rn->available)
+ return -EINVAL;
+
+ if (rn->rn_start != start || rn->rn_last != last)
+ return -EINVAL;
+
+ err = range_get_adjacent(rt, start, last, &left, &right);
+ if (err)
+ return err;
+
+ /* If no merging required, just make available. */
+ if (!range_available(left) && !range_available(right)) {
+ rn->available = true;
+ return 0;
+ }
+
+ /* Can merge, remove the range already. */
+ start = rn->rn_start;
+ last = rn->rn_last;
+ range_it_remove(rn, rt);
+ kfree_nolock(rn);
+
+ return range_tree_merge(rt, start, last, left, right);
+}
+
+/* Set the range in this range tree */
+static int range_tree_set(struct range_tree *rt, u32 start, u32 len, bool available)
+{
+ u32 last = start + len - 1;
+ struct range_node *right;
+ struct range_node *left;
+ int err;
+
+ /* Is this whole range already set ? */
+ left = range_it_iter_first(rt, start, last);
+ if (left && left->rn_start <= start && left->rn_last >= last &&
+ range_available(left) && available)
+ return 0;
+
+ /* Clear out everything in the range we want to set. */
+ err = range_tree_clear(rt, start, len);
+ if (err)
+ return err;
+
+ /* Get adjacent ranges and check for overlaps. */
+ err = range_get_adjacent(rt, start, last, &left, &right);
+ if (err)
+ return err;
+
+ /*
+ * If the range is not available for allocation, don't merge.
+ * Unavailable ranges are in the process of being freed and should
+ * be imminently marked available, so merging them with other
+ * unavailable ranges will just lead to splitting the range back
+ * almost immediately.
+ */
+ if (available) {
+ err = range_tree_merge(rt, start, last, left, right);
+ if (!err)
+ return 0;
}
+
+ left = kmalloc_nolock(sizeof(struct range_node), __GFP_ACCOUNT, NUMA_NO_NODE);
+ if (!left)
+ return -ENOMEM;
+ left->available = available;
+ left->rn_start = start;
+ left->rn_last = last;
+ range_it_insert(left, rt);
+
return 0;
}
+int range_tree_set_avail(struct range_tree *rt, u32 start, u32 len)
+{
+ return range_tree_set(rt, start, len, true);
+}
+
+int range_tree_set_unavail(struct range_tree *rt, u32 start, u32 len)
+{
+ return range_tree_set(rt, start, len, false);
+}
+
void range_tree_destroy(struct range_tree *rt)
{
struct range_node *rn;
diff --git a/kernel/bpf/range_tree.h b/kernel/bpf/range_tree.h
index ff0b9110eb71..aa27edf451bc 100644
--- a/kernel/bpf/range_tree.h
+++ b/kernel/bpf/range_tree.h
@@ -14,7 +14,9 @@ void range_tree_init(struct range_tree *rt);
void range_tree_destroy(struct range_tree *rt);
int range_tree_clear(struct range_tree *rt, u32 start, u32 len);
-int range_tree_set(struct range_tree *rt, u32 start, u32 len);
+int range_tree_set_avail(struct range_tree *rt, u32 start, u32 len);
+int range_tree_set_unavail(struct range_tree *rt, u32 start, u32 len);
+int range_tree_make_avail(struct range_tree *rt, u32 start, u32 len);
int is_range_tree_set(struct range_tree *rt, u32 start, u32 len);
s64 range_tree_find(struct range_tree *rt, u32 len);
--
2.55.0
next prev parent reply other threads:[~2026-09-02 7:02 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-02 7:02 [PATCH bpf-next 0/5] bpf: Fix arena memory incoherence Emil Tsalapatis
2026-09-02 7:02 ` [PATCH bpf-next 1/5] bpf: Update is_range_tree_set to work for consecutive ranges Emil Tsalapatis
2026-09-02 8:01 ` bot+bpf-ci
2026-09-02 7:02 ` Emil Tsalapatis [this message]
2026-09-02 8:20 ` [PATCH bpf-next 2/5] bpf: Track availability information for ranges in range tree bot+bpf-ci
2026-09-02 7:02 ` [PATCH bpf-next 3/5] bpf: Fix arena race between page free and alloc leading to incoherency Emil Tsalapatis
2026-09-02 8:20 ` bot+bpf-ci
2026-09-07 11:41 ` Puranjay Mohan
2026-09-02 7:02 ` [PATCH bpf-next 4/5] bpf: Atomically update PTE and range tree in arena VM fault handler Emil Tsalapatis
2026-09-02 7:19 ` sashiko-bot
2026-09-07 11:45 ` Puranjay Mohan
2026-09-02 7:02 ` [PATCH bpf-next 5/5] selftests/bpf: Add arena allocation race tests Emil Tsalapatis
2026-09-02 7:14 ` sashiko-bot
2026-09-02 8:20 ` bot+bpf-ci
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=20260902070239.16968-3-emil@etsalapatis.com \
--to=emil@etsalapatis.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=memxor@gmail.com \
--cc=nickolay.lysenko@gmail.com \
/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.