Merge branch 'bpf-arena-fix-range_tree-consistency-on-allocation-failure'

Yuan Chen says:

====================
bpf, arena: fix range_tree consistency on allocation failure

From: Yuan Chen <chenyuan@kylinos.cn>

The arena range tree can be left inconsistent when kmalloc_nolock()
fails mid-operation. Patch 1 fixes range_tree_clear(), patch 2 fixes
range_tree_set().

Thanks for the review.

Changes in v7:
  - Dropped the third patch (checking range_tree_set() return in
    arena_free_pages/arena_free_worker) per Alexei Starovoitov: a range
    that stays marked allocated while its pages were freed is the same
    state bpf_arena_reserve_pages() creates, and nothing relies on an
    allocated range having pages. The failed free only costs page_cnt
    pages of address space, while aborting the free would leak pages
    the prog cannot observe or retry. No separate series for
    arena_vm_fault() either.
  - range_tree_set(): do the adjacency lookup once, before the clear,
    and drop the unreachable -EFAULT checks, per Alexei Starovoitov.

Changes in v6:
  - Patch 3 only: on a failed range_tree_set() drop the span and leak the
    range, which keeps the range tree and the page tables consistent, and
    revert the arena_map_free() teardown changes that went with the
    retry. Patches 1 and 2 are unchanged from v5.

Changes in v5:
  - arena_map_free(): set a dying flag and steal orphaned spans before
    draining, and drain with flush_work() + irq_work_sync() +
    flush_work().  The worker retry queues arena->free_irq, which the
    old irq_work_sync() + flush_work() order could miss: the irq_work
    fired after the arena was freed and its callback scheduled
    free_work on freed memory.
  - arena_map_free(): retry the spinlock acquisition a bounded number
    of times (-EDEADLK is not retried) and WARN with the error code,
    instead of a bare WARN_ON_ONCE(1) and an immediate leak of the
    arena.
  - range_tree_set(): reword the comment describing the two lookups,
    as suggested by Alexei Starovoitov.  The pre-clear probe only
    decides whether a fresh node must be allocated, so that -ENOMEM
    leaves the tree unmodified; the post-clear lookup fetches the
    merge handles without depending on how range_tree_clear()
    truncates overlapping nodes.

Changes in v4:
  - arena_free_worker(): keep a span whose range_tree_set() failed on
    arena->free_spans and retry it on a later worker run, instead of
    leaving it in the drained list where the second loop would still
    zap user VMAs and free the span (dropping the free request), as
    pointed out by Emil Tsalapatis.

Changes in v3:
  - Check range_tree_set() return value in arena_alloc_pages()'s error
    path, which restores the unpopulated tail of a partially allocated
    range (previously ignored), as pointed out in review.

Changes in v2:
  - Fix multi-line comment style in patches 1 and 3 (opening /* on its
    own line), as pointed out in review.
====================

Link: https://patch.msgid.link/20260923085817.365930-1-chenyuan_fl@163.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
Alexei Starovoitov 2026-09-24 01:55:15 +00:00
commit b4d936547f
No known key found for this signature in database

View File

@ -143,16 +143,22 @@ int range_tree_clear(struct range_tree *rt, u32 start, u32 len)
if (rn->rn_start < start && rn->rn_last > last) {
u32 old_last = rn->rn_last;
/*
* Pre-allocate the right-half node before modifying
* the tree. If allocation fails we return -ENOMEM
* without altering the range tree.
*/
new_rn = kmalloc_nolock(sizeof(struct range_node),
__GFP_ACCOUNT, NUMA_NO_NODE);
if (!new_rn)
return -ENOMEM;
/* Overlaps with the entire clearing range */
range_it_remove(rn, rt);
rn->rn_last = start - 1;
range_it_insert(rn, rt);
/* Add a range */
new_rn = kmalloc_nolock(sizeof(struct range_node), __GFP_ACCOUNT,
NUMA_NO_NODE);
if (!new_rn)
return -ENOMEM;
/* Add right-half range */
new_rn->rn_start = last + 1;
new_rn->rn_last = old_last;
range_it_insert(new_rn, rt);
@ -193,6 +199,7 @@ int is_range_tree_set(struct range_tree *rt, u32 start, u32 len)
int range_tree_set(struct range_tree *rt, u32 start, u32 len)
{
u32 last = start + len - 1;
struct range_node *new_rn = NULL;
struct range_node *right;
struct range_node *left;
int err;
@ -202,20 +209,19 @@ int range_tree_set(struct range_tree *rt, u32 start, u32 len)
if (left && left->rn_start <= start && left->rn_last >= last)
return 0;
left = range_it_iter_first(rt, start - 1, start - 1);
right = range_it_iter_first(rt, last + 1, last + 1);
if (!left && !right) {
new_rn = kmalloc_nolock(sizeof(struct range_node),
__GFP_ACCOUNT, NUMA_NO_NODE);
if (!new_rn)
return -ENOMEM;
}
/* 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);
if (left && left->rn_last + 1 != start)
return -EFAULT;
/* Do we have a right-adjacent range ? */
right = range_it_iter_first(rt, last + 1, last + 1);
if (right && right->rn_start != last + 1)
return -EFAULT;
goto out_free_new;
if (left && right) {
/* Combine left and right adjacent ranges */
@ -235,14 +241,16 @@ int range_tree_set(struct range_tree *rt, u32 start, u32 len)
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 adjacent ranges; use the pre-allocated node */
new_rn->rn_start = start;
new_rn->rn_last = last;
range_it_insert(new_rn, rt);
}
return 0;
out_free_new:
kfree_nolock(new_rn);
return err;
}
void range_tree_destroy(struct range_tree *rt)