selftests/bpf: Test global subprog callback contexts

Exercise global subprogram verification from workqueue callbacks, which
can run in a sleepable context even when the containing program is not
sleepable. An unprotected callback must not let the global subprogram use
implicit RCU protection inherited from the program.

Add a negative case which loads an RCU-protected task kptr in a global
subprogram reached from a workqueue callback. It fails on an unfixed
kernel because the program is incorrectly accepted. Also cover a
workqueue callback protected by an explicit RCU read-side critical
section, where the same global subprogram remains valid.

Call the same harmless global subprogram directly from the main program
and from an unprotected callback. Mark it __weak __noinline so both calls
survive optimization, and check that its instruction statistics account
for both verification contexts. This also verifies that global calls
from callbacks are not rejected wholesale. Workqueue callbacks return
zero explicitly after the global call, as required by their contract.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Link: https://patch.msgid.link/20260914131923.2544250-3-memxor@gmail.com
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
This commit is contained in:
Kumar Kartikeya Dwivedi 2026-09-14 15:19:21 +02:00 committed by Eduard Zingerman
parent 40c2096961
commit a452e729b7

View File

@ -9,6 +9,11 @@
char _license[] SEC("license") = "GPL";
struct task_struct *bpf_task_acquire(struct task_struct *p) __ksym;
void bpf_task_release(struct task_struct *p) __ksym;
void bpf_rcu_read_lock(void) __ksym;
void bpf_rcu_read_unlock(void) __ksym;
/* Timer tests */
struct timer_elem {
@ -164,6 +169,7 @@ int syscall_btf_find_prog(void *ctx)
struct wq_elem {
struct bpf_wq w;
struct task_struct __kptr *task;
};
struct {
@ -217,6 +223,106 @@ int wq_sleepable_prog(void *ctx)
return 0;
}
__noinline int wq_global_acquire(void)
{
struct task_struct *task, *acquired;
struct wq_elem *val;
int key = 0;
val = bpf_map_lookup_elem(&wq_map, &key);
if (!val)
return 0;
task = val->task;
if (!task)
return 0;
acquired = bpf_task_acquire(task);
if (acquired)
bpf_task_release(acquired);
return 0;
}
static int wq_global_rcu_cb(void *map, int *key, void *value)
{
wq_global_acquire();
return 0;
}
SEC("fentry/bpf_fentry_test1")
__failure __msg("R1 must be a rcu pointer")
int wq_global_rcu_prog(void *ctx)
{
struct wq_elem *val;
int key = 0;
val = bpf_map_lookup_elem(&wq_map, &key);
if (!val)
return 0;
bpf_wq_init(&val->w, &wq_map, 0);
bpf_wq_set_callback(&val->w, wq_global_rcu_cb, 0);
return 0;
}
static int wq_global_rcu_lock_cb(void *map, int *key, void *value)
{
bpf_rcu_read_lock();
wq_global_acquire();
bpf_rcu_read_unlock();
return 0;
}
SEC("fentry/bpf_fentry_test1")
__success
int wq_global_rcu_lock_prog(void *ctx)
{
struct wq_elem *val;
int key = 0;
/* Verify the same global subprog in non-sleepable and protected contexts. */
wq_global_acquire();
val = bpf_map_lookup_elem(&wq_map, &key);
if (!val)
return 0;
bpf_wq_init(&val->w, &wq_map, 0);
bpf_wq_set_callback(&val->w, wq_global_rcu_lock_cb, 0);
return 0;
}
__weak __noinline int wq_global_no_rcu(void)
{
return 0;
}
static int wq_global_no_rcu_cb(void *map, int *key, void *value)
{
wq_global_no_rcu();
return 0;
}
SEC("fentry/bpf_fentry_test1")
__success __log_level(4)
__msg("subprog {{[0-9]+}} (wq_global_no_rcu) global insns_self 4 insns_total 4 stack 0")
int wq_global_no_rcu_prog(void *ctx)
{
struct wq_elem *val;
int key = 0;
/* Verify the same global in non-sleepable and unprotected contexts. */
wq_global_no_rcu();
val = bpf_map_lookup_elem(&wq_map, &key);
if (!val)
return 0;
bpf_wq_init(&val->w, &wq_map, 0);
bpf_wq_set_callback(&val->w, wq_global_no_rcu_cb, 0);
return 0;
}
/* Task work tests */
struct task_work_elem {