[PATCH v3 bpf] bpf: array: use u32 iterator for max_entries in alloc/free paths

From: meishaoming

Date: Thu Aug 27 2026 - 05:56:18 EST


BPF_MAP_TYPE_PROG_ARRAY (and other array maps) take max_entries as a u32
from userspace via bpf(BPF_MAP_CREATE). Several array map alloc/free
paths iterate max_entries with a signed int loop variable:

for (i = 0; i < array->map.max_entries; i++)

On arm64 (and any LP64 arch) int is 32-bit signed, while max_entries is
u32. With max_entries having bit 31 set (e.g. 0xFF00000A, observed from a
syzkaller run), the loop runs past i = 0x7FFFFFFF: i++ wraps to 0x80000000,
which as a signed int is negative. The address computation for
&array->ptrs[i] sign-extends the 32-bit index (sxtw on arm64) into a
64-bit negative offset:

array + sxtw(0x80000000) * 8 + offsetof(bpf_array, ptrs)
= array + 0xFFFFFFFC00000000 + 0x108
-> wraps to an unmapped address, level-1 translation fault, panic

This is reachable today because the existing overflow backstop in
array_map_alloc() lives inside the `if (!bypass_spec_v1)` block. Since
commit 2c78ee898d8f ("bpf: Implement CAP_BPF") renamed the old
`if (unpriv)` guard to `if (!bypass_spec_v1)`, the semantics inverted:
a root caller with CAP_PERFMON (or mitigations=off) takes the bypass
path and skips the -E2BIG check entirely, so the raw attr->max_entries
is stored into map->max_entries unchanged.

Rather than rejecting max_entries > INT_MAX at creation time — which
would artificially cut half the legitimate u32 range (a 3G-entry
single-byte map is a valid allocation when memory allows) — change the
iterators to u32 i so the index zero-extends into the 64-bit address,
landing inside the allocated map region instead of wrapping negative.
This keeps the full u32 capacity usable and matches the type of
max_entries itself.

Changed all int i iterators over max_entries in arraymap.c to u32:
- bpf_array_free_percpu (pptrs[i])
- bpf_array_alloc_percpu (pptrs[i])
- array_map_free_internal_structs
- array_map_free (pptrs[i & index_mask])
- fd_array_map_free (ptrs[i], the observed crash site)
- bpf_fd_array_map_clear (__fd_array_map_delete_elem)
- perf_event_fd_array_release (ptrs[i])

Fixes: 2c78ee898d8f ("bpf: Implement CAP_BPF")
Signed-off-by: meishaoming <meishaoming@xxxxxxxxxx>
---
Changes in v3:
- Drop the max_entries > INT_MAX rejection added in v2 (per review,
it would artificially limit legitimate allocatable capacity when
memory is available).
- Switch all int i iterators over max_entries in arraymap.c to u32 i
so the index zero-extends instead of sign-extending; this keeps the
full u32 range usable and fixes every alloc/free path at once.
---
kernel/bpf/arraymap.c | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/kernel/bpf/arraymap.c b/kernel/bpf/arraymap.c
index 248b4818178c..21e08d7fc99e 100644
--- a/kernel/bpf/arraymap.c
+++ b/kernel/bpf/arraymap.c
@@ -22,7 +22,7 @@

static void bpf_array_free_percpu(struct bpf_array *array)
{
- int i;
+ u32 i;

for (i = 0; i < array->map.max_entries; i++) {
free_percpu(array->pptrs[i]);
@@ -33,7 +33,7 @@ static void bpf_array_free_percpu(struct bpf_array *array)
static int bpf_array_alloc_percpu(struct bpf_array *array)
{
void __percpu *ptr;
- int i;
+ u32 i;

for (i = 0; i < array->map.max_entries; i++) {
ptr = bpf_map_alloc_percpu(&array->map, array->elem_size, 8,
@@ -460,7 +460,7 @@ static void *array_map_vmalloc_addr(struct bpf_array *array)
static void array_map_free_internal_structs(struct bpf_map *map)
{
struct bpf_array *array = container_of(map, struct bpf_array, map);
- int i;
+ u32 i;

/* We only free internal structs on uref dropping to zero */
if (!bpf_map_has_internal_structs(map))
@@ -474,7 +474,7 @@ static void array_map_free_internal_structs(struct bpf_map *map)
static void array_map_free(struct bpf_map *map)
{
struct bpf_array *array = container_of(map, struct bpf_array, map);
- int i;
+ u32 i;

if (!IS_ERR_OR_NULL(map->record)) {
if (array->map.map_type == BPF_MAP_TYPE_PERCPU_ARRAY) {
@@ -860,7 +860,7 @@ static int fd_array_map_alloc_check(union bpf_attr *attr)
static void fd_array_map_free(struct bpf_map *map)
{
struct bpf_array *array = container_of(map, struct bpf_array, map);
- int i;
+ u32 i;

/* make sure it's empty */
for (i = 0; i < array->map.max_entries; i++)
@@ -1011,7 +1011,7 @@ static u32 prog_fd_array_sys_lookup_elem(void *ptr)
static void bpf_fd_array_map_clear(struct bpf_map *map, bool need_defer)
{
struct bpf_array *array = container_of(map, struct bpf_array, map);
- int i;
+ u32 i;

for (i = 0; i < array->map.max_entries; i++) {
__fd_array_map_delete_elem(map, &i, need_defer);
@@ -1298,7 +1298,7 @@ static void perf_event_fd_array_release(struct bpf_map *map,
{
struct bpf_array *array = container_of(map, struct bpf_array, map);
struct bpf_event_entry *ee;
- int i;
+ u32 i;

if (map->map_flags & BPF_F_PRESERVE_ELEMS)
return;

base-commit: a13307e97d5c54b65720bb71fa379960ded1e51a
--
2.50.1 (Apple Git-155)

#/******本邮件及其附件含有小米公司的保密信息,仅限于发送给上面地址中列出的个人或群组。禁止任何其他人以任何形式使用(包括但不限于全部或部分地泄露、复制、或散发)本邮件中的信息。如果您错收了本邮件,请您立即电话或邮件通知发件人并删除本邮件! This e-mail and its attachments contain confidential information from XIAOMI, which is intended only for the person or entity whose address is listed above. Any use of the information contained herein in any way (including, but not limited to, total or partial disclosure, reproduction, or dissemination) by persons other than the intended recipient(s) is prohibited. If you receive this e-mail in error, please notify the sender by phone or email immediately and delete it!******/#