| // SPDX-License-Identifier: GPL-2.0 | |
| /* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */ | |
| #define _GNU_SOURCE | |
| #include <sched.h> | |
| #include <pthread.h> | |
| #include <test_progs.h> | |
| #include "timer_start_delete_race.skel.h" | |
| /* | |
| * Test for race between bpf_timer_start() and map element deletion. | |
| * | |
| * The race scenario: | |
| * - CPU 1: bpf_timer_start() proceeds to bpf_async_process() and is about | |
| * to call hrtimer_start() but hasn't yet | |
| * - CPU 2: map_delete_elem() calls __bpf_async_cancel_and_free(), since | |
| * timer is not scheduled yet hrtimer_try_to_cancel() is a nop, | |
| * then calls bpf_async_refcount_put() dropping refcnt to zero | |
| * and scheduling call_rcu_tasks_trace() | |
| * - CPU 1: continues and calls hrtimer_start() | |
| * - After RCU tasks trace grace period: memory is freed | |
| * - Timer callback fires on freed memory: UAF! | |
| * | |
| * This test stresses this race by having two threads: | |
| * - Thread 1: repeatedly starts timers | |
| * - Thread 2: repeatedly deletes map elements | |
| * | |
| * KASAN should detect use-after-free. | |
| */ | |
| #define ITERATIONS 1000 | |
| struct ctx { | |
| struct timer_start_delete_race *skel; | |
| volatile bool start; | |
| volatile bool stop; | |
| int errors; | |
| }; | |
| static void *start_timer_thread(void *arg) | |
| { | |
| struct ctx *ctx = arg; | |
| cpu_set_t cpuset; | |
| int fd, i; | |
| CPU_ZERO(&cpuset); | |
| CPU_SET(0, &cpuset); | |
| pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset); | |
| while (!ctx->start && !ctx->stop) | |
| usleep(1); | |
| if (ctx->stop) | |
| return NULL; | |
| fd = bpf_program__fd(ctx->skel->progs.start_timer); | |
| for (i = 0; i < ITERATIONS && !ctx->stop; i++) { | |
| LIBBPF_OPTS(bpf_test_run_opts, opts); | |
| int err; | |
| err = bpf_prog_test_run_opts(fd, &opts); | |
| if (err || opts.retval) { | |
| ctx->errors++; | |
| break; | |
| } | |
| } | |
| return NULL; | |
| } | |
| static void *delete_elem_thread(void *arg) | |
| { | |
| struct ctx *ctx = arg; | |
| cpu_set_t cpuset; | |
| int fd, i; | |
| CPU_ZERO(&cpuset); | |
| CPU_SET(1, &cpuset); | |
| pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset); | |
| while (!ctx->start && !ctx->stop) | |
| usleep(1); | |
| if (ctx->stop) | |
| return NULL; | |
| fd = bpf_program__fd(ctx->skel->progs.delete_elem); | |
| for (i = 0; i < ITERATIONS && !ctx->stop; i++) { | |
| LIBBPF_OPTS(bpf_test_run_opts, opts); | |
| int err; | |
| err = bpf_prog_test_run_opts(fd, &opts); | |
| if (err || opts.retval) { | |
| ctx->errors++; | |
| break; | |
| } | |
| } | |
| return NULL; | |
| } | |
| void test_timer_start_delete_race(void) | |
| { | |
| struct timer_start_delete_race *skel; | |
| pthread_t threads[2]; | |
| struct ctx ctx = {}; | |
| int err; | |
| skel = timer_start_delete_race__open_and_load(); | |
| if (!ASSERT_OK_PTR(skel, "skel_open_and_load")) | |
| return; | |
| ctx.skel = skel; | |
| err = pthread_create(&threads[0], NULL, start_timer_thread, &ctx); | |
| if (!ASSERT_OK(err, "create start_timer_thread")) { | |
| ctx.stop = true; | |
| goto cleanup; | |
| } | |
| err = pthread_create(&threads[1], NULL, delete_elem_thread, &ctx); | |
| if (!ASSERT_OK(err, "create delete_elem_thread")) { | |
| ctx.stop = true; | |
| pthread_join(threads[0], NULL); | |
| goto cleanup; | |
| } | |
| ctx.start = true; | |
| pthread_join(threads[0], NULL); | |
| pthread_join(threads[1], NULL); | |
| ASSERT_EQ(ctx.errors, 0, "thread_errors"); | |
| /* Either KASAN will catch UAF or kernel will crash or nothing happens */ | |
| cleanup: | |
| timer_start_delete_race__destroy(skel); | |
| } |