forked from torvalds/linux
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
selftests/bpf: Add timer lockup selftest
Add a selftest that tries to trigger a situation where two timer callbacks are attempting to cancel each other's timer. By running them continuously, we hit a condition where both run in parallel and cancel each other. Without the fix in the previous patch, this would cause a lockup as hrtimer_cancel on either side will wait for forward progress from the callback. Ensure that this situation leads to a EDEADLK error. Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net> Link: https://lore.kernel.org/bpf/20240711052709.2148616-1-memxor@gmail.com
- Loading branch information
Showing
2 changed files
with
178 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
|
||
#define _GNU_SOURCE | ||
#include <sched.h> | ||
#include <test_progs.h> | ||
#include <pthread.h> | ||
#include <network_helpers.h> | ||
|
||
#include "timer_lockup.skel.h" | ||
|
||
static long cpu; | ||
static int *timer1_err; | ||
static int *timer2_err; | ||
static bool skip; | ||
|
||
volatile int k = 0; | ||
|
||
static void *timer_lockup_thread(void *arg) | ||
{ | ||
LIBBPF_OPTS(bpf_test_run_opts, opts, | ||
.data_in = &pkt_v4, | ||
.data_size_in = sizeof(pkt_v4), | ||
.repeat = 1000, | ||
); | ||
int i, prog_fd = *(int *)arg; | ||
cpu_set_t cpuset; | ||
|
||
CPU_ZERO(&cpuset); | ||
CPU_SET(__sync_fetch_and_add(&cpu, 1), &cpuset); | ||
ASSERT_OK(pthread_setaffinity_np(pthread_self(), sizeof(cpuset), | ||
&cpuset), | ||
"cpu affinity"); | ||
|
||
for (i = 0; !READ_ONCE(*timer1_err) && !READ_ONCE(*timer2_err); i++) { | ||
bpf_prog_test_run_opts(prog_fd, &opts); | ||
/* Skip the test if we can't reproduce the race in a reasonable | ||
* amount of time. | ||
*/ | ||
if (i > 50) { | ||
WRITE_ONCE(skip, true); | ||
break; | ||
} | ||
} | ||
|
||
return NULL; | ||
} | ||
|
||
void test_timer_lockup(void) | ||
{ | ||
int timer1_prog, timer2_prog; | ||
struct timer_lockup *skel; | ||
pthread_t thrds[2]; | ||
void *ret; | ||
|
||
skel = timer_lockup__open_and_load(); | ||
if (!ASSERT_OK_PTR(skel, "timer_lockup__open_and_load")) | ||
return; | ||
|
||
timer1_prog = bpf_program__fd(skel->progs.timer1_prog); | ||
timer2_prog = bpf_program__fd(skel->progs.timer2_prog); | ||
|
||
timer1_err = &skel->bss->timer1_err; | ||
timer2_err = &skel->bss->timer2_err; | ||
|
||
if (!ASSERT_OK(pthread_create(&thrds[0], NULL, timer_lockup_thread, | ||
&timer1_prog), | ||
"pthread_create thread1")) | ||
goto out; | ||
if (!ASSERT_OK(pthread_create(&thrds[1], NULL, timer_lockup_thread, | ||
&timer2_prog), | ||
"pthread_create thread2")) { | ||
pthread_exit(&thrds[0]); | ||
goto out; | ||
} | ||
|
||
pthread_join(thrds[1], &ret); | ||
pthread_join(thrds[0], &ret); | ||
|
||
if (skip) { | ||
test__skip(); | ||
goto out; | ||
} | ||
|
||
if (*timer1_err != -EDEADLK && *timer1_err != 0) | ||
ASSERT_FAIL("timer1_err bad value"); | ||
if (*timer2_err != -EDEADLK && *timer2_err != 0) | ||
ASSERT_FAIL("timer2_err bad value"); | ||
out: | ||
timer_lockup__destroy(skel); | ||
return; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
|
||
#include <linux/bpf.h> | ||
#include <time.h> | ||
#include <errno.h> | ||
#include <bpf/bpf_helpers.h> | ||
#include <bpf/bpf_tracing.h> | ||
#include "bpf_misc.h" | ||
|
||
char _license[] SEC("license") = "GPL"; | ||
|
||
struct elem { | ||
struct bpf_timer t; | ||
}; | ||
|
||
struct { | ||
__uint(type, BPF_MAP_TYPE_ARRAY); | ||
__uint(max_entries, 1); | ||
__type(key, int); | ||
__type(value, struct elem); | ||
} timer1_map SEC(".maps"); | ||
|
||
struct { | ||
__uint(type, BPF_MAP_TYPE_ARRAY); | ||
__uint(max_entries, 1); | ||
__type(key, int); | ||
__type(value, struct elem); | ||
} timer2_map SEC(".maps"); | ||
|
||
int timer1_err; | ||
int timer2_err; | ||
|
||
static int timer_cb1(void *map, int *k, struct elem *v) | ||
{ | ||
struct bpf_timer *timer; | ||
int key = 0; | ||
|
||
timer = bpf_map_lookup_elem(&timer2_map, &key); | ||
if (timer) | ||
timer2_err = bpf_timer_cancel(timer); | ||
|
||
return 0; | ||
} | ||
|
||
static int timer_cb2(void *map, int *k, struct elem *v) | ||
{ | ||
struct bpf_timer *timer; | ||
int key = 0; | ||
|
||
timer = bpf_map_lookup_elem(&timer1_map, &key); | ||
if (timer) | ||
timer1_err = bpf_timer_cancel(timer); | ||
|
||
return 0; | ||
} | ||
|
||
SEC("tc") | ||
int timer1_prog(void *ctx) | ||
{ | ||
struct bpf_timer *timer; | ||
int key = 0; | ||
|
||
timer = bpf_map_lookup_elem(&timer1_map, &key); | ||
if (timer) { | ||
bpf_timer_init(timer, &timer1_map, CLOCK_BOOTTIME); | ||
bpf_timer_set_callback(timer, timer_cb1); | ||
bpf_timer_start(timer, 1, BPF_F_TIMER_CPU_PIN); | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
SEC("tc") | ||
int timer2_prog(void *ctx) | ||
{ | ||
struct bpf_timer *timer; | ||
int key = 0; | ||
|
||
timer = bpf_map_lookup_elem(&timer2_map, &key); | ||
if (timer) { | ||
bpf_timer_init(timer, &timer2_map, CLOCK_BOOTTIME); | ||
bpf_timer_set_callback(timer, timer_cb2); | ||
bpf_timer_start(timer, 1, BPF_F_TIMER_CPU_PIN); | ||
} | ||
|
||
return 0; | ||
} |