// SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */ #define _GNU_SOURCE #include #include #include #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); }