]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
libbpf: Fix handling of BPF arena relocations
authorAndrii Nakryiko <andrii@kernel.org>
Fri, 18 Jul 2025 00:10:09 +0000 (17:10 -0700)
committerAlexei Starovoitov <ast@kernel.org>
Fri, 18 Jul 2025 02:17:46 +0000 (19:17 -0700)
Initial __arena global variable support implementation in libbpf
contains a bug: it remembers struct bpf_map pointer for arena, which is
used later on to process relocations. Recording this pointer is
problematic because map pointers are not stable during ELF relocation
collection phase, as an array of struct bpf_map's can be reallocated,
invalidating all the pointers. Libbpf is dealing with similar issues by
using a stable internal map index, though for BPF arena map specifically
this approach wasn't used due to an oversight.

The resulting behavior is non-deterministic issue which depends on exact
layout of ELF object file, number of actual maps, etc. We didn't hit
this until very recently, when this bug started triggering crash in BPF
CI when validating one of sched-ext BPF programs.

The fix is rather straightforward: we just follow an established pattern
of remembering map index (just like obj->kconfig_map_idx, for example)
instead of `struct bpf_map *`, and resolving index to a pointer at the
point where map information is necessary.

While at it also add debug-level message for arena-related relocation
resolution information, which we already have for all other kinds of
maps.

Fixes: 2e7ba4f8fd1f ("libbpf: Recognize __arena global variables.")
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
Tested-by: Eduard Zingerman <eddyz87@gmail.com>
Link: https://lore.kernel.org/r/20250718001009.610955-1-andrii@kernel.org
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
tools/lib/bpf/libbpf.c

index 52e353368f586bca65725d580e66e99e77e04bd1..d41ee26b944359d2a8c307ee082b52d03b98bebf 100644 (file)
@@ -735,7 +735,7 @@ struct bpf_object {
 
        struct usdt_manager *usdt_man;
 
-       struct bpf_map *arena_map;
+       int arena_map_idx;
        void *arena_data;
        size_t arena_data_sz;
 
@@ -1517,6 +1517,7 @@ static struct bpf_object *bpf_object__new(const char *path,
        obj->efile.obj_buf_sz = obj_buf_sz;
        obj->efile.btf_maps_shndx = -1;
        obj->kconfig_map_idx = -1;
+       obj->arena_map_idx = -1;
 
        obj->kern_version = get_kernel_version();
        obj->state  = OBJ_OPEN;
@@ -2964,7 +2965,7 @@ static int init_arena_map_data(struct bpf_object *obj, struct bpf_map *map,
        const long page_sz = sysconf(_SC_PAGE_SIZE);
        size_t mmap_sz;
 
-       mmap_sz = bpf_map_mmap_sz(obj->arena_map);
+       mmap_sz = bpf_map_mmap_sz(map);
        if (roundup(data_sz, page_sz) > mmap_sz) {
                pr_warn("elf: sec '%s': declared ARENA map size (%zu) is too small to hold global __arena variables of size %zu\n",
                        sec_name, mmap_sz, data_sz);
@@ -3038,12 +3039,12 @@ static int bpf_object__init_user_btf_maps(struct bpf_object *obj, bool strict,
                if (map->def.type != BPF_MAP_TYPE_ARENA)
                        continue;
 
-               if (obj->arena_map) {
+               if (obj->arena_map_idx >= 0) {
                        pr_warn("map '%s': only single ARENA map is supported (map '%s' is also ARENA)\n",
-                               map->name, obj->arena_map->name);
+                               map->name, obj->maps[obj->arena_map_idx].name);
                        return -EINVAL;
                }
-               obj->arena_map = map;
+               obj->arena_map_idx = i;
 
                if (obj->efile.arena_data) {
                        err = init_arena_map_data(obj, map, ARENA_SEC, obj->efile.arena_data_shndx,
@@ -3053,7 +3054,7 @@ static int bpf_object__init_user_btf_maps(struct bpf_object *obj, bool strict,
                                return err;
                }
        }
-       if (obj->efile.arena_data && !obj->arena_map) {
+       if (obj->efile.arena_data && obj->arena_map_idx < 0) {
                pr_warn("elf: sec '%s': to use global __arena variables the ARENA map should be explicitly declared in SEC(\".maps\")\n",
                        ARENA_SEC);
                return -ENOENT;
@@ -4583,8 +4584,13 @@ static int bpf_program__record_reloc(struct bpf_program *prog,
        if (shdr_idx == obj->efile.arena_data_shndx) {
                reloc_desc->type = RELO_DATA;
                reloc_desc->insn_idx = insn_idx;
-               reloc_desc->map_idx = obj->arena_map - obj->maps;
+               reloc_desc->map_idx = obj->arena_map_idx;
                reloc_desc->sym_off = sym->st_value;
+
+               map = &obj->maps[obj->arena_map_idx];
+               pr_debug("prog '%s': found arena map %d (%s, sec %d, off %zu) for insn %u\n",
+                        prog->name, obj->arena_map_idx, map->name, map->sec_idx,
+                        map->sec_offset, insn_idx);
                return 0;
        }