Skip to content

Commit 1f7ed54

Browse files
etsalKernel Patches Daemon
authored andcommitted
libbpf: offset global arena data into the arena if possible
Currently, libbpf places global arena data at the very beginning of the arena mapping. Stray NULL dereferences into the arena then find valid data and lead to silent corruption instead of causing an arena page fault. The data is placed in the mapping at load time, preventing us from reserving the region using bpf_arena_reserve_pages(). Adjust the arena logic to attempt placing the data from an offset within the arena (currently 16 pages in) instead of the very beginning. If placing the data at an offset would lead to an allocation failure due to global data being as large as the entire arena, progressively reduce the offset down to 0 until placement succeeds. Adjust existing arena tests in the same commit to account for the new global data offset. New tests that explicitly consider the new feature are introduced in the next patch. Signed-off-by: Emil Tsalapatis <[email protected]>
1 parent fb183e2 commit 1f7ed54

File tree

2 files changed

+37
-7
lines changed

2 files changed

+37
-7
lines changed

tools/lib/bpf/libbpf.c

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,7 @@ struct bpf_object {
757757
int arena_map_idx;
758758
void *arena_data;
759759
size_t arena_data_sz;
760+
__u32 arena_data_off;
760761

761762
void *jumptables_data;
762763
size_t jumptables_data_sz;
@@ -2991,10 +2992,14 @@ 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);
2996+
/* default offset into the arena, may be resized */
2997+
const long max_off_pages = 16;
29942998
size_t mmap_sz;
2999+
long off_pages;
29953000

29963001
mmap_sz = bpf_map_mmap_sz(map);
2997-
if (roundup(data_sz, page_sz) > mmap_sz) {
3002+
if (data_alloc_sz > mmap_sz) {
29983003
pr_warn("elf: sec '%s': declared ARENA map size (%zu) is too small to hold global __arena variables of size %zu\n",
29993004
sec_name, mmap_sz, data_sz);
30003005
return -E2BIG;
@@ -3006,6 +3011,17 @@ static int init_arena_map_data(struct bpf_object *obj, struct bpf_map *map,
30063011
memcpy(obj->arena_data, data, data_sz);
30073012
obj->arena_data_sz = data_sz;
30083013

3014+
/*
3015+
* find the largest offset for global arena variables
3016+
* where they still fit in the arena
3017+
*/
3018+
for (off_pages = max_off_pages; off_pages > 0; off_pages >>= 1) {
3019+
if (off_pages * page_sz + data_alloc_sz <= mmap_sz)
3020+
break;
3021+
}
3022+
3023+
obj->arena_data_off = off_pages * page_sz;
3024+
30093025
/* make bpf_map__init_value() work for ARENA maps */
30103026
map->mmaped = obj->arena_data;
30113027

@@ -4663,7 +4679,7 @@ static int bpf_program__record_reloc(struct bpf_program *prog,
46634679
reloc_desc->type = RELO_DATA;
46644680
reloc_desc->insn_idx = insn_idx;
46654681
reloc_desc->map_idx = obj->arena_map_idx;
4666-
reloc_desc->sym_off = sym->st_value;
4682+
reloc_desc->sym_off = sym->st_value + obj->arena_data_off;
46674683

46684684
map = &obj->maps[obj->arena_map_idx];
46694685
pr_debug("prog '%s': found arena map %d (%s, sec %d, off %zu) for insn %u\n",
@@ -5624,7 +5640,8 @@ bpf_object__create_maps(struct bpf_object *obj)
56245640
return err;
56255641
}
56265642
if (obj->arena_data) {
5627-
memcpy(map->mmaped, obj->arena_data, obj->arena_data_sz);
5643+
memcpy(map->mmaped + obj->arena_data_off, obj->arena_data,
5644+
obj->arena_data_sz);
56285645
zfree(&obj->arena_data);
56295646
}
56305647
}
@@ -10557,8 +10574,11 @@ int bpf_map__data_offset(const struct bpf_map *map)
1055710574
if (!map->mmaped)
1055810575
return -EINVAL;
1055910576

10560-
/* No offsetting for now. */
10561-
return 0;
10577+
/* Only arenas have offsetting. */
10578+
if (map->def.type != BPF_MAP_TYPE_ARENA)
10579+
return 0;
10580+
10581+
return map->obj->arena_data_off;
1056210582
}
1056310583

1056410584

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "bpf_arena_common.h"
1111

1212
#define ARENA_SIZE (1ull << 32)
13+
#define GLOBAL_PGOFF (16)
1314

1415
struct {
1516
__uint(type, BPF_MAP_TYPE_ARENA);
@@ -31,8 +32,7 @@ int big_alloc1(void *ctx)
3132
if (!page1)
3233
return 1;
3334

34-
/* Account for global arena data. */
35-
if ((u64)page1 != base + PAGE_SIZE)
35+
if ((u64)page1 != base)
3636
return 15;
3737

3838
*page1 = 1;
@@ -216,6 +216,16 @@ int big_alloc2(void *ctx)
216216
__u8 __arena *pg;
217217
int i, err;
218218

219+
/*
220+
* The global data is placed in a page with global offset 16.
221+
* This test is about page allocation contiguity, so avoid
222+
* accounting for the stray allocation by also allocating
223+
* all pages before it. We never use the page range, so leak it.
224+
*/
225+
pg = bpf_arena_alloc_pages(&arena, NULL, GLOBAL_PGOFF, NUMA_NO_NODE, 0);
226+
if (!pg)
227+
return 10;
228+
219229
base = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
220230
if (!base)
221231
return 1;

0 commit comments

Comments
 (0)