selftests/bpf: Add tests for async cb context

Add tests to verify that async callback's sleepable attribute is
correctly determined by the callback type, not the arming program's
context, reflecting its true execution context.

Introduce verifier_async_cb_context.c with tests for all three async
callback primitives: bpf_timer, bpf_wq, and bpf_task_work. Each
primitive is tested when armed from both sleepable (lsm.s/file_open) and
non-sleepable (fentry) programs.

Test coverage:
- bpf_timer callbacks: Verify they are never sleepable, even when armed
  from sleepable programs. Both tests should fail when attempting to use
  sleepable helper bpf_copy_from_user() in the callback.

- bpf_wq callbacks: Verify they are always sleepable, even when armed
  from non-sleepable programs. Both tests should succeed when using
  sleepable helpers in the callback.

- bpf_task_work callbacks: Verify they are always sleepable, even when
  armed from non-sleepable programs. Both tests should succeed when
  using sleepable helpers in the callback.

Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Link: https://lore.kernel.org/r/20251007220349.3852807-4-memxor@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
Kumar Kartikeya Dwivedi 2025-10-07 22:03:49 +00:00 committed by Alexei Starovoitov
parent f233d48559
commit 5b1b5d380a
2 changed files with 183 additions and 0 deletions

View File

@ -7,6 +7,7 @@
#include "verifier_arena.skel.h"
#include "verifier_arena_large.skel.h"
#include "verifier_array_access.skel.h"
#include "verifier_async_cb_context.skel.h"
#include "verifier_basic_stack.skel.h"
#include "verifier_bitfield_write.skel.h"
#include "verifier_bounds.skel.h"
@ -280,6 +281,7 @@ void test_verifier_array_access(void)
verifier_array_access__elf_bytes,
init_array_access_maps);
}
void test_verifier_async_cb_context(void) { RUN(verifier_async_cb_context); }
static int init_value_ptr_arith_maps(struct bpf_object *obj)
{

View File

@ -0,0 +1,181 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include "bpf_misc.h"
#include "bpf_experimental.h"
char _license[] SEC("license") = "GPL";
/* Timer tests */
struct timer_elem {
struct bpf_timer t;
};
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 1);
__type(key, int);
__type(value, struct timer_elem);
} timer_map SEC(".maps");
static int timer_cb(void *map, int *key, struct bpf_timer *timer)
{
u32 data;
/* Timer callbacks are never sleepable, even from non-sleepable programs */
bpf_copy_from_user(&data, sizeof(data), NULL);
return 0;
}
SEC("fentry/bpf_fentry_test1")
__failure __msg("helper call might sleep in a non-sleepable prog")
int timer_non_sleepable_prog(void *ctx)
{
struct timer_elem *val;
int key = 0;
val = bpf_map_lookup_elem(&timer_map, &key);
if (!val)
return 0;
bpf_timer_init(&val->t, &timer_map, 0);
bpf_timer_set_callback(&val->t, timer_cb);
return 0;
}
SEC("lsm.s/file_open")
__failure __msg("helper call might sleep in a non-sleepable prog")
int timer_sleepable_prog(void *ctx)
{
struct timer_elem *val;
int key = 0;
val = bpf_map_lookup_elem(&timer_map, &key);
if (!val)
return 0;
bpf_timer_init(&val->t, &timer_map, 0);
bpf_timer_set_callback(&val->t, timer_cb);
return 0;
}
/* Workqueue tests */
struct wq_elem {
struct bpf_wq w;
};
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 1);
__type(key, int);
__type(value, struct wq_elem);
} wq_map SEC(".maps");
static int wq_cb(void *map, int *key, void *value)
{
u32 data;
/* Workqueue callbacks are always sleepable, even from non-sleepable programs */
bpf_copy_from_user(&data, sizeof(data), NULL);
return 0;
}
SEC("fentry/bpf_fentry_test1")
__success
int wq_non_sleepable_prog(void *ctx)
{
struct wq_elem *val;
int key = 0;
val = bpf_map_lookup_elem(&wq_map, &key);
if (!val)
return 0;
if (bpf_wq_init(&val->w, &wq_map, 0) != 0)
return 0;
if (bpf_wq_set_callback_impl(&val->w, wq_cb, 0, NULL) != 0)
return 0;
return 0;
}
SEC("lsm.s/file_open")
__success
int wq_sleepable_prog(void *ctx)
{
struct wq_elem *val;
int key = 0;
val = bpf_map_lookup_elem(&wq_map, &key);
if (!val)
return 0;
if (bpf_wq_init(&val->w, &wq_map, 0) != 0)
return 0;
if (bpf_wq_set_callback_impl(&val->w, wq_cb, 0, NULL) != 0)
return 0;
return 0;
}
/* Task work tests */
struct task_work_elem {
struct bpf_task_work tw;
};
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 1);
__type(key, int);
__type(value, struct task_work_elem);
} task_work_map SEC(".maps");
static int task_work_cb(struct bpf_map *map, void *key, void *value)
{
u32 data;
/* Task work callbacks are always sleepable, even from non-sleepable programs */
bpf_copy_from_user(&data, sizeof(data), NULL);
return 0;
}
SEC("fentry/bpf_fentry_test1")
__success
int task_work_non_sleepable_prog(void *ctx)
{
struct task_work_elem *val;
struct task_struct *task;
int key = 0;
val = bpf_map_lookup_elem(&task_work_map, &key);
if (!val)
return 0;
task = bpf_get_current_task_btf();
if (!task)
return 0;
bpf_task_work_schedule_resume(task, &val->tw, &task_work_map, task_work_cb, NULL);
return 0;
}
SEC("lsm.s/file_open")
__success
int task_work_sleepable_prog(void *ctx)
{
struct task_work_elem *val;
struct task_struct *task;
int key = 0;
val = bpf_map_lookup_elem(&task_work_map, &key);
if (!val)
return 0;
task = bpf_get_current_task_btf();
if (!task)
return 0;
bpf_task_work_schedule_resume(task, &val->tw, &task_work_map, task_work_cb, NULL);
return 0;
}