Skip to content

Commit 47833d5

Browse files
mykyta5Kernel Patches Daemon
authored andcommitted
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 4ac3c50 commit 47833d5

File tree

3 files changed

+379
-0
lines changed

3 files changed

+379
-0
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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+
char file_contents[256000];
12+
13+
enum file_reader_test {
14+
VALIDATE_LARGE_FILE = 1,
15+
SEARCH_ELF = 2,
16+
};
17+
18+
static int initialize_file_contents(void)
19+
{
20+
int fd;
21+
ssize_t n;
22+
int err = 0;
23+
24+
fd = open("/proc/self/exe", O_RDONLY);
25+
if (!ASSERT_GT(fd, 0, "Open /proc/self/exe\n"))
26+
return 1;
27+
28+
n = read(fd, file_contents, sizeof(file_contents));
29+
if (!ASSERT_EQ(n, sizeof(file_contents), "Read /proc/self/exe\n"))
30+
err = 1;
31+
32+
posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
33+
close(fd);
34+
return err;
35+
}
36+
37+
static void run_test(enum file_reader_test test_type)
38+
{
39+
struct file_reader *skel;
40+
int err;
41+
char data[256];
42+
LIBBPF_OPTS(bpf_test_run_opts, opts, .data_in = &data, .repeat = 1,
43+
.data_size_in = sizeof(data));
44+
45+
skel = file_reader__open();
46+
if (!ASSERT_OK_PTR(skel, "file_reader__open"))
47+
return;
48+
49+
skel->bss->user_ptr = (void *)user_ptr;
50+
skel->bss->user_buf = file_contents;
51+
skel->rodata->user_buf_sz = sizeof(file_contents);
52+
skel->rodata->test_type = test_type;
53+
54+
err = file_reader__load(skel);
55+
if (!ASSERT_OK(err, "file_reader__load"))
56+
return;
57+
58+
err = initialize_file_contents();
59+
if (!ASSERT_OK(err, "initialize file contents"))
60+
goto cleanup;
61+
62+
err = file_reader__attach(skel);
63+
if (!ASSERT_OK(err, "file_reader__attach"))
64+
goto cleanup;
65+
66+
getpid();
67+
68+
ASSERT_EQ(skel->bss->err, 0, "err");
69+
cleanup:
70+
file_reader__destroy(skel);
71+
}
72+
73+
void test_file_reader(void)
74+
{
75+
if (test__start_subtest("test_large_file"))
76+
run_test(VALIDATE_LARGE_FILE);
77+
if (test__start_subtest("test_search_elf"))
78+
run_test(SEARCH_ELF);
79+
80+
RUN_TESTS(file_reader_fail);
81+
}
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
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 {
41+
__uint(type, BPF_MAP_TYPE_RINGBUF);
42+
__uint(max_entries, 10000000);
43+
} ringbuf SEC(".maps");
44+
45+
struct elem {
46+
struct file *file;
47+
struct bpf_task_work tw;
48+
};
49+
50+
enum file_reader_test {
51+
VALIDATE_LARGE_FILE = 1,
52+
SEARCH_ELF = 2,
53+
};
54+
55+
int err;
56+
void *user_ptr;
57+
char buf[1024];
58+
char *user_buf;
59+
volatile const __u32 user_buf_sz;
60+
volatile const __s32 test_type = -1;
61+
62+
static int process_vma(struct task_struct *task, struct vm_area_struct *vma, void *data);
63+
static int search_elf(struct file *file);
64+
static int validate_large_file_read(struct file *file);
65+
static int task_work_callback(struct bpf_map *map, void *key, void *value);
66+
67+
SEC("raw_tp/sys_enter")
68+
int on_getpid(void *ctx)
69+
{
70+
struct task_struct *task = bpf_get_current_task_btf();
71+
struct elem *work;
72+
int key = 0;
73+
74+
work = bpf_map_lookup_elem(&arrmap, &key);
75+
if (!work) {
76+
err = 1;
77+
return 0;
78+
}
79+
bpf_task_work_schedule_signal(task, &work->tw, &arrmap, task_work_callback, NULL);
80+
return 0;
81+
}
82+
83+
static int task_work_callback(struct bpf_map *map, void *key, void *value)
84+
{
85+
struct task_struct *task = bpf_get_current_task_btf();
86+
87+
bpf_find_vma(task, (unsigned long)user_ptr, process_vma, NULL, 0);
88+
return 0;
89+
}
90+
91+
static int process_vma(struct task_struct *task, struct vm_area_struct *vma, void *data)
92+
{
93+
switch (test_type) {
94+
case VALIDATE_LARGE_FILE:
95+
err = validate_large_file_read(vma->vm_file);
96+
break;
97+
case SEARCH_ELF:
98+
err = search_elf(vma->vm_file);
99+
break;
100+
default:
101+
err = 1;
102+
}
103+
return err;
104+
}
105+
106+
static int validate_large_file_read(struct file *file)
107+
{
108+
struct bpf_dynptr dynptr;
109+
int err, i;
110+
char *rbuf1 = NULL, *rbuf2 = NULL;
111+
112+
if (!file) {
113+
err = 1;
114+
return 1;
115+
}
116+
117+
err = bpf_dynptr_from_file(file, 0, &dynptr);
118+
if (err)
119+
goto cleanup_file;
120+
121+
rbuf1 = bpf_ringbuf_reserve(&ringbuf, user_buf_sz, 0);
122+
if (!rbuf1)
123+
goto cleanup_file;
124+
125+
rbuf2 = bpf_ringbuf_reserve(&ringbuf, user_buf_sz, 0);
126+
if (!rbuf2)
127+
goto cleanup_all;
128+
129+
bpf_dynptr_read(rbuf1, user_buf_sz, &dynptr, 0, 0);
130+
bpf_copy_from_user(rbuf2, user_buf_sz, user_buf);
131+
/* Verify file contents read from BPF is the same as the one read from userspace */
132+
bpf_for(i, 0, user_buf_sz) {
133+
if (i >= 256000 || rbuf1[i] != rbuf2[i]) {
134+
err = 1;
135+
break;
136+
}
137+
}
138+
139+
cleanup_all:
140+
if (rbuf1)
141+
bpf_ringbuf_discard(rbuf1, 0);
142+
if (rbuf2)
143+
bpf_ringbuf_discard(rbuf2, 0);
144+
cleanup_file:
145+
bpf_dynptr_file_discard(&dynptr);
146+
return err ? 1 : 0;
147+
}
148+
149+
/* Finds thread local variable `tls_counter` in this executable's ELF */
150+
static int search_elf(struct file *file)
151+
{
152+
Elf64_Ehdr ehdr;
153+
Elf64_Shdr shdrs;
154+
Elf64_Shdr symtab, strtab, tmp;
155+
const Elf64_Sym *symbol;
156+
int count, off, i, e_shnum, e_shoff, e_shentsize, sections = 0;
157+
const char *string;
158+
struct bpf_dynptr dynptr;
159+
const __u32 slen = 11;
160+
static const char *needle = "tls_counter";
161+
162+
if (!file) {
163+
err = 1;
164+
return 1;
165+
}
166+
167+
err = bpf_dynptr_from_file(file, 0, &dynptr);
168+
if (err)
169+
goto fail;
170+
171+
err = bpf_dynptr_read(&ehdr, sizeof(ehdr), &dynptr, 0, 0);
172+
if (err)
173+
goto fail;
174+
175+
if (memcmp(ehdr.e_ident, ELFMAG, SELFMAG) != 0)
176+
goto fail;
177+
178+
if (ehdr.e_type != ET_EXEC && ehdr.e_type != ET_DYN)
179+
goto fail;
180+
181+
if (ehdr.e_ident[EI_CLASS] != ELFCLASS64)
182+
goto fail;
183+
184+
e_shnum = ehdr.e_shnum;
185+
e_shoff = ehdr.e_shoff;
186+
e_shentsize = ehdr.e_shentsize;
187+
188+
err = bpf_dynptr_read(&shdrs, sizeof(shdrs), &dynptr,
189+
e_shoff + e_shentsize * ehdr.e_shstrndx, 0);
190+
if (err)
191+
goto fail;
192+
193+
off = shdrs.sh_offset;
194+
195+
__builtin_memset(&symtab, 0, sizeof(symtab));
196+
__builtin_memset(&strtab, 0, sizeof(strtab));
197+
bpf_for(i, 0, e_shnum)
198+
{
199+
err = bpf_dynptr_read(&tmp, sizeof(Elf64_Shdr), &dynptr, e_shoff + e_shentsize * i,
200+
0);
201+
if (err)
202+
goto fail;
203+
204+
string = bpf_dynptr_slice(&dynptr, off + tmp.sh_name, buf, slen);
205+
if (!string)
206+
goto fail;
207+
208+
if (bpf_strncmp(string, slen, ".symtab") == 0) {
209+
symtab = tmp;
210+
++sections;
211+
} else if (bpf_strncmp(string, slen, ".strtab") == 0) {
212+
strtab = tmp;
213+
++sections;
214+
}
215+
if (sections == 2)
216+
break;
217+
}
218+
if (sections != 2)
219+
goto fail;
220+
221+
count = symtab.sh_size / sizeof(Elf64_Sym);
222+
bpf_for(i, 0, count)
223+
{
224+
symbol = bpf_dynptr_slice(&dynptr, symtab.sh_offset + sizeof(Elf64_Sym) * i, buf,
225+
sizeof(Elf64_Sym));
226+
if (!symbol)
227+
goto fail;
228+
if (symbol->st_name == 0 || ELF64_ST_TYPE(symbol->st_info) != STT_TLS)
229+
continue;
230+
string = bpf_dynptr_slice(&dynptr, strtab.sh_offset + symbol->st_name, buf, slen);
231+
if (!string)
232+
goto fail;
233+
if (bpf_strncmp(string, slen, needle) == 0)
234+
goto success;
235+
}
236+
fail:
237+
err = 1;
238+
success:
239+
bpf_dynptr_file_discard(&dynptr);
240+
return err ? 1 : 0;
241+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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+
}
37+
38+
SEC("xdp")
39+
__failure __msg("Expected a dynptr of type file as arg #0")
40+
int xdp_wrong_dynptr_type(struct xdp_md *xdp)
41+
{
42+
struct bpf_dynptr dynptr;
43+
44+
bpf_dynptr_from_xdp(xdp, 0, &dynptr);
45+
bpf_dynptr_file_discard(&dynptr);
46+
return 0;
47+
}
48+
49+
SEC("xdp")
50+
__failure __msg("Expected an initialized dynptr as arg #0")
51+
int xdp_no_dynptr_type(struct xdp_md *xdp)
52+
{
53+
struct bpf_dynptr dynptr;
54+
55+
bpf_dynptr_file_discard(&dynptr);
56+
return 0;
57+
}

0 commit comments

Comments
 (0)