mirror of
https://github.com/torvalds/linux.git
synced 2026-07-28 18:21:24 +02:00
Merge branch 'libbpf-move-arena-variables-out-of-the-zero-page'
Emil Tsalapatis says: ==================== libbpf: move arena variables out of the zero page Modify libbpf to place arena globals at the end of the arena mapping instead of the very beginning. This allows programs to leave the "zero page" of the arena unmapped, so that NULL arena pointer dereferences trigger a page fault and associated backtrace in BPF streams. In contrast, the current policy of placing global data in the zero pages means that NULL dereferences silently corrupt global data, e.g, arena qspinlock state. This makes arena bugs more difficult to debug. The patchset adds code to libbpf to move global arena data to the end of the arena. At load time, libbpf adjusts each symbol's location within the arena to point to the right location in the arena. The patchset also adjusts the arena skeleton pointer to point to the arena globals, now that they are not in the beginning of the arena region. CHANGESET ========= v3->v4: (https://lore.kernel.org/bpf/20251215161313.10120-1-emil@etsalapatis.com/T/#t) - Added Acks by Eduard - Changed jumptable sym_off to unsigned int for consistency (AI) - Adjusted selftests to ensure arena globals are actually mapped in (Eduard) - (Patch 2) Adjusted selftests that were failing because they were expecting the now removed "direct map offset" error message v2->v3: (https://lore.kernel.org/bpf/20251203162625.13152-1-emil@etsalapatis.com/) - Remove unnecessary kernel bounds check in resolve_pseudo_ldimm64 (Andrii) - Added patch to turn sym_off unsigned to prevent overflow (AI) - Remove obsolete references to offsets from test patch description (Andrii) - Use size_t for arena_data_off (Andrii) - Remove extra mutable variable from offset calculations (Andrii) v1->v2: (https://lore.kernel.org/bpf/20251118030058.162967-1-emil@etsalapatis.com) - Moved globals to the end of the mapping: (Andrii) - Removed extra parameter for offset and parameter picking logic - Removed padding in the skeleton - Removed additional libbpf call - Added Reviewed-by from Eduard on patch 1 Signed-off-by: Emil Tsalapatis <emil@etsalapatis.com> ==================== Link: https://patch.msgid.link/20251216173325.98465-1-emil@etsalapatis.com Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
This commit is contained in:
commit
e7a0adb03d
|
|
@ -21132,11 +21132,6 @@ static int resolve_pseudo_ldimm64(struct bpf_verifier_env *env)
|
|||
} else {
|
||||
u32 off = insn[1].imm;
|
||||
|
||||
if (off >= BPF_MAX_VAR_OFF) {
|
||||
verbose(env, "direct value offset of %u is not allowed\n", off);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!map->ops->map_direct_value_addr) {
|
||||
verbose(env, "no direct value access support for this map type\n");
|
||||
return -EINVAL;
|
||||
|
|
|
|||
|
|
@ -380,7 +380,7 @@ struct reloc_desc {
|
|||
const struct bpf_core_relo *core_relo; /* used when type == RELO_CORE */
|
||||
struct {
|
||||
int map_idx;
|
||||
int sym_off;
|
||||
unsigned int sym_off;
|
||||
/*
|
||||
* The following two fields can be unionized, as the
|
||||
* ext_idx field is used for extern symbols, and the
|
||||
|
|
@ -757,13 +757,14 @@ struct bpf_object {
|
|||
int arena_map_idx;
|
||||
void *arena_data;
|
||||
size_t arena_data_sz;
|
||||
size_t arena_data_off;
|
||||
|
||||
void *jumptables_data;
|
||||
size_t jumptables_data_sz;
|
||||
|
||||
struct {
|
||||
struct bpf_program *prog;
|
||||
int sym_off;
|
||||
unsigned int sym_off;
|
||||
int fd;
|
||||
} *jumptable_maps;
|
||||
size_t jumptable_map_cnt;
|
||||
|
|
@ -2991,10 +2992,11 @@ static int init_arena_map_data(struct bpf_object *obj, struct bpf_map *map,
|
|||
void *data, size_t data_sz)
|
||||
{
|
||||
const long page_sz = sysconf(_SC_PAGE_SIZE);
|
||||
const size_t data_alloc_sz = roundup(data_sz, page_sz);
|
||||
size_t mmap_sz;
|
||||
|
||||
mmap_sz = bpf_map_mmap_sz(map);
|
||||
if (roundup(data_sz, page_sz) > mmap_sz) {
|
||||
if (data_alloc_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);
|
||||
return -E2BIG;
|
||||
|
|
@ -3006,6 +3008,9 @@ 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;
|
||||
|
||||
/* make bpf_map__init_value() work for ARENA maps */
|
||||
map->mmaped = obj->arena_data;
|
||||
|
||||
|
|
@ -4663,7 +4668,7 @@ static int bpf_program__record_reloc(struct bpf_program *prog,
|
|||
reloc_desc->type = RELO_DATA;
|
||||
reloc_desc->insn_idx = insn_idx;
|
||||
reloc_desc->map_idx = obj->arena_map_idx;
|
||||
reloc_desc->sym_off = sym->st_value;
|
||||
reloc_desc->sym_off = sym->st_value + obj->arena_data_off;
|
||||
|
||||
map = &obj->maps[obj->arena_map_idx];
|
||||
pr_debug("prog '%s': found arena map %d (%s, sec %d, off %zu) for insn %u\n",
|
||||
|
|
@ -5624,7 +5629,8 @@ bpf_object__create_maps(struct bpf_object *obj)
|
|||
return err;
|
||||
}
|
||||
if (obj->arena_data) {
|
||||
memcpy(map->mmaped, obj->arena_data, obj->arena_data_sz);
|
||||
memcpy(map->mmaped + obj->arena_data_off, obj->arena_data,
|
||||
obj->arena_data_sz);
|
||||
zfree(&obj->arena_data);
|
||||
}
|
||||
}
|
||||
|
|
@ -6192,7 +6198,7 @@ static void poison_kfunc_call(struct bpf_program *prog, int relo_idx,
|
|||
insn->imm = POISON_CALL_KFUNC_BASE + ext_idx;
|
||||
}
|
||||
|
||||
static int find_jt_map(struct bpf_object *obj, struct bpf_program *prog, int sym_off)
|
||||
static int find_jt_map(struct bpf_object *obj, struct bpf_program *prog, unsigned int sym_off)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
|
|
@ -6210,7 +6216,7 @@ static int find_jt_map(struct bpf_object *obj, struct bpf_program *prog, int sym
|
|||
return -ENOENT;
|
||||
}
|
||||
|
||||
static int add_jt_map(struct bpf_object *obj, struct bpf_program *prog, int sym_off, int map_fd)
|
||||
static int add_jt_map(struct bpf_object *obj, struct bpf_program *prog, unsigned int sym_off, int map_fd)
|
||||
{
|
||||
size_t cnt = obj->jumptable_map_cnt;
|
||||
size_t size = sizeof(obj->jumptable_maps[0]);
|
||||
|
|
@ -6244,7 +6250,7 @@ static int find_subprog_idx(struct bpf_program *prog, int insn_idx)
|
|||
static int create_jt_map(struct bpf_object *obj, struct bpf_program *prog, struct reloc_desc *relo)
|
||||
{
|
||||
const __u32 jt_entry_size = 8;
|
||||
int sym_off = relo->sym_off;
|
||||
unsigned int sym_off = relo->sym_off;
|
||||
int jt_size = relo->sym_size;
|
||||
__u32 max_entries = jt_size / jt_entry_size;
|
||||
__u32 value_size = sizeof(struct bpf_insn_array_value);
|
||||
|
|
@ -6260,7 +6266,7 @@ static int create_jt_map(struct bpf_object *obj, struct bpf_program *prog, struc
|
|||
return map_fd;
|
||||
|
||||
if (sym_off % jt_entry_size) {
|
||||
pr_warn("map '.jumptables': jumptable start %d should be multiple of %u\n",
|
||||
pr_warn("map '.jumptables': jumptable start %u should be multiple of %u\n",
|
||||
sym_off, jt_entry_size);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
|
@ -6316,7 +6322,7 @@ static int create_jt_map(struct bpf_object *obj, struct bpf_program *prog, struc
|
|||
* should contain values that fit in u32.
|
||||
*/
|
||||
if (insn_off > UINT32_MAX) {
|
||||
pr_warn("map '.jumptables': invalid jump table value 0x%llx at offset %d\n",
|
||||
pr_warn("map '.jumptables': invalid jump table value 0x%llx at offset %u\n",
|
||||
(long long)jt[i], sym_off + i * jt_entry_size);
|
||||
err = -EINVAL;
|
||||
goto err_close;
|
||||
|
|
@ -14429,7 +14435,10 @@ int bpf_object__load_skeleton(struct bpf_object_skeleton *s)
|
|||
if (!map_skel->mmaped)
|
||||
continue;
|
||||
|
||||
*map_skel->mmaped = map->mmaped;
|
||||
if (map->def.type == BPF_MAP_TYPE_ARENA)
|
||||
*map_skel->mmaped = map->mmaped + map->obj->arena_data_off;
|
||||
else
|
||||
*map_skel->mmaped = map->mmaped;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
#include "verifier_and.skel.h"
|
||||
#include "verifier_arena.skel.h"
|
||||
#include "verifier_arena_large.skel.h"
|
||||
#include "verifier_arena_globals1.skel.h"
|
||||
#include "verifier_arena_globals2.skel.h"
|
||||
#include "verifier_array_access.skel.h"
|
||||
#include "verifier_async_cb_context.skel.h"
|
||||
#include "verifier_basic_stack.skel.h"
|
||||
|
|
@ -147,6 +149,8 @@ static void run_tests_aux(const char *skel_name,
|
|||
void test_verifier_and(void) { RUN(verifier_and); }
|
||||
void test_verifier_arena(void) { RUN(verifier_arena); }
|
||||
void test_verifier_arena_large(void) { RUN(verifier_arena_large); }
|
||||
void test_verifier_arena_globals1(void) { RUN(verifier_arena_globals1); }
|
||||
void test_verifier_arena_globals2(void) { RUN(verifier_arena_globals2); }
|
||||
void test_verifier_basic_stack(void) { RUN(verifier_basic_stack); }
|
||||
void test_verifier_bitfield_write(void) { RUN(verifier_bitfield_write); }
|
||||
void test_verifier_bounds(void) { RUN(verifier_bounds); }
|
||||
|
|
|
|||
87
tools/testing/selftests/bpf/progs/verifier_arena_globals1.c
Normal file
87
tools/testing/selftests/bpf/progs/verifier_arena_globals1.c
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
|
||||
|
||||
#define BPF_NO_KFUNC_PROTOTYPES
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include "bpf_experimental.h"
|
||||
#include "bpf_arena_common.h"
|
||||
#include "bpf_misc.h"
|
||||
|
||||
#define ARENA_PAGES (1UL<< (32 - 12))
|
||||
#define GLOBAL_PAGES (16)
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_ARENA);
|
||||
__uint(map_flags, BPF_F_MMAPABLE);
|
||||
__uint(max_entries, ARENA_PAGES);
|
||||
#ifdef __TARGET_ARCH_arm64
|
||||
__ulong(map_extra, (1ull << 32) | (~0u - __PAGE_SIZE * ARENA_PAGES + 1));
|
||||
#else
|
||||
__ulong(map_extra, (1ull << 44) | (~0u - __PAGE_SIZE * ARENA_PAGES + 1));
|
||||
#endif
|
||||
} arena SEC(".maps");
|
||||
|
||||
/*
|
||||
* Global data, to be placed at the end of the arena.
|
||||
*/
|
||||
volatile char __arena global_data[GLOBAL_PAGES][PAGE_SIZE];
|
||||
|
||||
SEC("syscall")
|
||||
__success __retval(0)
|
||||
int check_reserve1(void *ctx)
|
||||
{
|
||||
#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
|
||||
const u8 magic = 0x5a;
|
||||
__u8 __arena *guard, *globals;
|
||||
volatile char __arena *ptr;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
guard = (void __arena *)arena_base(&arena);
|
||||
globals = (void __arena *)(arena_base(&arena) + (ARENA_PAGES - GLOBAL_PAGES) * PAGE_SIZE);
|
||||
|
||||
/* Reserve the region we've offset the globals by. */
|
||||
ret = bpf_arena_reserve_pages(&arena, guard, ARENA_PAGES - GLOBAL_PAGES);
|
||||
if (ret)
|
||||
return 1;
|
||||
|
||||
/* Make sure the globals are in the expected offset. */
|
||||
ret = bpf_arena_reserve_pages(&arena, globals, 1);
|
||||
if (!ret)
|
||||
return 2;
|
||||
|
||||
/* Verify globals are properly mapped in by libbpf. */
|
||||
for (i = 0; i < GLOBAL_PAGES; i++) {
|
||||
ptr = &global_data[i][PAGE_SIZE / 2];
|
||||
|
||||
*ptr = magic;
|
||||
if (*ptr != magic)
|
||||
return i + 3;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Relocation check by reading directly into the global data w/o using symbols.
|
||||
*/
|
||||
SEC("syscall")
|
||||
__success __retval(0)
|
||||
int check_relocation(void *ctx)
|
||||
{
|
||||
#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
|
||||
const u8 magic = 0xfa;
|
||||
u8 __arena *ptr;
|
||||
|
||||
global_data[GLOBAL_PAGES - 1][PAGE_SIZE / 2] = magic;
|
||||
ptr = (u8 __arena *)((u64)(ARENA_PAGES * PAGE_SIZE - PAGE_SIZE / 2));
|
||||
if (*ptr != magic)
|
||||
return 1;
|
||||
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
49
tools/testing/selftests/bpf/progs/verifier_arena_globals2.c
Normal file
49
tools/testing/selftests/bpf/progs/verifier_arena_globals2.c
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
|
||||
|
||||
#define BPF_NO_KFUNC_PROTOTYPES
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include "bpf_misc.h"
|
||||
#include "bpf_experimental.h"
|
||||
#include "bpf_arena_common.h"
|
||||
|
||||
#define ARENA_PAGES (32)
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_ARENA);
|
||||
__uint(map_flags, BPF_F_MMAPABLE);
|
||||
__uint(max_entries, ARENA_PAGES);
|
||||
#ifdef __TARGET_ARCH_arm64
|
||||
__ulong(map_extra, (1ull << 32) | (~0u - __PAGE_SIZE * ARENA_PAGES + 1));
|
||||
#else
|
||||
__ulong(map_extra, (1ull << 44) | (~0u - __PAGE_SIZE * ARENA_PAGES + 1));
|
||||
#endif
|
||||
} arena SEC(".maps");
|
||||
|
||||
/*
|
||||
* Fill the entire arena with global data.
|
||||
* The offset into the arena should be 0.
|
||||
*/
|
||||
char __arena global_data[ARENA_PAGES][PAGE_SIZE];
|
||||
|
||||
SEC("syscall")
|
||||
__success __retval(0)
|
||||
int check_reserve2(void *ctx)
|
||||
{
|
||||
#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
|
||||
void __arena *guard;
|
||||
int ret;
|
||||
|
||||
guard = (void __arena *)arena_base(&arena);
|
||||
|
||||
/* Make sure the data at offset 0 case is properly handled. */
|
||||
ret = bpf_arena_reserve_pages(&arena, guard, 1);
|
||||
if (!ret)
|
||||
return 1;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
|
|
@ -23,18 +23,31 @@ int big_alloc1(void *ctx)
|
|||
{
|
||||
#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
|
||||
volatile char __arena *page1, *page2, *no_page, *page3;
|
||||
void __arena *base;
|
||||
u64 base;
|
||||
|
||||
page1 = base = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
|
||||
base = (u64)arena_base(&arena);
|
||||
|
||||
page1 = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
|
||||
if (!page1)
|
||||
return 1;
|
||||
|
||||
if ((u64)page1 != base)
|
||||
return 15;
|
||||
|
||||
*page1 = 1;
|
||||
page2 = bpf_arena_alloc_pages(&arena, base + ARENA_SIZE - PAGE_SIZE * 2,
|
||||
page2 = bpf_arena_alloc_pages(&arena, (void __arena *)(ARENA_SIZE - 2 * PAGE_SIZE),
|
||||
1, NUMA_NO_NODE, 0);
|
||||
if (!page2)
|
||||
return 2;
|
||||
*page2 = 2;
|
||||
no_page = bpf_arena_alloc_pages(&arena, base + ARENA_SIZE - PAGE_SIZE,
|
||||
|
||||
/* Test for the guard region at the end of the arena. */
|
||||
no_page = bpf_arena_alloc_pages(&arena, (void __arena *)ARENA_SIZE - PAGE_SIZE,
|
||||
1, NUMA_NO_NODE, 0);
|
||||
if (no_page)
|
||||
return 16;
|
||||
|
||||
no_page = bpf_arena_alloc_pages(&arena, (void __arena *)ARENA_SIZE,
|
||||
1, NUMA_NO_NODE, 0);
|
||||
if (no_page)
|
||||
return 3;
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@
|
|||
},
|
||||
.fixup_map_array_48b = { 1 },
|
||||
.result = REJECT,
|
||||
.errstr = "direct value offset of 4294967295 is not allowed",
|
||||
.errstr = "invalid access to map value pointer, value_size=48 off=4294967295",
|
||||
},
|
||||
{
|
||||
"direct map access, write test 8",
|
||||
|
|
@ -141,7 +141,7 @@
|
|||
},
|
||||
.fixup_map_array_48b = { 1 },
|
||||
.result = REJECT,
|
||||
.errstr = "direct value offset of 536870912 is not allowed",
|
||||
.errstr = "invalid access to map value pointer, value_size=48 off=536870912",
|
||||
},
|
||||
{
|
||||
"direct map access, write test 13",
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user