Skip to content

Commit eb03455

Browse files
committed
selftests/bpf: add file dynptr tests
Introducing selftests for validating file-backed dynptr works as expected. * validate implementation supports dynptr slice and read operations * validate destructors should be paired with initializers Signed-off-by: Mykyta Yatsenko <[email protected]>
1 parent 496c231 commit eb03455

File tree

3 files changed

+247
-0
lines changed

3 files changed

+247
-0
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
3+
4+
#include <test_progs.h>
5+
#include <network_helpers.h>
6+
#include "file_reader.skel.h"
7+
#include "file_reader_fail.skel.h"
8+
9+
__thread int tls_counter;
10+
const char *user_ptr = "hello world";
11+
12+
static void test_file_reader_elf(void)
13+
{
14+
struct file_reader *skel;
15+
int err;
16+
char data[256];
17+
LIBBPF_OPTS(bpf_test_run_opts, opts, .data_in = &data, .repeat = 1,
18+
.data_size_in = sizeof(data));
19+
20+
skel = file_reader__open_and_load();
21+
if (!ASSERT_OK_PTR(skel, "file_reader__open_and_load"))
22+
return;
23+
24+
skel->bss->user_ptr = (void *)user_ptr;
25+
26+
err = file_reader__attach(skel);
27+
if (!ASSERT_OK(err, "file_reader__attach"))
28+
goto cleanup;
29+
30+
getpid();
31+
32+
ASSERT_EQ(skel->bss->err, 0, "err");
33+
cleanup:
34+
file_reader__destroy(skel);
35+
}
36+
37+
void test_file_reader(void)
38+
{
39+
if (test__start_subtest("test_file_reader"))
40+
test_file_reader_elf();
41+
42+
RUN_TESTS(file_reader_fail);
43+
}
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
3+
4+
#include <vmlinux.h>
5+
#include <string.h>
6+
#include <stdbool.h>
7+
#include <bpf/bpf_tracing.h>
8+
#include "bpf_misc.h"
9+
10+
#define ELFMAG "\177ELF"
11+
#define SELFMAG 4
12+
#define ET_NONE 0
13+
#define ET_REL 1
14+
#define ET_EXEC 2
15+
#define ET_DYN 3
16+
#define ET_CORE 4
17+
#define ET_LOPROC 0xff00
18+
#define ET_HIPROC 0xffff
19+
#define EI_CLASS 4
20+
#define ELFCLASS32 1
21+
#define ELFCLASS64 2
22+
#define STT_TLS 6
23+
24+
#define ELF_ST_BIND(x) ((x) >> 4)
25+
#define ELF_ST_TYPE(x) ((x) & 0xf)
26+
#define ELF32_ST_BIND(x) ELF_ST_BIND(x)
27+
#define ELF32_ST_TYPE(x) ELF_ST_TYPE(x)
28+
#define ELF64_ST_BIND(x) ELF_ST_BIND(x)
29+
#define ELF64_ST_TYPE(x) ELF_ST_TYPE(x)
30+
31+
char _license[] SEC("license") = "GPL";
32+
33+
struct {
34+
__uint(type, BPF_MAP_TYPE_ARRAY);
35+
__uint(max_entries, 1);
36+
__type(key, int);
37+
__type(value, struct elem);
38+
} arrmap SEC(".maps");
39+
40+
struct elem {
41+
struct file *file;
42+
struct bpf_task_work tw;
43+
};
44+
45+
int err;
46+
void *user_ptr;
47+
char buf[1024];
48+
49+
static int process_elf(struct task_struct *task, struct vm_area_struct *vma, void *data);
50+
static int task_work_callback(struct bpf_map *map, void *key, void *value);
51+
52+
SEC("raw_tp/sys_enter")
53+
int on_getpid(void *ctx)
54+
{
55+
struct task_struct *task = bpf_get_current_task_btf();
56+
struct elem *work;
57+
int key = 0;
58+
59+
work = bpf_map_lookup_elem(&arrmap, &key);
60+
if (!work) {
61+
err = 1;
62+
return 0;
63+
}
64+
bpf_task_work_schedule_signal(task, &work->tw, &arrmap, task_work_callback, NULL);
65+
return 0;
66+
}
67+
68+
static int task_work_callback(struct bpf_map *map, void *key, void *value)
69+
{
70+
struct task_struct *task = bpf_get_current_task_btf();
71+
72+
bpf_find_vma(task, (unsigned long)user_ptr, process_elf, NULL, 0);
73+
return 0;
74+
}
75+
76+
/* Finds thread local variable `tls_counter` in this executable's ELF */
77+
static int process_elf(struct task_struct *task, struct vm_area_struct *vma, void *data)
78+
{
79+
Elf64_Ehdr ehdr;
80+
Elf64_Shdr shdrs;
81+
Elf64_Shdr symtab, strtab, tmp;
82+
const Elf64_Sym *symbol;
83+
int count, off, i, e_shnum, e_shoff, e_shentsize, sections = 0;
84+
const char *string;
85+
struct bpf_dynptr dynptr;
86+
const __u32 slen = 11;
87+
static const char *needle = "tls_counter";
88+
89+
if (!vma->vm_file) {
90+
err = 1;
91+
return 1;
92+
}
93+
94+
err = bpf_dynptr_from_file(vma->vm_file, 0, &dynptr);
95+
if (err)
96+
goto fail;
97+
98+
err = bpf_dynptr_read(&ehdr, sizeof(ehdr), &dynptr, 0, 0);
99+
if (err)
100+
goto fail;
101+
102+
if (memcmp(ehdr.e_ident, ELFMAG, SELFMAG) != 0)
103+
goto fail;
104+
105+
if (ehdr.e_type != ET_EXEC && ehdr.e_type != ET_DYN)
106+
goto fail;
107+
108+
if (ehdr.e_ident[EI_CLASS] != ELFCLASS64)
109+
goto fail;
110+
111+
e_shnum = ehdr.e_shnum;
112+
e_shoff = ehdr.e_shoff;
113+
e_shentsize = ehdr.e_shentsize;
114+
115+
err = bpf_dynptr_read(&shdrs, sizeof(shdrs), &dynptr,
116+
e_shoff + e_shentsize * ehdr.e_shstrndx, 0);
117+
if (err)
118+
goto fail;
119+
120+
off = shdrs.sh_offset;
121+
122+
__builtin_memset(&symtab, 0, sizeof(symtab));
123+
__builtin_memset(&strtab, 0, sizeof(strtab));
124+
bpf_for(i, 0, e_shnum)
125+
{
126+
err = bpf_dynptr_read(&tmp, sizeof(Elf64_Shdr), &dynptr, e_shoff + e_shentsize * i,
127+
0);
128+
if (err)
129+
goto fail;
130+
131+
string = bpf_dynptr_slice(&dynptr, off + tmp.sh_name, buf, slen);
132+
if (!string)
133+
goto fail;
134+
135+
if (bpf_strncmp(string, slen, ".symtab") == 0) {
136+
symtab = tmp;
137+
++sections;
138+
} else if (bpf_strncmp(string, slen, ".strtab") == 0) {
139+
strtab = tmp;
140+
++sections;
141+
}
142+
if (sections == 2)
143+
break;
144+
}
145+
if (sections != 2)
146+
goto fail;
147+
148+
count = symtab.sh_size / sizeof(Elf64_Sym);
149+
bpf_for(i, 0, count)
150+
{
151+
symbol = bpf_dynptr_slice(&dynptr, symtab.sh_offset + sizeof(Elf64_Sym) * i, buf,
152+
sizeof(Elf64_Sym));
153+
if (!symbol)
154+
goto fail;
155+
if (symbol->st_name == 0 || ELF64_ST_TYPE(symbol->st_info) != STT_TLS)
156+
continue;
157+
string = bpf_dynptr_slice(&dynptr, strtab.sh_offset + symbol->st_name, buf, slen);
158+
if (!string)
159+
goto fail;
160+
if (bpf_strncmp(string, slen, needle) == 0)
161+
goto success;
162+
}
163+
fail:
164+
err = 1;
165+
success:
166+
bpf_dynptr_file_discard(&dynptr);
167+
return err ? 1 : 0;
168+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
3+
4+
#include <vmlinux.h>
5+
#include <string.h>
6+
#include <stdbool.h>
7+
#include <bpf/bpf_tracing.h>
8+
#include "bpf_misc.h"
9+
10+
char _license[] SEC("license") = "GPL";
11+
12+
int err;
13+
void *user_ptr;
14+
15+
char buf[256];
16+
17+
static long process_vma_unreleased_ref(struct task_struct *task, struct vm_area_struct *vma,
18+
void *data)
19+
{
20+
struct bpf_dynptr dynptr;
21+
22+
if (!vma->vm_file)
23+
return 1;
24+
25+
err = bpf_dynptr_from_file(vma->vm_file, 0, &dynptr);
26+
return err ? 1 : 0;
27+
}
28+
29+
SEC("fentry.s/" SYS_PREFIX "sys_nanosleep")
30+
__failure __msg("Unreleased reference id=") int on_nanosleep_unreleased_ref(void *ctx)
31+
{
32+
struct task_struct *task = bpf_get_current_task_btf();
33+
34+
bpf_find_vma(task, (unsigned long)user_ptr, process_vma_unreleased_ref, NULL, 0);
35+
return 0;
36+
}

0 commit comments

Comments
 (0)