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 --- .../testing/selftests/bpf/bpf_experimental.h | 1 + .../testing/selftests/bpf/prog_tests/iters.c | 13 ++ .../selftests/bpf/progs/iters_task_vma.c | 39 ++++++ .../bpf/progs/iters_task_vma_nosleep.c | 125 ++++++++++++++++++ 4 files changed, 178 insertions(+) create mode 100644 tools/testing/selftests/bpf/progs/iters_task_vma_nosleep.c diff --git a/tools/testing/selftests/bpf/bpf_experimental.h b/tools/testing/selftests/bpf/bpf_experimental.h index 9df77e59d4f5..531d6c6aab45 100644 --- a/tools/testing/selftests/bpf/bpf_experimental.h +++ b/tools/testing/selftests/bpf/bpf_experimental.h @@ -165,6 +165,7 @@ extern int bpf_iter_task_vma_new(struct bpf_iter_task_vma *it, struct task_struct *task, __u64 addr) __ksym; extern struct vm_area_struct *bpf_iter_task_vma_next(struct bpf_iter_task_vma *it) __ksym; +extern void bpf_iter_task_vma_release(struct bpf_iter_task_vma *it) __ksym; extern void bpf_iter_task_vma_destroy(struct bpf_iter_task_vma *it) __ksym; /* Convenience macro to wrap over bpf_obj_drop_impl */ diff --git a/tools/testing/selftests/bpf/prog_tests/iters.c b/tools/testing/selftests/bpf/prog_tests/iters.c index a539980a2fbe..977114f0e88f 100644 --- a/tools/testing/selftests/bpf/prog_tests/iters.c +++ b/tools/testing/selftests/bpf/prog_tests/iters.c @@ -21,6 +21,7 @@ #include "iters_css_task.skel.h" #include "iters_css.skel.h" #include "iters_task_failure.skel.h" +#include "iters_task_vma_nosleep.skel.h" static void subtest_num_iters(void) { @@ -152,6 +153,17 @@ static void subtest_task_vma_iters(void) if (!ASSERT_EQ(skel->bss->vmas_seen, seen, "vmas_seen_eq")) goto cleanup; + /* Test release+sleepable: trigger the release_and_copy program */ + skel->bss->release_vmas_seen = 0; + err = iters_task_vma__attach(skel); + if (!ASSERT_OK(err, "skel_reattach")) + goto cleanup; + + getpgid(skel->bss->target_pid); + iters_task_vma__detach(skel); + + ASSERT_GT(skel->bss->release_vmas_seen, 0, "release_vmas_seen_gt_zero"); + cleanup: if (f) fclose(f); @@ -322,4 +334,5 @@ void test_iters(void) if (test__start_subtest("css")) subtest_css_iters(); RUN_TESTS(iters_task_failure); + RUN_TESTS(iters_task_vma_nosleep); } diff --git a/tools/testing/selftests/bpf/progs/iters_task_vma.c b/tools/testing/selftests/bpf/progs/iters_task_vma.c index dc0c3691dcc2..59065f9da4f8 100644 --- a/tools/testing/selftests/bpf/progs/iters_task_vma.c +++ b/tools/testing/selftests/bpf/progs/iters_task_vma.c @@ -40,4 +40,43 @@ int iter_task_vma_for_each(const void *ctx) return 0; } +unsigned int release_vmas_seen = 0; + +SEC("fentry.s/" SYS_PREFIX "sys_getpgid") +int iter_task_vma_release_and_copy(const void *ctx) +{ + struct task_struct *task = bpf_get_current_task_btf(); + struct vm_area_struct *vma; + unsigned int seen = 0; + + if (task->pid != target_pid) + return 0; + + if (release_vmas_seen) + return 0; + + bpf_for_each(task_vma, vma, task, 0) { + __u64 start; + char buf[8]; + + if (bpf_cmp_unlikely(seen, >=, 1000)) + break; + + /* Phase 1: mmap_lock held, read VMA data */ + start = vma->vm_start; + + /* Transition: release mmap_lock */ + bpf_iter_task_vma_release(&___it); + /* VMA pointer is now invalid; sleepable helpers allowed */ + + /* Phase 2: mmap_lock released, sleepable call */ + bpf_copy_from_user(&buf, sizeof(buf), (void *)start); + + seen++; + } + + release_vmas_seen = seen; + return 0; +} + char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/iters_task_vma_nosleep.c b/tools/testing/selftests/bpf/progs/iters_task_vma_nosleep.c new file mode 100644 index 000000000000..ab607e29b36a --- /dev/null +++ b/tools/testing/selftests/bpf/progs/iters_task_vma_nosleep.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */ + +#include "vmlinux.h" +#include "bpf_experimental.h" +#include +#include "bpf_misc.h" + +char _license[] SEC("license") = "GPL"; + +/* Negative test: sleepable call without release should be rejected */ +SEC("?fentry.s/" SYS_PREFIX "sys_getpgid") +__failure __msg("sleepable helper bpf_copy_from_user#148 in nosleep region") +int nosleep_iter_sleep_without_release(const void *ctx) +{ + struct task_struct *task = bpf_get_current_task_btf(); + struct vm_area_struct *vma; + + bpf_for_each(task_vma, vma, task, 0) { + char buf[8]; + + /* Attempt to call sleepable helper without releasing mmap_lock. + * Verifier should reject this. + */ + bpf_copy_from_user(&buf, sizeof(buf), (void *)vma->vm_start); + break; + } + return 0; +} + +/* Negative test: VMA access after release should be rejected */ +SEC("?fentry.s/" SYS_PREFIX "sys_getpgid") +__failure __msg("invalid mem access 'scalar'") +int nosleep_iter_vma_access_after_release(const void *ctx) +{ + struct task_struct *task = bpf_get_current_task_btf(); + struct vm_area_struct *vma; + __u64 val = 0; + + bpf_for_each(task_vma, vma, task, 0) { + bpf_iter_task_vma_release(&___it); + /* VMA pointer is now invalid. Accessing it should be rejected. */ + val = vma->vm_start; + break; + } + __sink(val); + return 0; +} + +/* Positive test: release then sleepable call should succeed */ +SEC("?fentry.s/" SYS_PREFIX "sys_getpgid") +__success +int nosleep_iter_release_then_sleep(const void *ctx) +{ + struct task_struct *task = bpf_get_current_task_btf(); + struct vm_area_struct *vma; + + bpf_for_each(task_vma, vma, task, 0) { + __u64 start = vma->vm_start; + char buf[8]; + + bpf_iter_task_vma_release(&___it); + bpf_copy_from_user(&buf, sizeof(buf), (void *)start); + break; + } + return 0; +} + +/* Negative test: double release should be rejected */ +SEC("?fentry.s/" SYS_PREFIX "sys_getpgid") +__failure __msg("no acquired reference to release") +int nosleep_iter_double_release(const void *ctx) +{ + struct task_struct *task = bpf_get_current_task_btf(); + struct vm_area_struct *vma; + + bpf_for_each(task_vma, vma, task, 0) { + bpf_iter_task_vma_release(&___it); + bpf_iter_task_vma_release(&___it); + break; + } + return 0; +} + +/* Negative test: release without any prior _next acquire */ +SEC("?fentry.s/" SYS_PREFIX "sys_getpgid") +__failure __msg("no acquired reference to release") +int nosleep_iter_release_without_acquire(const void *ctx) +{ + struct task_struct *task = bpf_get_current_task_btf(); + struct bpf_iter_task_vma it; + + bpf_iter_task_vma_new(&it, task, 0); + /* No _next called, so no acquired reference exists */ + bpf_iter_task_vma_release(&it); + bpf_iter_task_vma_destroy(&it); + return 0; +} + +/* Negative test: nested iterators, releasing inner should not allow sleeping + * because the outer iterator still holds a nosleep reference. + */ +SEC("?fentry.s/" SYS_PREFIX "sys_getpgid") +__failure __msg("sleepable helper bpf_copy_from_user#148 in nosleep region") +int nosleep_iter_nested_release_inner(const void *ctx) +{ + struct task_struct *task = bpf_get_current_task_btf(); + struct vm_area_struct *vma_outer, *vma_inner; + + bpf_for_each(task_vma, vma_outer, task, 0) { + bpf_for_each(task_vma, vma_inner, task, 0) { + __u64 start = vma_inner->vm_start; + char buf[8]; + + bpf_iter_task_vma_release(&___it); + /* Inner released, but outer still holds nosleep ref. + * Sleeping should still be forbidden. + */ + bpf_copy_from_user(&buf, sizeof(buf), (void *)start); + break; + } + break; + } + return 0; +} -- 2.47.3