BPF List
 help / color / mirror / Atom feed
* [PATCH RFC bpf-next 0/2] bpf: Do not let preemption cause false recursion drops in trampoline progs
@ 2026-10-25 11:02 Mike Bazov via B4 Relay
  2026-10-25 11:02 ` [PATCH RFC bpf-next 1/2] bpf: Keep recursion-checked trampoline programs non-preemptible on !PREEMPT_RT Mike Bazov via B4 Relay
  2026-10-25 11:02 ` [PATCH RFC bpf-next 2/2] selftests/bpf: Add trampoline preemption false-recursion test Mike Bazov via B4 Relay
  0 siblings, 2 replies; 4+ messages in thread
From: Mike Bazov via B4 Relay @ 2026-10-25 11:02 UTC (permalink / raw)
  To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko
  Cc: Martin KaFai Lau, Eduard Zingerman, Kumar Kartikeya Dwivedi,
	Song Liu, Yonghong Song, Jiri Olsa, John Fastabend, Shuah Khan,
	bpf, linux-kselftest, linux-kernel, Mike Bazov

Problem
=======

Non-sleepable fentry and fexit BPF programs silently skip invocations
from unrelated tasks whenever the task currently executing the program
on that CPU is preempted. Every skipped invocation is counted in
prog->stats->recursion_misses, even though no recursion occurred.

In __bpf_prog_enter_recur() (kernel/bpf/trampoline.c), the per-CPU
recursion counter prog->active is acquired under
rcu_read_lock_dont_migrate(), which disables CPU migration but leaves
preemption enabled:

  CPU N
  -----
  Task A enters fentry/foo -> prog->active on CPU N becomes 1
  Task A is preempted mid-program on CPU N (prog->active stays 1)
  Task B runs on CPU N, calls foo() -> __bpf_prog_enter_recur()
    bpf_prog_get_recursion_context() sees prog->active == 2 != 1
    increments recursion_misses and returns 0 (skips BPF program)
  Task A resumes on CPU N, finishes, drops prog->active to 0

