Skip to content

Commit 129c7e1

Browse files
committed
selftests/bpf: Add tests for split task_vma iterator
Add runtime and verifier tests for the split task_vma iteration model. The runtime test iter_task_vma_release_and_copy iterates VMAs, saves vm_start, releases mmap_lock via bpf_iter_task_vma_release(), then calls bpf_copy_from_user() on the saved address. The verifier tests cover: - nosleep_iter_release_then_sleep: release then sleep is accepted - nosleep_iter_sleep_without_release: sleep without release is rejected with "in nosleep region" - nosleep_iter_vma_access_after_release: VMA access after release is rejected with "invalid mem access 'scalar'" Signed-off-by: Puranjay Mohan <puranjay@kernel.org>
1 parent 8047b67 commit 129c7e1

File tree

4 files changed

+178
-0
lines changed

4 files changed

+178
-0
lines changed

tools/testing/selftests/bpf/bpf_experimental.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ extern int bpf_iter_task_vma_new(struct bpf_iter_task_vma *it,
165165
struct task_struct *task,
166166
__u64 addr) __ksym;
167167
extern struct vm_area_struct *bpf_iter_task_vma_next(struct bpf_iter_task_vma *it) __ksym;
168+
extern void bpf_iter_task_vma_release(struct bpf_iter_task_vma *it) __ksym;
168169
extern void bpf_iter_task_vma_destroy(struct bpf_iter_task_vma *it) __ksym;
169170

170171
/* Convenience macro to wrap over bpf_obj_drop_impl */

tools/testing/selftests/bpf/prog_tests/iters.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "iters_css_task.skel.h"
2222
#include "iters_css.skel.h"
2323
#include "iters_task_failure.skel.h"
24+
#include "iters_task_vma_nosleep.skel.h"
2425

2526
static void subtest_num_iters(void)
2627
{
@@ -152,6 +153,17 @@ static void subtest_task_vma_iters(void)
152153
if (!ASSERT_EQ(skel->bss->vmas_seen, seen, "vmas_seen_eq"))
153154
goto cleanup;
154155

156+
/* Test release+sleepable: trigger the release_and_copy program */
157+
skel->bss->release_vmas_seen = 0;
158+
err = iters_task_vma__attach(skel);
159+
if (!ASSERT_OK(err, "skel_reattach"))
160+
goto cleanup;
161+
162+
getpgid(skel->bss->target_pid);
163+
iters_task_vma__detach(skel);
164+
165+
ASSERT_GT(skel->bss->release_vmas_seen, 0, "release_vmas_seen_gt_zero");
166+
155167
cleanup:
156168
if (f)
157169
fclose(f);
@@ -322,4 +334,5 @@ void test_iters(void)
322334
if (test__start_subtest("css"))
323335
subtest_css_iters();
324336
RUN_TESTS(iters_task_failure);
337+
RUN_TESTS(iters_task_vma_nosleep);
325338
}

tools/testing/selftests/bpf/progs/iters_task_vma.c

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,43 @@ int iter_task_vma_for_each(const void *ctx)
4040
return 0;
4141
}
4242

