Skip to content

Commit e7a0adb

Browse files
committed
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/[email protected]/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/[email protected]/) - 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/[email protected]) - 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 <[email protected]> ==================== Link: https://patch.msgid.link/[email protected] Signed-off-by: Andrii Nakryiko <[email protected]>
2 parents 6f0b824 + 19f1243 commit e7a0adb

File tree

7 files changed

+179
-22
lines changed

7 files changed

+179
-22
lines changed

kernel/bpf/verifier.c

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21132,11 +21132,6 @@ static int resolve_pseudo_ldimm64(struct bpf_verifier_env *env)
2113221132
} else {
2113321133
u32 off = insn[1].imm;
2113421134

21135-
if (off >= BPF_MAX_VAR_OFF) {
21136-
verbose(env, "direct value offset of %u is not allowed\n", off);
21137-
return -EINVAL;
21138-
}
21139-
2114021135
if (!map->ops->map_direct_value_addr) {
2114121136
verbose(env, "no direct value access support for this map type\n");
2114221137
return -EINVAL;

tools/lib/bpf/libbpf.c

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ struct reloc_desc {
380380
const struct bpf_core_relo *core_relo; /* used when type == RELO_CORE */
381381
struct {
382382
int map_idx;
383-
int sym_off;
383+
unsigned int sym_off;
384384
/*
385385
* The following two fields can be unionized, as the
386386
* ext_idx field is used for extern symbols, and the
@@ -757,13 +757,14 @@ struct bpf_object {
757757
int arena_map_idx;
758758
void *arena_data;
759759
size_t arena_data_sz;
760+
size_t arena_data_off;
760761

761762
void *jumptables_data;
762763
size_t jumptables_data_sz;
763764

764765
struct {
765766
struct bpf_program *prog;
766-
int sym_off;
767+
unsigned int sym_off;
767768
int fd;
768769
} *jumptable_maps;
769770
size_t jumptable_map_cnt;
@@ -2991,10 +2992,11 @@ static int init_arena_map_data(struct bpf_object *obj, struct bpf_map *map,
29912992
void *data, size_t data_sz)
29922993
{
29932994
const long page_sz = sysconf(_SC_PAGE_SIZE);
2995+
const size_t data_alloc_sz = roundup(data_sz, page_sz);
29942996
size_t mmap_sz;
29952997

29962998
mmap_sz = bpf_map_mmap_sz(map);
2997-
if (roundup(data_sz, page_sz) > mmap_sz) {
2999+
if (data_alloc_sz > mmap_sz) {
29983000
pr_warn("elf: sec '%s': declared ARENA map size (%zu) is too small to hold global __arena variables of size %zu\n",
29993001
sec_name, mmap_sz, data_sz);
30003002
return -E2BIG;
@@ -3006,6 +3008,9 @@ static int init_arena_map_data(struct bpf_object *obj, struct bpf_map *map,
30063008
memcpy(obj->arena_data, data, data_sz);
30073009
obj->arena_data_sz = data_sz;
30083010

3011+
/* place globals at the end of the arena */
3012+
obj->arena_data_off = mmap_sz - data_alloc_sz;
3013+
30093014
/* make bpf_map__init_value() work for ARENA maps */
30103015
map->mmaped = obj->arena_data;
30113016

@@ -4663,7 +4668,7 @@ static int bpf_program__record_reloc(struct bpf_program *prog,
46634668
reloc_desc->type = RELO_DATA;
46644669
reloc_desc->insn_idx = insn_idx;
46654670
reloc_desc->map_idx = obj->arena_map_idx;
4666-
reloc_desc->sym_off = sym->st_value;
4671+
reloc_desc->sym_off = sym->st_value + obj->arena_data_off;
46674672

46684673
map = &obj->maps[obj->arena_map_idx];
46694674
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)
56245629
return err;
56255630
}
56265631
if (obj->arena_data) {
5627-
memcpy(map->mmaped, obj->arena_data, obj->arena_data_sz);
5632+
memcpy(map->mmaped + obj->arena_data_off, obj->arena_data,
5633+
obj->arena_data_sz);
56285634
zfree(&obj->arena_data);
56295635
}
56305636
}
@@ -6192,7 +6198,7 @@ static void poison_kfunc_call(struct bpf_program *prog, int relo_idx,
61926198
insn->imm = POISON_CALL_KFUNC_BASE + ext_idx;
61936199
}
61946200

6195-
static int find_jt_map(struct bpf_object *obj, struct bpf_program *prog, int sym_off)
6201+
static int find_jt_map(struct bpf_object *obj, struct bpf_program *prog, unsigned int sym_off)
61966202
{
61976203
size_t i;
61986204

@@ -6210,7 +6216,7 @@ static int find_jt_map(struct bpf_object *obj, struct bpf_program *prog, int sym
62106216
return -ENOENT;
62116217
}
62126218

6213-
static int add_jt_map(struct bpf_object *obj, struct bpf_program *prog, int sym_off, int map_fd)
6219+
static int add_jt_map(struct bpf_object *obj, struct bpf_program *prog, unsigned int sym_off, int map_fd)
62146220
{
62156221
size_t cnt = obj->jumptable_map_cnt;
62166222
size_t size = sizeof(obj->jumptable_maps[0]);
@@ -6244,7 +6250,7 @@ static int find_subprog_idx(struct bpf_program *prog, int insn_idx)
62446250
static int create_jt_map(struct bpf_object *obj, struct bpf_program *prog, struct reloc_desc *relo)
62456251
{
62466252
const __u32 jt_entry_size = 8;
6247-
int sym_off = relo->sym_off;
6253+
unsigned int sym_off = relo->sym_off;
62486254
int jt_size = relo->sym_size;
62496255
__u32 max_entries = jt_size / jt_entry_size;
62506256
__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
62606266
return map_fd;
62616267

62626268
if (sym_off % jt_entry_size) {
6263-
pr_warn("map '.jumptables': jumptable start %d should be multiple of %u\n",
6269+
pr_warn("map '.jumptables': jumptable start %u should be multiple of %u\n",
62646270
sym_off, jt_entry_size);
62656271
return -EINVAL;
62666272
}
@@ -6316,7 +6322,7 @@ static int create_jt_map(struct bpf_object *obj, struct bpf_program *prog, struc
63166322
* should contain values that fit in u32.
63176323
*/
63186324
if (insn_off > UINT32_MAX) {
6319-
pr_warn("map '.jumptables': invalid jump table value 0x%llx at offset %d\n",
6325+
pr_warn("map '.jumptables': invalid jump table value 0x%llx at offset %u\n",
63206326
(long long)jt[i], sym_off + i * jt_entry_size);
63216327
err = -EINVAL;
63226328
goto err_close;
@@ -14429,7 +14435,10 @@ int bpf_object__load_skeleton(struct bpf_object_skeleton *s)
1442914435
if (!map_skel->mmaped)
1443014436
continue;
1443114437

14432-
*map_skel->mmaped = map->mmaped;
14438+
if (map->def.type == BPF_MAP_TYPE_ARENA)
14439+
*map_skel->mmaped = map->mmaped + map->obj->arena_data_off;
14440+
else
14441+
*map_skel->mmaped = map->mmaped;
1443314442
}
1443414443

1443514444
return 0;

tools/testing/selftests/bpf/prog_tests/verifier.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
#include "verifier_and.skel.h"
77
#include "verifier_arena.skel.h"
88
#include "verifier_arena_large.skel.h"
9+
#include "verifier_arena_globals1.skel.h"
10+
#include "verifier_arena_globals2.skel.h"
911
#include "verifier_array_access.skel.h"
1012
#include "verifier_async_cb_context.skel.h"
1113
#include "verifier_basic_stack.skel.h"
@@ -147,6 +149,8 @@ static void run_tests_aux(const char *skel_name,
147149
void test_verifier_and(void) { RUN(verifier_and); }
148150
void test_verifier_arena(void) { RUN(verifier_arena); }
149151
void test_verifier_arena_large(void) { RUN(verifier_arena_large); }
152+
void test_verifier_arena_globals1(void) { RUN(verifier_arena_globals1); }
153+
void test_verifier_arena_globals2(void) { RUN(verifier_arena_globals2); }
150154
void test_verifier_basic_stack(void) { RUN(verifier_basic_stack); }
151155
void test_verifier_bitfield_write(void) { RUN(verifier_bitfield_write); }
152156
void test_verifier_bounds(void) { RUN(verifier_bounds); }
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
3+
4+
#define BPF_NO_KFUNC_PROTOTYPES
5+
#include <vmlinux.h>
6+
#include <bpf/bpf_helpers.h>
7+
#include <bpf/bpf_tracing.h>
8+
#include "bpf_experimental.h"
9+
#include "bpf_arena_common.h"
10+
#include "bpf_misc.h"
11+
12+
#define ARENA_PAGES (1UL<< (32 - 12))
13+
#define GLOBAL_PAGES (16)
14+
15+
struct {
16+
__uint(type, BPF_MAP_TYPE_ARENA);
17+
__uint(map_flags, BPF_F_MMAPABLE);
18+
__uint(max_entries, ARENA_PAGES);
19+
#ifdef __TARGET_ARCH_arm64
20+
__ulong(map_extra, (1ull << 32) | (~0u - __PAGE_SIZE * ARENA_PAGES + 1));
21+
#else
22+
__ulong(map_extra, (1ull << 44) | (~0u - __PAGE_SIZE * ARENA_PAGES + 1));
23+
#endif
24+
} arena SEC(".maps");
25+
26+
/*
27+
* Global data, to be placed at the end of the arena.
28+
*/
29+
volatile char __arena global_data[GLOBAL_PAGES][PAGE_SIZE];
30+
31+
SEC("syscall")
32+
__success __retval(0)
33+
int check_reserve1(void *ctx)
34+
{
35+
#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
36+
const u8 magic = 0x5a;
37+
__u8 __arena *guard, *globals;
38+
volatile char __arena *ptr;
39+
int i;
40+
int ret;
41+
42+
guard = (void __arena *)arena_base(&arena);
43+
globals = (void __arena *)(arena_base(&arena) + (ARENA_PAGES - GLOBAL_PAGES) * PAGE_SIZE);
44+
45+
/* Reserve the region we've offset the globals by. */
46+
ret = bpf_arena_reserve_pages(&arena, guard, ARENA_PAGES - GLOBAL_PAGES);
47+
if (ret)
48+
return 1;
49+
50+
/* Make sure the globals are in the expected offset. */
51+
ret = bpf_arena_reserve_pages(&arena, globals, 1);
52+
if (!ret)
53+
return 2;
54+
55+
/* Verify globals are properly mapped in by libbpf. */
56+
for (i = 0; i < GLOBAL_PAGES; i++) {
57+
ptr = &global_data[i][PAGE_SIZE / 2];
58+
59+
*ptr = magic;
60+
if (*ptr != magic)
61+
return i + 3;
62+
}
63+
#endif
64+
return 0;
65+
}
66+
67+
/*
68+
* Relocation check by reading directly into the global data w/o using symbols.
69+
*/
70+
SEC("syscall")
71+
__success __retval(0)
72+
int check_relocation(void *ctx)
73+
{
74+
#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
75+
const u8 magic = 0xfa;
76+
u8 __arena *ptr;
77+
78+
global_data[GLOBAL_PAGES - 1][PAGE_SIZE / 2] = magic;
79+
ptr = (u8 __arena *)((u64)(ARENA_PAGES * PAGE_SIZE - PAGE_SIZE / 2));
80+
if (*ptr != magic)
81+
return 1;
82+
83+
#endif
84+
return 0;
85+
}
86+
87+
char _license[] SEC("license") = "GPL";
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
3+
4+
#define BPF_NO_KFUNC_PROTOTYPES
5+
#include <vmlinux.h>
6+
#include <bpf/bpf_helpers.h>
7+
#include <bpf/bpf_tracing.h>
8+
#include "bpf_misc.h"
9+
#include "bpf_experimental.h"
10+
#include "bpf_arena_common.h"
11+
12+
#define ARENA_PAGES (32)
13+
14+
struct {
15+
__uint(type, BPF_MAP_TYPE_ARENA);
16+
__uint(map_flags, BPF_F_MMAPABLE);
17+
__uint(max_entries, ARENA_PAGES);
18+
#ifdef __TARGET_ARCH_arm64
19+
__ulong(map_extra, (1ull << 32) | (~0u - __PAGE_SIZE * ARENA_PAGES + 1));
20+
#else
21+
__ulong(map_extra, (1ull << 44) | (~0u - __PAGE_SIZE * ARENA_PAGES + 1));
22+
#endif
23+
} arena SEC(".maps");
24+
25+
/*
26+
* Fill the entire arena with global data.
27+
* The offset into the arena should be 0.
28+
*/
29+
char __arena global_data[ARENA_PAGES][PAGE_SIZE];
30+
31+
SEC("syscall")
32+
__success __retval(0)
33+
int check_reserve2(void *ctx)
34+
{
35+
#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
36+
void __arena *guard;
37+
int ret;
38+
39+
guard = (void __arena *)arena_base(&arena);
40+
41+
/* Make sure the data at offset 0 case is properly handled. */
42+
ret = bpf_arena_reserve_pages(&arena, guard, 1);
43+
if (!ret)
44+
return 1;
45+
#endif
46+
return 0;
47+
}
48+
49+
char _license[] SEC("license") = "GPL";

tools/testing/selftests/bpf/progs/verifier_arena_large.c

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,31 @@ int big_alloc1(void *ctx)
2323
{
2424
#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
2525
volatile char __arena *page1, *page2, *no_page, *page3;
26-
void __arena *base;
26+
u64 base;
2727

28-
page1 = base = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
28+
base = (u64)arena_base(&arena);
29+
30+
page1 = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
2931
if (!page1)
3032
return 1;
33+
34+
if ((u64)page1 != base)
35+
return 15;
36+
3137
*page1 = 1;
32-
page2 = bpf_arena_alloc_pages(&arena, base + ARENA_SIZE - PAGE_SIZE * 2,
38+
page2 = bpf_arena_alloc_pages(&arena, (void __arena *)(ARENA_SIZE - 2 * PAGE_SIZE),
3339
1, NUMA_NO_NODE, 0);
3440
if (!page2)
3541
return 2;
3642
*page2 = 2;
37-
no_page = bpf_arena_alloc_pages(&arena, base + ARENA_SIZE - PAGE_SIZE,
43+
44+
/* Test for the guard region at the end of the arena. */
45+
no_page = bpf_arena_alloc_pages(&arena, (void __arena *)ARENA_SIZE - PAGE_SIZE,
46+
1, NUMA_NO_NODE, 0);
47+
if (no_page)
48+
return 16;
49+
50+
no_page = bpf_arena_alloc_pages(&arena, (void __arena *)ARENA_SIZE,
3851
1, NUMA_NO_NODE, 0);
3952
if (no_page)
4053
return 3;

tools/testing/selftests/bpf/verifier/direct_value_access.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
},
8282
.fixup_map_array_48b = { 1 },
8383
.result = REJECT,
84-
.errstr = "direct value offset of 4294967295 is not allowed",
84+
.errstr = "invalid access to map value pointer, value_size=48 off=4294967295",
8585
},
8686
{
8787
"direct map access, write test 8",
@@ -141,7 +141,7 @@
141141
},
142142
.fixup_map_array_48b = { 1 },
143143
.result = REJECT,
144-
.errstr = "direct value offset of 536870912 is not allowed",
144+
.errstr = "invalid access to map value pointer, value_size=48 off=536870912",
145145
},
146146
{
147147
"direct map access, write test 13",

0 commit comments

Comments
 (0)