Skip to content

Commit 6a77e77

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 * validate sleepable progs can page in. Signed-off-by: Mykyta Yatsenko <[email protected]>
1 parent 15ecce6 commit 6a77e77

File tree

3 files changed

+348
-0
lines changed

3 files changed

+348
-0
lines changed
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
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+
#include <dlfcn.h>
9+
#include <sys/mman.h>
10+
11+
const char *user_ptr = "hello world";
12+
char file_contents[256000];
13+
14+
void *get_executable_base_addr(void)
15+
{
16+
Dl_info info;
17+
18+
if (!dladdr((void *)&get_executable_base_addr, &info)) {
19+
fprintf(stderr, "dladdr failed\n");
20+
return NULL;
21+
}
22+
23+
return info.dli_fbase;
24+
}
25+
26+
static int initialize_file_contents(void)
27+
{
28+
int fd, page_sz = sysconf(_SC_PAGESIZE);
29+
ssize_t n = 0, cur, off;
30+
void *addr;
31+
32+
fd = open("/proc/self/exe", O_RDONLY);
33+
if (!ASSERT_OK_FD(fd, "Open /proc/self/exe\n"))
34+
return 1;
35+
36+
do {
37+
cur = read(fd, file_contents + n, sizeof(file_contents) - n);
38+
if (!ASSERT_GT(cur, 0, "read success"))
39+
break;
40+
n += cur;
41+
} while (n < sizeof(file_contents));
42+
43+
close(fd);
44+
45+
if (!ASSERT_EQ(n, sizeof(file_contents), "Read /proc/self/exe\n"))
46+
return 1;
47+
48+
addr = get_executable_base_addr();
49+
if (!ASSERT_NEQ(addr, NULL, "get executable address"))
50+
return 1;
51+
52+
/* page-align base file address */
53+
addr = (void *)((unsigned long)addr & ~(page_sz - 1));
54+
55+
for (off = 0; off < sizeof(file_contents); off += page_sz) {
56+
if (!ASSERT_OK(madvise(addr + off, page_sz, MADV_PAGEOUT),
57+
"madvise pageout"))
58+
return errno;
59+
}
60+
61+
return 0;
62+
}
63+
64+
static void run_test(const char *prog_name)
65+
{
66+
struct file_reader *skel;
67+
struct bpf_program *prog;
68+
int err, fd;
69+
char data[256];
70+
LIBBPF_OPTS(bpf_test_run_opts, opts, .data_in = &data, .repeat = 1,
71+
.data_size_in = sizeof(data));
72+
73+
err = initialize_file_contents();
74+
if (!ASSERT_OK(err, "initialize file contents"))
75+
return;
76+
77+
skel = file_reader__open();
78+
if (!ASSERT_OK_PTR(skel, "file_reader__open"))
79+
return;
80+
81+
bpf_object__for_each_program(prog, skel->obj) {
82+
bpf_program__set_autoload(prog, strcmp(bpf_program__name(prog), prog_name) == 0);
83+
}
84+
85+
skel->bss->user_buf = file_contents;
86+
skel->rodata->user_buf_sz = sizeof(file_contents);
87+
skel->bss->pid = getpid();
88+
skel->bss->user_ptr = (char *)user_ptr;
89+
90+
err = file_reader__load(skel);
91+
if (!ASSERT_OK(err, "file_reader__load"))
92+
goto cleanup;
93+
94+
err = file_reader__attach(skel);
95+
if (!ASSERT_OK(err, "file_reader__attach"))
96+
goto cleanup;
97+
98+
fd = open("/proc/self/exe", O_RDONLY);
99+
if (fd >= 0)
100+
close(fd);
101+
102+
ASSERT_EQ(skel->bss->err, 0, "err");
103+
ASSERT_EQ(skel->bss->run_success, 1, "run_success");
104+
cleanup:
105+
file_reader__destroy(skel);
106+
}
107+
108+
void test_file_reader(void)
109+
{
110+
if (test__start_subtest("on_open_expect_fault"))
111+
run_test("on_open_expect_fault");
112+
113+
if (test__start_subtest("on_open_validate_file_read"))
114+
run_test("on_open_validate_file_read");
115+
116+
if (test__start_subtest("negative"))
117+
RUN_TESTS(file_reader_fail);
118+
}
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
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+
#include "errno.h"
10+
11+
char _license[] SEC("license") = "GPL";
12+
13+
struct {
14+
__uint(type, BPF_MAP_TYPE_ARRAY);
15+
__uint(max_entries, 1);
16+
__type(key, int);
17+
__type(value, struct elem);
18+
} arrmap SEC(".maps");
19+
20+
struct {
21+
__uint(type, BPF_MAP_TYPE_RINGBUF);
22+
__uint(max_entries, 10000000);
23+
} ringbuf SEC(".maps");
24+
25+
struct elem {
26+
struct file *file;
27+
struct bpf_task_work tw;
28+
};
29+
30+
int pid = 0;
31+
int err, run_success = 0;
32+
char *user_buf;
33+
const char *user_ptr;
34+
volatile const __u32 user_buf_sz;
35+
36+
static int validate_file_read(struct file *file);
37+
static int task_work_callback(struct bpf_map *map, void *key, void *value);
38+
39+
SEC("lsm/file_open")
40+
int on_open_expect_fault(void *c)
41+
{
42+
struct bpf_dynptr dynptr;
43+
struct file *file;
44+
char *rbuf = NULL;
45+
int local_err = 1;
46+
47+
if (bpf_get_current_pid_tgid() >> 32 != pid)
48+
return 0;
49+
50+
file = bpf_get_task_exe_file(bpf_get_current_task_btf());
51+
if (!file)
52+
return 0;
53+
54+
if (bpf_dynptr_from_file(file, 0, &dynptr))
55+
goto out;
56+
57+
rbuf = bpf_ringbuf_reserve(&ringbuf, user_buf_sz, 0);
58+
if (!rbuf)
59+
goto out;
60+
61+
local_err = bpf_dynptr_read(rbuf, user_buf_sz, &dynptr, 0, 0);
62+
if (local_err == -EFAULT) { /* Expect page fault */
63+
local_err = 0;
64+
run_success = 1;
65+
}
66+
out:
67+
if (rbuf)
68+
bpf_ringbuf_discard(rbuf, 0);
69+
bpf_dynptr_file_discard(&dynptr);
70+
if (local_err)
71+
err = local_err;
72+
bpf_put_file(file);
73+
return 0;
74+
}
75+
76+
SEC("lsm/file_open")
77+
int on_open_validate_file_read(void *c)
78+
{
79+
struct task_struct *task = bpf_get_current_task_btf();
80+
struct elem *work;
81+
int key = 0;
82+
83+
if (bpf_get_current_pid_tgid() >> 32 != pid)
84+
return 0;
85+
86+
work = bpf_map_lookup_elem(&arrmap, &key);
87+
if (!work) {
88+
err = 1;
89+
return 0;
90+
}
91+
bpf_task_work_schedule_signal(task, &work->tw, &arrmap, task_work_callback, NULL);
92+
return 0;
93+
}
94+
95+
/* Called in a sleepable context, read 256K bytes, cross check with user space read data */
96+
static int task_work_callback(struct bpf_map *map, void *key, void *value)
97+
{
98+
struct task_struct *task = bpf_get_current_task_btf();
99+
struct file *file = bpf_get_task_exe_file(task);
100+
101+
if (!file)
102+
return 0;
103+
104+
validate_file_read(file);
105+
bpf_put_file(file);
106+
return 0;
107+
}
108+
109+
static int verify_dynptr_read(struct bpf_dynptr *ptr, u32 off, char *user_buf, u32 len)
110+
{
111+
char *rbuf = NULL;
112+
int err = 1, i;
113+
114+
rbuf = bpf_ringbuf_reserve(&ringbuf, len, 0);
115+
if (!rbuf)
116+
goto cleanup;
117+
118+
if (bpf_dynptr_read(rbuf, len, ptr, off, 0))
119+
goto cleanup;
120+
121+
/* Verify file contents read from BPF is the same as the one read from userspace */
122+
bpf_for(i, 0, len)
123+
{
124+
if (rbuf[i] != user_buf[i])
125+
goto cleanup;
126+
}
127+
err = 0;
128+
129+
cleanup:
130+
if (rbuf)
131+
bpf_ringbuf_discard(rbuf, 0);
132+
return err;
133+
}
134+
135+
static int validate_file_read(struct file *file)
136+
{
137+
struct bpf_dynptr dynptr;
138+
int local_err = 1, off;
139+
char *ubuf = NULL;
140+
141+
if (bpf_dynptr_from_file(file, 0, &dynptr))
142+
goto cleanup_file;
143+
144+
ubuf = bpf_ringbuf_reserve(&ringbuf, user_buf_sz, 0);
145+
if (!ubuf)
146+
goto cleanup_all;
147+
148+
local_err = bpf_copy_from_user(ubuf, user_buf_sz, user_buf);
149+
if (local_err)
150+
goto cleanup_all;
151+
152+
local_err = verify_dynptr_read(&dynptr, 0, ubuf, user_buf_sz);
153+
off = 1;
154+
local_err = local_err ?: verify_dynptr_read(&dynptr, off, ubuf + off, user_buf_sz - off);
155+
off = user_buf_sz - 1;
156+
local_err = local_err ?: verify_dynptr_read(&dynptr, off, ubuf + off, user_buf_sz - off);
157+
/* Read file with random offset and length */
158+
off = 4097;
159+
local_err = local_err ?: verify_dynptr_read(&dynptr, off, ubuf + off, 100);
160+
161+
/* Adjust dynptr, verify read */
162+
local_err = local_err ?: bpf_dynptr_adjust(&dynptr, off, off + 1);
163+
local_err = local_err ?: verify_dynptr_read(&dynptr, 0, ubuf + off, 1);
164+
/* Can't read more than 1 byte */
165+
local_err = local_err ?: verify_dynptr_read(&dynptr, 0, ubuf + off, 2) == 0;
166+
/* Can't read with far offset */
167+
local_err = local_err ?: verify_dynptr_read(&dynptr, 1, ubuf + off, 1) == 0;
168+
cleanup_all:
169+
if (ubuf)
170+
bpf_ringbuf_discard(ubuf, 0);
171+
cleanup_file:
172+
bpf_dynptr_file_discard(&dynptr);
173+
if (local_err)
174+
err = local_err;
175+
else
176+
run_success = 1;
177+
return 0;
178+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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+
SEC("lsm/file_open")
16+
__failure
17+
__msg("Unreleased reference id=")
18+
int on_nanosleep_unreleased_ref(void *ctx)
19+
{
20+
struct task_struct *task = bpf_get_current_task_btf();
21+
struct file *file = bpf_get_task_exe_file(task);
22+
struct bpf_dynptr dynptr;
23+
24+
if (!file)
25+
return 0;
26+
27+
err = bpf_dynptr_from_file(file, 0, &dynptr);
28+
return err ? 1 : 0;
29+
}
30+
31+
SEC("xdp")
32+
__failure
33+
__msg("Expected a dynptr of type file as arg #0")
34+
int xdp_wrong_dynptr_type(struct xdp_md *xdp)
35+
{
36+
struct bpf_dynptr dynptr;
37+
38+
bpf_dynptr_from_xdp(xdp, 0, &dynptr);
39+
bpf_dynptr_file_discard(&dynptr);
40+
return 0;
41+
}
42+
43+
SEC("xdp")
44+
__failure
45+
__msg("Expected an initialized dynptr as arg #0")
46+
int xdp_no_dynptr_type(struct xdp_md *xdp)
47+
{
48+
struct bpf_dynptr dynptr;
49+
50+
bpf_dynptr_file_discard(&dynptr);
51+
return 0;
52+
}

0 commit comments

Comments
 (0)