43+
unsigned int release_vmas_seen = 0;
44+
45+
SEC("fentry.s/" SYS_PREFIX "sys_getpgid")
46+
int iter_task_vma_release_and_copy(const void *ctx)
47+
{
48+
struct task_struct *task = bpf_get_current_task_btf();
49+
struct vm_area_struct *vma;
50+
unsigned int seen = 0;
51+
52+
if (task->pid != target_pid)
53+
return 0;
54+
55+
if (release_vmas_seen)
56+
return 0;
57+
58+
bpf_for_each(task_vma, vma, task, 0) {
59+
__u64 start;
60+
char buf[8];
61+
62+
if (bpf_cmp_unlikely(seen, >=, 1000))
63+
break;
64+
65+
/* Phase 1: mmap_lock held, read VMA data */
66+
start = vma->vm_start;
67+
68+
/* Transition: release mmap_lock */
69+
bpf_iter_task_vma_release(&___it);
70+
/* VMA pointer is now invalid; sleepable helpers allowed */
71+
72+
/* Phase 2: mmap_lock released, sleepable call */
73+
bpf_copy_from_user(&buf, sizeof(buf), (void *)start);
74+
75+
seen++;
76+
}
77+
78+
release_vmas_seen = seen;
79+
return 0;
80+
}
81+
4382
char _license[] SEC("license") = "GPL";
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
3+
4+
#include "vmlinux.h"
5+
#include "bpf_experimental.h"
6+
#include <bpf/bpf_helpers.h>
7+
#include "bpf_misc.h"
8+
9+
char _license[] SEC("license") = "GPL";
10+
11+
/* Negative test: sleepable call without release should be rejected */
12+
SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
13+
__failure __msg("sleepable helper bpf_copy_from_user#148 in nosleep region")
14+
int nosleep_iter_sleep_without_release(const void *ctx)
15+
{
16+
struct task_struct *task = bpf_get_current_task_btf();
17+
struct vm_area_struct *vma;
18+
19+
bpf_for_each(task_vma, vma, task, 0) {
20+
char buf[8];
21+
22+
/* Attempt to call sleepable helper without releasing mmap_lock.
23+
* Verifier should reject this.
24+
*/
25+
bpf_copy_from_user(&buf, sizeof(buf), (void *)vma->vm_start);
26+
break;
27+
}
28+
return 0;
29+
}
30+
31+
/* Negative test: VMA access after release should be rejected */
32+
SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
33+
__failure __msg("invalid mem access 'scalar'")
34+
int nosleep_iter_vma_access_after_release(const void *ctx)
35+
{
36+
struct task_struct *task = bpf_get_current_task_btf();
37+
struct vm_area_struct *vma;
38+
__u64 val = 0;
39+
40+
bpf_for_each(task_vma, vma, task, 0) {
41+
bpf_iter_task_vma_release(&___it);
42+
/* VMA pointer is now invalid. Accessing it should be rejected. */
43+
val = vma->vm_start;
44+
break;
45+
}
46+
__sink(val);
47+
return 0;
48+
}
49+
50+
/* Positive test: release then sleepable call should succeed */
51+
SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
52+
__success
53+
int nosleep_iter_release_then_sleep(const void *ctx)
54+
{
55+
struct task_struct *task = bpf_get_current_task_btf();
56+
struct vm_area_struct *vma;
57+
58+
bpf_for_each(task_vma, vma, task, 0) {
59+
__u64 start = vma->vm_start;
60+
char buf[8];
61+
62+
bpf_iter_task_vma_release(&___it);
63+
bpf_copy_from_user(&buf, sizeof(buf), (void *)start);
64+
break;
65+
}
66+
return 0;
67+
}
68+
69+
/* Negative test: double release should be rejected */
70+
SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
71+
__failure __msg("no acquired reference to release")
72+
int nosleep_iter_double_release(const void *ctx)
73+
{
74+
struct task_struct *task = bpf_get_current_task_btf();
75+
struct vm_area_struct *vma;
76+
77+
bpf_for_each(task_vma, vma, task, 0) {
78+
bpf_iter_task_vma_release(&___it);
79+
bpf_iter_task_vma_release(&___it);
80+
break;
81+
}
82+
return 0;
83+
}
84+
85+
/* Negative test: release without any prior _next acquire */
86+
SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
87+
__failure __msg("no acquired reference to release")
88+
int nosleep_iter_release_without_acquire(const void *ctx)
89+
{
90+
struct task_struct *task = bpf_get_current_task_btf();
91+
struct bpf_iter_task_vma it;
92+
93+
bpf_iter_task_vma_new(&it, task, 0);
94+
/* No _next called, so no acquired reference exists */
95+
bpf_iter_task_vma_release(&it);
96+
bpf_iter_task_vma_destroy(&it);
97+
return 0;
98+
}
99+
100+
/* Negative test: nested iterators, releasing inner should not allow sleeping
101+
* because the outer iterator still holds a nosleep reference.
102+
*/
103+
SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
104+
__failure __msg("sleepable helper bpf_copy_from_user#148 in nosleep region")
105+
int nosleep_iter_nested_release_inner(const void *ctx)
106+
{
107+
struct task_struct *task = bpf_get_current_task_btf();
108+
struct vm_area_struct *vma_outer, *vma_inner;
109+
110+
bpf_for_each(task_vma, vma_outer, task, 0) {
111+
bpf_for_each(task_vma, vma_inner, task, 0) {
112+
__u64 start = vma_inner->vm_start;
113+
char buf[8];
114+
115+
bpf_iter_task_vma_release(&___it);
116+
/* Inner released, but outer still holds nosleep ref.
117+
* Sleeping should still be forbidden.
118+
*/
119+
bpf_copy_from_user(&buf, sizeof(buf), (void *)start);
120+
break;
121+
}
122+
break;
123+
}
124+
return 0;
125+
}

0 commit comments

Comments
 (0)