Skip to content

Commit 784cdf9

Browse files
mykyta5Alexei Starovoitov
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 * validate sleepable progs can page in. Signed-off-by: Mykyta Yatsenko <[email protected]> Reviewed-by: Eduard Zingerman <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Alexei Starovoitov <[email protected]>
1 parent 2c52e89 commit 784cdf9

File tree

3 files changed

+310
-0
lines changed

3 files changed

+310
-0
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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+
70+
err = initialize_file_contents();
71+
if (!ASSERT_OK(err, "initialize file contents"))
72+
return;
73+
74+
skel = file_reader__open();
75+
if (!ASSERT_OK_PTR(skel, "file_reader__open"))
76+
return;
77+
78+
bpf_object__for_each_program(prog, skel->obj) {
79+
bpf_program__set_autoload(prog, strcmp(bpf_program__name(prog), prog_name) == 0);
80+
}
81+
82+
memcpy(skel->bss->user_buf, file_contents, sizeof(file_contents));
83+
skel->bss->pid = getpid();
84+
85+
err = file_reader__load(skel);
86+
if (!ASSERT_OK(err, "file_reader__load"))
87+
goto cleanup;
88+
89+
err = file_reader__attach(skel);
90+
if (!ASSERT_OK(err, "file_reader__attach"))
91+
goto cleanup;
92+
93+
fd = open("/proc/self/exe", O_RDONLY);
94+
if (fd >= 0)
95+
close(fd);
96+
97+
ASSERT_EQ(skel->bss->err, 0, "err");
98+
ASSERT_EQ(skel->bss->run_success, 1, "run_success");
99+
cleanup:
100+
file_reader__destroy(skel);
101+
}
102+
103+
void test_file_reader(void)
104+
{
105+
if (test__start_subtest("on_open_expect_fault"))
106+
run_test("on_open_expect_fault");
107+
108+
if (test__start_subtest("on_open_validate_file_read"))
109+
run_test("on_open_validate_file_read");
110+
111+
if (test__start_subtest("negative"))
112+
RUN_TESTS(file_reader_fail);
113+
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
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 elem {
21+
struct file *file;
22+
struct bpf_task_work tw;
23+
};
24+
25+
char user_buf[256000];
26+
char tmp_buf[256000];
27+
28+
int pid = 0;
29+
int err, run_success = 0;
30+
31+
static int validate_file_read(struct file *file);
32+
static int task_work_callback(struct bpf_map *map, void *key, void *value);
33+
34+
SEC("lsm/file_open")
35+
int on_open_expect_fault(void *c)
36+
{
37+
struct bpf_dynptr dynptr;
38+
struct file *file;
39+
int local_err = 1;
40+
__u32 user_buf_sz = sizeof(user_buf);
41+
42+
if (bpf_get_current_pid_tgid() >> 32 != pid)
43+
return 0;
44+
45+
file = bpf_get_task_exe_file(bpf_get_current_task_btf());
46+
if (!file)
47+
return 0;
48+
49+
if (bpf_dynptr_from_file(file, 0, &dynptr))
50+
goto out;
51+
52+
local_err = bpf_dynptr_read(tmp_buf, user_buf_sz, &dynptr, 0, 0);
53+
if (local_err == -EFAULT) { /* Expect page fault */
54+
local_err = 0;
55+
run_success = 1;
56+
}
57+
out:
58+
bpf_dynptr_file_discard(&dynptr);
59+
if (local_err)
60+
err = local_err;
61+
bpf_put_file(file);
62+
return 0;
63+
}
64+
65+
SEC("lsm/file_open")
66+
int on_open_validate_file_read(void *c)
67+
{
68+
struct task_struct *task = bpf_get_current_task_btf();
69+
struct elem *work;
70+
int key = 0;
71+
72+
if (bpf_get_current_pid_tgid() >> 32 != pid)
73+
return 0;
74+
75+
work = bpf_map_lookup_elem(&arrmap, &key);
76+
if (!work) {
77+
err = 1;
78+
return 0;
79+
}
80+
bpf_task_work_schedule_signal(task, &work->tw, &arrmap, task_work_callback, NULL);
81+
return 0;
82+
}
83+
84+
/* Called in a sleepable context, read 256K bytes, cross check with user space read data */
85+
static int task_work_callback(struct bpf_map *map, void *key, void *value)
86+
{
87+
struct task_struct *task = bpf_get_current_task_btf();
88+
struct file *file = bpf_get_task_exe_file(task);
89+
90+
if (!file)
91+
return 0;
92+
93+
err = validate_file_read(file);
94+
if (!err)
95+
run_success = 1;
96+
bpf_put_file(file);
97+
return 0;
98+
}
99+
100+
static int verify_dynptr_read(struct bpf_dynptr *ptr, u32 off, char *user_buf, u32 len)
101+
{
102+
int i;
103+
104+
if (bpf_dynptr_read(tmp_buf, len, ptr, off, 0))
105+
return 1;
106+
107+
/* Verify file contents read from BPF is the same as the one read from userspace */
108+
bpf_for(i, 0, len)
109+
{
110+
if (tmp_buf[i] != user_buf[i])
111+
return 1;
112+
}
113+
return 0;
114+
}
115+
116+
static int validate_file_read(struct file *file)
117+
{
118+
struct bpf_dynptr dynptr;
119+
int loc_err = 1, off;
120+
__u32 user_buf_sz = sizeof(user_buf);
121+
122+
if (bpf_dynptr_from_file(file, 0, &dynptr))
123+
goto cleanup;
124+
125+
loc_err = verify_dynptr_read(&dynptr, 0, user_buf, user_buf_sz);
126+
off = 1;
127+
loc_err = loc_err ?: verify_dynptr_read(&dynptr, off, user_buf + off, user_buf_sz - off);
128+
off = user_buf_sz - 1;
129+
loc_err = loc_err ?: verify_dynptr_read(&dynptr, off, user_buf + off, user_buf_sz - off);
130+
/* Read file with random offset and length */
131+
off = 4097;
132+
loc_err = loc_err ?: verify_dynptr_read(&dynptr, off, user_buf + off, 100);
133+
134+
/* Adjust dynptr, verify read */
135+
loc_err = loc_err ?: bpf_dynptr_adjust(&dynptr, off, off + 1);
136+
loc_err = loc_err ?: verify_dynptr_read(&dynptr, 0, user_buf + off, 1);
137+
/* Can't read more than 1 byte */
138+
loc_err = loc_err ?: verify_dynptr_read(&dynptr, 0, user_buf + off, 2) == 0;
139+
/* Can't read with far offset */
140+
loc_err = loc_err ?: verify_dynptr_read(&dynptr, 1, user_buf + off, 1) == 0;
141+
142+
cleanup:
143+
bpf_dynptr_file_discard(&dynptr);
144+
return loc_err;
145+
}
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)