* [PATCH bpf-next 0/2] bpf: Reject arena frees below the arena base
@ 2026-06-30 10:12 Yiyang Chen
2026-06-30 10:12 ` [PATCH bpf-next 1/2] " Yiyang Chen
2026-06-30 10:12 ` [PATCH bpf-next 2/2] selftests/bpf: Cover scalar arena frees below the base Yiyang Chen
0 siblings, 2 replies; 5+ messages in thread
From: Yiyang Chen @ 2026-06-30 10:12 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi
Cc: Yiyang Chen, Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Shuah Khan, Emil Tsalapatis, Puranjay Mohan, bpf, linux-kselftest,
linux-kernel
bpf_arena_free_pages() can be called with a scalar arena address. The
runtime reconstructs a full user address from the arena base and the low
32 bits before returning the range to the arena free tree. A scalar one
page below the arena base can otherwise produce an out-of-domain free-tree
offset and make a later allocation return an address below the arena
mapping.
Patch 1 rejects frees whose reconstructed full user address is below
user_vm_start. Patch 2 adds verifier_arena coverage for the
scalar-below-base case.
Yiyang Chen (2):
bpf: Reject arena frees below the arena base
selftests/bpf: Cover scalar arena frees below the base
kernel/bpf/arena.c | 2 +
.../selftests/bpf/progs/verifier_arena.c | 41 ++++++++++++++++---
2 files changed, 38 insertions(+), 5 deletions(-)
base-commit: 7feeed42d8b97d0db0eab2c1c30aa86f110fe49c
--
2.34.1
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH bpf-next 1/2] bpf: Reject arena frees below the arena base
2026-06-30 10:12 [PATCH bpf-next 0/2] bpf: Reject arena frees below the arena base Yiyang Chen
@ 2026-06-30 10:12 ` Yiyang Chen
2026-07-01 21:12 ` Emil Tsalapatis
2026-06-30 10:12 ` [PATCH bpf-next 2/2] selftests/bpf: Cover scalar arena frees below the base Yiyang Chen
1 sibling, 1 reply; 5+ messages in thread
From: Yiyang Chen @ 2026-06-30 10:12 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi
Cc: Yiyang Chen, Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Shuah Khan, Emil Tsalapatis, Puranjay Mohan, bpf, linux-kselftest,
linux-kernel
bpf_arena_free_pages() accepts scalar arena addresses. The runtime
masks the address to the low 32 bits and reconstructs a full user
address from the arena base before returning the range to the arena
free tree.
When the scalar value is below the low 32 bits of the arena base,
full_uaddr falls below user_vm_start. The existing upper-end clipping
then turns this into an out-of-range free-tree offset. A later
allocation can reuse that offset and return an address below the arena
mapping.
Reject such frees before computing the clipped range.
Fixes: 317460317a02a ("bpf: Introduce bpf_arena.")
Signed-off-by: Yiyang Chen <chenyy23@mails.tsinghua.edu.cn>
---
kernel/bpf/arena.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/kernel/bpf/arena.c b/kernel/bpf/arena.c
index 49a8f7b1beef5..e28e83bed8c51 100644
--- a/kernel/bpf/arena.c
+++ b/kernel/bpf/arena.c
@@ -693,6 +693,8 @@ static void arena_free_pages(struct bpf_arena *arena, long uaddr, long page_cnt,
uaddr &= PAGE_MASK;
kaddr = bpf_arena_get_kern_vm_start(arena) + uaddr;
full_uaddr = clear_lo32(arena->user_vm_start) + uaddr;
+ if (full_uaddr < arena->user_vm_start)
+ return;
uaddr_end = min(arena->user_vm_end, full_uaddr + (page_cnt << PAGE_SHIFT));
if (full_uaddr >= uaddr_end)
return;
--
2.34.1
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH bpf-next 2/2] selftests/bpf: Cover scalar arena frees below the base
2026-06-30 10:12 [PATCH bpf-next 0/2] bpf: Reject arena frees below the arena base Yiyang Chen
2026-06-30 10:12 ` [PATCH bpf-next 1/2] " Yiyang Chen
@ 2026-06-30 10:12 ` Yiyang Chen
2026-07-01 21:13 ` Emil Tsalapatis
1 sibling, 1 reply; 5+ messages in thread
From: Yiyang Chen @ 2026-06-30 10:12 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi
Cc: Yiyang Chen, Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Shuah Khan, Emil Tsalapatis, Puranjay Mohan, bpf, linux-kselftest,
linux-kernel
Add a verifier_arena case that fills a two-page arena, calls
bpf_arena_free_pages() with a scalar address one page below the arena
base, and then verifies that another allocation is still rejected.
Before the runtime guard, the invalid free can repopulate the free
tree with an out-of-domain offset and the final allocation succeeds.
Signed-off-by: Yiyang Chen <chenyy23@mails.tsinghua.edu.cn>
---
.../selftests/bpf/progs/verifier_arena.c | 41 ++++++++++++++++---
1 file changed, 36 insertions(+), 5 deletions(-)
diff --git a/tools/testing/selftests/bpf/progs/verifier_arena.c b/tools/testing/selftests/bpf/progs/verifier_arena.c
index 62e282f4448aa..b4bd134646607 100644
--- a/tools/testing/selftests/bpf/progs/verifier_arena.c
+++ b/tools/testing/selftests/bpf/progs/verifier_arena.c
@@ -12,15 +12,17 @@
#define private(name) SEC(".bss." #name) __hidden __attribute__((aligned(8)))
+#ifdef __TARGET_ARCH_arm64
+#define ARENA_VM_START ((1ull << 32) | (~0u - __PAGE_SIZE * 2 + 1))
+#else
+#define ARENA_VM_START ((1ull << 44) | (~0u - __PAGE_SIZE * 2 + 1))
+#endif
+
struct {
__uint(type, BPF_MAP_TYPE_ARENA);
__uint(map_flags, BPF_F_MMAPABLE);
__uint(max_entries, 2); /* arena of two pages close to 32-bit boundary*/
-#ifdef __TARGET_ARCH_arm64
- __ulong(map_extra, (1ull << 32) | (~0u - __PAGE_SIZE * 2 + 1)); /* start of mmap() region */
-#else
- __ulong(map_extra, (1ull << 44) | (~0u - __PAGE_SIZE * 2 + 1)); /* start of mmap() region */
-#endif
+ __ulong(map_extra, ARENA_VM_START); /* start of mmap() region */
} arena SEC(".maps");
SEC("socket")
@@ -93,6 +95,35 @@ int basic_alloc1(void *ctx)
return 0;
}
+SEC("syscall")
+__success __retval(0)
+int free_scalar_below_arena(void *ctx)
+{
+ void __arena *page1, *page2, *page3;
+ __u64 bad_addr = ARENA_VM_START - __PAGE_SIZE;
+
+ page1 = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (!page1)
+ return 1;
+
+ page2 = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (!page2)
+ return 2;
+
+ page3 = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (page3)
+ return 3;
+
+ asm volatile("" : "+r"(bad_addr));
+ bpf_arena_free_pages(&arena, (void __arena *)bad_addr, 1);
+
+ page3 = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (page3)
+ return 4;
+
+ return 0;
+}
+
SEC("socket")
__success __retval(0)
int basic_alloc2_nosleep(void *ctx)
--
2.34.1
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH bpf-next 1/2] bpf: Reject arena frees below the arena base
2026-06-30 10:12 ` [PATCH bpf-next 1/2] " Yiyang Chen
@ 2026-07-01 21:12 ` Emil Tsalapatis
0 siblings, 0 replies; 5+ messages in thread
From: Emil Tsalapatis @ 2026-07-01 21:12 UTC (permalink / raw)
To: Yiyang Chen, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi
Cc: Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, Shuah Khan,
Emil Tsalapatis, Puranjay Mohan, bpf, linux-kselftest,
linux-kernel
On Tue Jun 30, 2026 at 6:12 AM EDT, Yiyang Chen wrote:
> bpf_arena_free_pages() accepts scalar arena addresses. The runtime
> masks the address to the low 32 bits and reconstructs a full user
> address from the arena base before returning the range to the arena
> free tree.
>
> When the scalar value is below the low 32 bits of the arena base,
> full_uaddr falls below user_vm_start. The existing upper-end clipping
> then turns this into an out-of-range free-tree offset. A later
> allocation can reuse that offset and return an address below the arena
> mapping.
>
This seems reasonable, my understanding is that this happens because
neither arena start nor end actually has to be aligned at a 4GiB
boundary, while the underlying kernel mapping does span the full
32 bits. AFAICT this bug returns an address that is not actually
mapped into userspace and is inaccessible from it, correct?
If the above is correct, feel free to add:
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
> Reject such frees before computing the clipped range.
>
> Fixes: 317460317a02a ("bpf: Introduce bpf_arena.")
> Signed-off-by: Yiyang Chen <chenyy23@mails.tsinghua.edu.cn>
> ---
> kernel/bpf/arena.c | 2 ++
> 1 file changed, 2 insertions(+)
>
> diff --git a/kernel/bpf/arena.c b/kernel/bpf/arena.c
> index 49a8f7b1beef5..e28e83bed8c51 100644
> --- a/kernel/bpf/arena.c
> +++ b/kernel/bpf/arena.c
> @@ -693,6 +693,8 @@ static void arena_free_pages(struct bpf_arena *arena, long uaddr, long page_cnt,
> uaddr &= PAGE_MASK;
> kaddr = bpf_arena_get_kern_vm_start(arena) + uaddr;
> full_uaddr = clear_lo32(arena->user_vm_start) + uaddr;
> + if (full_uaddr < arena->user_vm_start)
> + return;
> uaddr_end = min(arena->user_vm_end, full_uaddr + (page_cnt << PAGE_SHIFT));
> if (full_uaddr >= uaddr_end)
> return;
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH bpf-next 2/2] selftests/bpf: Cover scalar arena frees below the base
2026-06-30 10:12 ` [PATCH bpf-next 2/2] selftests/bpf: Cover scalar arena frees below the base Yiyang Chen
@ 2026-07-01 21:13 ` Emil Tsalapatis
0 siblings, 0 replies; 5+ messages in thread
From: Emil Tsalapatis @ 2026-07-01 21:13 UTC (permalink / raw)
To: Yiyang Chen, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi
Cc: Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, Shuah Khan,
Emil Tsalapatis, Puranjay Mohan, bpf, linux-kselftest,
linux-kernel
On Tue Jun 30, 2026 at 6:12 AM EDT, Yiyang Chen wrote:
> Add a verifier_arena case that fills a two-page arena, calls
> bpf_arena_free_pages() with a scalar address one page below the arena
> base, and then verifies that another allocation is still rejected.
>
> Before the runtime guard, the invalid free can repopulate the free
> tree with an out-of-domain offset and the final allocation succeeds.
>
> Signed-off-by: Yiyang Chen <chenyy23@mails.tsinghua.edu.cn>
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
Nit/question below.
> ---
> .../selftests/bpf/progs/verifier_arena.c | 41 ++++++++++++++++---
> 1 file changed, 36 insertions(+), 5 deletions(-)
>
> diff --git a/tools/testing/selftests/bpf/progs/verifier_arena.c b/tools/testing/selftests/bpf/progs/verifier_arena.c
> index 62e282f4448aa..b4bd134646607 100644
> --- a/tools/testing/selftests/bpf/progs/verifier_arena.c
> +++ b/tools/testing/selftests/bpf/progs/verifier_arena.c
> @@ -12,15 +12,17 @@
>
> #define private(name) SEC(".bss." #name) __hidden __attribute__((aligned(8)))
>
> +#ifdef __TARGET_ARCH_arm64
> +#define ARENA_VM_START ((1ull << 32) | (~0u - __PAGE_SIZE * 2 + 1))
> +#else
> +#define ARENA_VM_START ((1ull << 44) | (~0u - __PAGE_SIZE * 2 + 1))
> +#endif
> +
> struct {
> __uint(type, BPF_MAP_TYPE_ARENA);
> __uint(map_flags, BPF_F_MMAPABLE);
> __uint(max_entries, 2); /* arena of two pages close to 32-bit boundary*/
> -#ifdef __TARGET_ARCH_arm64
> - __ulong(map_extra, (1ull << 32) | (~0u - __PAGE_SIZE * 2 + 1)); /* start of mmap() region */
> -#else
> - __ulong(map_extra, (1ull << 44) | (~0u - __PAGE_SIZE * 2 + 1)); /* start of mmap() region */
> -#endif
> + __ulong(map_extra, ARENA_VM_START); /* start of mmap() region */
> } arena SEC(".maps");
>
> SEC("socket")
> @@ -93,6 +95,35 @@ int basic_alloc1(void *ctx)
> return 0;
> }
>
> +SEC("syscall")
> +__success __retval(0)
> +int free_scalar_below_arena(void *ctx)
> +{
> + void __arena *page1, *page2, *page3;
> + __u64 bad_addr = ARENA_VM_START - __PAGE_SIZE;
> +
> + page1 = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
> + if (!page1)
> + return 1;
> +
> + page2 = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
> + if (!page2)
> + return 2;
> +
> + page3 = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
> + if (page3)
> + return 3;
> +
> + asm volatile("" : "+r"(bad_addr));
Why the asm volatile? We use it right underneath, what does this give
us.
> + bpf_arena_free_pages(&arena, (void __arena *)bad_addr, 1);
> +
> + page3 = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
> + if (page3)
> + return 4;
> +
> + return 0;
> +}
> +
> SEC("socket")
> __success __retval(0)
> int basic_alloc2_nosleep(void *ctx)
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-07-01 21:13 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-30 10:12 [PATCH bpf-next 0/2] bpf: Reject arena frees below the arena base Yiyang Chen
2026-06-30 10:12 ` [PATCH bpf-next 1/2] " Yiyang Chen
2026-07-01 21:12 ` Emil Tsalapatis
2026-06-30 10:12 ` [PATCH bpf-next 2/2] selftests/bpf: Cover scalar arena frees below the base Yiyang Chen
2026-07-01 21:13 ` Emil Tsalapatis
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.