Skip to content

Commit 10f4420

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 10f4420

File tree

3 files changed

+333
-0
lines changed

3 files changed

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