Before Linux 7.0 this was hidden on most distribution kernels because
they defaulted to PREEMPT_VOLUNTARY, and non-sleepable BPF programs
contain no voluntary preemption points. Commit 7dadeaa6e851 ("sched:
Further restrict the preemption modes") removed PREEMPT_NONE and
PREEMPT_VOLUNTARY on x86 and arm64, leaving only lazy and full
preemption. As a result, every distribution kernel on 7.0+ now preempts
tasks inside non-sleepable fentry/fexit programs during normal operation
and silently drops unrelated tasks' events on the same CPU.

BPF program authors cannot work around this from inside the program:
prog->active is tested by the trampoline before the first BPF
instruction executes, and bpf_preempt_disable() cannot span tail calls.
By contrast, kprobes and classic perf tracepoints run BPF programs with
preemption disabled (trace_call_bpf() asserts cant_sleep(), and commit
16718274ee75 ("tracing: perf: Have perf tracepoint callbacks always
disable preemption") explicitly kept preemption disabled for perf
tracepoint callbacks), so the same BPF logic has different completeness
guarantees depending solely on whether it attaches via fentry/fexit or
kprobe/tracepoint.

Why This Matters in Practice
============================

We hit this in a production security agent that attaches paired
fentry/fexit probes to inet_sendmsg(), inet6_sendmsg(), inet_recvmsg(),
and inet6_recvmsg() to inspect stateful application-layer protocols:

1. Broken entry/exit pairing: the fentry program snapshots the msghdr /
   iov_iter state into per-socket storage and the fexit program consumes
   that snapshot once the call completes with the byte count. Because
   each program has its own prog->active counter, preemption skips one
   half of the pair independently -- either dropping the payload and
   leaving stale per-socket state (when fexit is skipped) or losing the
   capture entirely (when fentry is skipped).
2. Broken stateful protocol parsing: for stateful protocols (HTTP/1.1
   keep-alive/pipelining/chunked framing, HTTP/2 header state, database
   wire protocols, or per-session first-message classification), a
   single skipped sendmsg/recvmsg call desynchronizes the state machine
   for the remainder of the connection.
3. Kprobes are not a drop-in replacement: on hot socket paths fentry is
   ~2x faster than kprobe (40M/s vs 20M/s in trig-fentry vs trig-kprobe),
   and kretprobe relies on a fixed maxactive instance pool that can be
   exhausted on blocking calls like inet_recvmsg().

Minimal Standalone Reproducer (47 lines)
========================================

repro.bpf.c:

  #include <linux/bpf.h>
  #include <bpf/bpf_helpers.h>

  char LICENSE[] SEC("license") = "GPL";
  static int cb(__u32 i, void *ctx) { return 0; }

  SEC("fentry/__set_task_comm")
  int on_fentry(void *ctx) { bpf_loop(1000000, cb, NULL, 0); return 0; }

  SEC("fexit/__set_task_comm")
  int on_fexit(void *ctx) { bpf_loop(1000000, cb, NULL, 0); return 0; }

repro.c:

  #define _GNU_SOURCE
  #include <sched.h>
  #include <stdio.h>
  #include <unistd.h>
  #include <sys/prctl.h>
  #include <sys/wait.h>
  #include <bpf/bpf.h>
  #include <bpf/libbpf.h>
  #include "repro.skel.h"

  int main(void)
  {
      int stats = bpf_enable_stats(BPF_STATS_RUN_TIME);
      struct repro *skel = repro__open_and_load();
      struct bpf_program *p;
      cpu_set_t cpu;

      repro__attach(skel);
      CPU_ZERO(&cpu);
      CPU_SET(sched_getcpu(), &cpu);
      sched_setaffinity(0, sizeof(cpu), &cpu);

      pid_t pid = fork();
      for (int i = 0; i < 100; i++) {
          prctl(PR_SET_NAME, "repro");
          if (!pid) usleep(50);
      }
      if (!pid)
          return 0;
      wait(NULL);

      bpf_object__for_each_program(p, skel->obj) {
          struct bpf_prog_info info = {};
          __u32 len = sizeof(info);

          bpf_prog_get_info_by_fd(bpf_program__fd(p), &info, &len);
          printf("%-9s expected=200 run_cnt=%-4llu recursion_misses=%llu\n",
                 bpf_program__name(p), info.run_cnt, info.recursion_misses);
      }
      repro__destroy(skel);
      close(stats);
  }

Results across 10 runs (2,000 total calls per probe) on the same x86_64
VM, comparing unmodified mainline (a804c5742bff) with this series:

  Kernel & Preemption Mode        fentry skipped       fexit skipped
  -----------------------------   ------------------   ------------------
  6.8.0-55 stock (voluntary)         0 / 1000 ( 0%)       0 / 1000 ( 0%)
  6.8.0-55 stock (full)            254 / 1000 (25%)     263 / 1000 (26%)
  7.3.0 mainline (lazy)            553 / 2000 (28%)     691 / 2000 (35%)
  7.3.0 mainline (full)            694 / 2000 (35%)     713 / 2000 (36%)
  7.3.0 + patch  (lazy)              0 / 2000 ( 0%)       0 / 2000 ( 0%)
  7.3.0 + patch  (full)              0 / 2000 ( 0%)       0 / 2000 ( 0%)

Why This Fix (and Why Not Per-Task Recursion Tracking)
======================================================

Relaxing prog->active to check whether current is the same task would
not be safe on its own:

1. Per-CPU private stack: since commit a76ab5731e32 ("bpf: Find eligible
   subprogs for private stack support"), recursion-checked trampoline
   programs allocate a single per-CPU private stack
   (prog->aux->priv_stack_ptr) and rely on prog->active to guarantee
   that at most one task on a CPU uses that stack at a time. Allowing a
   second task on the same CPU to enter the program while the first task
   is preempted would corrupt the first task's private stack frames.
2. Per-CPU scratch maps: existing fentry/fexit programs rely on the
   same per-CPU non-reentrancy guarantee when building events in
   BPF_MAP_TYPE_PERCPU_ARRAY scratch buffers.

Therefore, Patch 1 disables preemption (preempt_disable_notrace() +
rcu_read_lock()) in __bpf_prog_enter_recur() / __bpf_prog_exit_recur()
when !IS_ENABLED(CONFIG_PREEMPT_RT), matching classic perf tracepoint
and kprobe BPF execution while leaving PREEMPT_RT and sleepable
trampoline programs (__bpf_prog_enter_sleepable_recur) unchanged.

Testing
=======

- A/B reproducer above on mainline 7.3 (lazy and full preemption):
  drops go from 28-36% to 0%.
- True same-task recursion (fentry/htab_map_delete_elem deleting from
  its own map, as in prog_tests/recursion.c): still blocked 10/10 times.
- Debug kernel with CONFIG_DEBUG_ATOMIC_SLEEP=y, CONFIG_DEBUG_PREEMPT=y,
  CONFIG_PROVE_LOCKING=y, and CONFIG_PROVE_RCU=y: zero lockdep, RCU, or
  atomic-sleep warnings across both the reproducer and the full
  test_progs run (debug_locks remains 1).
- Full test_progs A/B comparison on mainline 7.3: zero new failures
  introduced; all fentry/fexit/trampoline/recursion test suites pass.
- Selftests bench trigger throughput (3 x 5s runs, pinned producer):
    trig-fentry:  39.93M/s (base) -> 42.05M/s (patched)
    trig-fexit:   31.98M/s (base) -> 33.54M/s (patched)
- CONFIG_PREEMPT_RT=y kernel (7.3.0-rt): builds cleanly with W=1; booted
  and verified that trampoline_preempt skips via __kconfig while the
  recursion selftest passes.

Question for Discussion
=======================

Is disabling preemption in __bpf_prog_enter_recur() on !PREEMPT_RT the
preferred fix for fentry/fexit, or would you prefer a different
mechanism for reconciling preemptible execution with prog->active and
the per-CPU private stack?

Signed-off-by: Mike Bazov <mikeba@sentinelone.com>
---
Mike Bazov (2):
      bpf: Keep recursion-checked trampoline programs non-preemptible on !PREEMPT_RT
      selftests/bpf: Add trampoline preemption false-recursion test

 kernel/bpf/trampoline.c                            |  17 +-
 .../selftests/bpf/prog_tests/trampoline_preempt.c  | 222 +++++++++++++++++++++
 .../selftests/bpf/progs/trampoline_preempt.c       |  56 ++++++
 3 files changed, 293 insertions(+), 2 deletions(-)
---
base-commit: a804c5742bff224d4cb4d25dd383554d598be32b
change-id: 20261025-b4-bpf-trampoline-preempt-a1417898a8ed

Best regards,
-- 
Mike Bazov <mikeba@sentinelone.com>



^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH RFC bpf-next 1/2] bpf: Keep recursion-checked trampoline programs non-preemptible on !PREEMPT_RT
  2026-10-25 11:02 [PATCH RFC bpf-next 0/2] bpf: Do not let preemption cause false recursion drops in trampoline progs Mike Bazov via B4 Relay
@ 2026-10-25 11:02 ` Mike Bazov via B4 Relay
  2026-10-25 11:38   ` sashiko-bot
  2026-10-25 11:02 ` [PATCH RFC bpf-next 2/2] selftests/bpf: Add trampoline preemption false-recursion test Mike Bazov via B4 Relay
  1 sibling, 1 reply; 4+ messages in thread
From: Mike Bazov via B4 Relay @ 2026-10-25 11:02 UTC (permalink / raw)
  To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko
  Cc: Martin KaFai Lau, Eduard Zingerman, Kumar Kartikeya Dwivedi,
	Song Liu, Yonghong Song, Jiri Olsa, John Fastabend, Shuah Khan,
	bpf, linux-kselftest, linux-kernel, Mike Bazov

From: Mike Bazov <mikeba@sentinelone.com>

__bpf_prog_enter_recur() acquires the per-CPU recursion counter
(prog->active) under rcu_read_lock_dont_migrate(), which disables CPU
migration but leaves preemption enabled.

When a task executing a non-sleepable fentry/fexit program on CPU N is
preempted mid-program, prog->active remains 1 on CPU N while the
preempted task is off-CPU. Any unrelated task scheduled onto CPU N that
triggers the same hook observes prog->active != 1 in
bpf_prog_get_recursion_context(), increments recursion_misses, and has
its BPF program invocation silently skipped, even though no recursion
occurred.

Before Linux 7.0 this was mostly masked on distribution kernels that
defaulted to PREEMPT_VOLUNTARY, because non-sleepable BPF programs
contain no voluntary scheduling points. Commit 7dadeaa6e851 ("sched:
Further restrict the preemption modes") removed PREEMPT_NONE and
PREEMPT_VOLUNTARY on x86 and arm64, so every distribution kernel now
runs with lazy or full preemption and triggers these false recursion
drops during normal multi-task execution on the same CPU.

Simply switching prog->active to per-task tracking is not sufficient:
since commit a76ab5731e32 ("bpf: Find eligible subprogs for private
stack support"), recursion-checked trampoline programs also rely on
prog->active to guarantee exclusive ownership of the single per-CPU
private stack (prog->aux->priv_stack_ptr), as well as per-CPU scratch
maps.

Match the guarantee already provided by kprobe and classic perf
tracepoint BPF callbacks (commit 16718274ee75 ("tracing: perf: Have perf
tracepoint callbacks always disable preemption")) by disabling
preemption in __bpf_prog_enter_recur() / __bpf_prog_exit_recur() when
!IS_ENABLED(CONFIG_PREEMPT_RT). This preserves preemptible BPF execution
on PREEMPT_RT while eliminating false recursion drops and protecting
per-CPU private stacks on non-RT kernels.

Fixes: ca06f55b9002 ("bpf: Add per-program recursion prevention mechanism")
Assisted-by: Devin:gemini-4-argon-high
Signed-off-by: Mike Bazov <mikeba@sentinelone.com>
---
 kernel/bpf/trampoline.c | 17 +++++++++++++++--
 1 file changed, 15 insertions(+), 2 deletions(-)

diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c
index bf4cb3dd444d..632c104112b2 100644
--- a/kernel/bpf/trampoline.c
+++ b/kernel/bpf/trampoline.c
@@ -1339,7 +1339,15 @@ static __always_inline u64 notrace bpf_prog_start_time(void)
 static u64 notrace __bpf_prog_enter_recur(struct bpf_prog *prog, struct bpf_tramp_run_ctx *run_ctx)
 	__acquires(RCU)
 {
-	rcu_read_lock_dont_migrate();
+	if (IS_ENABLED(CONFIG_PREEMPT_RT)) {
+		rcu_read_lock_dont_migrate();
+	} else {
+		/* prog->active and the private stack are per-CPU: don't let
+		 * another task run on this CPU while we hold them.
+		 */
+		preempt_disable_notrace();
+		rcu_read_lock();
+	}
 
 	run_ctx->saved_run_ctx = bpf_set_run_ctx(&run_ctx->run_ctx);
 
@@ -1388,7 +1396,12 @@ static void notrace __bpf_prog_exit_recur(struct bpf_prog *prog, u64 start,
 
 	update_prog_stats(prog, start);
 	bpf_prog_put_recursion_context(prog);
-	rcu_read_unlock_migrate();
+	if (IS_ENABLED(CONFIG_PREEMPT_RT)) {
+		rcu_read_unlock_migrate();
+	} else {
+		rcu_read_unlock();
+		preempt_enable_notrace();
+	}
 }
 
 static u64 notrace __bpf_prog_enter_lsm_cgroup(struct bpf_prog *prog,

-- 
2.43.0



^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH RFC bpf-next 2/2] selftests/bpf: Add trampoline preemption false-recursion test
  2026-10-25 11:02 [PATCH RFC bpf-next 0/2] bpf: Do not let preemption cause false recursion drops in trampoline progs Mike Bazov via B4 Relay
  2026-10-25 11:02 ` [PATCH RFC bpf-next 1/2] bpf: Keep recursion-checked trampoline programs non-preemptible on !PREEMPT_RT Mike Bazov via B4 Relay
@ 2026-10-25 11:02 ` Mike Bazov via B4 Relay
  1 sibling, 0 replies; 4+ messages in thread
From: Mike Bazov via B4 Relay @ 2026-10-25 11:02 UTC (permalink / raw)
  To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko
  Cc: Martin KaFai Lau, Eduard Zingerman, Kumar Kartikeya Dwivedi,
	Song Liu, Yonghong Song, Jiri Olsa, John Fastabend, Shuah Khan,
	bpf, linux-kselftest, linux-kernel, Mike Bazov

From: Mike Bazov <mikeba@sentinelone.com>

Verify that preempting a task while it is executing a non-sleepable
fentry or fexit program on a CPU does not cause a second task on the
same CPU to have its fentry/fexit execution skipped as a false
recursion_miss.

Pin two threads to the same CPU in separate fentry and fexit subtests:
worker_b (SCHED_FIFO) wakes worker_a via a pipe and blocks in
sigtimedwait(); worker_a invokes prctl(PR_SET_NAME) and its fentry/fexit
program sends SIGUSR1 to worker_b via bpf_send_signal_task(). On an
unpatched preemptible kernel, sending the signal wakes worker_b and
immediately preempts worker_a mid-program, causing worker_b's
subsequent prctl(PR_SET_NAME) to be skipped with recursion_misses++.
Assert that all invocations run for both threads with zero
recursion_misses, and skip on PREEMPT_RT or when SCHED_FIFO is
unavailable.

Assisted-by: Devin:gemini-4-argon-high
Signed-off-by: Mike Bazov <mikeba@sentinelone.com>
---
 .../selftests/bpf/prog_tests/trampoline_preempt.c  | 222 +++++++++++++++++++++
 .../selftests/bpf/progs/trampoline_preempt.c       |  56 ++++++
 2 files changed, 278 insertions(+)

diff --git a/tools/testing/selftests/bpf/prog_tests/trampoline_preempt.c b/tools/testing/selftests/bpf/prog_tests/trampoline_preempt.c
new file mode 100644
index 000000000000..ba9d35e3f0af
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/trampoline_preempt.c
@@ -0,0 +1,222 @@
+// SPDX-License-Identifier: GPL-2.0
+#define _GNU_SOURCE
+#include <errno.h>
+#include <pthread.h>
+#include <sched.h>
+#include <signal.h>
+#include <sys/prctl.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+#include <test_progs.h>
+#include "trampoline_preempt.skel.h"
+
+#define NR_ROUNDS 20
+
+struct ctx {
+	struct trampoline_preempt *skel;
+	int cpu;
+	int go_pipe[2];
+	int done_pipe[2];
+	sigset_t sigset;
+	int err_a;
+	int err_b;
+};
+
+static int pin_to_cpu(int cpu)
+{
+	cpu_set_t set;
+
+	CPU_ZERO(&set);
+	CPU_SET(cpu, &set);
+	if (sched_setaffinity(0, sizeof(set), &set))
+		return -errno;
+	return 0;
+}
+
+static void *worker_a(void *arg)
+{
+	struct ctx *c = arg;
+	char comm[16] = {};
+	char ch = 'a';
+
+	c->err_a = pin_to_cpu(c->cpu);
+	if (!c->err_a && prctl(PR_GET_NAME, comm, 0, 0, 0))
+		c->err_a = -errno;
+	c->skel->bss->tid_a = (int)syscall(SYS_gettid);
+
+	if (write(c->done_pipe[1], &ch, 1) != 1 || c->err_a)
+		goto out;
+
+	while (read(c->go_pipe[0], &ch, 1) == 1) {
+		if (prctl(PR_SET_NAME, comm, 0, 0, 0)) {
+			c->err_a = -errno;
+			break;
+		}
+		if (write(c->done_pipe[1], &ch, 1) != 1) {
+			c->err_a = -EIO;
+			break;
+		}
+	}
+
+out:
+	close(c->done_pipe[1]);
+	c->done_pipe[1] = -1;
+	return NULL;
+}
+
+static void *worker_b(void *arg)
+{
+	struct sched_param sp = { .sched_priority = 1 };
+	struct timespec ts = { .tv_sec = 5 };
+	struct ctx *c = arg;
+	char comm[16] = {};
+	char ch = 'b';
+	int i;
+
+	c->err_b = pin_to_cpu(c->cpu);
+	if (c->err_b)
+		goto out;
+	if (sched_setscheduler(0, SCHED_FIFO, &sp)) {
+		c->err_b = -errno;
+		goto out;
+	}
+	if (prctl(PR_GET_NAME, comm, 0, 0, 0)) {
+		c->err_b = -errno;
+		goto out;
+	}
+	c->skel->bss->tid_b = (int)syscall(SYS_gettid);
+
+	for (i = 0; i < NR_ROUNDS; i++) {
+		if (write(c->go_pipe[1], &ch, 1) != 1) {
+			c->err_b = -EIO;
+			break;
+		}
+		while (sigtimedwait(&c->sigset, NULL, &ts) < 0) {
+			if (errno == EINTR)
+				continue;
+			c->err_b = -errno;
+			break;
+		}
+		if (c->err_b)
+			break;
+		if (prctl(PR_SET_NAME, comm, 0, 0, 0)) {
+			c->err_b = -errno;
+			break;
+		}
+		if (read(c->done_pipe[0], &ch, 1) != 1) {
+			c->err_b = -EIO;
+			break;
+		}
+	}
+
+out:
+	close(c->go_pipe[1]);
+	c->go_pipe[1] = -1;
+	return NULL;
+}
+
+static void run_subtest(bool is_fexit)
+{
+	struct ctx c = {
+		.go_pipe = { -1, -1 },
+		.done_pipe = { -1, -1 },
+	};
+	struct bpf_prog_info info = {};
+	__u32 info_len = sizeof(info);
+	struct bpf_program *prog;
+	struct bpf_link *link = NULL;
+	sigset_t oldset;
+	pthread_t ta, tb;
+	bool ta_started = false, tb_started = false, sigmask_set = false;
+	char ch;
+	int err;
+
+	c.skel = trampoline_preempt__open_and_load();
+	if (!ASSERT_OK_PTR(c.skel, "open_and_load"))
+		return;
+
+	if (c.skel->kconfig->CONFIG_PREEMPT_RT) {
+		test__skip();
+		goto out;
+	}
+
+	c.cpu = sched_getcpu();
+	if (!ASSERT_GE(c.cpu, 0, "sched_getcpu"))
+		goto out;
+
+	if (!ASSERT_OK(pipe(c.go_pipe), "go_pipe"))
+		goto out;
+	if (!ASSERT_OK(pipe(c.done_pipe), "done_pipe"))
+		goto out;
+
+	sigemptyset(&c.sigset);
+	sigaddset(&c.sigset, SIGUSR1);
+	if (!ASSERT_OK(pthread_sigmask(SIG_BLOCK, &c.sigset, &oldset), "sigmask"))
+		goto out;
+	sigmask_set = true;
+
+	c.skel->bss->sig = SIGUSR1;
+	prog = is_fexit ? c.skel->progs.on_fexit : c.skel->progs.on_fentry;
+	link = bpf_program__attach(prog);
+	if (!ASSERT_OK_PTR(link, "attach"))
+		goto out;
+
+	if (!ASSERT_OK(pthread_create(&ta, NULL, worker_a, &c), "pthread_a"))
+		goto out;
+	ta_started = true;
+
+	if (!ASSERT_EQ(read(c.done_pipe[0], &ch, 1), 1, "ready_a"))
+		goto out;
+	if (!ASSERT_OK(c.err_a, "setup_a"))
+		goto out;
+
+	if (!ASSERT_OK(pthread_create(&tb, NULL, worker_b, &c), "pthread_b"))
+		goto out;
+	tb_started = true;
+
+	pthread_join(tb, NULL);
+	tb_started = false;
+	pthread_join(ta, NULL);
+	ta_started = false;
+
+	if (c.err_b == -EPERM || c.err_b == -EACCES) {
+		test__skip();
+		goto out;
+	}
+
+	ASSERT_OK(c.err_a, "worker_a_err");
+	ASSERT_OK(c.err_b, "worker_b_err");
+	ASSERT_EQ(c.skel->bss->sig_err, 0, "sig_err");
+	ASSERT_EQ(c.skel->bss->runs_a, NR_ROUNDS, "runs_a");
+	ASSERT_EQ(c.skel->bss->runs_b, NR_ROUNDS, "runs_b");
+
+	err = bpf_prog_get_info_by_fd(bpf_program__fd(prog), &info, &info_len);
+	if (ASSERT_OK(err, "prog_info"))
+		ASSERT_EQ(info.recursion_misses, 0, "recursion_misses");
+
+out:
+	if (c.go_pipe[1] >= 0)
+		close(c.go_pipe[1]);
+	if (tb_started)
+		pthread_join(tb, NULL);
+	if (ta_started)
+		pthread_join(ta, NULL);
+	if (c.go_pipe[0] >= 0)
+		close(c.go_pipe[0]);
+	if (c.done_pipe[0] >= 0)
+		close(c.done_pipe[0]);
+	if (c.done_pipe[1] >= 0)
+		close(c.done_pipe[1]);
+	if (sigmask_set)
+		pthread_sigmask(SIG_SETMASK, &oldset, NULL);
+	bpf_link__destroy(link);
+	trampoline_preempt__destroy(c.skel);
+}
+
+void test_trampoline_preempt(void)
+{
+	if (test__start_subtest("fentry"))
+		run_subtest(false);
+	if (test__start_subtest("fexit"))
+		run_subtest(true);
+}
diff --git a/tools/testing/selftests/bpf/progs/trampoline_preempt.c b/tools/testing/selftests/bpf/progs/trampoline_preempt.c
new file mode 100644
index 000000000000..851037b33d03
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/trampoline_preempt.c
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+char _license[] SEC("license") = "GPL";
+
+extern bool CONFIG_PREEMPT_RT __kconfig __weak;
+
+struct task_struct *bpf_task_from_pid(s32 pid) __ksym;
+void bpf_task_release(struct task_struct *p) __ksym;
+int bpf_send_signal_task(struct task_struct *task, int sig, enum pid_type type, u64 value) __ksym;
+
+int tid_a;
+int tid_b;
+int sig;
+int runs_a;
+int runs_b;
+int sig_err;
+
+static int handle_comm(void)
+{
+	__u32 tid = (__u32)bpf_get_current_pid_tgid();
+
+	if (CONFIG_PREEMPT_RT)
+		return 0;
+
+	if (tid == tid_a) {
+		struct task_struct *task;
+
+		runs_a++;
+		task = bpf_task_from_pid(tid_b);
+		if (!task) {
+			sig_err = 1;
+			return 0;
+		}
+		if (bpf_send_signal_task(task, sig, PIDTYPE_PID, 0))
+			sig_err = 1;
+		bpf_task_release(task);
+	} else if (tid == tid_b) {
+		runs_b++;
+	}
+	return 0;
+}
+
+SEC("fentry/__set_task_comm")
+int BPF_PROG(on_fentry)
+{
+	return handle_comm();
+}
+
+SEC("fexit/__set_task_comm")
+int BPF_PROG(on_fexit)
+{
+	return handle_comm();
+}

-- 
2.43.0



^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH RFC bpf-next 1/2] bpf: Keep recursion-checked trampoline programs non-preemptible on !PREEMPT_RT
  2026-10-25 11:02 ` [PATCH RFC bpf-next 1/2] bpf: Keep recursion-checked trampoline programs non-preemptible on !PREEMPT_RT Mike Bazov via B4 Relay
@ 2026-10-25 11:38   ` sashiko-bot
  0 siblings, 0 replies; 4+ messages in thread
From: sashiko-bot @ 2026-10-25 11:38 UTC (permalink / raw)
  To: Mike Bazov; +Cc: bpf

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] bpf: multi-line comment style violation in __bpf_prog_enter_recur()
--

commit 264a83620cb7c053d29054a8d674730e0e75ac83
Author: Mike Bazov <mikeba@sentinelone.com>

bpf: Keep recursion-checked trampoline programs non-preemptible on !PREEMPT_RT

This commit disables preemption in __bpf_prog_enter_recur() and
__bpf_prog_exit_recur() when CONFIG_PREEMPT_RT is disabled so that
per-CPU recursion tracking and per-CPU private stacks are not shared
across preempted tasks on the same CPU.

> diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c
> index bf4cb3dd444d6..632c104112b2e 100644
> --- a/kernel/bpf/trampoline.c
> +++ b/kernel/bpf/trampoline.c
> @@ -1339,7 +1339,15 @@ static __always_inline u64 notrace bpf_prog_start_time(void)
>  static u64 notrace __bpf_prog_enter_recur(struct bpf_prog *prog, struct bpf_tramp_run_ctx *run_ctx)
>  	__acquires(RCU)
>  {
> -	rcu_read_lock_dont_migrate();
> +	if (IS_ENABLED(CONFIG_PREEMPT_RT)) {
> +		rcu_read_lock_dont_migrate();
> +	} else {
> +		/* prog->active and the private stack are per-CPU: don't let
> +		 * another task run on this CPU while we hold them.
> +		 */

[Severity: Low]
This isn't a bug, but in __bpf_prog_enter_recur() in
kernel/bpf/trampoline.c, could this multi-line comment place the opening
/* on its own line to follow the BPF subsystem comment style?

		/*
		 * prog->active and the private stack are per-CPU: don't let
		 * another task run on this CPU while we hold them.
		 */

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261025-b4-bpf-trampoline-preempt-v1-0-198dcdc0c6ac@sentinelone.com?part=1

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-10-25 11:38 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-25 11:02 [PATCH RFC bpf-next 0/2] bpf: Do not let preemption cause false recursion drops in trampoline progs Mike Bazov via B4 Relay
2026-10-25 11:02 ` [PATCH RFC bpf-next 1/2] bpf: Keep recursion-checked trampoline programs non-preemptible on !PREEMPT_RT Mike Bazov via B4 Relay
2026-10-25 11:38   ` sashiko-bot
2026-10-25 11:02 ` [PATCH RFC bpf-next 2/2] selftests/bpf: Add trampoline preemption false-recursion test Mike Bazov via B4 Relay

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox