Add feature gating for the arena globals relocation introduced in commit c1f61171d44b. The commit depends on a previous commit in the same patchset that is absent from older kernels (12a1fe6e12db, "bpf/verifier: Do not limit maximum direct offset into arena map"). Without this commit, arena globals relocation with arenas >= 512MiB fails to load and breaks libbpf's backwards compatibility. Introduce a libbpf feature to check whether the running kernel includes patch 12a1fe6e12db, and only relocate arena globals if it does. CHANGELOG --------- v1 -> v2 (https://lore.kernel.org/bpf/20260209210240.2051209-1-emil@etsalapatis.com/) - Reworked test to use simpler array-based program (Andrii) Fixes: c1f61171d44b ("libbpf: Move arena globals to the end of the arena") Signed-off-by: Emil Tsalapatis --- tools/lib/bpf/features.c | 92 +++++++++++++++++++++++++++++++++ tools/lib/bpf/libbpf.c | 7 ++- tools/lib/bpf/libbpf_internal.h | 2 + 3 files changed, 99 insertions(+), 2 deletions(-) diff --git a/tools/lib/bpf/features.c b/tools/lib/bpf/features.c index b842b83e2480..b1a088dd83ac 100644 --- a/tools/lib/bpf/features.c +++ b/tools/lib/bpf/features.c @@ -506,6 +506,95 @@ static int probe_kern_arg_ctx_tag(int token_fd) return probe_fd(prog_fd); } +static int probe_kern_arena_global_reloc(int token_fd) +{ + const size_t bufsz = 1024; + int btf_fd, prog_fd, map; + char log_buf[bufsz]; + int ret; + static const char strs[] = "\0f\0"; + const __u32 types[] = { + /* [1] FUNC_PROTO `void(void)` */ + BTF_TYPE_ENC(0, BTF_INFO_ENC(BTF_KIND_FUNC_PROTO, 0, 0), 0), + /* [2] FUNC 'f' -> FUNC_PROTO (main prog) */ + BTF_TYPE_ENC(1 /* "f" */, BTF_INFO_ENC(BTF_KIND_FUNC, 0, BTF_FUNC_GLOBAL), 1), + }; + LIBBPF_OPTS(bpf_map_create_opts, map_opts, + .token_fd = token_fd, + .map_flags = token_fd ? BPF_F_TOKEN_FD : 0, + ); + LIBBPF_OPTS(bpf_prog_load_opts, prog_opts, + .token_fd = token_fd, + .prog_flags = BPF_F_SLEEPABLE | (token_fd ? BPF_F_TOKEN_FD : 0), + .log_buf = log_buf, + .log_size = bufsz, + ); + struct bpf_insn insns[] = { + BPF_LD_MAP_VALUE(BPF_REG_1, 0, 1UL << 30), + BPF_EXIT_INSN(), + }; + const struct bpf_func_info_min func_infos[] = { + { 0, 2 }, /* main prog -> FUNC 'f' */ + }; + int insn_cnt = ARRAY_SIZE(insns); + + btf_fd = libbpf__load_raw_btf((char *)types, sizeof(types), strs, sizeof(strs), token_fd); + if (btf_fd < 0) + return 0; /* BTF not supported at all */ + + map = bpf_map_create(BPF_MAP_TYPE_ARRAY, "arr", sizeof(int), 1, 1, &map_opts); + if (map < 0) { + ret = -errno; + pr_warn("Error in %s(): %s. Couldn't create simple array map.\n", + __func__, errstr(ret)); + close(btf_fd); + return ret; + } + insns[0].imm = map; + + prog_opts.prog_btf_fd = btf_fd; + prog_opts.func_info = &func_infos; + prog_opts.func_info_cnt = ARRAY_SIZE(func_infos); + prog_opts.func_info_rec_size = sizeof(func_infos[0]); + + prog_fd = bpf_prog_load(BPF_PROG_TYPE_SYSCALL, "global_reloc", "GPL", insns, insn_cnt, &prog_opts); + + ret = -errno; + close(map); + close(btf_fd); + + if (prog_fd >= 0) { + pr_warn("Error in %s(): Program loading unexpectedly succeeded.\n", + __func__); + close(prog_fd); + return -EINVAL; + } + + /* Be conservative and NULL terminate the buffer to ensure strstr terminates. */ + log_buf[bufsz - 1] = '\0'; + + /* + * Feature is allowed if we're not failing with the error message "direct value + * offset of %u is not allowed that was removed in 12a1fe6e12db + * ("bpf/verifier: Do not limit maximum direct offset into arena map"). + * Instead, we should be failing with the message "invalid access to map value + * pointer". Ensure we match with one of the two and we're not failing with a + * different, unexpected message. + */ + if (strstr(log_buf, "direct value offset of")) + return 0; + + if (strstr(log_buf, "invalid access to map value pointer") == NULL) { + pr_warn("Error in %s(): Program unexpectedly failed with message: %s.\n", + __func__, log_buf); + return ret; + } + + /* Feature is on. */ + + return 1; +} + typedef int (*feature_probe_fn)(int /* token_fd */); static struct kern_feature_cache feature_cache; @@ -581,6 +670,9 @@ static struct kern_feature_desc { [FEAT_BTF_QMARK_DATASEC] = { "BTF DATASEC names starting from '?'", probe_kern_btf_qmark_datasec, }, + [FEAT_ARENA_GLOBAL_RELOC] = { + "Relocatable arena globals support", probe_kern_arena_global_reloc, + }, }; bool feat_supported(struct kern_feature_cache *cache, enum kern_feature_id feat_id) diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c index 0c8bf0b5cce4..0aeb740a5f47 100644 --- a/tools/lib/bpf/libbpf.c +++ b/tools/lib/bpf/libbpf.c @@ -3009,8 +3009,11 @@ static int init_arena_map_data(struct bpf_object *obj, struct bpf_map *map, memcpy(obj->arena_data, data, data_sz); obj->arena_data_sz = data_sz; - /* place globals at the end of the arena */ - obj->arena_data_off = mmap_sz - data_alloc_sz; + /* place globals at the end of the arena (if supported) */ + if (kernel_supports(obj, FEAT_ARENA_GLOBAL_RELOC)) + obj->arena_data_off = mmap_sz - data_alloc_sz; + else + obj->arena_data_off = 0; /* make bpf_map__init_value() work for ARENA maps */ map->mmaped = obj->arena_data; diff --git a/tools/lib/bpf/libbpf_internal.h b/tools/lib/bpf/libbpf_internal.h index fc59b21b51b5..e0223a6e4c95 100644 --- a/tools/lib/bpf/libbpf_internal.h +++ b/tools/lib/bpf/libbpf_internal.h @@ -392,6 +392,8 @@ enum kern_feature_id { FEAT_ARG_CTX_TAG, /* Kernel supports '?' at the front of datasec names */ FEAT_BTF_QMARK_DATASEC, + /* Kernel supports relocating arena globals to end of the arena */ + FEAT_ARENA_GLOBAL_RELOC, __FEAT_CNT, }; -- 2.49.0