| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/09/11 14:19 | flow | patch-triage |
1mArgs:null Results: {
"EnableConfigs": [
"RCU_TASKS_PREEMPT_QS",
"TASKS_RCU",
"ARCH_HAS_RCU_TASKS_PREEMPT_QS",
"DYNAMIC_FTRACE_WITH_DIRECT_CALLS",
"OPTPROBES",
"BPF_SYSCALL",
"BPF_JIT"
],
"FocusSymbols": [
"rcu_tasks_ip_in_trampoline",
"rcu_tasks_irq_ip_holds",
"arch_rcu_tasks_ip_in_trampoline",
"kprobe_in_optimized_region",
"kprobe_optimizer",
"register_ftrace_direct",
"update_ftrace_direct_add",
"update_ftrace_direct_mod",
"arch_prepare_bpf_trampoline"
],
"KMSANReasoning": "The patch series implements CONFIG_ARCH_HAS_RCU_TASKS_PREEMPT_QS, allowing involuntary context switches outside trampolines to be treated as Tasks-RCU quiescent states. It introduces tracking fields (rcu_tramp_nesting, rcu_tasks_irq_ip) to struct task_struct, updates ftrace, BPF JIT, kprobe optprobes, and irqentry code across x86 and arm64 architectures.\n\nNone of the changes introduce uninitialized memory risks or info-leaks:\n1. No user-space data copying (copy_to_user, netlink, ioctl, etc.) or info-leak channels are modified or created.\n2. The newly added fields in task_struct are explicitly initialized to zero in fork (rcu_copy_process()) and statically initialized for init_task.\n3. No variable-length buffer handling, uninitialized stack variables, or uninitialized heap allocations are read or exposed.\n\nAny potential bugs in this patch would relate to RCU grace period synchronization, CPU preemption handling, or premature trampoline reclamation leading to use-after-free or memory corruption, which are covered by KASAN and standard kernel debugging facilities, not KMSAN. Therefore, a dedicated KMSAN fuzzing session is not required.",
"NeedsKMSAN": false,
"Reasoning": "The patch series introduces CONFIG_RCU_TASKS_PREEMPT_QS (selected via ARCH_HAS_RCU_TASKS_PREEMPT_QS on x86_64), allowing involuntary context switches outside trampolines to serve as Tasks-RCU quiescent states. It modifies core RCU state evaluation, IRQ-entry preemption hooks, BPF trampoline emission, optimized kprobe holdout checks, and dynamic ftrace direct call tracking. These changes modify core synchronization mechanisms in reachable code and warrant fuzzing.",
"WorthFuzzing": true
} |
| 1/1 | 2026/09/11 14:19 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 8f1d757b3e477c6ed5382f9cfd33bf64b258acdc\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Fri Sep 11 14:19:54 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/RCU/Design/Requirements/Requirements.rst b/Documentation/RCU/Design/Requirements/Requirements.rst\nindex 8101fe6229d57..428b5e8f4b4e5 100644\n--- a/Documentation/RCU/Design/Requirements/Requirements.rst\n+++ b/Documentation/RCU/Design/Requirements/Requirements.rst\n@@ -2739,13 +2739,27 @@ userspace execution also delimit tasks-RCU read-side critical sections.\n Idle tasks are ignored by Tasks RCU, and Tasks Rude RCU may be used to\n interact with them.\n \n-Note well that involuntary context switches are *not* Tasks-RCU quiescent\n-states. After all, in preemptible kernels, a task executing code in a\n-trampoline might be preempted. In this case, the Tasks-RCU grace period\n-clearly cannot end until that task resumes and its execution leaves that\n-trampoline. This means, among other things, that cond_resched() does\n-not provide a Tasks RCU quiescent state. (Instead, use rcu_softirq_qs()\n-from softirq or rcu_tasks_classic_qs() otherwise.)\n+Note well that, by default, involuntary context switches are *not*\n+Tasks-RCU quiescent states. After all, in preemptible kernels, a task\n+executing code in a trampoline might be preempted. In this case, the\n+Tasks-RCU grace period clearly cannot end until that task resumes and its\n+execution leaves that trampoline. This means, among other things, that\n+cond_resched() does not provide a Tasks RCU quiescent state. (Instead,\n+use rcu_softirq_qs() from softirq or rcu_tasks_classic_qs() otherwise.)\n+\n+Architectures that select ``CONFIG_ARCH_HAS_RCU_TASKS_PREEMPT_QS`` relax\n+this: there, every trampoline whose lifetime Tasks RCU guards (the ftrace\n+and BPF trampolines, optprobe slots, out-of-line ftrace direct-call\n+trampolines) increments ``current-\u003ercu_tramp_nesting`` before calling out\n+and decrements it before returning, and the irq-exit preemption path\n+covers the few instructions the counter cannot (see\n+rcu_tasks_ip_in_trampoline() and rcu_tasks_irq_ip_holds()). A task that\n+is preempted with that count at zero is therefore known not to be in, or\n+called from, any trampoline, and such a preemption *is* a Tasks-RCU\n+quiescent state. The obligation moves to the trampolines: anything that\n+relies on synchronize_rcu_tasks() to protect code a task may be preempted\n+in must maintain the count (see register_ftrace_direct()), or Tasks RCU\n+will not wait for it on those architectures.\n \n The tasks-RCU API is quite compact, consisting only of\n call_rcu_tasks(), synchronize_rcu_tasks(), and\ndiff --git a/Documentation/RCU/checklist.rst b/Documentation/RCU/checklist.rst\nindex 4b30f701225fd..28df48fecac7f 100644\n--- a/Documentation/RCU/checklist.rst\n+++ b/Documentation/RCU/checklist.rst\n@@ -252,7 +252,13 @@ over a rather long period of time, but improvements are always welcome!\n \ta.\tIf the updater uses synchronize_rcu_tasks() or\n \t\tcall_rcu_tasks(), then the readers must refrain from\n \t\texecuting voluntary context switches, that is, from\n-\t\tblocking.\n+\t\tblocking. On architectures that select\n+\t\tCONFIG_ARCH_HAS_RCU_TASKS_PREEMPT_QS an involuntary\n+\t\tcontext switch is also a quiescent state unless\n+\t\tcurrent-\u003ercu_tramp_nesting is non-zero, so a reader\n+\t\tthere is a trampoline that maintains that count (see\n+\t\trcu_tasks_trampoline_enter()), not an arbitrary\n+\t\tstretch of kernel code.\n \n \tb.\tIf the updater uses call_rcu_tasks_trace()\n \t\tor synchronize_rcu_tasks_trace(), then the\ndiff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig\nindex b5a51b0ef9440..0e6c1e0b236f9 100644\n--- a/arch/arm64/Kconfig\n+++ b/arch/arm64/Kconfig\n@@ -44,6 +44,7 @@ config ARM64\n \tselect ARCH_HAS_PREEMPT_LAZY\n \tselect ARCH_HAS_PTDUMP\n \tselect ARCH_HAS_PTE_SPECIAL\n+\tselect ARCH_HAS_RCU_TASKS_PREEMPT_QS if DYNAMIC_FTRACE_WITH_ARGS\n \tselect ARCH_HAS_HW_PTE_YOUNG\n \tselect ARCH_HAS_SETUP_DMA_OPS\n \tselect ARCH_HAS_SET_DIRECT_MAP\ndiff --git a/arch/arm64/kernel/asm-offsets.c b/arch/arm64/kernel/asm-offsets.c\nindex 9c853ed3ceab8..f6655a284f183 100644\n--- a/arch/arm64/kernel/asm-offsets.c\n+++ b/arch/arm64/kernel/asm-offsets.c\n@@ -39,6 +39,9 @@ int main(void)\n DEFINE(TSK_STACK,\t\toffsetof(struct task_struct, stack));\n #ifdef CONFIG_STACKPROTECTOR\n DEFINE(TSK_STACK_CANARY,\toffsetof(struct task_struct, stack_canary));\n+#endif\n+#ifdef CONFIG_TASKS_RCU\n+ DEFINE(TSK_RCU_TRAMP_NESTING,\toffsetof(struct task_struct, rcu_tramp_nesting));\n #endif\n BLANK();\n DEFINE(THREAD_CPU_CONTEXT,\toffsetof(struct task_struct, thread.cpu_context));\ndiff --git a/arch/arm64/kernel/entry-ftrace.S b/arch/arm64/kernel/entry-ftrace.S\nindex 025140caafe74..46a102e7199ac 100644\n--- a/arch/arm64/kernel/entry-ftrace.S\n+++ b/arch/arm64/kernel/entry-ftrace.S\n@@ -14,6 +14,33 @@\n #include \u003casm/insn.h\u003e\n \n #ifdef CONFIG_DYNAMIC_FTRACE_WITH_ARGS\n+/*\n+ * Tasks RCU trampoline nesting, see rcu_tasks_trampoline_enter(). The whole\n+ * of ftrace_caller is treated as trampoline text by the irq-exit IP check (see\n+ * arch_rcu_tasks_ip_in_trampoline()), so these only need to bracket the call\n+ * out to ops-\u003efunc; everything before the increment and after the decrement,\n+ * including the direct-call tails that carry a BPF trampoline address in x17,\n+ * is covered by that. The count is only modified by current and every nested\n+ * user (interrupts) is balanced, so a plain ldr/add/str is sufficient.\n+ */\n+\t.macro rcu_tasks_tramp_enter, tsk:req, tmp:req\n+#ifdef CONFIG_TASKS_RCU\n+\tmrs\t\\tsk, sp_el0\n+\tldr\t\\tmp, [\\tsk, #TSK_RCU_TRAMP_NESTING]\n+\tadd\t\\tmp, \\tmp, #1\n+\tstr\t\\tmp, [\\tsk, #TSK_RCU_TRAMP_NESTING]\n+#endif\n+\t.endm\n+\n+\t.macro rcu_tasks_tramp_exit, tsk:req, tmp:req\n+#ifdef CONFIG_TASKS_RCU\n+\tmrs\t\\tsk, sp_el0\n+\tldr\t\\tmp, [\\tsk, #TSK_RCU_TRAMP_NESTING]\n+\tsub\t\\tmp, \\tmp, #1\n+\tstr\t\\tmp, [\\tsk, #TSK_RCU_TRAMP_NESTING]\n+#endif\n+\t.endm\n+\n /*\n * Due to -fpatchable-function-entry=2, the compiler has placed two NOPs before\n * the regular function prologue. For an enabled callsite, ftrace_init_nop() and\n@@ -94,6 +121,8 @@ SYM_CODE_START(ftrace_caller)\n \tstp\tx29, x30, [sp, #FREGS_SIZE]\n \tadd\tx29, sp, #FREGS_SIZE\n \n+\trcu_tasks_tramp_enter x12, w13\n+\n \t/* Prepare arguments for the tracer func */\n \tsub\tx0, x30, #AARCH64_INSN_SIZE\t\t// ip (callsite's BL insn)\n \tmov\tx1, x9\t\t\t\t\t// parent_ip (callsite's LR)\n@@ -111,6 +140,8 @@ SYM_INNER_LABEL(ftrace_call, SYM_L_GLOBAL)\n \tbl ftrace_stub\t\t\t\t// func(ip, parent_ip, op, regs)\n #endif\n \n+\trcu_tasks_tramp_exit x12, w13\n+\n /*\n * At the callsite x0-x8 and x19-x30 were live. Any C code will have preserved\n * x19-x29 per the AAPCS, and we created frame records upon entry, so we need\n@@ -178,6 +209,10 @@ SYM_CODE_START(ftrace_stub_direct_tramp)\n SYM_CODE_END(ftrace_stub_direct_tramp)\n #endif /* CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS */\n \n+/* End of [ftrace_caller, ...) for arch_rcu_tasks_ip_in_trampoline(). */\n+SYM_CODE_START(ftrace_static_tramp_end)\n+SYM_CODE_END(ftrace_static_tramp_end)\n+\n #else /* CONFIG_DYNAMIC_FTRACE_WITH_ARGS */\n \n /*\ndiff --git a/arch/arm64/kernel/ftrace.c b/arch/arm64/kernel/ftrace.c\nindex e1a3c0b3a0514..1b7ac2afed0df 100644\n--- a/arch/arm64/kernel/ftrace.c\n+++ b/arch/arm64/kernel/ftrace.c\n@@ -17,6 +17,22 @@\n #include \u003casm/insn.h\u003e\n #include \u003casm/text-patching.h\u003e\n \n+#ifdef CONFIG_RCU_TASKS_PREEMPT_QS\n+extern void ftrace_static_tramp_end(void);\n+\n+/*\n+ * See rcu_tasks_ip_in_trampoline(). ftrace_caller and ftrace_stub_direct_tramp\n+ * are core kernel text but must be treated as trampolines: a task preempted in\n+ * them may be carrying an ops pointer (x11) or a direct-call BPF trampoline\n+ * address (x17) whose lifetime is guarded only by Tasks RCU.\n+ */\n+bool arch_rcu_tasks_ip_in_trampoline(unsigned long ip)\n+{\n+\treturn ip \u003e= (unsigned long)ftrace_caller \u0026\u0026\n+\t ip \u003c (unsigned long)ftrace_static_tramp_end;\n+}\n+#endif\n+\n #ifdef CONFIG_DYNAMIC_FTRACE_WITH_ARGS\n struct fregs_offset {\n \tconst char *name;\ndiff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c\nindex c18e005a41dbe..5c9a7bde5cc9c 100644\n--- a/arch/arm64/net/bpf_jit_comp.c\n+++ b/arch/arm64/net/bpf_jit_comp.c\n@@ -2591,6 +2591,34 @@ static void emit_arena_arg_conv(struct jit_ctx *ctx, u8 dst, u8 src, bool nullab\n \temit(A64_SUB(0, dst, src, base_lo), ctx);\n }\n \n+/*\n+ * Tasks RCU trampoline nesting, see rcu_tasks_trampoline_enter().\n+ *\n+ * mrs x10, sp_el0\n+ * ldr w11, [x10, #offsetof(struct task_struct, rcu_tramp_nesting)]\n+ * add/sub w11, w11, #1\n+ * str w11, [x10, #...]\n+ *\n+ * x10/x11 are scratch in the trampoline at every point this is emitted.\n+ */\n+static void emit_rcu_tasks_tramp_nesting(struct jit_ctx *ctx, bool enter)\n+{\n+#ifdef CONFIG_TASKS_RCU\n+\tconst int off = offsetof(struct task_struct, rcu_tramp_nesting);\n+\tconst u8 tsk = A64_R(10), cnt = A64_R(11);\n+\n+\tBUILD_BUG_ON(off \u0026 3 || off \u003e= SZ_16K);\t/* LDR/STR (imm12, scaled) */\n+\n+\temit(A64_MRS_SP_EL0(tsk), ctx);\n+\temit(A64_LDR32I(cnt, tsk, off), ctx);\n+\tif (enter)\n+\t\temit(A64_ADD_I(0, cnt, cnt, 1), ctx);\n+\telse\n+\t\temit(A64_SUB_I(0, cnt, cnt, 1), ctx);\n+\temit(A64_STR32I(cnt, tsk, off), ctx);\n+#endif\n+}\n+\n static void save_args(struct jit_ctx *ctx, int bargs_off, int oargs_off,\n \t\t const struct btf_func_model *m, const struct arg_aux *a,\n \t\t bool for_call_origin, bool is_struct_ops, u64 arena_base)\n@@ -2854,6 +2882,13 @@ static int prepare_trampoline(struct jit_ctx *ctx, struct bpf_tramp_image *im,\n \temit(A64_STR64I(A64_R(19), A64_SP, regs_off), ctx);\n \temit(A64_STR64I(A64_R(20), A64_SP, regs_off + 8), ctx);\n \n+\t/*\n+\t * From here until the matching decrement in the epilogue, a preemption\n+\t * of this task is not a Tasks RCU quiescent state. The instructions\n+\t * above this point are covered by the irq-exit IP check.\n+\t */\n+\temit_rcu_tasks_tramp_nesting(ctx, true);\n+\n \tif (flags \u0026 BPF_TRAMP_F_CALL_ORIG) {\n \t\t/* for the first pass, assume the worst case */\n \t\tif (!ctx-\u003eimage)\n@@ -2898,12 +2933,20 @@ static int prepare_trampoline(struct jit_ctx *ctx, struct bpf_tramp_image *im,\n \tif (flags \u0026 BPF_TRAMP_F_CALL_ORIG) {\n \t\t/* the original func takes kernel addresses, never converted ones */\n \t\tsave_args(ctx, bargs_off, oargs_off, m, a, true, is_struct_ops, 0);\n+\t\t/*\n+\t\t * The original function may run for a long time without\n+\t\t * sleeping; do not let it pin a Tasks RCU grace period. The\n+\t\t * trampoline frame above it is held by im-\u003epcref\n+\t\t * (__bpf_tramp_enter()), not by Tasks RCU, across the call.\n+\t\t */\n+\t\temit_rcu_tasks_tramp_nesting(ctx, false);\n \t\t/* call original func */\n \t\temit(A64_LDR64I(A64_R(10), A64_SP, retaddr_off), ctx);\n \t\temit(A64_ADR(A64_LR, AARCH64_INSN_SIZE * 2), ctx);\n \t\temit(A64_RET(A64_R(10)), ctx);\n \t\t/* store return value */\n \t\temit(A64_STR64I(A64_R(0), A64_SP, retval_off), ctx);\n+\t\temit_rcu_tasks_tramp_nesting(ctx, true);\n \t\t/* reserve a nop for bpf_tramp_image_put */\n \t\tim-\u003eip_after_call = ctx-\u003ero_image + ctx-\u003eidx;\n \t\temit(A64_NOP, ctx);\n@@ -2945,6 +2988,9 @@ static int prepare_trampoline(struct jit_ctx *ctx, struct bpf_tramp_image *im,\n \tif (flags \u0026 BPF_TRAMP_F_RESTORE_REGS)\n \t\trestore_args(ctx, bargs_off, a-\u003eregs_for_args);\n \n+\t/* Remaining instructions are covered by the irq-exit IP check. */\n+\temit_rcu_tasks_tramp_nesting(ctx, false);\n+\n \t/* restore callee saved register x19 and x20 */\n \temit(A64_LDR64I(A64_R(19), A64_SP, regs_off), ctx);\n \temit(A64_LDR64I(A64_R(20), A64_SP, regs_off + 8), ctx);\ndiff --git a/arch/x86/Kconfig b/arch/x86/Kconfig\nindex 15fd9ec5ecacb..0a64270193454 100644\n--- a/arch/x86/Kconfig\n+++ b/arch/x86/Kconfig\n@@ -99,6 +99,7 @@ config X86\n \tselect ARCH_HAS_PREEMPT_LAZY\n \tselect ARCH_HAS_PTDUMP\n \tselect ARCH_HAS_PTE_SPECIAL\n+\tselect ARCH_HAS_RCU_TASKS_PREEMPT_QS\tif X86_64\n \tselect ARCH_HAS_HW_PTE_YOUNG\n \tselect ARCH_HAS_NONLEAF_PMD_YOUNG\tif PGTABLE_LEVELS \u003e 2\n \tselect ARCH_HAS_UACCESS_FLUSHCACHE\tif X86_64\ndiff --git a/arch/x86/kernel/asm-offsets.c b/arch/x86/kernel/asm-offsets.c\nindex 081816888f7a0..4f3b1caa5a309 100644\n--- a/arch/x86/kernel/asm-offsets.c\n+++ b/arch/x86/kernel/asm-offsets.c\n@@ -46,6 +46,9 @@ static void __used common(void)\n #ifdef CONFIG_STACKPROTECTOR\n \tOFFSET(TASK_stack_canary, task_struct, stack_canary);\n #endif\n+#ifdef CONFIG_TASKS_RCU\n+\tOFFSET(TASK_rcu_tramp_nesting, task_struct, rcu_tramp_nesting);\n+#endif\n \n \tBLANK();\n \tOFFSET(pbe_address, pbe, address);\ndiff --git a/arch/x86/kernel/ftrace.c b/arch/x86/kernel/ftrace.c\nindex 17d6edfcb7e00..8f63cd4b543c7 100644\n--- a/arch/x86/kernel/ftrace.c\n+++ b/arch/x86/kernel/ftrace.c\n@@ -275,6 +275,43 @@ static inline void tramp_free(void *tramp)\n \texecmem_free(tramp);\n }\n \n+#ifdef CONFIG_RCU_TASKS_PREEMPT_QS\n+extern void ftrace_static_tramp_end(void);\n+extern char __return_thunk_start[], __return_thunk_end[];\n+extern char __rethunk_safe_start[], __rethunk_safe_end[];\n+\n+/*\n+ * See rcu_tasks_ip_in_trampoline(). Some core kernel text behaves like a\n+ * trampoline for Tasks RCU purposes because a task executing there with\n+ * rcu_tramp_nesting == 0 may still be about to enter a Tasks-RCU-protected\n+ * trampoline whose address it already holds:\n+ *\n+ * - the static ftrace_caller / ftrace_regs_caller / ftrace_stub_direct_tramp\n+ * stubs, which carry a direct-call target on the stack until their final\n+ * RET, and\n+ * - the return thunks that RET expands to under CONFIG_MITIGATION_RETHUNK,\n+ * which run after leaving the stubs above and before landing in that\n+ * target.\n+ */\n+bool arch_rcu_tasks_ip_in_trampoline(unsigned long ip)\n+{\n+\tif (ip \u003e= (unsigned long)ftrace_caller \u0026\u0026\n+\t ip \u003c (unsigned long)ftrace_static_tramp_end)\n+\t\treturn true;\n+#ifdef CONFIG_MITIGATION_RETPOLINE\n+\tif (ip \u003e= (unsigned long)__return_thunk_start \u0026\u0026\n+\t ip \u003c (unsigned long)__return_thunk_end)\n+\t\treturn true;\n+#endif\n+#ifdef CONFIG_MITIGATION_SRSO\n+\tif (ip \u003e= (unsigned long)__rethunk_safe_start \u0026\u0026\n+\t ip \u003c (unsigned long)__rethunk_safe_end)\n+\t\treturn true;\n+#endif\n+\treturn false;\n+}\n+#endif /* CONFIG_RCU_TASKS_PREEMPT_QS */\n+\n /* Defined as markers to the end of the ftrace default trampolines */\n extern void ftrace_regs_caller_end(void);\n extern void ftrace_caller_end(void);\ndiff --git a/arch/x86/kernel/ftrace_64.S b/arch/x86/kernel/ftrace_64.S\nindex 62c1c93aa1c6a..902472c41798b 100644\n--- a/arch/x86/kernel/ftrace_64.S\n+++ b/arch/x86/kernel/ftrace_64.S\n@@ -7,6 +7,7 @@\n #include \u003clinux/cfi_types.h\u003e\n #include \u003clinux/linkage.h\u003e\n #include \u003casm/asm-offsets.h\u003e\n+#include \u003casm/percpu.h\u003e\n #include \u003casm/ptrace.h\u003e\n #include \u003casm/ftrace.h\u003e\n #include \u003casm/nospec-branch.h\u003e\n@@ -145,6 +146,27 @@ SYM_FUNC_END(ftrace_stub_graph)\n \n #ifdef CONFIG_DYNAMIC_FTRACE\n \n+/*\n+ * Tasks RCU trampoline nesting, see rcu_tasks_trampoline_enter(). These live\n+ * inside the region copied into dynamic trampolines; the %rip-relative per-CPU\n+ * reference is fixed up by text_poke_apply_relocation() in create_trampoline().\n+ * The increment must precede the function_trace_op load: between that load and\n+ * the call, the ops pointer in %rdx is protected only by Tasks RCU.\n+ */\n+.macro RCU_TASKS_TRAMP_ENTER reg:req\n+#ifdef CONFIG_TASKS_RCU\n+\tmovq PER_CPU_VAR(current_task), \\reg\n+\tincl TASK_rcu_tramp_nesting(\\reg)\n+#endif\n+.endm\n+\n+.macro RCU_TASKS_TRAMP_EXIT reg:req\n+#ifdef CONFIG_TASKS_RCU\n+\tmovq PER_CPU_VAR(current_task), \\reg\n+\tdecl TASK_rcu_tramp_nesting(\\reg)\n+#endif\n+.endm\n+\n SYM_FUNC_START(__fentry__)\n \tANNOTATE_NOENDBR\n \tCALL_DEPTH_ACCOUNT\n@@ -163,6 +185,8 @@ SYM_FUNC_START(ftrace_caller)\n \tleaq MCOUNT_REG_SIZE+8(%rsp), %rcx\n \tmovq %rcx, RSP(%rsp)\n \n+\tRCU_TASKS_TRAMP_ENTER %rdx\n+\n SYM_INNER_LABEL(ftrace_caller_op_ptr, SYM_L_GLOBAL)\n \tANNOTATE_NOENDBR\n \t/* Load the ftrace_ops into the 3rd parameter */\n@@ -181,6 +205,8 @@ SYM_INNER_LABEL(ftrace_call, SYM_L_GLOBAL)\n \tANNOTATE_NOENDBR\n \tcall ftrace_stub\n \n+\tRCU_TASKS_TRAMP_EXIT %rdx\n+\n \t/* Handlers can change the RIP */\n \tmovq RIP(%rsp), %rax\n \tmovq %rax, MCOUNT_REG_SIZE(%rsp)\n@@ -209,6 +235,8 @@ SYM_FUNC_START(ftrace_regs_caller)\n \n \tCALL_DEPTH_ACCOUNT\n \n+\tRCU_TASKS_TRAMP_ENTER %rdx\n+\n SYM_INNER_LABEL(ftrace_regs_caller_op_ptr, SYM_L_GLOBAL)\n \tANNOTATE_NOENDBR\n \t/* Load the ftrace_ops into the 3rd parameter */\n@@ -246,6 +274,8 @@ SYM_INNER_LABEL(ftrace_regs_call, SYM_L_GLOBAL)\n \tANNOTATE_NOENDBR\n \tcall ftrace_stub\n \n+\tRCU_TASKS_TRAMP_EXIT %rdx\n+\n \t/* Copy flags back to SS, to restore them */\n \tmovq EFLAGS(%rsp), %rax\n \tmovq %rax, MCOUNT_REG_SIZE(%rsp)\n@@ -328,6 +358,19 @@ SYM_FUNC_START(ftrace_stub_direct_tramp)\n \tRET\n SYM_FUNC_END(ftrace_stub_direct_tramp)\n \n+/*\n+ * [ftrace_caller, ftrace_static_tramp_end) is treated as trampoline text by\n+ * rcu_tasks_ip_in_trampoline(): after RCU_TASKS_TRAMP_EXIT the stubs may\n+ * still hold a direct-call target (a BPF trampoline) on the stack until the\n+ * final RET, and that target's lifetime is guarded by Tasks RCU. With\n+ * return thunks the RET itself runs elsewhere; arch_rcu_tasks_ip_in_trampoline()\n+ * covers the thunk text too.\n+ */\n+SYM_CODE_START_NOALIGN(ftrace_static_tramp_end)\n+\tUNWIND_HINT_UNDEFINED\n+\tANNOTATE_NOENDBR\n+SYM_CODE_END(ftrace_static_tramp_end)\n+\n #else /* ! CONFIG_DYNAMIC_FTRACE */\n \n SYM_FUNC_START(__fentry__)\ndiff --git a/arch/x86/kernel/kprobes/opt.c b/arch/x86/kernel/kprobes/opt.c\nindex 3f8fea52619f9..f722520bb9891 100644\n--- a/arch/x86/kernel/kprobes/opt.c\n+++ b/arch/x86/kernel/kprobes/opt.c\n@@ -31,6 +31,7 @@\n #include \u003casm/set_memory.h\u003e\n #include \u003casm/sections.h\u003e\n #include \u003casm/nospec-branch.h\u003e\n+#include \u003casm/asm-offsets.h\u003e\n \n #include \"common.h\"\n \n@@ -101,6 +102,23 @@ static void synthesize_set_arg1(kprobe_opcode_t *addr, unsigned long val)\n \t*(unsigned long *)addr = val;\n }\n \n+/*\n+ * Tasks RCU trampoline nesting, see rcu_tasks_trampoline_enter(). The\n+ * template is memcpy()d into the slot without relocation processing, so the\n+ * per-CPU reference must be absolute, not %rip-relative.\n+ */\n+#if defined(CONFIG_TASKS_RCU) \u0026\u0026 defined(CONFIG_X86_64)\n+#define OPTPROBE_RCU_TASKS_ENTER\t\t\t\t\t\\\n+\t\t\t\"\tmovq %gs:current_task, %rax\\n\"\t\t\\\n+\t\t\t\"\tincl \" __stringify(TASK_rcu_tramp_nesting) \"(%rax)\\n\"\n+#define OPTPROBE_RCU_TASKS_EXIT\t\t\t\t\t\\\n+\t\t\t\"\tmovq %gs:current_task, %rax\\n\"\t\t\\\n+\t\t\t\"\tdecl \" __stringify(TASK_rcu_tramp_nesting) \"(%rax)\\n\"\n+#else\n+#define OPTPROBE_RCU_TASKS_ENTER\n+#define OPTPROBE_RCU_TASKS_EXIT\n+#endif\n+\n asm (\n \t\t\t\".pushsection .rodata\\n\"\n \t\t\t\".global optprobe_template_entry\\n\"\n@@ -114,6 +132,7 @@ asm (\n \t\t\t\"optprobe_template_clac:\\n\"\n \t\t\tASM_NOP3\n \t\t\tSAVE_REGS_STRING\n+\t\t\tOPTPROBE_RCU_TASKS_ENTER\n \t\t\t\"\tmovq %rsp, %rsi\\n\"\n \t\t\t\".global optprobe_template_val\\n\"\n \t\t\t\"optprobe_template_val:\\n\"\n@@ -122,6 +141,7 @@ asm (\n \t\t\t\".global optprobe_template_call\\n\"\n \t\t\t\"optprobe_template_call:\\n\"\n \t\t\tASM_NOP5\n+\t\t\tOPTPROBE_RCU_TASKS_EXIT\n \t\t\t/* Copy 'regs-\u003eflags' into 'regs-\u003ess'. */\n \t\t\t\"\tmovq 18*8(%rsp), %rdx\\n\"\n \t\t\t\"\tmovq %rdx, 20*8(%rsp)\\n\"\ndiff --git a/arch/x86/kernel/vmlinux.lds.S b/arch/x86/kernel/vmlinux.lds.S\nindex 2438b89a4620a..e546283dc2679 100644\n--- a/arch/x86/kernel/vmlinux.lds.S\n+++ b/arch/x86/kernel/vmlinux.lds.S\n@@ -151,7 +151,9 @@ SECTIONS\n \t\t * definition.\n \t\t */\n \t\t. = srso_alias_untrain_ret | (1 \u003c\u003c 2) | (1 \u003c\u003c 8) | (1 \u003c\u003c 14) | (1 \u003c\u003c 20);\n+\t\t__rethunk_safe_start = .;\n \t\t*(.text..__x86.rethunk_safe)\n+\t\t__rethunk_safe_end = .;\n #endif\n \t\tALIGN_ENTRY_TEXT_END\n \n@@ -162,7 +164,9 @@ SECTIONS\n \t\tSOFTIRQENTRY_TEXT\n #ifdef CONFIG_MITIGATION_RETPOLINE\n \t\t*(.text..__x86.indirect_thunk)\n+\t\t__return_thunk_start = .;\n \t\t*(.text..__x86.return_thunk)\n+\t\t__return_thunk_end = .;\n #endif\n \t\tSTATIC_CALL_TEXT\n \t\t*(.gnu.warning)\ndiff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c\nindex 2853e87797a78..a375c1b7bd50c 100644\n--- a/arch/x86/net/bpf_jit_comp.c\n+++ b/arch/x86/net/bpf_jit_comp.c\n@@ -722,6 +722,31 @@ static void emit_indirect_jump(u8 **pprog, int bpf_reg, u8 *ip)\n \t*pprog = prog;\n }\n \n+/*\n+ * Tasks RCU trampoline nesting, see rcu_tasks_trampoline_enter().\n+ *\n+ * mov r11, QWORD PTR gs:[current_task]\n+ * inc/dec DWORD PTR [r11 + offsetof(struct task_struct, rcu_tramp_nesting)]\n+ *\n+ * r11 (AUX_REG) is scratch in the trampoline at every point this is emitted.\n+ */\n+static void emit_rcu_tasks_tramp_nesting(u8 **pprog, bool enter)\n+{\n+#ifdef CONFIG_TASKS_RCU\n+\tu8 *prog = *pprog;\n+\n+\t/* mov r11, gs:[abs32] */\n+\tEMIT2(0x65, 0x4C);\n+\tEMIT3(0x8B, 0x1C, 0x25);\n+\tEMIT((u32)(unsigned long)\u0026current_task, 4);\n+\t/* inc/dec dword ptr [r11 + disp32] */\n+\tEMIT3(0x41, 0xFF, enter ? 0x83 : 0x8B);\n+\tEMIT(offsetof(struct task_struct, rcu_tramp_nesting), 4);\n+\n+\t*pprog = prog;\n+#endif\n+}\n+\n static void emit_return(u8 **pprog, u8 *ip)\n {\n \tu8 *prog = *pprog;\n@@ -3610,6 +3635,13 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im\n \t/* mov QWORD PTR [rbp - rbx_off], rbx */\n \temit_stx(\u0026prog, BPF_DW, BPF_REG_FP, BPF_REG_6, -rbx_off);\n \n+\t/*\n+\t * From here until the matching decrement before the final return, a\n+\t * preemption of this task is not a Tasks RCU quiescent state. The\n+\t * instructions above this point are covered by the irq-exit IP check.\n+\t */\n+\temit_rcu_tasks_tramp_nesting(\u0026prog, true);\n+\n \tfunc_meta = nr_regs;\n \t/* Store number of argument registers of the traced function */\n \temit_store_stack_imm64(\u0026prog, BPF_REG_0, -func_meta_off, func_meta);\n@@ -3670,6 +3702,13 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im\n \t\t\tLOAD_TRAMP_TAIL_CALL_CNT_PTR(stack_size);\n \t\t}\n \n+\t\t/*\n+\t\t * The original function may run for a long time without\n+\t\t * sleeping; do not let it pin a Tasks RCU grace period. The\n+\t\t * trampoline frame above it is held by im-\u003epcref\n+\t\t * (__bpf_tramp_enter()), not by Tasks RCU, across the call.\n+\t\t */\n+\t\temit_rcu_tasks_tramp_nesting(\u0026prog, false);\n \t\tif (flags \u0026 BPF_TRAMP_F_ORIG_STACK) {\n \t\t\temit_ldx(\u0026prog, BPF_DW, BPF_REG_6, BPF_REG_FP, 8);\n \t\t\tEMIT2(0xff, 0xd3); /* call *rbx */\n@@ -3680,6 +3719,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im\n \t\t\t\tgoto cleanup;\n \t\t\t}\n \t\t}\n+\t\temit_rcu_tasks_tramp_nesting(\u0026prog, true);\n \t\t/* remember return value in a stack for bpf prog to access */\n \t\temit_stx(\u0026prog, BPF_DW, BPF_REG_FP, BPF_REG_0, -8);\n \t\tim-\u003eip_after_call = image + (prog - (u8 *)rw_image);\n@@ -3741,6 +3781,9 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im\n \tif (save_ret)\n \t\temit_ldx(\u0026prog, BPF_DW, BPF_REG_0, BPF_REG_FP, -8);\n \n+\t/* Remaining instructions are covered by the irq-exit IP check. */\n+\temit_rcu_tasks_tramp_nesting(\u0026prog, false);\n+\n \temit_ldx(\u0026prog, BPF_DW, BPF_REG_6, BPF_REG_FP, -rbx_off);\n \n \tEMIT1(0xC9); /* leave */\ndiff --git a/arch/x86/xen/enlighten_pv.c b/arch/x86/xen/enlighten_pv.c\nindex 2c64b388f6164..3d85035f5624f 100644\n--- a/arch/x86/xen/enlighten_pv.c\n+++ b/arch/x86/xen/enlighten_pv.c\n@@ -739,7 +739,7 @@ __visible noinstr void xen_pv_evtchn_do_upcall(struct pt_regs *regs)\n \n \tinhcall = get_and_clear_inhcall();\n \tif (inhcall \u0026\u0026 !WARN_ON_ONCE(state.exit_rcu)) {\n-\t\tirqentry_exit_cond_resched();\n+\t\tirqentry_exit_cond_resched(regs);\n \t\tinstrumentation_end();\n \t\trestore_inhcall(inhcall);\n \t} else {\ndiff --git a/include/linux/irq-entry-common.h b/include/linux/irq-entry-common.h\nindex 0bb6c03481fad..fc04725ae46b6 100644\n--- a/include/linux/irq-entry-common.h\n+++ b/include/linux/irq-entry-common.h\n@@ -5,6 +5,7 @@\n #include \u003clinux/context_tracking.h\u003e\n #include \u003clinux/hrtimer_rearm.h\u003e\n #include \u003clinux/kmsan.h\u003e\n+#include \u003clinux/rcupdate.h\u003e\n #include \u003clinux/rseq_entry.h\u003e\n #include \u003clinux/static_call_types.h\u003e\n #include \u003clinux/syscalls.h\u003e\n@@ -214,6 +215,7 @@ static __always_inline void __exit_to_user_mode_validate(void)\n {\n \t/* Ensure that kernel state is sane for a return to userspace */\n \tkmap_assert_nomap();\n+\trcu_tasks_trampoline_assert_none();\n \tlockdep_assert_irqs_disabled();\n \tlockdep_sys_exit();\n }\n@@ -346,21 +348,21 @@ typedef struct irqentry_state {\n *\n * Conditional reschedule with additional sanity checks.\n */\n-void raw_irqentry_exit_cond_resched(void);\n+void raw_irqentry_exit_cond_resched(struct pt_regs *regs);\n \n #ifdef CONFIG_PREEMPT_DYNAMIC\n #if defined(CONFIG_HAVE_PREEMPT_DYNAMIC_CALL)\n #define irqentry_exit_cond_resched_dynamic_enabled\traw_irqentry_exit_cond_resched\n #define irqentry_exit_cond_resched_dynamic_disabled\tNULL\n DECLARE_STATIC_CALL(irqentry_exit_cond_resched, raw_irqentry_exit_cond_resched);\n-#define irqentry_exit_cond_resched()\tstatic_call(irqentry_exit_cond_resched)()\n+#define irqentry_exit_cond_resched(regs)\tstatic_call(irqentry_exit_cond_resched)(regs)\n #elif defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY)\n DECLARE_STATIC_KEY_TRUE(sk_dynamic_irqentry_exit_cond_resched);\n-void dynamic_irqentry_exit_cond_resched(void);\n-#define irqentry_exit_cond_resched()\tdynamic_irqentry_exit_cond_resched()\n+void dynamic_irqentry_exit_cond_resched(struct pt_regs *regs);\n+#define irqentry_exit_cond_resched(regs)\tdynamic_irqentry_exit_cond_resched(regs)\n #endif\n #else /* CONFIG_PREEMPT_DYNAMIC */\n-#define irqentry_exit_cond_resched()\traw_irqentry_exit_cond_resched()\n+#define irqentry_exit_cond_resched(regs)\traw_irqentry_exit_cond_resched(regs)\n #endif /* CONFIG_PREEMPT_DYNAMIC */\n \n /**\n@@ -465,7 +467,7 @@ static inline void irqentry_exit_to_kernel_mode_preempt(struct pt_regs *regs,\n \t\treturn;\n \n \tif (IS_ENABLED(CONFIG_PREEMPTION))\n-\t\tirqentry_exit_cond_resched();\n+\t\tirqentry_exit_cond_resched(regs);\n }\n \n /**\ndiff --git a/include/linux/kprobes.h b/include/linux/kprobes.h\nindex e6de7ae55bda0..74cc48c044173 100644\n--- a/include/linux/kprobes.h\n+++ b/include/linux/kprobes.h\n@@ -530,11 +530,17 @@ static inline bool is_kprobe_insn_slot(unsigned long addr)\n }\n #endif /* !CONFIG_KPROBES */\n \n-#ifndef CONFIG_OPTPROBES\n+#ifdef CONFIG_OPTPROBES\n+bool kprobe_in_optimized_region(unsigned long addr);\n+#else /* !CONFIG_OPTPROBES */\n static inline bool is_kprobe_optinsn_slot(unsigned long addr)\n {\n \treturn false;\n }\n+static inline bool kprobe_in_optimized_region(unsigned long addr)\n+{\n+\treturn false;\n+}\n #endif /* !CONFIG_OPTPROBES */\n \n #ifdef CONFIG_KRETPROBES\ndiff --git a/include/linux/module.h b/include/linux/module.h\nindex 96cc98568eea5..ea4727f53fabf 100644\n--- a/include/linux/module.h\n+++ b/include/linux/module.h\n@@ -521,6 +521,13 @@ struct module {\n \tunsigned int num_ftrace_callsites;\n \tunsigned long *ftrace_callsites;\n #endif\n+#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS\n+\t/*\n+\t * An ftrace direct-call trampoline lives in this module's text; see\n+\t * rcu_tasks_ip_in_trampoline(). Sticky once set.\n+\t */\n+\tbool ftrace_direct_tramp;\n+#endif\n #ifdef CONFIG_KPROBES\n \tvoid *kprobes_text_start;\n \tunsigned int kprobes_text_size;\ndiff --git a/include/linux/rcupdate.h b/include/linux/rcupdate.h\nindex 44c07a66edfff..9509f99ec965f 100644\n--- a/include/linux/rcupdate.h\n+++ b/include/linux/rcupdate.h\n@@ -173,6 +173,9 @@ static inline void rcu_nocb_flush_deferred_wakeup(void) { }\n \n #endif /* #else #ifdef CONFIG_RCU_NOCB_CPU */\n \n+/* Arch hook for rcu_tasks_ip_in_trampoline(); see kernel/rcu/tasks.h. */\n+bool arch_rcu_tasks_ip_in_trampoline(unsigned long ip);\n+\n /*\n * Note a quasi-voluntary context switch for RCU-tasks's benefit.\n * This is a macro rather than an inline function to avoid #include hell.\n@@ -180,9 +183,80 @@ static inline void rcu_nocb_flush_deferred_wakeup(void) { }\n #ifdef CONFIG_TASKS_RCU_GENERIC\n \n # ifdef CONFIG_TASKS_RCU\n+\n+/*\n+ * Trampoline nesting: dynamically allocated text (ftrace trampolines, BPF\n+ * trampoline images, kprobe optinsn slots) that relies on Tasks RCU for its\n+ * lifetime brackets itself with an increment/decrement of\n+ * current-\u003ercu_tramp_nesting. While the count is non-zero the task is inside,\n+ * or was called from, such text and an involuntary context switch must not be\n+ * treated as a Tasks RCU quiescent state.\n+ *\n+ * The increment and decrement themselves live inside the trampoline, so there\n+ * is a window of a few instructions at entry (before the increment) and exit\n+ * (after the decrement) where the count is zero but the CPU is executing\n+ * trampoline text, or text on the way into one (a static ftrace stub or a\n+ * return thunk holding the trampoline's address). In that window the task\n+ * cannot be preempted synchronously, only from an interrupt, so the irq-exit\n+ * preemption path covers it by checking regs-\u003eip with\n+ * rcu_tasks_ip_in_trampoline() and holding the count elevated across\n+ * preempt_schedule_irq() when it matches.\n+ *\n+ * The one non-trampoline user, kprobe jump optimization, waits for tasks\n+ * preempted inside ordinary instruction bytes it is about to overwrite. A\n+ * task can be parked there from before the kprobe even existed, so that\n+ * cannot be decided once at preemption time: irqentry_preempt() records the\n+ * interrupted IP in current-\u003ercu_tasks_irq_ip for the duration of the\n+ * preemption and rcu_tasks_irq_ip_holds() checks it at every quiescent-state\n+ * decision, locally and from the grace-period kthread.\n+ *\n+ * With both in place, on architectures that select\n+ * ARCH_HAS_RCU_TASKS_PREEMPT_QS, a preemption with rcu_tramp_nesting == 0 is\n+ * a Tasks RCU quiescent state, and a CPU-bound kernel thread no longer needs\n+ * to volunteer one via cond_resched_tasks_rcu_qs().\n+ *\n+ * Only current writes the count and only current (or an interrupt on the same\n+ * CPU) reads it, so plain accesses suffice.\n+ */\n+static __always_inline void rcu_tasks_trampoline_enter(void)\n+{\n+\tcurrent-\u003ercu_tramp_nesting++;\n+\tbarrier();\n+}\n+\n+static __always_inline void rcu_tasks_trampoline_exit(void)\n+{\n+\tbarrier();\n+\tcurrent-\u003ercu_tramp_nesting--;\n+}\n+\n+/* A task must never reach userspace with a trampoline on its stack. */\n+static __always_inline void rcu_tasks_trampoline_assert_none(void)\n+{\n+\tif (IS_ENABLED(CONFIG_PROVE_RCU))\n+\t\tWARN_ON_ONCE(current-\u003ercu_tramp_nesting);\n+}\n+\n+bool rcu_tasks_ip_in_trampoline(unsigned long ip);\n+bool rcu_tasks_irq_ip_holds(struct task_struct *t);\n+\n+/* Record where current is being irq-preempted; 0 once it has resumed. */\n+static __always_inline void rcu_tasks_note_irq_ip(unsigned long ip)\n+{\n+\tWRITE_ONCE(current-\u003ercu_tasks_irq_ip, ip);\n+}\n+\n+#ifdef CONFIG_RCU_TASKS_PREEMPT_QS\n+#define rcu_tasks_preempt_is_qs(t)\t\t\t\t\t\\\n+\t(!READ_ONCE((t)-\u003ercu_tramp_nesting) \u0026\u0026 !rcu_tasks_irq_ip_holds(t))\n+#else\n+#define rcu_tasks_preempt_is_qs(t)\tfalse\n+#endif\n+\n # define rcu_tasks_classic_qs(t, preempt)\t\t\t\t\\\n \tdo {\t\t\t\t\t\t\t\t\\\n-\t\tif (!(preempt) \u0026\u0026 READ_ONCE((t)-\u003ercu_tasks_holdout))\t\\\n+\t\tif (READ_ONCE((t)-\u003ercu_tasks_holdout) \u0026\u0026\t\t\\\n+\t\t (!(preempt) || rcu_tasks_preempt_is_qs(t)))\t\t\\\n \t\t\tWRITE_ONCE((t)-\u003ercu_tasks_holdout, false);\t\\\n \t} while (0)\n void call_rcu_tasks(struct rcu_head *head, rcu_callback_t func);\n@@ -192,6 +266,11 @@ void rcu_tasks_torture_stats_print(char *tt, char *tf);\n # define rcu_tasks_classic_qs(t, preempt) do { } while (0)\n # define call_rcu_tasks call_rcu\n # define synchronize_rcu_tasks synchronize_rcu\n+static inline void rcu_tasks_trampoline_enter(void) { }\n+static inline void rcu_tasks_trampoline_exit(void) { }\n+static inline void rcu_tasks_trampoline_assert_none(void) { }\n+static inline bool rcu_tasks_ip_in_trampoline(unsigned long ip) { return false; }\n+static inline void rcu_tasks_note_irq_ip(unsigned long ip) { }\n # endif\n \n #define rcu_tasks_qs(t, preempt) rcu_tasks_classic_qs((t), (preempt))\n@@ -208,6 +287,11 @@ void exit_tasks_rcu_finish(void);\n #define rcu_tasks_classic_qs(t, preempt) do { } while (0)\n #define rcu_tasks_qs(t, preempt) do { } while (0)\n #define rcu_note_voluntary_context_switch(t) do { } while (0)\n+static inline void rcu_tasks_trampoline_enter(void) { }\n+static inline void rcu_tasks_trampoline_exit(void) { }\n+static inline void rcu_tasks_trampoline_assert_none(void) { }\n+static inline bool rcu_tasks_ip_in_trampoline(unsigned long ip) { return false; }\n+static inline void rcu_tasks_note_irq_ip(unsigned long ip) { }\n #define call_rcu_tasks call_rcu\n #define synchronize_rcu_tasks synchronize_rcu\n static inline void exit_tasks_rcu_start(void) { }\ndiff --git a/include/linux/sched.h b/include/linux/sched.h\nindex 8b3d47a325cca..7f0bdc81fba33 100644\n--- a/include/linux/sched.h\n+++ b/include/linux/sched.h\n@@ -956,6 +956,8 @@ struct task_struct {\n \tunsigned long\t\t\trcu_tasks_nvcsw;\n \tu8\t\t\t\trcu_tasks_holdout;\n \tu8\t\t\t\trcu_tasks_idx;\n+\tint\t\t\t\trcu_tramp_nesting;\n+\tunsigned long\t\t\trcu_tasks_irq_ip;\n \tint\t\t\t\trcu_tasks_idle_cpu;\n \tstruct list_head\t\trcu_tasks_holdout_list;\n \tint\t\t\t\trcu_tasks_exit_cpu;\ndiff --git a/include/linux/trace_recursion.h b/include/linux/trace_recursion.h\nindex e6ca052b2a85a..2da23a52ca4a4 100644\n--- a/include/linux/trace_recursion.h\n+++ b/include/linux/trace_recursion.h\n@@ -153,6 +153,17 @@ static __always_inline int trace_test_and_set_recursion(unsigned long ip, unsign\n \tcurrent-\u003etrace_recursion = val;\n \tbarrier();\n \n+\t/*\n+\t * Callbacks reached from static trampoline text (return_to_handler,\n+\t * the rethook and kretprobe trampolines) do not maintain\n+\t * current-\u003ercu_tramp_nesting themselves; they rely on this\n+\t * preempt_disable() to keep the task from being preempted, and thus\n+\t * from reporting a Tasks RCU quiescent state, while an ftrace_ops or\n+\t * its data is in use. If the preempt_disable() is ever removed from\n+\t * the recursion protection, this must rcu_tasks_trampoline_enter()\n+\t * here and rcu_tasks_trampoline_exit() in trace_clear_recursion()\n+\t * instead. See CONFIG_RCU_TASKS_PREEMPT_QS.\n+\t */\n \tpreempt_disable_notrace();\n \n \treturn bit;\ndiff --git a/kernel/entry/common.c b/kernel/entry/common.c\nindex e3d381fd3d251..b372f2670d4f7 100644\n--- a/kernel/entry/common.c\n+++ b/kernel/entry/common.c\n@@ -134,7 +134,37 @@ static inline bool arch_irqentry_exit_need_resched(void);\n static inline bool arch_irqentry_exit_need_resched(void) { return true; }\n #endif\n \n-void raw_irqentry_exit_cond_resched(void)\n+/*\n+ * Preempt the interrupted kernel context. If the interrupt landed in text\n+ * that may be a Tasks-RCU-protected trampoline (see\n+ * rcu_tasks_trampoline_enter()), hold current-\u003ercu_tramp_nesting elevated\n+ * across the context switch so that it is not mistaken for a Tasks RCU\n+ * quiescent state. This closes the few-instruction windows at trampoline\n+ * entry/exit where the trampoline's own increment has not yet run or its\n+ * decrement already has. The interrupted IP is also recorded for the\n+ * duration, for conditions that must be re-evaluated at each quiescent-state\n+ * decision rather than once here (see rcu_tasks_irq_ip_holds()); nested\n+ * irq-exit preemption cannot happen inside preempt_schedule_irq(), so one\n+ * slot per task is enough.\n+ */\n+static void irqentry_preempt(struct pt_regs *regs)\n+{\n+\tunsigned long ip = instruction_pointer(regs);\n+\tbool in_tramp = IS_ENABLED(CONFIG_RCU_TASKS_PREEMPT_QS) \u0026\u0026\n+\t\t\trcu_tasks_ip_in_trampoline(ip);\n+\n+\tif (in_tramp)\n+\t\trcu_tasks_trampoline_enter();\n+\tif (IS_ENABLED(CONFIG_RCU_TASKS_PREEMPT_QS))\n+\t\trcu_tasks_note_irq_ip(ip);\n+\tpreempt_schedule_irq();\n+\tif (IS_ENABLED(CONFIG_RCU_TASKS_PREEMPT_QS))\n+\t\trcu_tasks_note_irq_ip(0);\n+\tif (in_tramp)\n+\t\trcu_tasks_trampoline_exit();\n+}\n+\n+void raw_irqentry_exit_cond_resched(struct pt_regs *regs)\n {\n \tif (!preempt_count()) {\n \t\t/* Sanity check RCU and thread stack */\n@@ -142,7 +172,7 @@ void raw_irqentry_exit_cond_resched(void)\n \t\tif (IS_ENABLED(CONFIG_DEBUG_ENTRY))\n \t\t\tWARN_ON_ONCE(!on_thread_stack());\n \t\tif (need_resched() \u0026\u0026 arch_irqentry_exit_need_resched())\n-\t\t\tpreempt_schedule_irq();\n+\t\t\tirqentry_preempt(regs);\n \t}\n }\n #ifdef CONFIG_PREEMPT_DYNAMIC\n@@ -150,11 +180,11 @@ void raw_irqentry_exit_cond_resched(void)\n DEFINE_STATIC_CALL(irqentry_exit_cond_resched, raw_irqentry_exit_cond_resched);\n #elif defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY)\n DEFINE_STATIC_KEY_TRUE(sk_dynamic_irqentry_exit_cond_resched);\n-void dynamic_irqentry_exit_cond_resched(void)\n+void dynamic_irqentry_exit_cond_resched(struct pt_regs *regs)\n {\n \tif (!static_branch_unlikely(\u0026sk_dynamic_irqentry_exit_cond_resched))\n \t\treturn;\n-\traw_irqentry_exit_cond_resched();\n+\traw_irqentry_exit_cond_resched(regs);\n }\n #endif\n #endif\ndiff --git a/kernel/fork.c b/kernel/fork.c\nindex 416758c8a3d43..1277603bc472f 100644\n--- a/kernel/fork.c\n+++ b/kernel/fork.c\n@@ -1869,6 +1869,8 @@ static inline void rcu_copy_process(struct task_struct *p)\n #endif /* #ifdef CONFIG_PREEMPT_RCU */\n #ifdef CONFIG_TASKS_RCU\n \tp-\u003ercu_tasks_holdout = false;\n+\tp-\u003ercu_tramp_nesting = 0;\n+\tp-\u003ercu_tasks_irq_ip = 0;\n \tINIT_LIST_HEAD(\u0026p-\u003ercu_tasks_holdout_list);\n \tp-\u003ercu_tasks_idle_cpu = -1;\n \tINIT_LIST_HEAD(\u0026p-\u003ercu_tasks_exit_list);\ndiff --git a/kernel/kprobes.c b/kernel/kprobes.c\nindex 6337da5cab9e7..cf2ea278fdf5b 100644\n--- a/kernel/kprobes.c\n+++ b/kernel/kprobes.c\n@@ -511,6 +511,48 @@ static struct kprobe *get_optimized_kprobe(kprobe_opcode_t *addr)\n \treturn NULL;\n }\n \n+/*\n+ * True while kprobe_optimizer() is waiting for its Tasks RCU grace period.\n+ * Only in that window can a preemption inside an optprobe's jump region\n+ * matter to it, so kprobe_in_optimized_region() does no work otherwise.\n+ */\n+static bool kprobe_optimizer_waiting;\n+\n+/**\n+ * kprobe_in_optimized_region - Could @addr be inside bytes a jump-optimized\n+ *\tkprobe replaces?\n+ * @addr: kernel text address, typically an interrupted instruction pointer\n+ *\n+ * kprobe_optimizer() relies on synchronize_rcu_tasks() to wait for tasks that\n+ * were preempted on an instruction boundary inside the region about to be\n+ * overwritten by the optimized jump; such a task must not report a Tasks RCU\n+ * quiescent state when it is preempted (see rcu_tasks_ip_in_trampoline()).\n+ * This is the lockless, conservative form of get_optimized_kprobe(): it does\n+ * not care whether the kprobe found is, or ever will be, optimized. May be\n+ * called from any context with preemption disabled; the kprobe hash is\n+ * RCU-protected and every free path waits for a grace period after unhashing.\n+ *\n+ * The hash walk only runs while the optimizer is actually waiting. A\n+ * preemption that does not observe kprobe_optimizer_waiting predates the\n+ * grace period (its leading synchronize_rcu() publishes the store to every\n+ * interrupts-disabled reader before any task is sampled as a holdout); such a\n+ * task is then an ordinary preempted holdout, and the jump is not written\n+ * until it has run again and left the region.\n+ */\n+bool kprobe_in_optimized_region(unsigned long addr)\n+{\n+\tint i;\n+\n+\tif (!READ_ONCE(kprobe_optimizer_waiting))\n+\t\treturn false;\n+\n+\tfor (i = 1; i \u003c MAX_OPTIMIZED_LENGTH / sizeof(kprobe_opcode_t); i++)\n+\t\tif (get_kprobe((kprobe_opcode_t *)addr - i))\n+\t\t\treturn true;\n+\treturn false;\n+}\n+NOKPROBE_SYMBOL(kprobe_in_optimized_region);\n+\n /* Optimization staging list, protected by 'kprobe_mutex' */\n static LIST_HEAD(optimizing_list);\n static LIST_HEAD(unoptimizing_list);\n@@ -644,8 +686,12 @@ static void kprobe_optimizer(void)\n \t\t * to 2nd-Nth byte of jump instruction. This wait is for avoiding it.\n \t\t * Note that on non-preemptive kernel, this is transparently converted\n \t\t * to synchronoze_sched() to wait for all interrupts to have completed.\n+\t\t * kprobe_optimizer_waiting lets Tasks RCU recognise tasks preempted\n+\t\t * in such a region while we wait, see kprobe_in_optimized_region().\n \t\t */\n+\t\tWRITE_ONCE(kprobe_optimizer_waiting, true);\n \t\tsynchronize_rcu_tasks();\n+\t\tWRITE_ONCE(kprobe_optimizer_waiting, false);\n \n \t\t/* Step 3: Optimize kprobes after quiesence period */\n \t\tdo_optimize_kprobes();\ndiff --git a/kernel/rcu/Kconfig b/kernel/rcu/Kconfig\nindex 332df7a7a6347..8e7c943291054 100644\n--- a/kernel/rcu/Kconfig\n+++ b/kernel/rcu/Kconfig\n@@ -94,9 +94,10 @@ config FORCE_TASKS_RCU\n \tdefault n\n \thelp\n \t This option force-enables a task-based RCU implementation\n-\t that uses only voluntary context switch (not preemption!),\n-\t idle, and user-mode execution as quiescent states. Not for\n-\t manual selection in most cases.\n+\t that uses only voluntary context switch (not preemption, unless\n+\t the architecture selects ARCH_HAS_RCU_TASKS_PREEMPT_QS and the\n+\t task is outside any trampoline), idle, and user-mode execution\n+\t as quiescent states. Not for manual selection in most cases.\n \n config NEED_TASKS_RCU\n \tbool\n@@ -107,6 +108,16 @@ config TASKS_RCU\n \tdefault NEED_TASKS_RCU \u0026\u0026 PREEMPTION\n \tselect IRQ_WORK\n \n+# Selected by architectures whose ftrace, BPF and kprobe trampolines maintain\n+# current-\u003ercu_tramp_nesting and which use the generic irqentry code, so that\n+# a preemption outside any trampoline can be treated as a Tasks RCU\n+# quiescent state. See rcu_tasks_trampoline_enter().\n+config ARCH_HAS_RCU_TASKS_PREEMPT_QS\n+\tbool\n+\n+config RCU_TASKS_PREEMPT_QS\n+\tdef_bool TASKS_RCU \u0026\u0026 ARCH_HAS_RCU_TASKS_PREEMPT_QS \u0026\u0026 GENERIC_IRQ_ENTRY\n+\n config FORCE_TASKS_RUDE_RCU\n \tbool \"Force selection of Tasks Rude RCU\"\n \tdepends on RCU_EXPERT\ndiff --git a/kernel/rcu/rcutorture.c b/kernel/rcu/rcutorture.c\nindex 794937e13e7c3..df6dd708cea78 100644\n--- a/kernel/rcu/rcutorture.c\n+++ b/kernel/rcu/rcutorture.c\n@@ -1144,11 +1144,17 @@ static struct rcu_torture_ops trivial_preempt_ops = {\n \n static int tasks_torture_read_lock(void)\n {\n+\t/*\n+\t * Model a trampoline: with CONFIG_RCU_TASKS_PREEMPT_QS a preemption is\n+\t * otherwise a quiescent state and rcu_read_delay() preempts on purpose.\n+\t */\n+\trcu_tasks_trampoline_enter();\n \treturn 0;\n }\n \n static void tasks_torture_read_unlock(int idx)\n {\n+\trcu_tasks_trampoline_exit();\n }\n \n static void rcu_tasks_torture_deferred_free(struct rcu_torture *p)\ndiff --git a/kernel/rcu/tasks.h b/kernel/rcu/tasks.h\nindex 627295396cd91..02d2592ab7a3d 100644\n--- a/kernel/rcu/tasks.h\n+++ b/kernel/rcu/tasks.h\n@@ -905,7 +905,10 @@ static void rcu_tasks_wait_gp(struct rcu_tasks *rtp)\n //\n // Simple variant of RCU whose quiescent states are voluntary context\n // switch, cond_resched_tasks_rcu_qs(), user-space execution, and idle.\n-// As such, grace periods can take one good long time. There are no\n+// With CONFIG_RCU_TASKS_PREEMPT_QS, a preemption taken while the task is\n+// not inside a trampoline (current-\u003ercu_tramp_nesting == 0, see\n+// rcu_tasks_trampoline_enter()) is a quiescent state as well; without it,\n+// grace periods can take one good long time. There are no\n // read-side primitives similar to rcu_read_lock() and rcu_read_unlock()\n // because this implementation is intended to get the system into a safe\n // state for some of the manipulations involved in tracing and the like.\n@@ -1011,10 +1014,49 @@ static bool rcu_tasks_is_holdout(struct task_struct *t)\n \treturn true;\n }\n \n+#ifdef CONFIG_RCU_TASKS_PREEMPT_QS\n+/* task_call_func() callback: is @t switched out with no trampoline in play? */\n+static int rcu_tasks_switched_out_clean(struct task_struct *t, void *arg)\n+{\n+\t/*\n+\t * With @t pinned, !task_curr() means it last left the CPU through\n+\t * __schedule(), so its rcu_tramp_nesting and rcu_tasks_irq_ip are\n+\t * stable and ordered before the rq lock we hold. A task preempted\n+\t * from irq exit inside trampoline text has the count held non-zero\n+\t * across the switch by irqentry_preempt(), so zero here means neither\n+\t * in nor called from a trampoline; rcu_tasks_irq_ip_holds() covers the\n+\t * one case that can become true after the task was switched out (a\n+\t * kprobe jump-optimization window). Both clear: already quiescent,\n+\t * whether or not it ever runs again.\n+\t */\n+\treturn !task_curr(t) \u0026\u0026 !READ_ONCE(t-\u003ercu_tramp_nesting) \u0026\u0026\n+\t !rcu_tasks_irq_ip_holds(t);\n+}\n+\n+/* Is @t, right now, switched out somewhere that is a quiescent state? */\n+static bool rcu_tasks_preempted_qs(struct task_struct *t)\n+{\n+\treturn task_call_func(t, rcu_tasks_switched_out_clean, NULL);\n+}\n+\n+/* Make a running holdout pass through __schedule() soon, tick or no tick. */\n+static void rcu_tasks_kick_running(struct task_struct *t)\n+{\n+\tint cpu = task_cpu(t);\n+\n+\tif (task_curr(t) \u0026\u0026 cpu_online(cpu))\n+\t\tresched_cpu(cpu);\n+}\n+#else\n+static bool rcu_tasks_preempted_qs(struct task_struct *t) { return false; }\n+static void rcu_tasks_kick_running(struct task_struct *t) { }\n+#endif\n+\n /* Per-task initial processing. */\n static void rcu_tasks_pertask(struct task_struct *t, struct list_head *hop)\n {\n-\tif (t != current \u0026\u0026 rcu_tasks_is_holdout(t)) {\n+\tif (t != current \u0026\u0026 rcu_tasks_is_holdout(t) \u0026\u0026\n+\t !rcu_tasks_preempted_qs(t)) {\n \t\tget_task_struct(t);\n \t\tt-\u003ercu_tasks_nvcsw = READ_ONCE(t-\u003envcsw);\n \t\tWRITE_ONCE(t-\u003ercu_tasks_holdout, true);\n@@ -1089,6 +1131,86 @@ static void rcu_tasks_postscan(struct list_head *hop)\n \t\ttimer_delete_sync(\u0026tasks_rcu_exit_stall_timer);\n }\n \n+/*\n+ * Architectures selecting ARCH_HAS_RCU_TASKS_PREEMPT_QS override this to flag\n+ * core kernel text that must be treated like a trampoline, e.g. static ftrace\n+ * entry stubs and return thunks that run with a trampoline address in hand.\n+ */\n+bool __weak arch_rcu_tasks_ip_in_trampoline(unsigned long ip)\n+{\n+\treturn false;\n+}\n+\n+/**\n+ * rcu_tasks_ip_in_trampoline - Could a task interrupted at @ip be a Tasks RCU reader?\n+ * @ip: interrupted instruction pointer\n+ *\n+ * Called from the irq-exit preemption path with interrupts disabled, to decide\n+ * whether the imminent preemption may be reported as a Tasks RCU quiescent\n+ * state when current-\u003ercu_tramp_nesting is zero. Returns true, meaning \"do\n+ * not report\", when @ip is:\n+ *\n+ * - outside static kernel and module text, i.e. possibly in an ftrace\n+ * trampoline, BPF trampoline image or program, kprobe insn/optinsn slot or\n+ * other dynamically allocated text whose lifetime Tasks RCU guards. This\n+ * deliberately does not consult is_ftrace_trampoline() and friends: text\n+ * being torn down may already be unregistered there while a task still\n+ * stands on it;\n+ * - in core text the architecture flags via arch_rcu_tasks_ip_in_trampoline();\n+ * - in the text of a module that hosts an ftrace direct-call trampoline,\n+ * which covers the instructions before that trampoline's increment and\n+ * after its decrement (see ftrace_direct_mark_module()).\n+ *\n+ * A false positive only defers the quiescent state to the task's next\n+ * context switch.\n+ */\n+bool rcu_tasks_ip_in_trampoline(unsigned long ip)\n+{\n+\tbool ret = true;\n+\n+\tif (core_kernel_text(ip))\n+\t\treturn arch_rcu_tasks_ip_in_trampoline(ip);\n+\n+#ifdef CONFIG_MODULES\n+\tscoped_guard(rcu) {\n+\t\tstruct module *mod = __module_text_address(ip);\n+\n+#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS\n+\t\tif (mod)\n+\t\t\tret = READ_ONCE(mod-\u003eftrace_direct_tramp);\n+#else\n+\t\tif (mod)\n+\t\t\tret = false;\n+#endif\n+\t}\n+#endif\n+\treturn ret;\n+}\n+NOKPROBE_SYMBOL(rcu_tasks_ip_in_trampoline);\n+\n+/**\n+ * rcu_tasks_irq_ip_holds - Is @t irq-preempted somewhere that must hold off Tasks RCU?\n+ * @t: a task inside preempt_schedule_irq() (t-\u003ercu_tasks_irq_ip != 0), or not\n+ *\n+ * Unlike trampoline text, which a task can only be interrupted in while the\n+ * trampoline exists, the bytes kprobe_optimizer() is about to overwrite with a\n+ * jump are ordinary text a task may have been parked in since before the\n+ * kprobe was registered, and the optimizer may start waiting while the task is\n+ * already switched out. So this is evaluated against the IP recorded by\n+ * irqentry_preempt() at every quiescent-state decision -- each pass through\n+ * __schedule() in preempt_schedule_irq()'s loop, and the grace-period\n+ * kthread's scans -- rather than once at preemption time. A task switched out\n+ * synchronously cannot have a resume point inside such a window (a call there\n+ * returns beyond it), so only the irq-exit IP needs checking.\n+ */\n+bool rcu_tasks_irq_ip_holds(struct task_struct *t)\n+{\n+\tunsigned long ip = READ_ONCE(t-\u003ercu_tasks_irq_ip);\n+\n+\treturn ip \u0026\u0026 kprobe_in_optimized_region(ip);\n+}\n+NOKPROBE_SYMBOL(rcu_tasks_irq_ip_holds);\n+\n /* See if tasks are still holding out, complain if so. */\n static void check_holdout_task(struct task_struct *t,\n \t\t\t bool needreport, bool *firstreport)\n@@ -1098,6 +1220,7 @@ static void check_holdout_task(struct task_struct *t,\n \tif (!READ_ONCE(t-\u003ercu_tasks_holdout) ||\n \t t-\u003ercu_tasks_nvcsw != READ_ONCE(t-\u003envcsw) ||\n \t !rcu_tasks_is_holdout(t) ||\n+\t rcu_tasks_preempted_qs(t) ||\n \t (IS_ENABLED(CONFIG_NO_HZ_FULL) \u0026\u0026\n \t !is_idle_task(t) \u0026\u0026 READ_ONCE(t-\u003ercu_tasks_idle_cpu) \u003e= 0)) {\n \t\tWRITE_ONCE(t-\u003ercu_tasks_holdout, false);\n@@ -1106,6 +1229,7 @@ static void check_holdout_task(struct task_struct *t,\n \t\treturn;\n \t}\n \trcu_request_urgent_qs_task(t);\n+\trcu_tasks_kick_running(t);\n \tif (!needreport)\n \t\treturn;\n \tif (*firstreport) {\n@@ -1113,10 +1237,11 @@ static void check_holdout_task(struct task_struct *t,\n \t\t*firstreport = false;\n \t}\n \tcpu = task_cpu(t);\n-\tpr_alert(\"%p: %c%c nvcsw: %lu/%lu holdout: %d idle_cpu: %d/%d\\n\",\n+\tpr_alert(\"%p: %c%c nvcsw: %lu/%lu holdout: %d tramp_nesting: %d idle_cpu: %d/%d\\n\",\n \t\t t, \".I\"[is_idle_task(t)],\n \t\t \"N.\"[cpu \u003c 0 || !tick_nohz_full_cpu(cpu)],\n \t\t t-\u003ercu_tasks_nvcsw, t-\u003envcsw, t-\u003ercu_tasks_holdout,\n+\t\t data_race(t-\u003ercu_tramp_nesting),\n \t\t data_race(t-\u003ercu_tasks_idle_cpu), cpu);\n \tsched_show_task(t);\n }\n@@ -1182,8 +1307,11 @@ static void tasks_rcu_exit_stall(struct timer_list *unused)\n * period elapses, in other words after all currently executing rcu-tasks\n * read-side critical sections have completed. call_rcu_tasks() assumes\n * that the read-side critical sections end at a voluntary context\n- * switch (not a preemption!), cond_resched_tasks_rcu_qs(), entry into idle,\n- * or transition to usermode execution. As such, there are no read-side\n+ * switch, cond_resched_tasks_rcu_qs(), entry into idle, transition to\n+ * usermode execution, or, with CONFIG_RCU_TASKS_PREEMPT_QS, a preemption\n+ * taken outside any trampoline (current-\u003ercu_tramp_nesting == 0, see\n+ * rcu_tasks_trampoline_enter()); otherwise a preemption is not a\n+ * quiescent state. As such, there are no read-side\n * primitives analogous to rcu_read_lock() and rcu_read_unlock() because\n * this primitive is intended to determine that all tasks have passed\n * through a safe state, not so much for data-structure synchronization.\n@@ -1205,7 +1333,8 @@ EXPORT_SYMBOL_GPL(call_rcu_tasks);\n * executing rcu-tasks read-side critical sections have elapsed. These\n * read-side critical sections are delimited by calls to schedule(),\n * cond_resched_tasks_rcu_qs(), idle execution, userspace execution, calls\n- * to synchronize_rcu_tasks(), and (in theory, anyway) cond_resched().\n+ * to synchronize_rcu_tasks(), (in theory, anyway) cond_resched(), and,\n+ * with CONFIG_RCU_TASKS_PREEMPT_QS, preemption outside any trampoline.\n *\n * This is a very specialized primitive, intended only for a few uses in\n * tracing and other situations requiring manipulation of function\ndiff --git a/kernel/rcu/update.c b/kernel/rcu/update.c\nindex b62735a678842..23be7e97c3b5f 100644\n--- a/kernel/rcu/update.c\n+++ b/kernel/rcu/update.c\n@@ -41,6 +41,8 @@\n #include \u003clinux/rcupdate_wait.h\u003e\n #include \u003clinux/sched/isolation.h\u003e\n #include \u003clinux/kprobes.h\u003e\n+#include \u003clinux/kallsyms.h\u003e\n+#include \u003clinux/module.h\u003e\n #include \u003clinux/slab.h\u003e\n #include \u003clinux/irq_work.h\u003e\n #include \u003clinux/rcupdate_trace.h\u003e\ndiff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c\nindex 53d5db60bfa56..14f27b8872319 100644\n--- a/kernel/trace/ftrace.c\n+++ b/kernel/trace/ftrace.c\n@@ -6076,6 +6076,29 @@ static void reset_direct(struct ftrace_ops *ops, unsigned long addr)\n \tops-\u003etrampoline = 0;\n }\n \n+/*\n+ * A direct trampoline may live in module text rather than in dynamically\n+ * allocated text that rcu_tasks_ip_in_trampoline() recognises on its own (see\n+ * samples/ftrace/ftrace-direct*.c). The trampoline itself must hold\n+ * current-\u003ercu_tramp_nesting across its call-out (see register_ftrace_direct());\n+ * marking the owning module here covers the instructions before that increment\n+ * and after the decrement, where a task interrupted in the module's text must\n+ * not be treated as Tasks-RCU quiescent, so that ftrace_shutdown()'s\n+ * synchronize_rcu_tasks() still keeps the module text from being freed under\n+ * it.\n+ */\n+static void ftrace_direct_mark_module(unsigned long addr)\n+{\n+#ifdef CONFIG_MODULES\n+\tstruct module *mod;\n+\n+\tguard(rcu)();\n+\tmod = __module_text_address(addr);\n+\tif (mod)\n+\t\tWRITE_ONCE(mod-\u003eftrace_direct_tramp, true);\n+#endif\n+}\n+\n /**\n * register_ftrace_direct - Call a custom trampoline directly\n * for multiple functions registered in @ops\n@@ -6090,6 +6113,17 @@ static void reset_direct(struct ftrace_ops *ops, unsigned long addr)\n * and save the parameters of the function being traced, and restore them\n * (or inject new ones if needed), before returning.\n *\n+ * Nothing but Tasks RCU keeps the trampoline at @addr alive while a task is\n+ * executing it or is preempted in something it called. On architectures that\n+ * select ARCH_HAS_RCU_TASKS_PREEMPT_QS a preemption is a Tasks RCU quiescent\n+ * state unless current-\u003ercu_tramp_nesting is non-zero, so the trampoline must\n+ * increment it before calling out and decrement it before returning, as the\n+ * ftrace and BPF trampolines do (see rcu_tasks_trampoline_enter() and\n+ * samples/ftrace/ftrace-direct.h). The few instructions before the increment\n+ * and after the decrement are covered by the irq-exit IP check: automatically\n+ * for trampolines outside kernel and module text (e.g. BPF images), and via\n+ * ftrace_direct_mark_module() for trampolines in module text.\n+ *\n * Returns:\n * 0 on success\n * -EINVAL - The @ops object was already registered with this call or\n@@ -6169,6 +6203,7 @@ int register_ftrace_direct(struct ftrace_ops *ops, unsigned long addr)\n \tops-\u003eflags |= MULTI_FLAGS;\n \tops-\u003etrampoline = FTRACE_REGS_ADDR;\n \tops-\u003edirect_call = addr;\n+\tftrace_direct_mark_module(addr);\n \n \terr = register_ftrace_function_nolock(ops);\n \tif (err)\n@@ -6237,6 +6272,8 @@ __modify_ftrace_direct(struct ftrace_ops *ops, unsigned long addr)\n \n \tlockdep_assert_held_once(\u0026direct_mutex);\n \n+\tftrace_direct_mark_module(addr);\n+\n \t/* Enable the tmp_ops to have the same functions as the direct ops */\n \tftrace_ops_init(\u0026tmp_ops);\n \ttmp_ops.func_hash = ops-\u003efunc_hash;\n@@ -6419,6 +6456,7 @@ int update_ftrace_direct_add(struct ftrace_ops *ops, struct ftrace_hash *hash)\n \t\thlist_for_each_entry(entry, \u0026hash-\u003ebuckets[i], hlist) {\n \t\t\tif (__ftrace_lookup_ip(direct_functions, entry-\u003eip))\n \t\t\t\tgoto out_unlock;\n+\t\t\tftrace_direct_mark_module(entry-\u003edirect);\n \t\t}\n \t}\n \n@@ -6702,6 +6740,7 @@ int update_ftrace_direct_mod(struct ftrace_ops *ops, struct ftrace_hash *hash, b\n \t\t\ttmp = __ftrace_lookup_ip(direct_hash, entry-\u003eip);\n \t\t\tif (!tmp)\n \t\t\t\tcontinue;\n+\t\t\tftrace_direct_mark_module(entry-\u003edirect);\n \t\t\ttmp-\u003edirect = entry-\u003edirect;\n \t\t}\n \t}\ndiff --git a/samples/ftrace/ftrace-direct-modify.c b/samples/ftrace/ftrace-direct-modify.c\nindex 164d9dd6fd921..eb8230fa4242b 100644\n--- a/samples/ftrace/ftrace-direct-modify.c\n+++ b/samples/ftrace/ftrace-direct-modify.c\n@@ -2,6 +2,7 @@\n #include \u003clinux/module.h\u003e\n #include \u003clinux/kthread.h\u003e\n #include \u003clinux/ftrace.h\u003e\n+#include \"ftrace-direct.h\"\n #if !defined(CONFIG_ARM64) \u0026\u0026 !defined(CONFIG_PPC32)\n #include \u003casm/asm-offsets.h\u003e\n #endif\n@@ -73,7 +74,9 @@ asm (\n \"\tpushq %rbp\\n\"\n \"\tmovq %rsp, %rbp\\n\"\n \tCALL_DEPTH_ACCOUNT\n+\tRCU_TASKS_TRAMP_ENTER\n \"\tcall my_direct_func1\\n\"\n+\tRCU_TASKS_TRAMP_EXIT\n \"\tleave\\n\"\n \tASM_RET\n \"\t.size\t\tmy_tramp1, .-my_tramp1\\n\"\n@@ -85,7 +88,9 @@ asm (\n \"\tpushq %rbp\\n\"\n \"\tmovq %rsp, %rbp\\n\"\n \tCALL_DEPTH_ACCOUNT\n+\tRCU_TASKS_TRAMP_ENTER\n \"\tcall my_direct_func2\\n\"\n+\tRCU_TASKS_TRAMP_EXIT\n \"\tleave\\n\"\n \tASM_RET\n \"\t.size\t\tmy_tramp2, .-my_tramp2\\n\"\n@@ -141,11 +146,13 @@ asm (\n \"\t.globl\t\tmy_tramp1\\n\"\n \" my_tramp1:\"\n \"\thint\t34\\n\" // bti\tc\n+\tRCU_TASKS_TRAMP_ENTER\n \"\tsub\tsp, sp, #16\\n\"\n \"\tstp\tx9, x30, [sp]\\n\"\n \"\tbl\tmy_direct_func1\\n\"\n \"\tldp\tx30, x9, [sp]\\n\"\n \"\tadd\tsp, sp, #16\\n\"\n+\tRCU_TASKS_TRAMP_EXIT\n \"\tret\tx9\\n\"\n \"\t.size\t\tmy_tramp1, .-my_tramp1\\n\"\n \n@@ -153,11 +160,13 @@ asm (\n \"\t.globl\t\tmy_tramp2\\n\"\n \" my_tramp2:\"\n \"\thint\t34\\n\" // bti\tc\n+\tRCU_TASKS_TRAMP_ENTER\n \"\tsub\tsp, sp, #16\\n\"\n \"\tstp\tx9, x30, [sp]\\n\"\n \"\tbl\tmy_direct_func2\\n\"\n \"\tldp\tx30, x9, [sp]\\n\"\n \"\tadd\tsp, sp, #16\\n\"\n+\tRCU_TASKS_TRAMP_EXIT\n \"\tret\tx9\\n\"\n \"\t.size\t\tmy_tramp2, .-my_tramp2\\n\"\n \"\t.popsection\\n\"\ndiff --git a/samples/ftrace/ftrace-direct-multi-modify.c b/samples/ftrace/ftrace-direct-multi-modify.c\nindex b03766c6217bc..c8f1062e5d1ad 100644\n--- a/samples/ftrace/ftrace-direct-multi-modify.c\n+++ b/samples/ftrace/ftrace-direct-multi-modify.c\n@@ -2,6 +2,7 @@\n #include \u003clinux/module.h\u003e\n #include \u003clinux/kthread.h\u003e\n #include \u003clinux/ftrace.h\u003e\n+#include \"ftrace-direct.h\"\n #if !defined(CONFIG_ARM64) \u0026\u0026 !defined(CONFIG_PPC32)\n #include \u003casm/asm-offsets.h\u003e\n #endif\n@@ -77,10 +78,12 @@ asm (\n \"\tpushq %rbp\\n\"\n \"\tmovq %rsp, %rbp\\n\"\n \tCALL_DEPTH_ACCOUNT\n+\tRCU_TASKS_TRAMP_ENTER\n \"\tpushq %rdi\\n\"\n \"\tmovq 8(%rbp), %rdi\\n\"\n \"\tcall my_direct_func1\\n\"\n \"\tpopq %rdi\\n\"\n+\tRCU_TASKS_TRAMP_EXIT\n \"\tleave\\n\"\n \tASM_RET\n \"\t.size\t\tmy_tramp1, .-my_tramp1\\n\"\n@@ -92,10 +95,12 @@ asm (\n \"\tpushq %rbp\\n\"\n \"\tmovq %rsp, %rbp\\n\"\n \tCALL_DEPTH_ACCOUNT\n+\tRCU_TASKS_TRAMP_ENTER\n \"\tpushq %rdi\\n\"\n \"\tmovq 8(%rbp), %rdi\\n\"\n \"\tcall my_direct_func2\\n\"\n \"\tpopq %rdi\\n\"\n+\tRCU_TASKS_TRAMP_EXIT\n \"\tleave\\n\"\n \tASM_RET\n \"\t.size\t\tmy_tramp2, .-my_tramp2\\n\"\n@@ -154,6 +159,7 @@ asm (\n \"\t.globl\t\tmy_tramp1\\n\"\n \" my_tramp1:\"\n \"\thint\t34\\n\" // bti\tc\n+\tRCU_TASKS_TRAMP_ENTER\n \"\tsub\tsp, sp, #32\\n\"\n \"\tstp\tx9, x30, [sp]\\n\"\n \"\tstr\tx0, [sp, #16]\\n\"\n@@ -162,6 +168,7 @@ asm (\n \"\tldp\tx30, x9, [sp]\\n\"\n \"\tldr\tx0, [sp, #16]\\n\"\n \"\tadd\tsp, sp, #32\\n\"\n+\tRCU_TASKS_TRAMP_EXIT\n \"\tret\tx9\\n\"\n \"\t.size\t\tmy_tramp1, .-my_tramp1\\n\"\n \n@@ -169,6 +176,7 @@ asm (\n \"\t.globl\t\tmy_tramp2\\n\"\n \" my_tramp2:\"\n \"\thint\t34\\n\" // bti\tc\n+\tRCU_TASKS_TRAMP_ENTER\n \"\tsub\tsp, sp, #32\\n\"\n \"\tstp\tx9, x30, [sp]\\n\"\n \"\tstr\tx0, [sp, #16]\\n\"\n@@ -177,6 +185,7 @@ asm (\n \"\tldp\tx30, x9, [sp]\\n\"\n \"\tldr\tx0, [sp, #16]\\n\"\n \"\tadd\tsp, sp, #32\\n\"\n+\tRCU_TASKS_TRAMP_EXIT\n \"\tret\tx9\\n\"\n \"\t.size\t\tmy_tramp2, .-my_tramp2\\n\"\n \"\t.popsection\\n\"\ndiff --git a/samples/ftrace/ftrace-direct-multi.c b/samples/ftrace/ftrace-direct-multi.c\nindex 3fe6ddaf0b69f..bc6a88dd4ffcb 100644\n--- a/samples/ftrace/ftrace-direct-multi.c\n+++ b/samples/ftrace/ftrace-direct-multi.c\n@@ -3,6 +3,7 @@\n \n #include \u003clinux/mm.h\u003e /* for handle_mm_fault() */\n #include \u003clinux/ftrace.h\u003e\n+#include \"ftrace-direct.h\"\n #include \u003clinux/sched/stat.h\u003e\n #if !defined(CONFIG_ARM64) \u0026\u0026 !defined(CONFIG_PPC32)\n #include \u003casm/asm-offsets.h\u003e\n@@ -56,10 +57,12 @@ asm (\n \"\tpushq %rbp\\n\"\n \"\tmovq %rsp, %rbp\\n\"\n \tCALL_DEPTH_ACCOUNT\n+\tRCU_TASKS_TRAMP_ENTER\n \"\tpushq %rdi\\n\"\n \"\tmovq 8(%rbp), %rdi\\n\"\n \"\tcall my_direct_func\\n\"\n \"\tpopq %rdi\\n\"\n+\tRCU_TASKS_TRAMP_EXIT\n \"\tleave\\n\"\n \tASM_RET\n \"\t.size\t\tmy_tramp, .-my_tramp\\n\"\n@@ -101,6 +104,7 @@ asm (\n \"\t.globl\t\tmy_tramp\\n\"\n \" my_tramp:\"\n \"\thint\t34\\n\" // bti\tc\n+\tRCU_TASKS_TRAMP_ENTER\n \"\tsub\tsp, sp, #32\\n\"\n \"\tstp\tx9, x30, [sp]\\n\"\n \"\tstr\tx0, [sp, #16]\\n\"\n@@ -109,6 +113,7 @@ asm (\n \"\tldp\tx30, x9, [sp]\\n\"\n \"\tldr\tx0, [sp, #16]\\n\"\n \"\tadd\tsp, sp, #32\\n\"\n+\tRCU_TASKS_TRAMP_EXIT\n \"\tret\tx9\\n\"\n \"\t.size\t\tmy_tramp, .-my_tramp\\n\"\n \"\t.popsection\\n\"\ndiff --git a/samples/ftrace/ftrace-direct-too.c b/samples/ftrace/ftrace-direct-too.c\nindex bf2411aa6fd7a..247e418644a29 100644\n--- a/samples/ftrace/ftrace-direct-too.c\n+++ b/samples/ftrace/ftrace-direct-too.c\n@@ -3,6 +3,7 @@\n \n #include \u003clinux/mm.h\u003e /* for handle_mm_fault() */\n #include \u003clinux/ftrace.h\u003e\n+#include \"ftrace-direct.h\"\n #if !defined(CONFIG_ARM64) \u0026\u0026 !defined(CONFIG_PPC32)\n #include \u003casm/asm-offsets.h\u003e\n #endif\n@@ -61,6 +62,7 @@ asm (\n \"\tpushq %rbp\\n\"\n \"\tmovq %rsp, %rbp\\n\"\n \tCALL_DEPTH_ACCOUNT\n+\tRCU_TASKS_TRAMP_ENTER\n \"\tpushq %rdi\\n\"\n \"\tpushq %rsi\\n\"\n \"\tpushq %rdx\\n\"\n@@ -70,6 +72,7 @@ asm (\n \"\tpopq %rdx\\n\"\n \"\tpopq %rsi\\n\"\n \"\tpopq %rdi\\n\"\n+\tRCU_TASKS_TRAMP_EXIT\n \"\tleave\\n\"\n \tASM_RET\n \"\t.size\t\tmy_tramp, .-my_tramp\\n\"\n@@ -110,6 +113,7 @@ asm (\n \"\t.globl\t\tmy_tramp\\n\"\n \" my_tramp:\"\n \"\thint\t34\\n\" // bti\tc\n+\tRCU_TASKS_TRAMP_ENTER\n \"\tsub\tsp, sp, #48\\n\"\n \"\tstp\tx9, x30, [sp]\\n\"\n \"\tstp\tx0, x1, [sp, #16]\\n\"\n@@ -119,6 +123,7 @@ asm (\n \"\tldp\tx0, x1, [sp, #16]\\n\"\n \"\tldp\tx2, x3, [sp, #32]\\n\"\n \"\tadd\tsp, sp, #48\\n\"\n+\tRCU_TASKS_TRAMP_EXIT\n \"\tret\tx9\\n\"\n \"\t.size\t\tmy_tramp, .-my_tramp\\n\"\n \"\t.popsection\\n\"\ndiff --git a/samples/ftrace/ftrace-direct.c b/samples/ftrace/ftrace-direct.c\nindex 5368c8c39cbb4..9e1964baf28b4 100644\n--- a/samples/ftrace/ftrace-direct.c\n+++ b/samples/ftrace/ftrace-direct.c\n@@ -3,6 +3,7 @@\n \n #include \u003clinux/sched.h\u003e /* for wake_up_process() */\n #include \u003clinux/ftrace.h\u003e\n+#include \"ftrace-direct.h\"\n #if !defined(CONFIG_ARM64) \u0026\u0026 !defined(CONFIG_PPC32)\n #include \u003casm/asm-offsets.h\u003e\n #endif\n@@ -54,9 +55,11 @@ asm (\n \"\tpushq %rbp\\n\"\n \"\tmovq %rsp, %rbp\\n\"\n \tCALL_DEPTH_ACCOUNT\n+\tRCU_TASKS_TRAMP_ENTER\n \"\tpushq %rdi\\n\"\n \"\tcall my_direct_func\\n\"\n \"\tpopq %rdi\\n\"\n+\tRCU_TASKS_TRAMP_EXIT\n \"\tleave\\n\"\n \tASM_RET\n \"\t.size\t\tmy_tramp, .-my_tramp\\n\"\n@@ -97,6 +100,7 @@ asm (\n \"\t.globl\t\tmy_tramp\\n\"\n \" my_tramp:\"\n \"\thint\t34\\n\" // bti\tc\n+\tRCU_TASKS_TRAMP_ENTER\n \"\tsub\tsp, sp, #32\\n\"\n \"\tstp\tx9, x30, [sp]\\n\"\n \"\tstr\tx0, [sp, #16]\\n\"\n@@ -104,6 +108,7 @@ asm (\n \"\tldp\tx30, x9, [sp]\\n\"\n \"\tldr\tx0, [sp, #16]\\n\"\n \"\tadd\tsp, sp, #32\\n\"\n+\tRCU_TASKS_TRAMP_EXIT\n \"\tret\tx9\\n\"\n \"\t.size\t\tmy_tramp, .-my_tramp\\n\"\n \"\t.popsection\\n\"\ndiff --git a/samples/ftrace/ftrace-direct.h b/samples/ftrace/ftrace-direct.h\nnew file mode 100644\nindex 0000000000000..d0313f33f47f8\n--- /dev/null\n+++ b/samples/ftrace/ftrace-direct.h\n@@ -0,0 +1,64 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+#ifndef _SAMPLES_FTRACE_DIRECT_H\n+#define _SAMPLES_FTRACE_DIRECT_H\n+\n+#include \u003clinux/stringify.h\u003e\n+\n+/*\n+ * A direct-call trampoline is entered with no lock, refcount or RCU marker\n+ * held; only Tasks RCU keeps it (and, for a module, its text) alive while a\n+ * task is inside it or preempted in something it called. On architectures\n+ * that select ARCH_HAS_RCU_TASKS_PREEMPT_QS a preemption is a Tasks RCU\n+ * quiescent state unless current-\u003ercu_tramp_nesting is non-zero, so the\n+ * trampoline must raise it before calling out and drop it afterwards, exactly\n+ * like the ftrace and BPF trampolines do. See rcu_tasks_trampoline_enter()\n+ * and register_ftrace_direct(). The instructions before the increment and\n+ * after the decrement are covered by ftrace_direct_mark_module().\n+ *\n+ * These expand to instruction strings for use inside the samples' asm()\n+ * trampolines. The scratch register is caller-saved and not an argument\n+ * register, so it is dead on entry to and exit from an fentry trampoline.\n+ *\n+ * The generated asm-offsets.h is only pulled in on the architectures that need\n+ * it here: it is not generally safe to include from C (e.g. PPC32's TASK_SIZE\n+ * and arm64's TRAMP_VALIAS clash with the C definitions), which is why the\n+ * samples themselves guard their own include of it.\n+ */\n+#if defined(CONFIG_TASKS_RCU) \u0026\u0026 defined(CONFIG_X86_64)\n+\n+#include \u003casm/asm-offsets.h\u003e\n+\n+#define RCU_TASKS_TRAMP_ENTER\t\t\t\t\t\t\\\n+\t\"\tmovq %gs:current_task(%rip), %r11\\n\"\t\t\t\t\\\n+\t\"\tincl \" __stringify(TASK_rcu_tramp_nesting) \"(%r11)\\n\"\n+#define RCU_TASKS_TRAMP_EXIT\t\t\t\t\t\t\\\n+\t\"\tmovq %gs:current_task(%rip), %r11\\n\"\t\t\t\t\\\n+\t\"\tdecl \" __stringify(TASK_rcu_tramp_nesting) \"(%r11)\\n\"\n+\n+#elif defined(CONFIG_TASKS_RCU) \u0026\u0026 defined(CONFIG_ARM64)\n+\n+/* arm64's asm-offsets.h redefines TRAMP_VALIAS from \u003casm/fixmap.h\u003e. */\n+#pragma push_macro(\"TRAMP_VALIAS\")\n+#undef TRAMP_VALIAS\n+#include \u003casm/asm-offsets.h\u003e\n+#pragma pop_macro(\"TRAMP_VALIAS\")\n+\n+#define RCU_TASKS_TRAMP_ENTER\t\t\t\t\t\t\\\n+\t\"\tmrs\tx12, sp_el0\\n\"\t\t\t\t\t\t\\\n+\t\"\tldr\tw13, [x12, #\" __stringify(TSK_RCU_TRAMP_NESTING) \"]\\n\"\t\\\n+\t\"\tadd\tw13, w13, #1\\n\"\t\t\t\t\t\t\\\n+\t\"\tstr\tw13, [x12, #\" __stringify(TSK_RCU_TRAMP_NESTING) \"]\\n\"\n+#define RCU_TASKS_TRAMP_EXIT\t\t\t\t\t\t\\\n+\t\"\tmrs\tx12, sp_el0\\n\"\t\t\t\t\t\t\\\n+\t\"\tldr\tw13, [x12, #\" __stringify(TSK_RCU_TRAMP_NESTING) \"]\\n\"\t\\\n+\t\"\tsub\tw13, w13, #1\\n\"\t\t\t\t\t\t\\\n+\t\"\tstr\tw13, [x12, #\" __stringify(TSK_RCU_TRAMP_NESTING) \"]\\n\"\n+\n+#else\n+\n+#define RCU_TASKS_TRAMP_ENTER\n+#define RCU_TASKS_TRAMP_EXIT\n+\n+#endif\n+\n+#endif /* _SAMPLES_FTRACE_DIRECT_H */\n"
} |
| 2/1 | 2026/09/11 14:19 | agent | patch-evaluator |
0mModel:core Args: null Results: {
"EnableConfigs": [
"RCU_TASKS_PREEMPT_QS",
"TASKS_RCU",
"ARCH_HAS_RCU_TASKS_PREEMPT_QS",
"DYNAMIC_FTRACE_WITH_DIRECT_CALLS",
"OPTPROBES",
"BPF_SYSCALL",
"BPF_JIT"
],
"FocusSymbols": [
"rcu_tasks_ip_in_trampoline",
"rcu_tasks_irq_ip_holds",
"arch_rcu_tasks_ip_in_trampoline",
"kprobe_in_optimized_region",
"kprobe_optimizer",
"register_ftrace_direct",
"update_ftrace_direct_add",
"update_ftrace_direct_mod",
"arch_prepare_bpf_trampoline"
],
"Reasoning": "The patch series introduces CONFIG_RCU_TASKS_PREEMPT_QS (selected via ARCH_HAS_RCU_TASKS_PREEMPT_QS on x86_64), allowing involuntary context switches outside trampolines to serve as Tasks-RCU quiescent states. It modifies core RCU state evaluation, IRQ-entry preemption hooks, BPF trampoline emission, optimized kprobe holdout checks, and dynamic ftrace direct call tracking. These changes modify core synchronization mechanisms in reachable code and warrant fuzzing.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.
IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.
================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.
- REACHABILITY IS THE PRIMARY GATE:
Fuzzing can only discover bugs in code that can actually execute in standard virtualized
environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
regardless of whether it adds assertions or complex logic.
- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
Patch authors routinely label changes as "cleanups", "refactorings", or state
"No functional change intended". Do NOT take these claims at face value.
Code refactorings that rearrange logic, introduce helper functions, or alter state management
in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
If reachable executable code is modified or refactored, it MUST be fuzzed.
- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
Even if the author believes the invariant always holds, fuzzing is essential to verify whether
an unusual sequence of operations can violate it.
================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:
- Non-kernel and non-executable changes:
* Modifications to Documentation/, comments, or spelling fixes.
* User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
that do not affect the compiled kernel image (vmlinux) or kernel modules.
* Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
that do not alter control flow or data structures.
* Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
* Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
* Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
physical PCI hot-unplug or manual sysfs driver unbinding.
* Code paths exclusive to architectures other than the target architecture.
================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
* Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
* Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
* Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
* Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).
================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:
- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
Do NOT list generic, ubiquitous functions called by almost every program in the corpus
(including, but not limited to: general memory allocators and deallocators, page fault
and trap handlers, or core synchronization primitives; this is not an exhaustive list).
Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
which severely dilutes fuzzing effort away from the actual changes.
- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
List functions that specifically implement the logic being added or altered, or direct API entrypoints
for the subsystem feature under review.
- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).
================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit 8f1d757b3e477c6ed5382f9cfd33bf64b258acdc
Author: syz-cluster <triage@syzkaller.com>
Date: Fri Sep 11 14:19:54 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/RCU/Design/Requirements/Requirements.rst b/Documentation/RCU/Design/Requirements/Requirements.rst
index 8101fe6229d57..428b5e8f4b4e5 100644
--- a/Documentation/RCU/Design/Requirements/Requirements.rst
+++ b/Documentation/RCU/Design/Requirements/Requirements.rst
@@ -2739,13 +2739,27 @@ userspace execution also delimit tasks-RCU read-side critical sections.
Idle tasks are ignored by Tasks RCU, and Tasks Rude RCU may be used to
interact with them.
-Note well that involuntary context switches are *not* Tasks-RCU quiescent
-states. After all, in preemptible kernels, a task executing code in a
-trampoline might be preempted. In this case, the Tasks-RCU grace period
-clearly cannot end until that task resumes and its execution leaves that
-trampoline. This means, among other things, that cond_resched() does
-not provide a Tasks RCU quiescent state. (Instead, use rcu_softirq_qs()
-from softirq or rcu_tasks_classic_qs() otherwise.)
+Note well that, by default, involuntary context switches are *not*
+Tasks-RCU quiescent states. After all, in preemptible kernels, a task
+executing code in a trampoline might be preempted. In this case, the
+Tasks-RCU grace period clearly cannot end until that task resumes and its
+execution leaves that trampoline. This means, among other things, that
+cond_resched() does not provide a Tasks RCU quiescent state. (Instead,
+use rcu_softirq_qs() from softirq or rcu_tasks_classic_qs() otherwise.)
+
+Architectures that select ``CONFIG_ARCH_HAS_RCU_TASKS_PREEMPT_QS`` relax
+this: there, every trampoline whose lifetime Tasks RCU guards (the ftrace
+and BPF trampolines, optprobe slots, out-of-line ftrace direct-call
+trampolines) increments ``current->rcu_tramp_nesting`` before calling out
+and decrements it before returning, and the irq-exit preemption path
+covers the few instructions the counter cannot (see
+rcu_tasks_ip_in_trampoline() and rcu_tasks_irq_ip_holds()). A task that
+is preempted with that count at zero is therefore known not to be in, or
+called from, any trampoline, and such a preemption *is* a Tasks-RCU
+quiescent state. The obligation moves to the trampolines: anything that
+relies on synchronize_rcu_tasks() to protect code a task may be preempted
+in must maintain the count (see register_ftrace_direct()), or Tasks RCU
+will not wait for it on those architectures.
The tasks-RCU API is quite compact, consisting only of
call_rcu_tasks(), synchronize_rcu_tasks(), and
diff --git a/Documentation/RCU/checklist.rst b/Documentation/RCU/checklist.rst
index 4b30f701225fd..28df48fecac7f 100644
--- a/Documentation/RCU/checklist.rst
+++ b/Documentation/RCU/checklist.rst
@@ -252,7 +252,13 @@ over a rather long period of time, but improvements are always welcome!
a. If the updater uses synchronize_rcu_tasks() or
call_rcu_tasks(), then the readers must refrain from
executing voluntary context switches, that is, from
- blocking.
+ blocking. On architectures that select
+ CONFIG_ARCH_HAS_RCU_TASKS_PREEMPT_QS an involuntary
+ context switch is also a quiescent state unless
+ current->rcu_tramp_nesting is non-zero, so a reader
+ there is a trampoline that maintains that count (see
+ rcu_tasks_trampoline_enter()), not an arbitrary
+ stretch of kernel code.
b. If the updater uses call_rcu_tasks_trace()
or synchronize_rcu_tasks_trace(), then the
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index b5a51b0ef9440..0e6c1e0b236f9 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -44,6 +44,7 @@ config ARM64
select ARCH_HAS_PREEMPT_LAZY
select ARCH_HAS_PTDUMP
select ARCH_HAS_PTE_SPECIAL
+ select ARCH_HAS_RCU_TASKS_PREEMPT_QS if DYNAMIC_FTRACE_WITH_ARGS
select ARCH_HAS_HW_PTE_YOUNG
select ARCH_HAS_SETUP_DMA_OPS
select ARCH_HAS_SET_DIRECT_MAP
diff --git a/arch/arm64/kernel/asm-offsets.c b/arch/arm64/kernel/asm-offsets.c
index 9c853ed3ceab8..f6655a284f183 100644
--- a/arch/arm64/kernel/asm-offsets.c
+++ b/arch/arm64/kernel/asm-offsets.c
@@ -39,6 +39,9 @@ int main(void)
DEFINE(TSK_STACK, offsetof(struct task_struct, stack));
#ifdef CONFIG_STACKPROTECTOR
DEFINE(TSK_STACK_CANARY, offsetof(struct task_struct, stack_canary));
+#endif
+#ifdef CONFIG_TASKS_RCU
+ DEFINE(TSK_RCU_TRAMP_NESTING, offsetof(struct task_struct, rcu_tramp_nesting));
#endif
BLANK();
DEFINE(THREAD_CPU_CONTEXT, offsetof(struct task_struct, thread.cpu_context));
diff --git a/arch/arm64/kernel/entry-ftrace.S b/arch/arm64/kernel/entry-ftrace.S
index 025140caafe74..46a102e7199ac 100644
--- a/arch/arm64/kernel/entry-ftrace.S
+++ b/arch/arm64/kernel/entry-ftrace.S
@@ -14,6 +14,33 @@
#include <asm/insn.h>
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_ARGS
+/*
+ * Tasks RCU trampoline nesting, see rcu_tasks_trampoline_enter(). The whole
+ * of ftrace_caller is treated as trampoline text by the irq-exit IP check (see
+ * arch_rcu_tasks_ip_in_trampoline()), so these only need to bracket the call
+ * out to ops->func; everything before the increment and after the decrement,
+ * including the direct-call tails that carry a BPF trampoline address in x17,
+ * is covered by that. The count is only modified by current and every nested
+ * user (interrupts) is balanced, so a plain ldr/add/str is sufficient.
+ */
+ .macro rcu_tasks_tramp_enter, tsk:req, tmp:req
+#ifdef CONFIG_TASKS_RCU
+ mrs \tsk, sp_el0
+ ldr \tmp, [\tsk, #TSK_RCU_TRAMP_NESTING]
+ add \tmp, \tmp, #1
+ str \tmp, [\tsk, #TSK_RCU_TRAMP_NESTING]
+#endif
+ .endm
+
+ .macro rcu_tasks_tramp_exit, tsk:req, tmp:req
+#ifdef CONFIG_TASKS_RCU
+ mrs \tsk, sp_el0
+ ldr \tmp, [\tsk, #TSK_RCU_TRAMP_NESTING]
+ sub \tmp, \tmp, #1
+ str \tmp, [\tsk, #TSK_RCU_TRAMP_NESTING]
+#endif
+ .endm
+
/*
* Due to -fpatchable-function-entry=2, the compiler has placed two NOPs before
* the regular function prologue. For an enabled callsite, ftrace_init_nop() and
@@ -94,6 +121,8 @@ SYM_CODE_START(ftrace_caller)
stp x29, x30, [sp, #FREGS_SIZE]
add x29, sp, #FREGS_SIZE
+ rcu_tasks_tramp_enter x12, w13
+
/* Prepare arguments for the tracer func */
sub x0, x30, #AARCH64_INSN_SIZE // ip (callsite's BL insn)
mov x1, x9 // parent_ip (callsite's LR)
@@ -111,6 +140,8 @@ SYM_INNER_LABEL(ftrace_call, SYM_L_GLOBAL)
bl ftrace_stub // func(ip, parent_ip, op, regs)
#endif
+ rcu_tasks_tramp_exit x12, w13
+
/*
* At the callsite x0-x8 and x19-x30 were live. Any C code will have preserved
* x19-x29 per the AAPCS, and we created frame records upon entry, so we need
@@ -178,6 +209,10 @@ SYM_CODE_START(ftrace_stub_direct_tramp)
SYM_CODE_END(ftrace_stub_direct_tramp)
#endif /* CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS */
+/* End of [ftrace_caller, ...) for arch_rcu_tasks_ip_in_trampoline(). */
+SYM_CODE_START(ftrace_static_tramp_end)
+SYM_CODE_END(ftrace_static_tramp_end)
+
#else /* CONFIG_DYNAMIC_FTRACE_WITH_ARGS */
/*
diff --git a/arch/arm64/kernel/ftrace.c b/arch/arm64/kernel/ftrace.c
index e1a3c0b3a0514..1b7ac2afed0df 100644
--- a/arch/arm64/kernel/ftrace.c
+++ b/arch/arm64/kernel/ftrace.c
@@ -17,6 +17,22 @@
#include <asm/insn.h>
#include <asm/text-patching.h>
+#ifdef CONFIG_RCU_TASKS_PREEMPT_QS
+extern void ftrace_static_tramp_end(void);
+
+/*
+ * See rcu_tasks_ip_in_trampoline(). ftrace_caller and ftrace_stub_direct_tramp
+ * are core kernel text but must be treated as trampolines: a task preempted in
+ * them may be carrying an ops pointer (x11) or a direct-call BPF trampoline
+ * address (x17) whose lifetime is guarded only by Tasks RCU.
+ */
+bool arch_rcu_tasks_ip_in_trampoline(unsigned long ip)
+{
+ return ip >= (unsigned long)ftrace_caller &&
+ ip < (unsigned long)ftrace_static_tramp_end;
+}
+#endif
+
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_ARGS
struct fregs_offset {
const char *name;
diff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c
index c18e005a41dbe..5c9a7bde5cc9c 100644
--- a/arch/arm64/net/bpf_jit_comp.c
+++ b/arch/arm64/net/bpf_jit_comp.c
@@ -2591,6 +2591,34 @@ static void emit_arena_arg_conv(struct jit_ctx *ctx, u8 dst, u8 src, bool nullab
emit(A64_SUB(0, dst, src, base_lo), ctx);
}
+/*
+ * Tasks RCU trampoline nesting, see rcu_tasks_trampoline_enter().
+ *
+ * mrs x10, sp_el0
+ * ldr w11, [x10, #offsetof(struct task_struct, rcu_tramp_nesting)]
+ * add/sub w11, w11, #1
+ * str w11, [x10, #...]
+ *
+ * x10/x11 are scratch in the trampoline at every point this is emitted.
+ */
+static void emit_rcu_tasks_tramp_nesting(struct jit_ctx *ctx, bool enter)
+{
+#ifdef CONFIG_TASKS_RCU
+ const int off = offsetof(struct task_struct, rcu_tramp_nesting);
+ const u8 tsk = A64_R(10), cnt = A64_R(11);
+
+ BUILD_BUG_ON(off & 3 || off >= SZ_16K); /* LDR/STR (imm12, scaled) */
+
+ emit(A64_MRS_SP_EL0(tsk), ctx);
+ emit(A64_LDR32I(cnt, tsk, off), ctx);
+ if (enter)
+ emit(A64_ADD_I(0, cnt, cnt, 1), ctx);
+ else
+ emit(A64_SUB_I(0, cnt, cnt, 1), ctx);
+ emit(A64_STR32I(cnt, tsk, off), ctx);
+#endif
+}
+
static void save_args(struct jit_ctx *ctx, int bargs_off, int oargs_off,
const struct btf_func_model *m, const struct arg_aux *a,
bool for_call_origin, bool is_struct_ops, u64 arena_base)
@@ -2854,6 +2882,13 @@ static int prepare_trampoline(struct jit_ctx *ctx, struct bpf_tramp_image *im,
emit(A64_STR64I(A64_R(19), A64_SP, regs_off), ctx);
emit(A64_STR64I(A64_R(20), A64_SP, regs_off + 8), ctx);
+ /*
+ * From here until the matching decrement in the epilogue, a preemption
+ * of this task is not a Tasks RCU quiescent state. The instructions
+ * above this point are covered by the irq-exit IP check.
+ */
+ emit_rcu_tasks_tramp_nesting(ctx, true);
+
if (flags & BPF_TRAMP_F_CALL_ORIG) {
/* for the first pass, assume the worst case */
if (!ctx->image)
@@ -2898,12 +2933,20 @@ static int prepare_trampoline(struct jit_ctx *ctx, struct bpf_tramp_image *im,
if (flags & BPF_TRAMP_F_CALL_ORIG) {
/* the original func takes kernel addresses, never converted ones */
save_args(ctx, bargs_off, oargs_off, m, a, true, is_struct_ops, 0);
+ /*
+ * The original function may run for a long time without
+ * sleeping; do not let it pin a Tasks RCU grace period. The
+ * trampoline frame above it is held by im->pcref
+ * (__bpf_tramp_enter()), not by Tasks RCU, across the call.
+ */
+ emit_rcu_tasks_tramp_nesting(ctx, false);
/* call original func */
emit(A64_LDR64I(A64_R(10), A64_SP, retaddr_off), ctx);
emit(A64_ADR(A64_LR, AARCH64_INSN_SIZE * 2), ctx);
emit(A64_RET(A64_R(10)), ctx);
/* store return value */
emit(A64_STR64I(A64_R(0), A64_SP, retval_off), ctx);
+ emit_rcu_tasks_tramp_nesting(ctx, true);
/* reserve a nop for bpf_tramp_image_put */
im->ip_after_call = ctx->ro_image + ctx->idx;
emit(A64_NOP, ctx);
@@ -2945,6 +2988,9 @@ static int prepare_trampoline(struct jit_ctx *ctx, struct bpf_tramp_image *im,
if (flags & BPF_TRAMP_F_RESTORE_REGS)
restore_args(ctx, bargs_off, a->regs_for_args);
+ /* Remaining instructions are covered by the irq-exit IP check. */
+ emit_rcu_tasks_tramp_nesting(ctx, false);
+
/* restore callee saved register x19 and x20 */
emit(A64_LDR64I(A64_R(19), A64_SP, regs_off), ctx);
emit(A64_LDR64I(A64_R(20), A64_SP, regs_off + 8), ctx);
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index 15fd9ec5ecacb..0a64270193454 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -99,6 +99,7 @@ config X86
select ARCH_HAS_PREEMPT_LAZY
select ARCH_HAS_PTDUMP
select ARCH_HAS_PTE_SPECIAL
+ select ARCH_HAS_RCU_TASKS_PREEMPT_QS if X86_64
select ARCH_HAS_HW_PTE_YOUNG
select ARCH_HAS_NONLEAF_PMD_YOUNG if PGTABLE_LEVELS > 2
select ARCH_HAS_UACCESS_FLUSHCACHE if X86_64
diff --git a/arch/x86/kernel/asm-offsets.c b/arch/x86/kernel/asm-offsets.c
index 081816888f7a0..4f3b1caa5a309 100644
--- a/arch/x86/kernel/asm-offsets.c
+++ b/arch/x86/kernel/asm-offsets.c
@@ -46,6 +46,9 @@ static void __used common(void)
#ifdef CONFIG_STACKPROTECTOR
OFFSET(TASK_stack_canary, task_struct, stack_canary);
#endif
+#ifdef CONFIG_TASKS_RCU
+ OFFSET(TASK_rcu_tramp_nesting, task_struct, rcu_tramp_nesting);
+#endif
BLANK();
OFFSET(pbe_address, pbe, address);
diff --git a/arch/x86/kernel/ftrace.c b/arch/x86/kernel/ftrace.c
index 17d6edfcb7e00..8f63cd4b543c7 100644
--- a/arch/x86/kernel/ftrace.c
+++ b/arch/x86/kernel/ftrace.c
@@ -275,6 +275,43 @@ static inline void tramp_free(void *tramp)
execmem_free(tramp);
}
+#ifdef CONFIG_RCU_TASKS_PREEMPT_QS
+extern void ftrace_static_tramp_end(void);
+extern char __return_thunk_start[], __return_thunk_end[];
+extern char __rethunk_safe_start[], __rethunk_safe_end[];
+
+/*
+ * See rcu_tasks_ip_in_trampoline(). Some core kernel text behaves like a
+ * trampoline for Tasks RCU purposes because a task executing there with
+ * rcu_tramp_nesting == 0 may still be about to enter a Tasks-RCU-protected
+ * trampoline whose address it already holds:
+ *
+ * - the static ftrace_caller / ftrace_regs_caller / ftrace_stub_direct_tramp
+ * stubs, which carry a direct-call target on the stack until their final
+ * RET, and
+ * - the return thunks that RET expands to under CONFIG_MITIGATION_RETHUNK,
+ * which run after leaving the stubs above and before landing in that
+ * target.
+ */
+bool arch_rcu_tasks_ip_in_trampoline(unsigned long ip)
+{
+ if (ip >= (unsigned long)ftrace_caller &&
+ ip < (unsigned long)ftrace_static_tramp_end)
+ return true;
+#ifdef CONFIG_MITIGATION_RETPOLINE
+ if (ip >= (unsigned long)__return_thunk_start &&
+ ip < (unsigned long)__return_thunk_end)
+ return true;
+#endif
+#ifdef CONFIG_MITIGATION_SRSO
+ if (ip >= (unsigned long)__rethunk_safe_start &&
+ ip < (unsigned long)__rethunk_safe_end)
+ return true;
+#endif
+ return false;
+}
+#endif /* CONFIG_RCU_TASKS_PREEMPT_QS */
+
/* Defined as markers to the end of the ftrace default trampolines */
extern void ftrace_regs_caller_end(void);
extern void ftrace_caller_end(void);
diff --git a/arch/x86/kernel/ftrace_64.S b/arch/x86/kernel/ftrace_64.S
index 62c1c93aa1c6a..902472c41798b 100644
--- a/arch/x86/kernel/ftrace_64.S
+++ b/arch/x86/kernel/ftrace_64.S
@@ -7,6 +7,7 @@
#include <linux/cfi_types.h>
#include <linux/linkage.h>
#include <asm/asm-offsets.h>
+#include <asm/percpu.h>
#include <asm/ptrace.h>
#include <asm/ftrace.h>
#include <asm/nospec-branch.h>
@@ -145,6 +146,27 @@ SYM_FUNC_END(ftrace_stub_graph)
#ifdef CONFIG_DYNAMIC_FTRACE
+/*
+ * Tasks RCU trampoline nesting, see rcu_tasks_trampoline_enter(). These live
+ * inside the region copied into dynamic trampolines; the %rip-relative per-CPU
+ * reference is fixed up by text_poke_apply_relocation() in create_trampoline().
+ * The increment must precede the function_trace_op load: between that load and
+ * the call, the ops pointer in %rdx is protected only by Tasks RCU.
+ */
+.macro RCU_TASKS_TRAMP_ENTER reg:req
+#ifdef CONFIG_TASKS_RCU
+ movq PER_CPU_VAR(current_task), \reg
+ incl TASK_rcu_tramp_nesting(\reg)
+#endif
+.endm
+
+.macro RCU_TASKS_TRAMP_EXIT reg:req
+#ifdef CONFIG_TASKS_RCU
+ movq PER_CPU_VAR(current_task), \reg
+ decl TASK_rcu_tramp_nesting(\reg)
+#endif
+.endm
+
SYM_FUNC_START(__fentry__)
ANNOTATE_NOENDBR
CALL_DEPTH_ACCOUNT
@@ -163,6 +185,8 @@ SYM_FUNC_START(ftrace_caller)
leaq MCOUNT_REG_SIZE+8(%rsp), %rcx
movq %rcx, RSP(%rsp)
+ RCU_TASKS_TRAMP_ENTER %rdx
+
SYM_INNER_LABEL(ftrace_caller_op_ptr, SYM_L_GLOBAL)
ANNOTATE_NOENDBR
/* Load the ftrace_ops into the 3rd parameter */
@@ -181,6 +205,8 @@ SYM_INNER_LABEL(ftrace_call, SYM_L_GLOBAL)
ANNOTATE_NOENDBR
call ftrace_stub
+ RCU_TASKS_TRAMP_EXIT %rdx
+
/* Handlers can change the RIP */
movq RIP(%rsp), %rax
movq %rax, MCOUNT_REG_SIZE(%rsp)
@@ -209,6 +235,8 @@ SYM_FUNC_START(ftrace_regs_caller)
CALL_DEPTH_ACCOUNT
+ RCU_TASKS_TRAMP_ENTER %rdx
+
SYM_INNER_LABEL(ftrace_regs_caller_op_ptr, SYM_L_GLOBAL)
ANNOTATE_NOENDBR
/* Load the ftrace_ops into the 3rd parameter */
@@ -246,6 +274,8 @@ SYM_INNER_LABEL(ftrace_regs_call, SYM_L_GLOBAL)
ANNOTATE_NOENDBR
call ftrace_stub
+ RCU_TASKS_TRAMP_EXIT %rdx
+
/* Copy flags back to SS, to restore them */
movq EFLAGS(%rsp), %rax
movq %rax, MCOUNT_REG_SIZE(%rsp)
@@ -328,6 +358,19 @@ SYM_FUNC_START(ftrace_stub_direct_tramp)
RET
SYM_FUNC_END(ftrace_stub_direct_tramp)
+/*
+ * [ftrace_caller, ftrace_static_tramp_end) is treated as trampoline text by
+ * rcu_tasks_ip_in_trampoline(): after RCU_TASKS_TRAMP_EXIT the stubs may
+ * still hold a direct-call target (a BPF trampoline) on the stack until the
+ * final RET, and that target's lifetime is guarded by Tasks RCU. With
+ * return thunks the RET itself runs elsewhere; arch_rcu_tasks_ip_in_trampoline()
+ * covers the thunk text too.
+ */
+SYM_CODE_START_NOALIGN(ftrace_static_tramp_end)
+ UNWIND_HINT_UNDEFINED
+ ANNOTATE_NOENDBR
+SYM_CODE_END(ftrace_static_tramp_end)
+
#else /* ! CONFIG_DYNAMIC_FTRACE */
SYM_FUNC_START(__fentry__)
diff --git a/arch/x86/kernel/kprobes/opt.c b/arch/x86/kernel/kprobes/opt.c
index 3f8fea52619f9..f722520bb9891 100644
--- a/arch/x86/kernel/kprobes/opt.c
+++ b/arch/x86/kernel/kprobes/opt.c
@@ -31,6 +31,7 @@
#include <asm/set_memory.h>
#include <asm/sections.h>
#include <asm/nospec-branch.h>
+#include <asm/asm-offsets.h>
#include "common.h"
@@ -101,6 +102,23 @@ static void synthesize_set_arg1(kprobe_opcode_t *addr, unsigned long val)
*(unsigned long *)addr = val;
}
+/*
+ * Tasks RCU trampoline nesting, see rcu_tasks_trampoline_enter(). The
+ * template is memcpy()d into the slot without relocation processing, so the
+ * per-CPU reference must be absolute, not %rip-relative.
+ */
+#if defined(CONFIG_TASKS_RCU) && defined(CONFIG_X86_64)
+#define OPTPROBE_RCU_TASKS_ENTER \
+ " movq %gs:current_task, %rax\n" \
+ " incl " __stringify(TASK_rcu_tramp_nesting) "(%rax)\n"
+#define OPTPROBE_RCU_TASKS_EXIT \
+ " movq %gs:current_task, %rax\n" \
+ " decl " __stringify(TASK_rcu_tramp_nesting) "(%rax)\n"
+#else
+#define OPTPROBE_RCU_TASKS_ENTER
+#define OPTPROBE_RCU_TASKS_EXIT
+#endif
+
asm (
".pushsection .rodata\n"
".global optprobe_template_entry\n"
@@ -114,6 +132,7 @@ asm (
"optprobe_template_clac:\n"
ASM_NOP3
SAVE_REGS_STRING
+ OPTPROBE_RCU_TASKS_ENTER
" movq %rsp, %rsi\n"
".global optprobe_template_val\n"
"optprobe_template_val:\n"
@@ -122,6 +141,7 @@ asm (
".global optprobe_template_call\n"
"optprobe_template_call:\n"
ASM_NOP5
+ OPTPROBE_RCU_TASKS_EXIT
/* Copy 'regs->flags' into 'regs->ss'. */
" movq 18*8(%rsp), %rdx\n"
" movq %rdx, 20*8(%rsp)\n"
diff --git a/arch/x86/kernel/vmlinux.lds.S b/arch/x86/kernel/vmlinux.lds.S
index 2438b89a4620a..e546283dc2679 100644
--- a/arch/x86/kernel/vmlinux.lds.S
+++ b/arch/x86/kernel/vmlinux.lds.S
@@ -151,7 +151,9 @@ SECTIONS
* definition.
*/
. = srso_alias_untrain_ret | (1 << 2) | (1 << 8) | (1 << 14) | (1 << 20);
+ __rethunk_safe_start = .;
*(.text..__x86.rethunk_safe)
+ __rethunk_safe_end = .;
#endif
ALIGN_ENTRY_TEXT_END
@@ -162,7 +164,9 @@ SECTIONS
SOFTIRQENTRY_TEXT
#ifdef CONFIG_MITIGATION_RETPOLINE
*(.text..__x86.indirect_thunk)
+ __return_thunk_start = .;
*(.text..__x86.return_thunk)
+ __return_thunk_end = .;
#endif
STATIC_CALL_TEXT
*(.gnu.warning)
diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index 2853e87797a78..a375c1b7bd50c 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -722,6 +722,31 @@ static void emit_indirect_jump(u8 **pprog, int bpf_reg, u8 *ip)
*pprog = prog;
}
+/*
+ * Tasks RCU trampoline nesting, see rcu_tasks_trampoline_enter().
+ *
+ * mov r11, QWORD PTR gs:[current_task]
+ * inc/dec DWORD PTR [r11 + offsetof(struct task_struct, rcu_tramp_nesting)]
+ *
+ * r11 (AUX_REG) is scratch in the trampoline at every point this is emitted.
+ */
+static void emit_rcu_tasks_tramp_nesting(u8 **pprog, bool enter)
+{
+#ifdef CONFIG_TASKS_RCU
+ u8 *prog = *pprog;
+
+ /* mov r11, gs:[abs32] */
+ EMIT2(0x65, 0x4C);
+ EMIT3(0x8B, 0x1C, 0x25);
+ EMIT((u32)(unsigned long)¤t_task, 4);
+ /* inc/dec dword ptr [r11 + disp32] */
+ EMIT3(0x41, 0xFF, enter ? 0x83 : 0x8B);
+ EMIT(offsetof(struct task_struct, rcu_tramp_nesting), 4);
+
+ *pprog = prog;
+#endif
+}
+
static void emit_return(u8 **pprog, u8 *ip)
{
u8 *prog = *pprog;
@@ -3610,6 +3635,13 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
/* mov QWORD PTR [rbp - rbx_off], rbx */
emit_stx(&prog, BPF_DW, BPF_REG_FP, BPF_REG_6, -rbx_off);
+ /*
+ * From here until the matching decrement before the final return, a
+ * preemption of this task is not a Tasks RCU quiescent state. The
+ * instructions above this point are covered by the irq-exit IP check.
+ */
+ emit_rcu_tasks_tramp_nesting(&prog, true);
+
func_meta = nr_regs;
/* Store number of argument registers of the traced function */
emit_store_stack_imm64(&prog, BPF_REG_0, -func_meta_off, func_meta);
@@ -3670,6 +3702,13 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
LOAD_TRAMP_TAIL_CALL_CNT_PTR(stack_size);
}
+ /*
+ * The original function may run for a long time without
+ * sleeping; do not let it pin a Tasks RCU grace period. The
+ * trampoline frame above it is held by im->pcref
+ * (__bpf_tramp_enter()), not by Tasks RCU, across the call.
+ */
+ emit_rcu_tasks_tramp_nesting(&prog, false);
if (flags & BPF_TRAMP_F_ORIG_STACK) {
emit_ldx(&prog, BPF_DW, BPF_REG_6, BPF_REG_FP, 8);
EMIT2(0xff, 0xd3); /* call *rbx */
@@ -3680,6 +3719,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
goto cleanup;
}
}
+ emit_rcu_tasks_tramp_nesting(&prog, true);
/* remember return value in a stack for bpf prog to access */
emit_stx(&prog, BPF_DW, BPF_REG_FP, BPF_REG_0, -8);
im->ip_after_call = image + (prog - (u8 *)rw_image);
@@ -3741,6 +3781,9 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
if (save_ret)
emit_ldx(&prog, BPF_DW, BPF_REG_0, BPF_REG_FP, -8);
+ /* Remaining instructions are covered by the irq-exit IP check. */
+ emit_rcu_tasks_tramp_nesting(&prog, false);
+
emit_ldx(&prog, BPF_DW, BPF_REG_6, BPF_REG_FP, -rbx_off);
EMIT1(0xC9); /* leave */
diff --git a/arch/x86/xen/enlighten_pv.c b/arch/x86/xen/enlighten_pv.c
index 2c64b388f6164..3d85035f5624f 100644
--- a/arch/x86/xen/enlighten_pv.c
+++ b/arch/x86/xen/enlighten_pv.c
@@ -739,7 +739,7 @@ __visible noinstr void xen_pv_evtchn_do_upcall(struct pt_regs *regs)
inhcall = get_and_clear_inhcall();
if (inhcall && !WARN_ON_ONCE(state.exit_rcu)) {
- irqentry_exit_cond_resched();
+ irqentry_exit_cond_resched(regs);
instrumentation_end();
restore_inhcall(inhcall);
} else {
diff --git a/include/linux/irq-entry-common.h b/include/linux/irq-entry-common.h
index 0bb6c03481fad..fc04725ae46b6 100644
--- a/include/linux/irq-entry-common.h
+++ b/include/linux/irq-entry-common.h
@@ -5,6 +5,7 @@
#include <linux/context_tracking.h>
#include <linux/hrtimer_rearm.h>
#include <linux/kmsan.h>
+#include <linux/rcupdate.h>
#include <linux/rseq_entry.h>
#include <linux/static_call_types.h>
#include <linux/syscalls.h>
@@ -214,6 +215,7 @@ static __always_inline void __exit_to_user_mode_validate(void)
{
/* Ensure that kernel state is sane for a return to userspace */
kmap_assert_nomap();
+ rcu_tasks_trampoline_assert_none();
lockdep_assert_irqs_disabled();
lockdep_sys_exit();
}
@@ -346,21 +348,21 @@ typedef struct irqentry_state {
*
* Conditional reschedule with additional sanity checks.
*/
-void raw_irqentry_exit_cond_resched(void);
+void raw_irqentry_exit_cond_resched(struct pt_regs *regs);
#ifdef CONFIG_PREEMPT_DYNAMIC
#if defined(CONFIG_HAVE_PREEMPT_DYNAMIC_CALL)
#define irqentry_exit_cond_resched_dynamic_enabled raw_irqentry_exit_cond_resched
#define irqentry_exit_cond_resched_dynamic_disabled NULL
DECLARE_STATIC_CALL(irqentry_exit_cond_resched, raw_irqentry_exit_cond_resched);
-#define irqentry_exit_cond_resched() static_call(irqentry_exit_cond_resched)()
+#define irqentry_exit_cond_resched(regs) static_call(irqentry_exit_cond_resched)(regs)
#elif defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY)
DECLARE_STATIC_KEY_TRUE(sk_dynamic_irqentry_exit_cond_resched);
-void dynamic_irqentry_exit_cond_resched(void);
-#define irqentry_exit_cond_resched() dynamic_irqentry_exit_cond_resched()
+void dynamic_irqentry_exit_cond_resched(struct pt_regs *regs);
+#define irqentry_exit_cond_resched(regs) dynamic_irqentry_exit_cond_resched(regs)
#endif
#else /* CONFIG_PREEMPT_DYNAMIC */
-#define irqentry_exit_cond_resched() raw_irqentry_exit_cond_resched()
+#define irqentry_exit_cond_resched(regs) raw_irqentry_exit_cond_resched(regs)
#endif /* CONFIG_PREEMPT_DYNAMIC */
/**
@@ -465,7 +467,7 @@ static inline void irqentry_exit_to_kernel_mode_preempt(struct pt_regs *regs,
return;
if (IS_ENABLED(CONFIG_PREEMPTION))
- irqentry_exit_cond_resched();
+ irqentry_exit_cond_resched(regs);
}
/**
diff --git a/include/linux/kprobes.h b/include/linux/kprobes.h
index e6de7ae55bda0..74cc48c044173 100644
--- a/include/linux/kprobes.h
+++ b/include/linux/kprobes.h
@@ -530,11 +530,17 @@ static inline bool is_kprobe_insn_slot(unsigned long addr)
}
#endif /* !CONFIG_KPROBES */
-#ifndef CONFIG_OPTPROBES
+#ifdef CONFIG_OPTPROBES
+bool kprobe_in_optimized_region(unsigned long addr);
+#else /* !CONFIG_OPTPROBES */
static inline bool is_kprobe_optinsn_slot(unsigned long addr)
{
return false;
}
+static inline bool kprobe_in_optimized_region(unsigned long addr)
+{
+ return false;
+}
#endif /* !CONFIG_OPTPROBES */
#ifdef CONFIG_KRETPROBES
diff --git a/include/linux/module.h b/include/linux/module.h
index 96cc98568eea5..ea4727f53fabf 100644
--- a/include/linux/module.h
+++ b/include/linux/module.h
@@ -521,6 +521,13 @@ struct module {
unsigned int num_ftrace_callsites;
unsigned long *ftrace_callsites;
#endif
+#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
+ /*
+ * An ftrace direct-call trampoline lives in this module's text; see
+ * rcu_tasks_ip_in_trampoline(). Sticky once set.
+ */
+ bool ftrace_direct_tramp;
+#endif
#ifdef CONFIG_KPROBES
void *kprobes_text_start;
unsigned int kprobes_text_size;
diff --git a/include/linux/rcupdate.h b/include/linux/rcupdate.h
index 44c07a66edfff..9509f99ec965f 100644
--- a/include/linux/rcupdate.h
+++ b/include/linux/rcupdate.h
@@ -173,6 +173,9 @@ static inline void rcu_nocb_flush_deferred_wakeup(void) { }
#endif /* #else #ifdef CONFIG_RCU_NOCB_CPU */
+/* Arch hook for rcu_tasks_ip_in_trampoline(); see kernel/rcu/tasks.h. */
+bool arch_rcu_tasks_ip_in_trampoline(unsigned long ip);
+
/*
* Note a quasi-voluntary context switch for RCU-tasks's benefit.
* This is a macro rather than an inline function to avoid #include hell.
@@ -180,9 +183,80 @@ static inline void rcu_nocb_flush_deferred_wakeup(void) { }
#ifdef CONFIG_TASKS_RCU_GENERIC
# ifdef CONFIG_TASKS_RCU
+
+/*
+ * Trampoline nesting: dynamically allocated text (ftrace trampolines, BPF
+ * trampoline images, kprobe optinsn slots) that relies on Tasks RCU for its
+ * lifetime brackets itself with an increment/decrement of
+ * current->rcu_tramp_nesting. While the count is non-zero the task is inside,
+ * or was called from, such text and an involuntary context switch must not be
+ * treated as a Tasks RCU quiescent state.
+ *
+ * The increment and decrement themselves live inside the trampoline, so there
+ * is a window of a few instructions at entry (before the increment) and exit
+ * (after the decrement) where the count is zero but the CPU is executing
+ * trampoline text, or text on the way into one (a static ftrace stub or a
+ * return thunk holding the trampoline's address). In that window the task
+ * cannot be preempted synchronously, only from an interrupt, so the irq-exit
+ * preemption path covers it by checking regs->ip with
+ * rcu_tasks_ip_in_trampoline() and holding the count elevated across
+ * preempt_schedule_irq() when it matches.
+ *
+ * The one non-trampoline user, kprobe jump optimization, waits for tasks
+ * preempted inside ordinary instruction bytes it is about to overwrite. A
+ * task can be parked there from before the kprobe even existed, so that
+ * cannot be decided once at preemption time: irqentry_preempt() records the
+ * interrupted IP in current->rcu_tasks_irq_ip for the duration of the
+ * preemption and rcu_tasks_irq_ip_holds() checks it at every quiescent-state
+ * decision, locally and from the grace-period kthread.
+ *
+ * With both in place, on architectures that select
+ * ARCH_HAS_RCU_TASKS_PREEMPT_QS, a preemption with rcu_tramp_nesting == 0 is
+ * a Tasks RCU quiescent state, and a CPU-bound kernel thread no longer needs
+ * to volunteer one via cond_resched_tasks_rcu_qs().
+ *
+ * Only current writes the count and only current (or an interrupt on the same
+ * CPU) reads it, so plain accesses suffice.
+ */
+static __always_inline void rcu_tasks_trampoline_enter(void)
+{
+ current->rcu_tramp_nesting++;
+ barrier();
+}
+
+static __always_inline void rcu_tasks_trampoline_exit(void)
+{
+ barrier();
+ current->rcu_tramp_nesting--;
+}
+
+/* A task must never reach userspace with a trampoline on its stack. */
+static __always_inline void rcu_tasks_trampoline_assert_none(void)
+{
+ if (IS_ENABLED(CONFIG_PROVE_RCU))
+ WARN_ON_ONCE(current->rcu_tramp_nesting);
+}
+
+bool rcu_tasks_ip_in_trampoline(unsigned long ip);
+bool rcu_tasks_irq_ip_holds(struct task_struct *t);
+
+/* Record where current is being irq-preempted; 0 once it has resumed. */
+static __always_inline void rcu_tasks_note_irq_ip(unsigned long ip)
+{
+ WRITE_ONCE(current->rcu_tasks_irq_ip, ip);
+}
+
+#ifdef CONFIG_RCU_TASKS_PREEMPT_QS
+#define rcu_tasks_preempt_is_qs(t) \
+ (!READ_ONCE((t)->rcu_tramp_nesting) && !rcu_tasks_irq_ip_holds(t))
+#else
+#define rcu_tasks_preempt_is_qs(t) false
+#endif
+
# define rcu_tasks_classic_qs(t, preempt) \
do { \
- if (!(preempt) && READ_ONCE((t)->rcu_tasks_holdout)) \
+ if (READ_ONCE((t)->rcu_tasks_holdout) && \
+ (!(preempt) || rcu_tasks_preempt_is_qs(t))) \
WRITE_ONCE((t)->rcu_tasks_holdout, false); \
} while (0)
void call_rcu_tasks(struct rcu_head *head, rcu_callback_t func);
@@ -192,6 +266,11 @@ void rcu_tasks_torture_stats_print(char *tt, char *tf);
# define rcu_tasks_classic_qs(t, preempt) do { } while (0)
# define call_rcu_tasks call_rcu
# define synchronize_rcu_tasks synchronize_rcu
+static inline void rcu_tasks_trampoline_enter(void) { }
+static inline void rcu_tasks_trampoline_exit(void) { }
+static inline void rcu_tasks_trampoline_assert_none(void) { }
+static inline bool rcu_tasks_ip_in_trampoline(unsigned long ip) { return false; }
+static inline void rcu_tasks_note_irq_ip(unsigned long ip) { }
# endif
#define rcu_tasks_qs(t, preempt) rcu_tasks_classic_qs((t), (preempt))
@@ -208,6 +287,11 @@ void exit_tasks_rcu_finish(void);
#define rcu_tasks_classic_qs(t, preempt) do { } while (0)
#define rcu_tasks_qs(t, preempt) do { } while (0)
#define rcu_note_voluntary_context_switch(t) do { } while (0)
+static inline void rcu_tasks_trampoline_enter(void) { }
+static inline void rcu_tasks_trampoline_exit(void) { }
+static inline void rcu_tasks_trampoline_assert_none(void) { }
+static inline bool rcu_tasks_ip_in_trampoline(unsigned long ip) { return false; }
+static inline void rcu_tasks_note_irq_ip(unsigned long ip) { }
#define call_rcu_tasks call_rcu
#define synchronize_rcu_tasks synchronize_rcu
static inline void exit_tasks_rcu_start(void) { }
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 8b3d47a325cca..7f0bdc81fba33 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -956,6 +956,8 @@ struct task_struct {
unsigned long rcu_tasks_nvcsw;
u8 rcu_tasks_holdout;
u8 rcu_tasks_idx;
+ int rcu_tramp_nesting;
+ unsigned long rcu_tasks_irq_ip;
int rcu_tasks_idle_cpu;
struct list_head rcu_tasks_holdout_list;
int rcu_tasks_exit_cpu;
diff --git a/include/linux/trace_recursion.h b/include/linux/trace_recursion.h
index e6ca052b2a85a..2da23a52ca4a4 100644
--- a/include/linux/trace_recursion.h
+++ b/include/linux/trace_recursion.h
@@ -153,6 +153,17 @@ static __always_inline int trace_test_and_set_recursion(unsigned long ip, unsign
current->trace_recursion = val;
barrier();
+ /*
+ * Callbacks reached from static trampoline text (return_to_handler,
+ * the rethook and kretprobe trampolines) do not maintain
+ * current->rcu_tramp_nesting themselves; they rely on this
+ * preempt_disable() to keep the task from being preempted, and thus
+ * from reporting a Tasks RCU quiescent state, while an ftrace_ops or
+ * its data is in use. If the preempt_disable() is ever removed from
+ * the recursion protection, this must rcu_tasks_trampoline_enter()
+ * here and rcu_tasks_trampoline_exit() in trace_clear_recursion()
+ * instead. See CONFIG_RCU_TASKS_PREEMPT_QS.
+ */
preempt_disable_notrace();
return bit;
diff --git a/kernel/entry/common.c b/kernel/entry/common.c
index e3d381fd3d251..b372f2670d4f7 100644
--- a/kernel/entry/common.c
+++ b/kernel/entry/common.c
@@ -134,7 +134,37 @@ static inline bool arch_irqentry_exit_need_resched(void);
static inline bool arch_irqentry_exit_need_resched(void) { return true; }
#endif
-void raw_irqentry_exit_cond_resched(void)
+/*
+ * Preempt the interrupted kernel context. If the interrupt landed in text
+ * that may be a Tasks-RCU-protected trampoline (see
+ * rcu_tasks_trampoline_enter()), hold current->rcu_tramp_nesting elevated
+ * across the context switch so that it is not mistaken for a Tasks RCU
+ * quiescent state. This closes the few-instruction windows at trampoline
+ * entry/exit where the trampoline's own increment has not yet run or its
+ * decrement already has. The interrupted IP is also recorded for the
+ * duration, for conditions that must be re-evaluated at each quiescent-state
+ * decision rather than once here (see rcu_tasks_irq_ip_holds()); nested
+ * irq-exit preemption cannot happen inside preempt_schedule_irq(), so one
+ * slot per task is enough.
+ */
+static void irqentry_preempt(struct pt_regs *regs)
+{
+ unsigned long ip = instruction_pointer(regs);
+ bool in_tramp = IS_ENABLED(CONFIG_RCU_TASKS_PREEMPT_QS) &&
+ rcu_tasks_ip_in_trampoline(ip);
+
+ if (in_tramp)
+ rcu_tasks_trampoline_enter();
+ if (IS_ENABLED(CONFIG_RCU_TASKS_PREEMPT_QS))
+ rcu_tasks_note_irq_ip(ip);
+ preempt_schedule_irq();
+ if (IS_ENABLED(CONFIG_RCU_TASKS_PREEMPT_QS))
+ rcu_tasks_note_irq_ip(0);
+ if (in_tramp)
+ rcu_tasks_trampoline_exit();
+}
+
+void raw_irqentry_exit_cond_resched(struct pt_regs *regs)
{
if (!preempt_count()) {
/* Sanity check RCU and thread stack */
@@ -142,7 +172,7 @@ void raw_irqentry_exit_cond_resched(void)
if (IS_ENABLED(CONFIG_DEBUG_ENTRY))
WARN_ON_ONCE(!on_thread_stack());
if (need_resched() && arch_irqentry_exit_need_resched())
- preempt_schedule_irq();
+ irqentry_preempt(regs);
}
}
#ifdef CONFIG_PREEMPT_DYNAMIC
@@ -150,11 +180,11 @@ void raw_irqentry_exit_cond_resched(void)
DEFINE_STATIC_CALL(irqentry_exit_cond_resched, raw_irqentry_exit_cond_resched);
#elif defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY)
DEFINE_STATIC_KEY_TRUE(sk_dynamic_irqentry_exit_cond_resched);
-void dynamic_irqentry_exit_cond_resched(void)
+void dynamic_irqentry_exit_cond_resched(struct pt_regs *regs)
{
if (!static_branch_unlikely(&sk_dynamic_irqentry_exit_cond_resched))
return;
- raw_irqentry_exit_cond_resched();
+ raw_irqentry_exit_cond_resched(regs);
}
#endif
#endif
diff --git a/kernel/fork.c b/kernel/fork.c
index 416758c8a3d43..1277603bc472f 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -1869,6 +1869,8 @@ static inline void rcu_copy_process(struct task_struct *p)
#endif /* #ifdef CONFIG_PREEMPT_RCU */
#ifdef CONFIG_TASKS_RCU
p->rcu_tasks_holdout = false;
+ p->rcu_tramp_nesting = 0;
+ p->rcu_tasks_irq_ip = 0;
INIT_LIST_HEAD(&p->rcu_tasks_holdout_list);
p->rcu_tasks_idle_cpu = -1;
INIT_LIST_HEAD(&p->rcu_tasks_exit_list);
diff --git a/kernel/kprobes.c b/kernel/kprobes.c
index 6337da5cab9e7..cf2ea278fdf5b 100644
--- a/kernel/kprobes.c
+++ b/kernel/kprobes.c
@@ -511,6 +511,48 @@ static struct kprobe *get_optimized_kprobe(kprobe_opcode_t *addr)
return NULL;
}
+/*
+ * True while kprobe_optimizer() is waiting for its Tasks RCU grace period.
+ * Only in that window can a preemption inside an optprobe's jump region
+ * matter to it, so kprobe_in_optimized_region() does no work otherwise.
+ */
+static bool kprobe_optimizer_waiting;
+
+/**
+ * kprobe_in_optimized_region - Could @addr be inside bytes a jump-optimized
+ * kprobe replaces?
+ * @addr: kernel text address, typically an interrupted instruction pointer
+ *
+ * kprobe_optimizer() relies on synchronize_rcu_tasks() to wait for tasks that
+ * were preempted on an instruction boundary inside the region about to be
+ * overwritten by the optimized jump; such a task must not report a Tasks RCU
+ * quiescent state when it is preempted (see rcu_tasks_ip_in_trampoline()).
+ * This is the lockless, conservative form of get_optimized_kprobe(): it does
+ * not care whether the kprobe found is, or ever will be, optimized. May be
+ * called from any context with preemption disabled; the kprobe hash is
+ * RCU-protected and every free path waits for a grace period after unhashing.
+ *
+ * The hash walk only runs while the optimizer is actually waiting. A
+ * preemption that does not observe kprobe_optimizer_waiting predates the
+ * grace period (its leading synchronize_rcu() publishes the store to every
+ * interrupts-disabled reader before any task is sampled as a holdout); such a
+ * task is then an ordinary preempted holdout, and the jump is not written
+ * until it has run again and left the region.
+ */
+bool kprobe_in_optimized_region(unsigned long addr)
+{
+ int i;
+
+ if (!READ_ONCE(kprobe_optimizer_waiting))
+ return false;
+
+ for (i = 1; i < MAX_OPTIMIZED_LENGTH / sizeof(kprobe_opcode_t); i++)
+ if (get_kprobe((kprobe_opcode_t *)addr - i))
+ return true;
+ return false;
+}
+NOKPROBE_SYMBOL(kprobe_in_optimized_region);
+
/* Optimization staging list, protected by 'kprobe_mutex' */
static LIST_HEAD(optimizing_list);
static LIST_HEAD(unoptimizing_list);
@@ -644,8 +686,12 @@ static void kprobe_optimizer(void)
* to 2nd-Nth byte of jump instruction. This wait is for avoiding it.
* Note that on non-preemptive kernel, this is transparently converted
* to synchronoze_sched() to wait for all interrupts to have completed.
+ * kprobe_optimizer_waiting lets Tasks RCU recognise tasks preempted
+ * in such a region while we wait, see kprobe_in_optimized_region().
*/
+ WRITE_ONCE(kprobe_optimizer_waiting, true);
synchronize_rcu_tasks();
+ WRITE_ONCE(kprobe_optimizer_waiting, false);
/* Step 3: Optimize kprobes after quiesence period */
do_optimize_kprobes();
diff --git a/kernel/rcu/Kconfig b/kernel/rcu/Kconfig
index 332df7a7a6347..8e7c943291054 100644
--- a/kernel/rcu/Kconfig
+++ b/kernel/rcu/Kconfig
@@ -94,9 +94,10 @@ config FORCE_TASKS_RCU
default n
help
This option force-enables a task-based RCU implementation
- that uses only voluntary context switch (not preemption!),
- idle, and user-mode execution as quiescent states. Not for
- manual selection in most cases.
+ that uses only voluntary context switch (not preemption, unless
+ the architecture selects ARCH_HAS_RCU_TASKS_PREEMPT_QS and the
+ task is outside any trampoline), idle, and user-mode execution
+ as quiescent states. Not for manual selection in most cases.
config NEED_TASKS_RCU
bool
@@ -107,6 +108,16 @@ config TASKS_RCU
default NEED_TASKS_RCU && PREEMPTION
select IRQ_WORK
+# Selected by architectures whose ftrace, BPF and kprobe trampolines maintain
+# current->rcu_tramp_nesting and which use the generic irqentry code, so that
+# a preemption outside any trampoline can be treated as a Tasks RCU
+# quiescent state. See rcu_tasks_trampoline_enter().
+config ARCH_HAS_RCU_TASKS_PREEMPT_QS
+ bool
+
+config RCU_TASKS_PREEMPT_QS
+ def_bool TASKS_RCU && ARCH_HAS_RCU_TASKS_PREEMPT_QS && GENERIC_IRQ_ENTRY
+
config FORCE_TASKS_RUDE_RCU
bool "Force selection of Tasks Rude RCU"
depends on RCU_EXPERT
diff --git a/kernel/rcu/rcutorture.c b/kernel/rcu/rcutorture.c
index 794937e13e7c3..df6dd708cea78 100644
--- a/kernel/rcu/rcutorture.c
+++ b/kernel/rcu/rcutorture.c
@@ -1144,11 +1144,17 @@ static struct rcu_torture_ops trivial_preempt_ops = {
static int tasks_torture_read_lock(void)
{
+ /*
+ * Model a trampoline: with CONFIG_RCU_TASKS_PREEMPT_QS a preemption is
+ * otherwise a quiescent state and rcu_read_delay() preempts on purpose.
+ */
+ rcu_tasks_trampoline_enter();
return 0;
}
static void tasks_torture_read_unlock(int idx)
{
+ rcu_tasks_trampoline_exit();
}
static void rcu_tasks_torture_deferred_free(struct rcu_torture *p)
diff --git a/kernel/rcu/tasks.h b/kernel/rcu/tasks.h
index 627295396cd91..02d2592ab7a3d 100644
--- a/kernel/rcu/tasks.h
+++ b/kernel/rcu/tasks.h
@@ -905,7 +905,10 @@ static void rcu_tasks_wait_gp(struct rcu_tasks *rtp)
//
// Simple variant of RCU whose quiescent states are voluntary context
// switch, cond_resched_tasks_rcu_qs(), user-space execution, and idle.
-// As such, grace periods can take one good long time. There are no
+// With CONFIG_RCU_TASKS_PREEMPT_QS, a preemption taken while the task is
+// not inside a trampoline (current->rcu_tramp_nesting == 0, see
+// rcu_tasks_trampoline_enter()) is a quiescent state as well; without it,
+// grace periods can take one good long time. There are no
// read-side primitives similar to rcu_read_lock() and rcu_read_unlock()
// because this implementation is intended to get the system into a safe
// state for some of the manipulations involved in tracing and the like.
@@ -1011,10 +1014,49 @@ static bool rcu_tasks_is_holdout(struct task_struct *t)
return true;
}
+#ifdef CONFIG_RCU_TASKS_PREEMPT_QS
+/* task_call_func() callback: is @t switched out with no trampoline in play? */
+static int rcu_tasks_switched_out_clean(struct task_struct *t, void *arg)
+{
+ /*
+ * With @t pinned, !task_curr() means it last left the CPU through
+ * __schedule(), so its rcu_tramp_nesting and rcu_tasks_irq_ip are
+ * stable and ordered before the rq lock we hold. A task preempted
+ * from irq exit inside trampoline text has the count held non-zero
+ * across the switch by irqentry_preempt(), so zero here means neither
+ * in nor called from a trampoline; rcu_tasks_irq_ip_holds() covers the
+ * one case that can become true after the task was switched out (a
+ * kprobe jump-optimization window). Both clear: already quiescent,
+ * whether or not it ever runs again.
+ */
+ return !task_curr(t) && !READ_ONCE(t->rcu_tramp_nesting) &&
+ !rcu_tasks_irq_ip_holds(t);
+}
+
+/* Is @t, right now, switched out somewhere that is a quiescent state? */
+static bool rcu_tasks_preempted_qs(struct task_struct *t)
+{
+ return task_call_func(t, rcu_tasks_switched_out_clean, NULL);
+}
+
+/* Make a running holdout pass through __schedule() soon, tick or no tick. */
+static void rcu_tasks_kick_running(struct task_struct *t)
+{
+ int cpu = task_cpu(t);
+
+ if (task_curr(t) && cpu_online(cpu))
+ resched_cpu(cpu);
+}
+#else
+static bool rcu_tasks_preempted_qs(struct task_struct *t) { return false; }
+static void rcu_tasks_kick_running(struct task_struct *t) { }
+#endif
+
/* Per-task initial processing. */
static void rcu_tasks_pertask(struct task_struct *t, struct list_head *hop)
{
- if (t != current && rcu_tasks_is_holdout(t)) {
+ if (t != current && rcu_tasks_is_holdout(t) &&
+ !rcu_tasks_preempted_qs(t)) {
get_task_struct(t);
t->rcu_tasks_nvcsw = READ_ONCE(t->nvcsw);
WRITE_ONCE(t->rcu_tasks_holdout, true);
@@ -1089,6 +1131,86 @@ static void rcu_tasks_postscan(struct list_head *hop)
timer_delete_sync(&tasks_rcu_exit_stall_timer);
}
+/*
+ * Architectures selecting ARCH_HAS_RCU_TASKS_PREEMPT_QS override this to flag
+ * core kernel text that must be treated like a trampoline, e.g. static ftrace
+ * entry stubs and return thunks that run with a trampoline address in hand.
+ */
+bool __weak arch_rcu_tasks_ip_in_trampoline(unsigned long ip)
+{
+ return false;
+}
+
+/**
+ * rcu_tasks_ip_in_trampoline - Could a task interrupted at @ip be a Tasks RCU reader?
+ * @ip: interrupted instruction pointer
+ *
+ * Called from the irq-exit preemption path with interrupts disabled, to decide
+ * whether the imminent preemption may be reported as a Tasks RCU quiescent
+ * state when current->rcu_tramp_nesting is zero. Returns true, meaning "do
+ * not report", when @ip is:
+ *
+ * - outside static kernel and module text, i.e. possibly in an ftrace
+ * trampoline, BPF trampoline image or program, kprobe insn/optinsn slot or
+ * other dynamically allocated text whose lifetime Tasks RCU guards. This
+ * deliberately does not consult is_ftrace_trampoline() and friends: text
+ * being torn down may already be unregistered there while a task still
+ * stands on it;
+ * - in core text the architecture flags via arch_rcu_tasks_ip_in_trampoline();
+ * - in the text of a module that hosts an ftrace direct-call trampoline,
+ * which covers the instructions before that trampoline's increment and
+ * after its decrement (see ftrace_direct_mark_module()).
+ *
+ * A false positive only defers the quiescent state to the task's next
+ * context switch.
+ */
+bool rcu_tasks_ip_in_trampoline(unsigned long ip)
+{
+ bool ret = true;
+
+ if (core_kernel_text(ip))
+ return arch_rcu_tasks_ip_in_trampoline(ip);
+
+#ifdef CONFIG_MODULES
+ scoped_guard(rcu) {
+ struct module *mod = __module_text_address(ip);
+
+#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
+ if (mod)
+ ret = READ_ONCE(mod->ftrace_direct_tramp);
+#else
+ if (mod)
+ ret = false;
+#endif
+ }
+#endif
+ return ret;
+}
+NOKPROBE_SYMBOL(rcu_tasks_ip_in_trampoline);
+
+/**
+ * rcu_tasks_irq_ip_holds - Is @t irq-preempted somewhere that must hold off Tasks RCU?
+ * @t: a task inside preempt_schedule_irq() (t->rcu_tasks_irq_ip != 0), or not
+ *
+ * Unlike trampoline text, which a task can only be interrupted in while the
+ * trampoline exists, the bytes kprobe_optimizer() is about to overwrite with a
+ * jump are ordinary text a task may have been parked in since before the
+ * kprobe was registered, and the optimizer may start waiting while the task is
+ * already switched out. So this is evaluated against the IP recorded by
+ * irqentry_preempt() at every quiescent-state decision -- each pass through
+ * __schedule() in preempt_schedule_irq()'s loop, and the grace-period
+ * kthread's scans -- rather than once at preemption time. A task switched out
+ * synchronously cannot have a resume point inside such a window (a call there
+ * returns beyond it), so only the irq-exit IP needs checking.
+ */
+bool rcu_tasks_irq_ip_holds(struct task_struct *t)
+{
+ unsigned long ip = READ_ONCE(t->rcu_tasks_irq_ip);
+
+ return ip && kprobe_in_optimized_region(ip);
+}
+NOKPROBE_SYMBOL(rcu_tasks_irq_ip_holds);
+
/* See if tasks are still holding out, complain if so. */
static void check_holdout_task(struct task_struct *t,
bool needreport, bool *firstreport)
@@ -1098,6 +1220,7 @@ static void check_holdout_task(struct task_struct *t,
if (!READ_ONCE(t->rcu_tasks_holdout) ||
t->rcu_tasks_nvcsw != READ_ONCE(t->nvcsw) ||
!rcu_tasks_is_holdout(t) ||
+ rcu_tasks_preempted_qs(t) ||
(IS_ENABLED(CONFIG_NO_HZ_FULL) &&
!is_idle_task(t) && READ_ONCE(t->rcu_tasks_idle_cpu) >= 0)) {
WRITE_ONCE(t->rcu_tasks_holdout, false);
@@ -1106,6 +1229,7 @@ static void check_holdout_task(struct task_struct *t,
return;
}
rcu_request_urgent_qs_task(t);
+ rcu_tasks_kick_running(t);
if (!needreport)
return;
if (*firstreport) {
@@ -1113,10 +1237,11 @@ static void check_holdout_task(struct task_struct *t,
*firstreport = false;
}
cpu = task_cpu(t);
- pr_alert("%p: %c%c nvcsw: %lu/%lu holdout: %d idle_cpu: %d/%d\n",
+ pr_alert("%p: %c%c nvcsw: %lu/%lu holdout: %d tramp_nesting: %d idle_cpu: %d/%d\n",
t, ".I"[is_idle_task(t)],
"N."[cpu < 0 || !tick_nohz_full_cpu(cpu)],
t->rcu_tasks_nvcsw, t->nvcsw, t->rcu_tasks_holdout,
+ data_race(t->rcu_tramp_nesting),
data_race(t->rcu_tasks_idle_cpu), cpu);
sched_show_task(t);
}
@@ -1182,8 +1307,11 @@ static void tasks_rcu_exit_stall(struct timer_list *unused)
* period elapses, in other words after all currently executing rcu-tasks
* read-side critical sections have completed. call_rcu_tasks() assumes
* that the read-side critical sections end at a voluntary context
- * switch (not a preemption!), cond_resched_tasks_rcu_qs(), entry into idle,
- * or transition to usermode execution. As such, there are no read-side
+ * switch, cond_resched_tasks_rcu_qs(), entry into idle, transition to
+ * usermode execution, or, with CONFIG_RCU_TASKS_PREEMPT_QS, a preemption
+ * taken outside any trampoline (current->rcu_tramp_nesting == 0, see
+ * rcu_tasks_trampoline_enter()); otherwise a preemption is not a
+ * quiescent state. As such, there are no read-side
* primitives analogous to rcu_read_lock() and rcu_read_unlock() because
* this primitive is intended to determine that all tasks have passed
* through a safe state, not so much for data-structure synchronization.
@@ -1205,7 +1333,8 @@ EXPORT_SYMBOL_GPL(call_rcu_tasks);
* executing rcu-tasks read-side critical sections have elapsed. These
* read-side critical sections are delimited by calls to schedule(),
* cond_resched_tasks_rcu_qs(), idle execution, userspace execution, calls
- * to synchronize_rcu_tasks(), and (in theory, anyway) cond_resched().
+ * to synchronize_rcu_tasks(), (in theory, anyway) cond_resched(), and,
+ * with CONFIG_RCU_TASKS_PREEMPT_QS, preemption outside any trampoline.
*
* This is a very specialized primitive, intended only for a few uses in
* tracing and other situations requiring manipulation of function
diff --git a/kernel/rcu/update.c b/kernel/rcu/update.c
index b62735a678842..23be7e97c3b5f 100644
--- a/kernel/rcu/update.c
+++ b/kernel/rcu/update.c
@@ -41,6 +41,8 @@
#include <linux/rcupdate_wait.h>
#include <linux/sched/isolation.h>
#include <linux/kprobes.h>
+#include <linux/kallsyms.h>
+#include <linux/module.h>
#include <linux/slab.h>
#include <linux/irq_work.h>
#include <linux/rcupdate_trace.h>
diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c
index 53d5db60bfa56..14f27b8872319 100644
--- a/kernel/trace/ftrace.c
+++ b/kernel/trace/ftrace.c
@@ -6076,6 +6076,29 @@ static void reset_direct(struct ftrace_ops *ops, unsigned long addr)
ops->trampoline = 0;
}
+/*
+ * A direct trampoline may live in module text rather than in dynamically
+ * allocated text that rcu_tasks_ip_in_trampoline() recognises on its own (see
+ * samples/ftrace/ftrace-direct*.c). The trampoline itself must hold
+ * current->rcu_tramp_nesting across its call-out (see register_ftrace_direct());
+ * marking the owning module here covers the instructions before that increment
+ * and after the decrement, where a task interrupted in the module's text must
+ * not be treated as Tasks-RCU quiescent, so that ftrace_shutdown()'s
+ * synchronize_rcu_tasks() still keeps the module text from being freed under
+ * it.
+ */
+static void ftrace_direct_mark_module(unsigned long addr)
+{
+#ifdef CONFIG_MODULES
+ struct module *mod;
+
+ guard(rcu)();
+ mod = __module_text_address(addr);
+ if (mod)
+ WRITE_ONCE(mod->ftrace_direct_tramp, true);
+#endif
+}
+
/**
* register_ftrace_direct - Call a custom trampoline directly
* for multiple functions registered in @ops
@@ -6090,6 +6113,17 @@ static void reset_direct(struct ftrace_ops *ops, unsigned long addr)
* and save the parameters of the function being traced, and restore them
* (or inject new ones if needed), before returning.
*
+ * Nothing but Tasks RCU keeps the trampoline at @addr alive while a task is
+ * executing it or is preempted in something it called. On architectures that
+ * select ARCH_HAS_RCU_TASKS_PREEMPT_QS a preemption is a Tasks RCU quiescent
+ * state unless current->rcu_tramp_nesting is non-zero, so the trampoline must
+ * increment it before calling out and decrement it before returning, as the
+ * ftrace and BPF trampolines do (see rcu_tasks_trampoline_enter() and
+ * samples/ftrace/ftrace-direct.h). The few instructions before the increment
+ * and after the decrement are covered by the irq-exit IP check: automatically
+ * for trampolines outside kernel and module text (e.g. BPF images), and via
+ * ftrace_direct_mark_module() for trampolines in module text.
+ *
* Returns:
* 0 on success
* -EINVAL - The @ops object was already registered with this call or
@@ -6169,6 +6203,7 @@ int register_ftrace_direct(struct ftrace_ops *ops, unsigned long addr)
ops->flags |= MULTI_FLAGS;
ops->trampoline = FTRACE_REGS_ADDR;
ops->direct_call = addr;
+ ftrace_direct_mark_module(addr);
err = register_ftrace_function_nolock(ops);
if (err)
@@ -6237,6 +6272,8 @@ __modify_ftrace_direct(struct ftrace_ops *ops, unsigned long addr)
lockdep_assert_held_once(&direct_mutex);
+ ftrace_direct_mark_module(addr);
+
/* Enable the tmp_ops to have the same functions as the direct ops */
ftrace_ops_init(&tmp_ops);
tmp_ops.func_hash = ops->func_hash;
@@ -6419,6 +6456,7 @@ int update_ftrace_direct_add(struct ftrace_ops *ops, struct ftrace_hash *hash)
hlist_for_each_entry(entry, &hash->buckets[i], hlist) {
if (__ftrace_lookup_ip(direct_functions, entry->ip))
goto out_unlock;
+ ftrace_direct_mark_module(entry->direct);
}
}
@@ -6702,6 +6740,7 @@ int update_ftrace_direct_mod(struct ftrace_ops *ops, struct ftrace_hash *hash, b
tmp = __ftrace_lookup_ip(direct_hash, entry->ip);
if (!tmp)
continue;
+ ftrace_direct_mark_module(entry->direct);
tmp->direct = entry->direct;
}
}
diff --git a/samples/ftrace/ftrace-direct-modify.c b/samples/ftrace/ftrace-direct-modify.c
index 164d9dd6fd921..eb8230fa4242b 100644
--- a/samples/ftrace/ftrace-direct-modify.c
+++ b/samples/ftrace/ftrace-direct-modify.c
@@ -2,6 +2,7 @@
#include <linux/module.h>
#include <linux/kthread.h>
#include <linux/ftrace.h>
+#include "ftrace-direct.h"
#if !defined(CONFIG_ARM64) && !defined(CONFIG_PPC32)
#include <asm/asm-offsets.h>
#endif
@@ -73,7 +74,9 @@ asm (
" pushq %rbp\n"
" movq %rsp, %rbp\n"
CALL_DEPTH_ACCOUNT
+ RCU_TASKS_TRAMP_ENTER
" call my_direct_func1\n"
+ RCU_TASKS_TRAMP_EXIT
" leave\n"
ASM_RET
" .size my_tramp1, .-my_tramp1\n"
@@ -85,7 +88,9 @@ asm (
" pushq %rbp\n"
" movq %rsp, %rbp\n"
CALL_DEPTH_ACCOUNT
+ RCU_TASKS_TRAMP_ENTER
" call my_direct_func2\n"
+ RCU_TASKS_TRAMP_EXIT
" leave\n"
ASM_RET
" .size my_tramp2, .-my_tramp2\n"
@@ -141,11 +146,13 @@ asm (
" .globl my_tramp1\n"
" my_tramp1:"
" hint 34\n" // bti c
+ RCU_TASKS_TRAMP_ENTER
" sub sp, sp, #16\n"
" stp x9, x30, [sp]\n"
" bl my_direct_func1\n"
" ldp x30, x9, [sp]\n"
" add sp, sp, #16\n"
+ RCU_TASKS_TRAMP_EXIT
" ret x9\n"
" .size my_tramp1, .-my_tramp1\n"
@@ -153,11 +160,13 @@ asm (
" .globl my_tramp2\n"
" my_tramp2:"
" hint 34\n" // bti c
+ RCU_TASKS_TRAMP_ENTER
" sub sp, sp, #16\n"
" stp x9, x30, [sp]\n"
" bl my_direct_func2\n"
" ldp x30, x9, [sp]\n"
" add sp, sp, #16\n"
+ RCU_TASKS_TRAMP_EXIT
" ret x9\n"
" .size my_tramp2, .-my_tramp2\n"
" .popsection\n"
diff --git a/samples/ftrace/ftrace-direct-multi-modify.c b/samples/ftrace/ftrace-direct-multi-modify.c
index b03766c6217bc..c8f1062e5d1ad 100644
--- a/samples/ftrace/ftrace-direct-multi-modify.c
+++ b/samples/ftrace/ftrace-direct-multi-modify.c
@@ -2,6 +2,7 @@
#include <linux/module.h>
#include <linux/kthread.h>
#include <linux/ftrace.h>
+#include "ftrace-direct.h"
#if !defined(CONFIG_ARM64) && !defined(CONFIG_PPC32)
#include <asm/asm-offsets.h>
#endif
@@ -77,10 +78,12 @@ asm (
" pushq %rbp\n"
" movq %rsp, %rbp\n"
CALL_DEPTH_ACCOUNT
+ RCU_TASKS_TRAMP_ENTER
" pushq %rdi\n"
" movq 8(%rbp), %rdi\n"
" call my_direct_func1\n"
" popq %rdi\n"
+ RCU_TASKS_TRAMP_EXIT
" leave\n"
ASM_RET
" .size my_tramp1, .-my_tramp1\n"
@@ -92,10 +95,12 @@ asm (
" pushq %rbp\n"
" movq %rsp, %rbp\n"
CALL_DEPTH_ACCOUNT
+ RCU_TASKS_TRAMP_ENTER
" pushq %rdi\n"
" movq 8(%rbp), %rdi\n"
" call my_direct_func2\n"
" popq %rdi\n"
+ RCU_TASKS_TRAMP_EXIT
" leave\n"
ASM_RET
" .size my_tramp2, .-my_tramp2\n"
@@ -154,6 +159,7 @@ asm (
" .globl my_tramp1\n"
" my_tramp1:"
" hint 34\n" // bti c
+ RCU_TASKS_TRAMP_ENTER
" sub sp, sp, #32\n"
" stp x9, x30, [sp]\n"
" str x0, [sp, #16]\n"
@@ -162,6 +168,7 @@ asm (
" ldp x30, x9, [sp]\n"
" ldr x0, [sp, #16]\n"
" add sp, sp, #32\n"
+ RCU_TASKS_TRAMP_EXIT
" ret x9\n"
" .size my_tramp1, .-my_tramp1\n"
@@ -169,6 +176,7 @@ asm (
" .globl my_tramp2\n"
" my_tramp2:"
" hint 34\n" // bti c
+ RCU_TASKS_TRAMP_ENTER
" sub sp, sp, #32\n"
" stp x9, x30, [sp]\n"
" str x0, [sp, #16]\n"
@@ -177,6 +185,7 @@ asm (
" ldp x30, x9, [sp]\n"
" ldr x0, [sp, #16]\n"
" add sp, sp, #32\n"
+ RCU_TASKS_TRAMP_EXIT
" ret x9\n"
" .size my_tramp2, .-my_tramp2\n"
" .popsection\n"
diff --git a/samples/ftrace/ftrace-direct-multi.c b/samples/ftrace/ftrace-direct-multi.c
index 3fe6ddaf0b69f..bc6a88dd4ffcb 100644
--- a/samples/ftrace/ftrace-direct-multi.c
+++ b/samples/ftrace/ftrace-direct-multi.c
@@ -3,6 +3,7 @@
#include <linux/mm.h> /* for handle_mm_fault() */
#include <linux/ftrace.h>
+#include "ftrace-direct.h"
#include <linux/sched/stat.h>
#if !defined(CONFIG_ARM64) && !defined(CONFIG_PPC32)
#include <asm/asm-offsets.h>
@@ -56,10 +57,12 @@ asm (
" pushq %rbp\n"
" movq %rsp, %rbp\n"
CALL_DEPTH_ACCOUNT
+ RCU_TASKS_TRAMP_ENTER
" pushq %rdi\n"
" movq 8(%rbp), %rdi\n"
" call my_direct_func\n"
" popq %rdi\n"
+ RCU_TASKS_TRAMP_EXIT
" leave\n"
ASM_RET
" .size my_tramp, .-my_tramp\n"
@@ -101,6 +104,7 @@ asm (
" .globl my_tramp\n"
" my_tramp:"
" hint 34\n" // bti c
+ RCU_TASKS_TRAMP_ENTER
" sub sp, sp, #32\n"
" stp x9, x30, [sp]\n"
" str x0, [sp, #16]\n"
@@ -109,6 +113,7 @@ asm (
" ldp x30, x9, [sp]\n"
" ldr x0, [sp, #16]\n"
" add sp, sp, #32\n"
+ RCU_TASKS_TRAMP_EXIT
" ret x9\n"
" .size my_tramp, .-my_tramp\n"
" .popsection\n"
diff --git a/samples/ftrace/ftrace-direct-too.c b/samples/ftrace/ftrace-direct-too.c
index bf2411aa6fd7a..247e418644a29 100644
--- a/samples/ftrace/ftrace-direct-too.c
+++ b/samples/ftrace/ftrace-direct-too.c
@@ -3,6 +3,7 @@
#include <linux/mm.h> /* for handle_mm_fault() */
#include <linux/ftrace.h>
+#include "ftrace-direct.h"
#if !defined(CONFIG_ARM64) && !defined(CONFIG_PPC32)
#include <asm/asm-offsets.h>
#endif
@@ -61,6 +62,7 @@ asm (
" pushq %rbp\n"
" movq %rsp, %rbp\n"
CALL_DEPTH_ACCOUNT
+ RCU_TASKS_TRAMP_ENTER
" pushq %rdi\n"
" pushq %rsi\n"
" pushq %rdx\n"
@@ -70,6 +72,7 @@ asm (
" popq %rdx\n"
" popq %rsi\n"
" popq %rdi\n"
+ RCU_TASKS_TRAMP_EXIT
" leave\n"
ASM_RET
" .size my_tramp, .-my_tramp\n"
@@ -110,6 +113,7 @@ asm (
" .globl my_tramp\n"
" my_tramp:"
" hint 34\n" // bti c
+ RCU_TASKS_TRAMP_ENTER
" sub sp, sp, #48\n"
" stp x9, x30, [sp]\n"
" stp x0, x1, [sp, #16]\n"
@@ -119,6 +123,7 @@ asm (
" ldp x0, x1, [sp, #16]\n"
" ldp x2, x3, [sp, #32]\n"
" add sp, sp, #48\n"
+ RCU_TASKS_TRAMP_EXIT
" ret x9\n"
" .size my_tramp, .-my_tramp\n"
" .popsection\n"
diff --git a/samples/ftrace/ftrace-direct.c b/samples/ftrace/ftrace-direct.c
index 5368c8c39cbb4..9e1964baf28b4 100644
--- a/samples/ftrace/ftrace-direct.c
+++ b/samples/ftrace/ftrace-direct.c
@@ -3,6 +3,7 @@
#include <linux/sched.h> /* for wake_up_process() */
#include <linux/ftrace.h>
+#include "ftrace-direct.h"
#if !defined(CONFIG_ARM64) && !defined(CONFIG_PPC32)
#include <asm/asm-offsets.h>
#endif
@@ -54,9 +55,11 @@ asm (
" pushq %rbp\n"
" movq %rsp, %rbp\n"
CALL_DEPTH_ACCOUNT
+ RCU_TASKS_TRAMP_ENTER
" pushq %rdi\n"
" call my_direct_func\n"
" popq %rdi\n"
+ RCU_TASKS_TRAMP_EXIT
" leave\n"
ASM_RET
" .size my_tramp, .-my_tramp\n"
@@ -97,6 +100,7 @@ asm (
" .globl my_tramp\n"
" my_tramp:"
" hint 34\n" // bti c
+ RCU_TASKS_TRAMP_ENTER
" sub sp, sp, #32\n"
" stp x9, x30, [sp]\n"
" str x0, [sp, #16]\n"
@@ -104,6 +108,7 @@ asm (
" ldp x30, x9, [sp]\n"
" ldr x0, [sp, #16]\n"
" add sp, sp, #32\n"
+ RCU_TASKS_TRAMP_EXIT
" ret x9\n"
" .size my_tramp, .-my_tramp\n"
" .popsection\n"
diff --git a/samples/ftrace/ftrace-direct.h b/samples/ftrace/ftrace-direct.h
new file mode 100644
index 0000000000000..d0313f33f47f8
--- /dev/null
+++ b/samples/ftrace/ftrace-direct.h
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _SAMPLES_FTRACE_DIRECT_H
+#define _SAMPLES_FTRACE_DIRECT_H
+
+#include <linux/stringify.h>
+
+/*
+ * A direct-call trampoline is entered with no lock, refcount or RCU marker
+ * held; only Tasks RCU keeps it (and, for a module, its text) alive while a
+ * task is inside it or preempted in something it called. On architectures
+ * that select ARCH_HAS_RCU_TASKS_PREEMPT_QS a preemption is a Tasks RCU
+ * quiescent state unless current->rcu_tramp_nesting is non-zero, so the
+ * trampoline must raise it before calling out and drop it afterwards, exactly
+ * like the ftrace and BPF trampolines do. See rcu_tasks_trampoline_enter()
+ * and register_ftrace_direct(). The instructions before the increment and
+ * after the decrement are covered by ftrace_direct_mark_module().
+ *
+ * These expand to instruction strings for use inside the samples' asm()
+ * trampolines. The scratch register is caller-saved and not an argument
+ * register, so it is dead on entry to and exit from an fentry trampoline.
+ *
+ * The generated asm-offsets.h is only pulled in on the architectures that need
+ * it here: it is not generally safe to include from C (e.g. PPC32's TASK_SIZE
+ * and arm64's TRAMP_VALIAS clash with the C definitions), which is why the
+ * samples themselves guard their own include of it.
+ */
+#if defined(CONFIG_TASKS_RCU) && defined(CONFIG_X86_64)
+
+#include <asm/asm-offsets.h>
+
+#define RCU_TASKS_TRAMP_ENTER \
+ " movq %gs:current_task(%rip), %r11\n" \
+ " incl " __stringify(TASK_rcu_tramp_nesting) "(%r11)\n"
+#define RCU_TASKS_TRAMP_EXIT \
+ " movq %gs:current_task(%rip), %r11\n" \
+ " decl " __stringify(TASK_rcu_tramp_nesting) "(%r11)\n"
+
+#elif defined(CONFIG_TASKS_RCU) && defined(CONFIG_ARM64)
+
+/* arm64's asm-offsets.h redefines TRAMP_VALIAS from <asm/fixmap.h>. */
+#pragma push_macro("TRAMP_VALIAS")
+#undef TRAMP_VALIAS
+#include <asm/asm-offsets.h>
+#pragma pop_macro("TRAMP_VALIAS")
+
+#define RCU_TASKS_TRAMP_ENTER \
+ " mrs x12, sp_el0\n" \
+ " ldr w13, [x12, #" __stringify(TSK_RCU_TRAMP_NESTING) "]\n" \
+ " add w13, w13, #1\n" \
+ " str w13, [x12, #" __stringify(TSK_RCU_TRAMP_NESTING) "]\n"
+#define RCU_TASKS_TRAMP_EXIT \
+ " mrs x12, sp_el0\n" \
+ " ldr w13, [x12, #" __stringify(TSK_RCU_TRAMP_NESTING) "]\n" \
+ " sub w13, w13, #1\n" \
+ " str w13, [x12, #" __stringify(TSK_RCU_TRAMP_NESTING) "]\n"
+
+#else
+
+#define RCU_TASKS_TRAMP_ENTER
+#define RCU_TASKS_TRAMP_EXIT
+
+#endif
+
+#endif /* _SAMPLES_FTRACE_DIRECT_H */
|
| 3/2 | 2026/09/11 14:19 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 28168 output: 25 thoughts: 417 Thoughts: **Analyzing the New Tasks RCU Optimization** Okay, so this patch is a big one – it's all about introducing a major optimization/feature for Tasks RCU: `CONFIG_ARCH_HAS_RCU_TASKS_PREEMPT_QS` and `CONFIG_RCU_TASKS_PREEMPT_QS`. My expert instinct kicks in immediately: this deserves a thorough look. Let's break it down methodically. First, I need to figure out if fuzzing is warranted. My initial assessment? Absolutely. Looking at the changes, I can see the impact is pretty broad. I need to nail down the focus symbols and the necessary configurations to test with. A quick skim of the changes confirms my hunch. We're talking core RCU and task scheduling tweaks, hitting key files like `rcupdate.h`, `sched.h`, and, significantly, `kernel/rcu/tasks.h`. I'm also seeing modifications in critical areas like entry code, kprobes, ftrace, and even the BPF JIT and x86 optprobes. This isn't just a minor tweak; it touches reachable core logic, synchronization, quiescent state reporting in Tasks RCU, interrupt entry preemption, and even BPF trampoline, to name a few. My initial assessment is that this is **Definitely WorthFuzzing = true**! Now, time to dig deeper with the tools and see what functions and configurations are involved to begin the fuzzing campaign. |
| 4/2 | 2026/09/11 14:20 | tool | grepper |
0mArgs:{
"Expression": "config RCU_TASKS_PREEMPT_QS"
}Results: {
"Output": "kernel/rcu/Kconfig=115=config ARCH_HAS_RCU_TASKS_PREEMPT_QS\n--\nkernel/rcu/Kconfig-117-\nkernel/rcu/Kconfig:118:config RCU_TASKS_PREEMPT_QS\nkernel/rcu/Kconfig-119-\tdef_bool TASKS_RCU \u0026\u0026 ARCH_HAS_RCU_TASKS_PREEMPT_QS \u0026\u0026 GENERIC_IRQ_ENTRY\n"
} |
| 5/2 | 2026/09/11 14:20 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 29045 output: 34 thoughts: 27 Thoughts: Here's a summary of my thought process, as requested: **RCU Configuration Exploration** Okay, let's dive into the RCU configuration options. My instinct tells me the relevant Kconfig options are likely clustered in the `kernel/rcu/` directory, and I need to hone in on the core settings, so I should examine `Kconfig`. Specifically, I'm thinking that somewhere around lines 90 to 130 of that file is a good starting point for the options I'm interested in. I'll reach for `read-file` to get a quick view of that section and start making some informed assessments. Hopefully, this quick scan will give me what I need to see about the fundamental settings. |
| 6/2 | 2026/09/11 14:20 | tool | read-file |
0mArgs:{
"File": "kernel/rcu/Kconfig",
"FirstLine": 90,
"LineCount": 40
}Results: {
"Contents": " 90:\tconfig FORCE_TASKS_RCU\n 91:\t\tbool \"Force selection of TASKS_RCU\"\n 92:\t\tdepends on RCU_EXPERT\n 93:\t\tselect TASKS_RCU\n 94:\t\tdefault n\n 95:\t\thelp\n 96:\t\t This option force-enables a task-based RCU implementation\n 97:\t\t that uses only voluntary context switch (not preemption, unless\n 98:\t\t the architecture selects ARCH_HAS_RCU_TASKS_PREEMPT_QS and the\n 99:\t\t task is outside any trampoline), idle, and user-mode execution\n 100:\t\t as quiescent states. Not for manual selection in most cases.\n 101:\t\n 102:\tconfig NEED_TASKS_RCU\n 103:\t\tbool\n 104:\t\tdefault n\n 105:\t\n 106:\tconfig TASKS_RCU\n 107:\t\tbool\n 108:\t\tdefault NEED_TASKS_RCU \u0026\u0026 PREEMPTION\n 109:\t\tselect IRQ_WORK\n 110:\t\n 111:\t# Selected by architectures whose ftrace, BPF and kprobe trampolines maintain\n 112:\t# current-\u003ercu_tramp_nesting and which use the generic irqentry code, so that\n 113:\t# a preemption outside any trampoline can be treated as a Tasks RCU\n 114:\t# quiescent state. See rcu_tasks_trampoline_enter().\n 115:\tconfig ARCH_HAS_RCU_TASKS_PREEMPT_QS\n 116:\t\tbool\n 117:\t\n 118:\tconfig RCU_TASKS_PREEMPT_QS\n 119:\t\tdef_bool TASKS_RCU \u0026\u0026 ARCH_HAS_RCU_TASKS_PREEMPT_QS \u0026\u0026 GENERIC_IRQ_ENTRY\n 120:\t\n 121:\tconfig FORCE_TASKS_RUDE_RCU\n 122:\t\tbool \"Force selection of Tasks Rude RCU\"\n 123:\t\tdepends on RCU_EXPERT\n 124:\t\tselect TASKS_RUDE_RCU\n 125:\t\tdefault n\n 126:\t\thelp\n 127:\t\t This option force-enables a task-based RCU implementation\n 128:\t\t that uses only context switch (including preemption) and\n 129:\t\t user-mode execution as quiescent states. It forces IPIs and\n"
} |
| 7/2 | 2026/09/11 14:20 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 29898 output: 36 thoughts: 23 Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person: **Analyzing the `NEED_TASKS_RCU` and `TASKS_RCU` Selects** Right, let's dive in. My immediate thought is, "Okay, what's driving the selection of *either* `NEED_TASKS_RCU` *or* `TASKS_RCU`?" It's not a matter of simply understanding *what* they are (we know that), but *when* and *why* the build system chooses one over the other. I need to understand the conditional logic, the configuration options, the dependencies – the whole selection process. Are there specific kernel configurations at play here? Are we talking about architectures, specific drivers, or particular kernel versions that influence this choice? Are there compatibility considerations involved? Is this something directly exposed to the user as a configuration option, or is it an internal dependency chain? I'm thinking I need to trace this back. I'll probably start with the Kconfig files, look for the 'select' statements themselves, and then back-track to the definition of those symbols. Perhaps there are more intermediate symbols or macros that determine the final selection. I should also check out the documentation on RCU and the kernel's configuration options. Any clues about the intended usage scenarios for each choice are vital. Basically, I want to understand the *semantics* behind the selection, not just the mechanics. Knowing the rational behind this design is the key. |
| 8/2 | 2026/09/11 14:20 | tool | grepper |
0mArgs:{
"Expression": "(NEED_TASKS_RCU|TASKS_RCU)",
"PathPrefix": "kernel"
}Results: {
"Output": "kernel/bpf/Kconfig=27=config BPF_SYSCALL\n--\nkernel/bpf/Kconfig-30-\tselect IRQ_WORK\nkernel/bpf/Kconfig:31:\tselect NEED_TASKS_RCU\nkernel/bpf/Kconfig-32-\tselect TASKS_TRACE_RCU\n--\nkernel/bpf/trampoline.c=612=static void bpf_tramp_image_put(struct bpf_tramp_image *im)\n--\nkernel/bpf/trampoline.c-644-\t\tWARN_ON(err);\nkernel/bpf/trampoline.c:645:\t\tif (IS_ENABLED(CONFIG_TASKS_RCU))\nkernel/bpf/trampoline.c-646-\t\t\tcall_rcu_tasks(\u0026im-\u003ercu, __bpf_tramp_image_put_rcu_tasks);\n--\nkernel/context_tracking.c=42=static __always_inline void rcu_task_exit(void)\nkernel/context_tracking.c-43-{\nkernel/context_tracking.c:44:#if defined(CONFIG_TASKS_RCU) \u0026\u0026 defined(CONFIG_NO_HZ_FULL)\nkernel/context_tracking.c-45-\tWRITE_ONCE(current-\u003ercu_tasks_idle_cpu, smp_processor_id());\nkernel/context_tracking.c:46:#endif /* #if defined(CONFIG_TASKS_RCU) \u0026\u0026 defined(CONFIG_NO_HZ_FULL) */\nkernel/context_tracking.c-47-}\n--\nkernel/context_tracking.c=50=static __always_inline void rcu_task_enter(void)\nkernel/context_tracking.c-51-{\nkernel/context_tracking.c:52:#if defined(CONFIG_TASKS_RCU) \u0026\u0026 defined(CONFIG_NO_HZ_FULL)\nkernel/context_tracking.c-53-\tWRITE_ONCE(current-\u003ercu_tasks_idle_cpu, -1);\nkernel/context_tracking.c:54:#endif /* #if defined(CONFIG_TASKS_RCU) \u0026\u0026 defined(CONFIG_NO_HZ_FULL) */\nkernel/context_tracking.c-55-}\n--\nkernel/fork.c=1862=static inline void rcu_copy_process(struct task_struct *p)\n--\nkernel/fork.c-1869-#endif /* #ifdef CONFIG_PREEMPT_RCU */\nkernel/fork.c:1870:#ifdef CONFIG_TASKS_RCU\nkernel/fork.c-1871-\tp-\u003ercu_tasks_holdout = false;\n--\nkernel/fork.c-1876-\tINIT_LIST_HEAD(\u0026p-\u003ercu_tasks_exit_list);\nkernel/fork.c:1877:#endif /* #ifdef CONFIG_TASKS_RCU */\nkernel/fork.c-1878-#ifdef CONFIG_TASKS_TRACE_RCU\n--\nkernel/rcu/Kconfig=81=config NEED_SRCU_NMI_SAFE\n--\nkernel/rcu/Kconfig-83-\nkernel/rcu/Kconfig:84:config TASKS_RCU_GENERIC\nkernel/rcu/Kconfig:85:\tdef_bool TASKS_RCU || TASKS_RUDE_RCU\nkernel/rcu/Kconfig-86-\thelp\n--\nkernel/rcu/Kconfig-89-\nkernel/rcu/Kconfig:90:config FORCE_TASKS_RCU\nkernel/rcu/Kconfig:91:\tbool \"Force selection of TASKS_RCU\"\nkernel/rcu/Kconfig-92-\tdepends on RCU_EXPERT\nkernel/rcu/Kconfig:93:\tselect TASKS_RCU\nkernel/rcu/Kconfig-94-\tdefault n\n--\nkernel/rcu/Kconfig-101-\nkernel/rcu/Kconfig:102:config NEED_TASKS_RCU\nkernel/rcu/Kconfig-103-\tbool\n--\nkernel/rcu/Kconfig-105-\nkernel/rcu/Kconfig:106:config TASKS_RCU\nkernel/rcu/Kconfig-107-\tbool\nkernel/rcu/Kconfig:108:\tdefault NEED_TASKS_RCU \u0026\u0026 PREEMPTION\nkernel/rcu/Kconfig-109-\tselect IRQ_WORK\n--\nkernel/rcu/Kconfig=118=config RCU_TASKS_PREEMPT_QS\nkernel/rcu/Kconfig:119:\tdef_bool TASKS_RCU \u0026\u0026 ARCH_HAS_RCU_TASKS_PREEMPT_QS \u0026\u0026 GENERIC_IRQ_ENTRY\nkernel/rcu/Kconfig-120-\n--\nkernel/rcu/Kconfig=186=config RCU_NEED_SEGCBLIST\nkernel/rcu/Kconfig:187:\tdef_bool ( TREE_RCU || TREE_SRCU || TASKS_RCU_GENERIC )\nkernel/rcu/Kconfig-188-\n--\nkernel/rcu/rcu.h=397=extern void rcu_init_geometry(void);\n--\nkernel/rcu/rcu.h-452-\nkernel/rcu/rcu.h:453:#if !defined(CONFIG_TINY_RCU) || defined(CONFIG_TASKS_RCU_GENERIC)\nkernel/rcu/rcu.h-454-\n--\nkernel/rcu/rcu.h=500=do {\t\t\t\t\t\t\t\t\t\\\n--\nkernel/rcu/rcu.h-525-\nkernel/rcu/rcu.h:526:#endif // #if !defined(CONFIG_TINY_RCU) || defined(CONFIG_TASKS_RCU_GENERIC)\nkernel/rcu/rcu.h-527-\n--\nkernel/rcu/rcu.h=547=bool rcu_cpu_online(int cpu);\nkernel/rcu/rcu.h:548:#ifdef CONFIG_TASKS_RCU_GENERIC\nkernel/rcu/rcu.h-549-void show_rcu_tasks_gp_kthreads(void);\nkernel/rcu/rcu.h:550:#else /* #ifdef CONFIG_TASKS_RCU_GENERIC */\nkernel/rcu/rcu.h-551-static inline void show_rcu_tasks_gp_kthreads(void) {}\nkernel/rcu/rcu.h:552:#endif /* #else #ifdef CONFIG_TASKS_RCU_GENERIC */\nkernel/rcu/rcu.h-553-#endif /* #else #ifdef CONFIG_TINY_RCU */\nkernel/rcu/rcu.h-554-\nkernel/rcu/rcu.h:555:#ifdef CONFIG_TASKS_RCU\nkernel/rcu/rcu.h-556-struct task_struct *get_rcu_tasks_gp_kthread(void);\nkernel/rcu/rcu.h=557=void rcu_tasks_get_gp_data(int *flags, unsigned long *gp_seq);\nkernel/rcu/rcu.h:558:#endif // # ifdef CONFIG_TASKS_RCU\nkernel/rcu/rcu.h-559-\n--\nkernel/rcu/rcu.h=562=void rcu_tasks_rude_get_gp_data(int *flags, unsigned long *gp_seq);\n--\nkernel/rcu/rcu.h-564-\nkernel/rcu/rcu.h:565:#ifdef CONFIG_TASKS_RCU_GENERIC\nkernel/rcu/rcu.h-566-void tasks_cblist_init_generic(void);\nkernel/rcu/rcu.h:567:#else /* #ifdef CONFIG_TASKS_RCU_GENERIC */\nkernel/rcu/rcu.h-568-static inline void tasks_cblist_init_generic(void) { }\nkernel/rcu/rcu.h:569:#endif /* #else #ifdef CONFIG_TASKS_RCU_GENERIC */\nkernel/rcu/rcu.h-570-\n--\nkernel/rcu/rcu.h=677=static inline void rcu_bind_current_to_nocb(void) { }\n--\nkernel/rcu/rcu.h-679-\nkernel/rcu/rcu.h:680:#if !defined(CONFIG_TINY_RCU) \u0026\u0026 defined(CONFIG_TASKS_RCU)\nkernel/rcu/rcu.h-681-void show_rcu_tasks_classic_gp_kthread(void);\n--\nkernel/rcu/rcuscale.c=279=static struct rcu_scale_ops srcud_ops = {\n--\nkernel/rcu/rcuscale.c-295-\nkernel/rcu/rcuscale.c:296:#ifdef CONFIG_TASKS_RCU\nkernel/rcu/rcuscale.c-297-\n--\nkernel/rcu/rcuscale.c=316=static struct rcu_scale_ops tasks_ops = {\n--\nkernel/rcu/rcuscale.c-333-\nkernel/rcu/rcuscale.c:334:#else // #ifdef CONFIG_TASKS_RCU\nkernel/rcu/rcuscale.c-335-\n--\nkernel/rcu/rcuscale.c-337-\nkernel/rcu/rcuscale.c:338:#endif // #else // #ifdef CONFIG_TASKS_RCU\nkernel/rcu/rcuscale.c-339-\n--\nkernel/rcu/rcutorture.c=1117=static struct rcu_torture_ops trivial_preempt_ops = {\n--\nkernel/rcu/rcutorture.c-1138-\nkernel/rcu/rcutorture.c:1139:#ifdef CONFIG_TASKS_RCU\nkernel/rcu/rcutorture.c-1140-\n--\nkernel/rcu/rcutorture.c=1170=static struct rcu_torture_ops tasks_ops = {\n--\nkernel/rcu/rcutorture.c-1190-\nkernel/rcu/rcutorture.c:1191:#else // #ifdef CONFIG_TASKS_RCU\nkernel/rcu/rcutorture.c-1192-\n--\nkernel/rcu/rcutorture.c-1194-\nkernel/rcu/rcutorture.c:1195:#endif // #else #ifdef CONFIG_TASKS_RCU\nkernel/rcu/rcutorture.c-1196-\n--\nkernel/rcu/refscale.c=277=static const struct ref_scale_ops srcu_fast_updown_ops = {\n--\nkernel/rcu/refscale.c-283-\nkernel/rcu/refscale.c:284:#ifdef CONFIG_TASKS_RCU\nkernel/rcu/refscale.c-285-\n--\nkernel/rcu/refscale.c=304=static const struct ref_scale_ops rcu_tasks_ops = {\n--\nkernel/rcu/refscale.c-312-\nkernel/rcu/refscale.c:313:#else // #ifdef CONFIG_TASKS_RCU\nkernel/rcu/refscale.c-314-\n--\nkernel/rcu/refscale.c-316-\nkernel/rcu/refscale.c:317:#endif // #else // #ifdef CONFIG_TASKS_RCU\nkernel/rcu/refscale.c-318-\n--\nkernel/rcu/tasks.h-7-\nkernel/rcu/tasks.h:8:#ifdef CONFIG_TASKS_RCU_GENERIC\nkernel/rcu/tasks.h-9-#include \"rcu_segcblist.h\"\n--\nkernel/rcu/tasks.h=136=static struct rcu_tasks rt_name =\t\t\t\t\t\t\t\\\n--\nkernel/rcu/tasks.h-154-\nkernel/rcu/tasks.h:155:#ifdef CONFIG_TASKS_RCU\nkernel/rcu/tasks.h-156-\n--\nkernel/rcu/tasks.h=695=static void __init rcu_tasks_bootup_oddness(void)\nkernel/rcu/tasks.h-696-{\nkernel/rcu/tasks.h:697:#if defined(CONFIG_TASKS_RCU) || defined(CONFIG_TASKS_TRACE_RCU)\nkernel/rcu/tasks.h-698-\tint rtsimc;\n--\nkernel/rcu/tasks.h-706-\t}\nkernel/rcu/tasks.h:707:#endif /* #ifdef CONFIG_TASKS_RCU */\nkernel/rcu/tasks.h:708:#ifdef CONFIG_TASKS_RCU\nkernel/rcu/tasks.h-709-\tpr_info(\"\\tTrampoline variant of Tasks RCU enabled.\\n\");\nkernel/rcu/tasks.h:710:#endif /* #ifdef CONFIG_TASKS_RCU */\nkernel/rcu/tasks.h-711-#ifdef CONFIG_TASKS_RUDE_RCU\n--\nkernel/rcu/tasks.h=758=static void rcu_tasks_torture_stats_print_generic(struct rcu_tasks *rtp, char *tt,\n--\nkernel/rcu/tasks.h-804-\nkernel/rcu/tasks.h:805:#if defined(CONFIG_TASKS_RCU)\nkernel/rcu/tasks.h-806-\n--\nkernel/rcu/tasks.h=812=static void rcu_tasks_wait_gp(struct rcu_tasks *rtp)\n--\nkernel/rcu/tasks.h-899-\nkernel/rcu/tasks.h:900:#endif /* #if defined(CONFIG_TASKS_RCU) */\nkernel/rcu/tasks.h-901-\nkernel/rcu/tasks.h:902:#ifdef CONFIG_TASKS_RCU\nkernel/rcu/tasks.h-903-\n--\nkernel/rcu/tasks.h=1442=void exit_tasks_rcu_finish(void)\n--\nkernel/rcu/tasks.h-1454-\nkernel/rcu/tasks.h:1455:#else /* #ifdef CONFIG_TASKS_RCU */\nkernel/rcu/tasks.h-1456-void exit_tasks_rcu_start(void) { }\nkernel/rcu/tasks.h=1457=void exit_tasks_rcu_finish(void) { }\nkernel/rcu/tasks.h:1458:#endif /* #else #ifdef CONFIG_TASKS_RCU */\nkernel/rcu/tasks.h-1459-\n--\nkernel/rcu/tasks.h=1595=static struct rcu_tasks_test_desc tests[] = {\n--\nkernel/rcu/tasks.h-1598-\t\t/* If not defined, the test is skipped. */\nkernel/rcu/tasks.h:1599:\t\t.notrun = IS_ENABLED(CONFIG_TASKS_RCU),\nkernel/rcu/tasks.h-1600-\t\t/* Dump rcu tasks status, if test failed. */\n--\nkernel/rcu/tasks.h-1609-\nkernel/rcu/tasks.h:1610:#if defined(CONFIG_TASKS_RCU) || defined(CONFIG_TASKS_TRACE_RCU)\nkernel/rcu/tasks.h-1611-static void test_rcu_tasks_callback(struct rcu_head *rhp)\n--\nkernel/rcu/tasks.h-1619-}\nkernel/rcu/tasks.h:1620:#endif // #if defined(CONFIG_TASKS_RCU) || defined(CONFIG_TASKS_TRACE_RCU)\nkernel/rcu/tasks.h-1621-\nkernel/rcu/tasks.h=1622=static void rcu_tasks_initiate_self_tests(void)\nkernel/rcu/tasks.h-1623-{\nkernel/rcu/tasks.h:1624:#ifdef CONFIG_TASKS_RCU\nkernel/rcu/tasks.h-1625-\tpr_info(\"Running RCU Tasks wait API self tests\\n\");\n--\nkernel/rcu/tasks.h=1702=void __init tasks_cblist_init_generic(void)\n--\nkernel/rcu/tasks.h-1706-\nkernel/rcu/tasks.h:1707:#ifdef CONFIG_TASKS_RCU\nkernel/rcu/tasks.h-1708-\tcblist_init_generic(\u0026rcu_tasks);\n--\nkernel/rcu/tasks.h=1716=static int __init rcu_init_tasks_generic(void)\nkernel/rcu/tasks.h-1717-{\nkernel/rcu/tasks.h:1718:#ifdef CONFIG_TASKS_RCU\nkernel/rcu/tasks.h-1719-\trcu_spawn_tasks_kthread();\n--\nkernel/rcu/tasks.h=1731=core_initcall(rcu_init_tasks_generic);\nkernel/rcu/tasks.h-1732-\nkernel/rcu/tasks.h:1733:#else /* #ifdef CONFIG_TASKS_RCU_GENERIC */\nkernel/rcu/tasks.h-1734-static inline void rcu_tasks_bootup_oddness(void) {}\nkernel/rcu/tasks.h:1735:#endif /* #else #ifdef CONFIG_TASKS_RCU_GENERIC */\nkernel/rcu/tasks.h-1736-\n--\nkernel/trace/Kconfig=179=config TRACING\n--\nkernel/trace/Kconfig-187-\tselect TRACE_CLOCK\nkernel/trace/Kconfig:188:\tselect NEED_TASKS_RCU\nkernel/trace/Kconfig-189-\n--\nkernel/trace/Kconfig=235=config FUNCTION_TRACER\n--\nkernel/trace/Kconfig-241-\tselect GLOB\nkernel/trace/Kconfig:242:\tselect NEED_TASKS_RCU\nkernel/trace/Kconfig-243-\tselect TASKS_RUDE_RCU\n"
} |
| 9/2 | 2026/09/11 14:20 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 34880 output: 46 thoughts: 2037 Thoughts: **Analysis of RCU Tasks Preemption with Quick Start (QS) and Configuration for Focused Fuzzing** Alright, let's break this down. My goal is to identify the relevant configuration options and functions for focused fuzzing related to the `RCU_TASKS_PREEMPT_QS` feature on x86-64. First, I see that `CONFIG_ARCH_HAS_RCU_TASKS_PREEMPT_QS` is automatically selected for x86 if `X86_64` is enabled in `arch/x86/Kconfig`. This in turn enables `CONFIG_RCU_TASKS_PREEMPT_QS` if `TASKS_RCU`, `ARCH_HAS_RCU_TASKS_PREEMPT_QS`, and `GENERIC_IRQ_ENTRY` are also set. `TASKS_RCU` depends on `NEED_TASKS_RCU` and `PREEMPTION`. And `NEED_TASKS_RCU` is driven by features like `BPF_SYSCALL`, `TRACING`, `FUNCTION_TRACER`, or if `FORCE_TASKS_RCU` is explicitly set. So, `PREEMPTION` is a key dependency here. Now, let's identify the specific functions affected by this change. The code touches a number of areas: `kernel/rcu/tasks.h`, `arch/x86/kernel/ftrace.c`, `kernel/kprobes.c`, `kernel/entry/common.c`, `kernel/trace/ftrace.c`, `arch/x86/net/bpf_jit_comp.c`, and `arch/x86/xen/enlighten_pv.c`. I'm especially interested in understanding the scope of these functions in the lifecycle. I've examined the functions: `rcu_tasks_switched_out_clean`, `rcu_tasks_preempted_qs`, `rcu_tasks_kick_running`, `rcu_tasks_pertask`, `arch_rcu_tasks_ip_in_trampoline`, `rcu_tasks_ip_in_trampoline`, `rcu_tasks_irq_ip_holds`, `check_holdout_task`, `kprobe_in_optimized_region`, `kprobe_optimizer`, `irqentry_preempt`, `raw_irqentry_exit_cond_resched`, `dynamic_irqentry_exit_cond_resched`, `ftrace_direct_mark_module`, `register_ftrace_direct`, `__modify_ftrace_direct`, `update_ftrace_direct_add`, `update_ftrace_direct_mod`, `emit_rcu_tasks_tramp_nesting`, `__arch_prepare_bpf_trampoline`, and `xen_pv_evtchn_do_upcall`. Now, the important part: identifying the relevant non-ubiquitous functions. I need to make sure I avoid listing functions that are part of the core kernel lifecycle, such as page faults or interrupt handling, or ubiquitous helpers. Otherwise, the fuzzing won't be as effective. From the lists I've compiled, the critical ones for fuzzing are: * `rcu_tasks_ip_in_trampoline` * `rcu_tasks_irq_ip_holds` * `arch_rcu_tasks_ip_in_trampoline` * `kprobe_in_optimized_region` * `register_ftrace_direct` * `update_ftrace_direct_add` * `update_ftrace_direct_mod` * `__arch_prepare_bpf_trampoline` * `kprobe_optimizer` We need the following configurations for the fuzzing: * `RCU_TASKS_PREEMPT_QS` * `TASKS_RCU` (which requires `PREEMPTION` or `NEED_TASKS_RCU`.) * `DYNAMIC_FTRACE_WITH_DIRECT_CALLS` * `OPTPROBES` or `KPROBES` * `BPF_SYSCALL` * `BPF_JIT` The key is that the fuzzer will be most effective when focused on specific functions related to RCU tasks, ftrace, kprobes and BPF trampolines. I can see the fuzzing is worth it because it touches the core RCU subsystem, sched/entry logic, ftrace, kprobes, and BPF JIT trampolines on x86_64. |
| 10/2 | 2026/09/11 14:20 | tool | grepper |
0mArgs:{
"Expression": "__arch_prepare_bpf_trampoline",
"PathPrefix": "arch/x86/net/bpf_jit_comp.c"
}Results: {
"Output": "arch/x86/net/bpf_jit_comp.c=3382=static int invoke_bpf_mod_ret(const struct btf_func_model *m, u8 **pprog,\n--\narch/x86/net/bpf_jit_comp.c-3483- */\narch/x86/net/bpf_jit_comp.c:3484:static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_image,\narch/x86/net/bpf_jit_comp.c-3485-\t\t\t\t\t void *rw_image_end, void *image,\n--\narch/x86/net/bpf_jit_comp.c=3825=int arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *image, void *image_end,\n--\narch/x86/net/bpf_jit_comp.c-3840-\narch/x86/net/bpf_jit_comp.c:3841:\tret = __arch_prepare_bpf_trampoline(im, rw_image, rw_image + size, image, m,\narch/x86/net/bpf_jit_comp.c-3842-\t\t\t\t\t flags, tnodes, func_addr);\n--\narch/x86/net/bpf_jit_comp.c=3854=int arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags,\n--\narch/x86/net/bpf_jit_comp.c-3860-\narch/x86/net/bpf_jit_comp.c:3861:\t/* Allocate a temporary buffer for __arch_prepare_bpf_trampoline().\narch/x86/net/bpf_jit_comp.c-3862-\t *\n--\narch/x86/net/bpf_jit_comp.c-3871-\narch/x86/net/bpf_jit_comp.c:3872:\tret = __arch_prepare_bpf_trampoline(\u0026im, image, image + PAGE_SIZE, image,\narch/x86/net/bpf_jit_comp.c-3873-\t\t\t\t\t m, flags, tnodes, func_addr);\n"
} |
| 11/2 | 2026/09/11 14:20 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 38583 output: 35 thoughts: 88 Thoughts: Here's my summary, as I see it: **Focusing in on BPF Trampolines** Okay, so I'm noticing a bit of a pattern here with these BPF trampoline functions. Specifically, `arch_prepare_bpf_trampoline` – it's non-static, which is a bit unusual in itself, and it calls `__arch_prepare_bpf_trampoline`. That's interesting, and so is the fact that `arch_bpf_trampoline_size` relies on that same function. To my mind, both `arch_prepare_bpf_trampoline` and `__arch_prepare_bpf_trampoline` are prime candidates for closer inspection. They seem to be at the heart of how BPF trampolines are handled at the architecture level. Given the potential impact on performance and security, I should probably check `kprobes.c` for any related code. |
| 12/2 | 2026/09/11 14:20 | tool | grepper |
0mArgs:{
"Expression": "kprobe_in_optimized_region",
"PathPrefix": "kernel/kprobes.c"
}Results: {
"Output": "kernel/kprobes.c=495=static struct kprobe *get_optimized_kprobe(kprobe_opcode_t *addr)\n--\nkernel/kprobes.c-516- * Only in that window can a preemption inside an optprobe's jump region\nkernel/kprobes.c:517: * matter to it, so kprobe_in_optimized_region() does no work otherwise.\nkernel/kprobes.c-518- */\nkernel/kprobes.c=519=static bool kprobe_optimizer_waiting;\n--\nkernel/kprobes.c-521-/**\nkernel/kprobes.c:522: * kprobe_in_optimized_region - Could @addr be inside bytes a jump-optimized\nkernel/kprobes.c-523- *\tkprobe replaces?\n--\nkernel/kprobes.c-541- */\nkernel/kprobes.c:542:bool kprobe_in_optimized_region(unsigned long addr)\nkernel/kprobes.c-543-{\n--\nkernel/kprobes.c-553-}\nkernel/kprobes.c:554:NOKPROBE_SYMBOL(kprobe_in_optimized_region);\nkernel/kprobes.c-555-\n--\nkernel/kprobes.c=668=static void kprobe_optimizer(void)\n--\nkernel/kprobes.c-689-\t\t * kprobe_optimizer_waiting lets Tasks RCU recognise tasks preempted\nkernel/kprobes.c:690:\t\t * in such a region while we wait, see kprobe_in_optimized_region().\nkernel/kprobes.c-691-\t\t */\n"
} |
| 13/2 | 2026/09/11 14:20 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 39335 output: 36 thoughts: 14 Thoughts: Here's my take on those thoughts, tailored for an expert audience: **Diving into `ftrace_direct_mark_module`** Okay, so we're looking at `ftrace_direct_mark_module`. My first thought is to refresh my mental model of how Ftrace's module marking mechanism works. What are the key data structures involved? Is this related to the "function graph" tracer, or is it more generic? I need to quickly recall how direct calls are identified and managed within the tracer framework. I'm probably thinking about the implications for overhead. This function likely represents a core piece of the tracing infrastructure. Any performance bottlenecks here would ripple across everything else. We need to assess what kind of locking is happening in this function, and if any contention is likely, especially under high-load tracing scenarios. Also, are there any potential race conditions that come to mind related to module loading/unloading while tracing is active? And how does it interact with the per-cpu buffer model? Any optimization possibilities immediately jump out? Memory allocation strategies? Use of atomic operations where possible? I'm also curious about its relationship to the kernel's module management system. Does it utilize kprobes or something similar? Are there any potential security implications to consider? We're dealing with something that is able to directly interact with modules at a low level, so understanding the security context is paramount. Finally, is there any relevant documentation to quickly check. I want to build a comprehensive picture. |
| 14/2 | 2026/09/11 14:20 | tool | grepper |
0mArgs:{
"Expression": "ftrace_direct_mark_module",
"PathPrefix": "kernel/trace/ftrace.c"
}Results: {
"Output": "kernel/trace/ftrace.c=6068=static void reset_direct(struct ftrace_ops *ops, unsigned long addr)\n--\nkernel/trace/ftrace.c-6089- */\nkernel/trace/ftrace.c:6090:static void ftrace_direct_mark_module(unsigned long addr)\nkernel/trace/ftrace.c-6091-{\n--\nkernel/trace/ftrace.c-6124- * for trampolines outside kernel and module text (e.g. BPF images), and via\nkernel/trace/ftrace.c:6125: * ftrace_direct_mark_module() for trampolines in module text.\nkernel/trace/ftrace.c-6126- *\n--\nkernel/trace/ftrace.c=6135=int register_ftrace_direct(struct ftrace_ops *ops, unsigned long addr)\n--\nkernel/trace/ftrace.c-6205-\tops-\u003edirect_call = addr;\nkernel/trace/ftrace.c:6206:\tftrace_direct_mark_module(addr);\nkernel/trace/ftrace.c-6207-\n--\nkernel/trace/ftrace.c=6262=__modify_ftrace_direct(struct ftrace_ops *ops, unsigned long addr)\n--\nkernel/trace/ftrace.c-6274-\nkernel/trace/ftrace.c:6275:\tftrace_direct_mark_module(addr);\nkernel/trace/ftrace.c-6276-\n--\nkernel/trace/ftrace.c=6437=int update_ftrace_direct_add(struct ftrace_ops *ops, struct ftrace_hash *hash)\n--\nkernel/trace/ftrace.c-6458-\t\t\t\tgoto out_unlock;\nkernel/trace/ftrace.c:6459:\t\t\tftrace_direct_mark_module(entry-\u003edirect);\nkernel/trace/ftrace.c-6460-\t\t}\n--\nkernel/trace/ftrace.c=6678=int update_ftrace_direct_mod(struct ftrace_ops *ops, struct ftrace_hash *hash, bool do_direct_lock)\n--\nkernel/trace/ftrace.c-6742-\t\t\t\tcontinue;\nkernel/trace/ftrace.c:6743:\t\t\tftrace_direct_mark_module(entry-\u003edirect);\nkernel/trace/ftrace.c-6744-\t\t\ttmp-\u003edirect = entry-\u003edirect;\n"
} |
| 15/2 | 2026/09/11 14:20 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 40329 output: 317 thoughts: 658 Thoughts: **Analyzing the Impact of the RCU Tasks Preemption Patch Series** Okay, let's break this down. First, I need to verify that we've accounted for all relevant symbols in the code, so I'm cross-referencing `kernel/rcu/tasks.h` and `kernel/rcu/update.c` (and elsewhere if needed) for potential targets. I've noted a collection of symbols, including `rcu_tasks_ip_in_trampoline`, `rcu_tasks_irq_ip_holds`, and others, which look like key indicators. Next, I'm reviewing the configuration options. It looks like `RCU_TASKS_PREEMPT_QS` is the crucial config here, created in `kernel/rcu/Kconfig`. We're also dealing with `TASKS_RCU` (the base) and `ARCH_HAS_RCU_TASKS_PREEMPT_QS`, indicating arch selection. I'm also seeing dependencies on `DYNAMIC_FTRACE_WITH_DIRECT_CALLS`, `OPTPROBES`, `BPF_SYSCALL`, and `BPF_JIT`. These are all common in modern kernel configurations, but I need to make sure we consider how they interact. Now, let's confirm the patch series is actually worth fuzzing. The reasoning is solid: this patch series introduces `CONFIG_ARCH_HAS_RCU_TASKS_PREEMPT_QS` / `CONFIG_RCU_TASKS_PREEMPT_QS`, allowing preemption outside trampolines to be treated as Tasks-RCU quiescent states, at least on x86_64 and arm64. It sounds like it touches core RCU quiescent state tracking, IRQ preemption handling, BPF JIT trampoline code generation, kprobe optimization, and ftrace direct calls, all areas that deal with synchronization and are potentially reachable in virtualized environments. This confirms it. So, the enablement config is important: we need `RCU_TASKS_PREEMPT_QS`, `TASKS_RCU`, `ARCH_HAS_RCU_TASKS_PREEMPT_QS`, `DYNAMIC_FTRACE_WITH_DIRECT_CALLS`, `OPTPROBES`, `BPF_SYSCALL`, and `BPF_JIT` enabled. Finally, let's identify the specific symbols we should be focusing our fuzzing efforts on: I'm seeing `rcu_tasks_ip_in_trampoline`, `rcu_tasks_irq_ip_holds`, and `arch_rcu_tasks_ip_in_trampoline` which seem pretty key. Also, `kprobe_in_optimized_region`, `kprobe_optimizer`, `register_ftrace_direct`, `update_ftrace_direct_add`, `update_ftrace_direct_mod`, and `arch_prepare_bpf_trampoline`. These look like critical points of interaction with the new RCU changes. Alright, I'm satisfied. Let's call `set-results`. |
| 16/2 | 2026/09/11 14:20 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"RCU_TASKS_PREEMPT_QS",
"TASKS_RCU",
"ARCH_HAS_RCU_TASKS_PREEMPT_QS",
"DYNAMIC_FTRACE_WITH_DIRECT_CALLS",
"OPTPROBES",
"BPF_SYSCALL",
"BPF_JIT"
],
"FocusSymbols": [
"rcu_tasks_ip_in_trampoline",
"rcu_tasks_irq_ip_holds",
"arch_rcu_tasks_ip_in_trampoline",
"kprobe_in_optimized_region",
"kprobe_optimizer",
"register_ftrace_direct",
"update_ftrace_direct_add",
"update_ftrace_direct_mod",
"arch_prepare_bpf_trampoline"
],
"Reasoning": "The patch series introduces CONFIG_RCU_TASKS_PREEMPT_QS (selected via ARCH_HAS_RCU_TASKS_PREEMPT_QS on x86_64), allowing involuntary context switches outside trampolines to serve as Tasks-RCU quiescent states. It modifies core RCU state evaluation, IRQ-entry preemption hooks, BPF trampoline emission, optimized kprobe holdout checks, and dynamic ftrace direct call tracking. These changes modify core synchronization mechanisms in reachable code and warrant fuzzing.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"RCU_TASKS_PREEMPT_QS",
"TASKS_RCU",
"ARCH_HAS_RCU_TASKS_PREEMPT_QS",
"DYNAMIC_FTRACE_WITH_DIRECT_CALLS",
"OPTPROBES",
"BPF_SYSCALL",
"BPF_JIT"
],
"FocusSymbols": [
"rcu_tasks_ip_in_trampoline",
"rcu_tasks_irq_ip_holds",
"arch_rcu_tasks_ip_in_trampoline",
"kprobe_in_optimized_region",
"kprobe_optimizer",
"register_ftrace_direct",
"update_ftrace_direct_add",
"update_ftrace_direct_mod",
"arch_prepare_bpf_trampoline"
],
"Reasoning": "The patch series introduces CONFIG_RCU_TASKS_PREEMPT_QS (selected via ARCH_HAS_RCU_TASKS_PREEMPT_QS on x86_64), allowing involuntary context switches outside trampolines to serve as Tasks-RCU quiescent states. It modifies core RCU state evaluation, IRQ-entry preemption hooks, BPF trampoline emission, optimized kprobe holdout checks, and dynamic ftrace direct call tracking. These changes modify core synchronization mechanisms in reachable code and warrant fuzzing.",
"WorthFuzzing": true
} |
| 17/1 | 2026/09/11 14:20 | action | If |
0mArgs:{
"WorthFuzzing": true
}Results: null |
| 18/2 | 2026/09/11 14:20 | agent | kmsan-evaluator |
0mModel:core Args: null Results: {
"KMSANReasoning": "The patch series implements CONFIG_ARCH_HAS_RCU_TASKS_PREEMPT_QS, allowing involuntary context switches outside trampolines to be treated as Tasks-RCU quiescent states. It introduces tracking fields (rcu_tramp_nesting, rcu_tasks_irq_ip) to struct task_struct, updates ftrace, BPF JIT, kprobe optprobes, and irqentry code across x86 and arm64 architectures.\n\nNone of the changes introduce uninitialized memory risks or info-leaks:\n1. No user-space data copying (copy_to_user, netlink, ioctl, etc.) or info-leak channels are modified or created.\n2. The newly added fields in task_struct are explicitly initialized to zero in fork (rcu_copy_process()) and statically initialized for init_task.\n3. No variable-length buffer handling, uninitialized stack variables, or uninitialized heap allocations are read or exposed.\n\nAny potential bugs in this patch would relate to RCU grace period synchronization, CPU preemption handling, or premature trampoline reclamation leading to use-after-free or memory corruption, which are covered by KASAN and standard kernel debugging facilities, not KMSAN. Therefore, a dedicated KMSAN fuzzing session is not required.",
"NeedsKMSAN": false
}Instruction: You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.
CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
a comprehensive suite of debugging tools and sanitizers, including KASAN
(out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
(locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
or page allocations) and kernel-to-user memory info-leaks.
Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.
Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.
Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
attributes, ioctl output arguments, socket options, or BPF buffers) where fields
or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
uninitialized bytes of existing buffers.
Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
dereferences, locking deadlocks, or use-after-free bugs (these are already caught
by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.
Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit 8f1d757b3e477c6ed5382f9cfd33bf64b258acdc
Author: syz-cluster <triage@syzkaller.com>
Date: Fri Sep 11 14:19:54 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/RCU/Design/Requirements/Requirements.rst b/Documentation/RCU/Design/Requirements/Requirements.rst
index 8101fe6229d57..428b5e8f4b4e5 100644
--- a/Documentation/RCU/Design/Requirements/Requirements.rst
+++ b/Documentation/RCU/Design/Requirements/Requirements.rst
@@ -2739,13 +2739,27 @@ userspace execution also delimit tasks-RCU read-side critical sections.
Idle tasks are ignored by Tasks RCU, and Tasks Rude RCU may be used to
interact with them.
-Note well that involuntary context switches are *not* Tasks-RCU quiescent
-states. After all, in preemptible kernels, a task executing code in a
-trampoline might be preempted. In this case, the Tasks-RCU grace period
-clearly cannot end until that task resumes and its execution leaves that
-trampoline. This means, among other things, that cond_resched() does
-not provide a Tasks RCU quiescent state. (Instead, use rcu_softirq_qs()
-from softirq or rcu_tasks_classic_qs() otherwise.)
+Note well that, by default, involuntary context switches are *not*
+Tasks-RCU quiescent states. After all, in preemptible kernels, a task
+executing code in a trampoline might be preempted. In this case, the
+Tasks-RCU grace period clearly cannot end until that task resumes and its
+execution leaves that trampoline. This means, among other things, that
+cond_resched() does not provide a Tasks RCU quiescent state. (Instead,
+use rcu_softirq_qs() from softirq or rcu_tasks_classic_qs() otherwise.)
+
+Architectures that select ``CONFIG_ARCH_HAS_RCU_TASKS_PREEMPT_QS`` relax
+this: there, every trampoline whose lifetime Tasks RCU guards (the ftrace
+and BPF trampolines, optprobe slots, out-of-line ftrace direct-call
+trampolines) increments ``current->rcu_tramp_nesting`` before calling out
+and decrements it before returning, and the irq-exit preemption path
+covers the few instructions the counter cannot (see
+rcu_tasks_ip_in_trampoline() and rcu_tasks_irq_ip_holds()). A task that
+is preempted with that count at zero is therefore known not to be in, or
+called from, any trampoline, and such a preemption *is* a Tasks-RCU
+quiescent state. The obligation moves to the trampolines: anything that
+relies on synchronize_rcu_tasks() to protect code a task may be preempted
+in must maintain the count (see register_ftrace_direct()), or Tasks RCU
+will not wait for it on those architectures.
The tasks-RCU API is quite compact, consisting only of
call_rcu_tasks(), synchronize_rcu_tasks(), and
diff --git a/Documentation/RCU/checklist.rst b/Documentation/RCU/checklist.rst
index 4b30f701225fd..28df48fecac7f 100644
--- a/Documentation/RCU/checklist.rst
+++ b/Documentation/RCU/checklist.rst
@@ -252,7 +252,13 @@ over a rather long period of time, but improvements are always welcome!
a. If the updater uses synchronize_rcu_tasks() or
call_rcu_tasks(), then the readers must refrain from
executing voluntary context switches, that is, from
- blocking.
+ blocking. On architectures that select
+ CONFIG_ARCH_HAS_RCU_TASKS_PREEMPT_QS an involuntary
+ context switch is also a quiescent state unless
+ current->rcu_tramp_nesting is non-zero, so a reader
+ there is a trampoline that maintains that count (see
+ rcu_tasks_trampoline_enter()), not an arbitrary
+ stretch of kernel code.
b. If the updater uses call_rcu_tasks_trace()
or synchronize_rcu_tasks_trace(), then the
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index b5a51b0ef9440..0e6c1e0b236f9 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -44,6 +44,7 @@ config ARM64
select ARCH_HAS_PREEMPT_LAZY
select ARCH_HAS_PTDUMP
select ARCH_HAS_PTE_SPECIAL
+ select ARCH_HAS_RCU_TASKS_PREEMPT_QS if DYNAMIC_FTRACE_WITH_ARGS
select ARCH_HAS_HW_PTE_YOUNG
select ARCH_HAS_SETUP_DMA_OPS
select ARCH_HAS_SET_DIRECT_MAP
diff --git a/arch/arm64/kernel/asm-offsets.c b/arch/arm64/kernel/asm-offsets.c
index 9c853ed3ceab8..f6655a284f183 100644
--- a/arch/arm64/kernel/asm-offsets.c
+++ b/arch/arm64/kernel/asm-offsets.c
@@ -39,6 +39,9 @@ int main(void)
DEFINE(TSK_STACK, offsetof(struct task_struct, stack));
#ifdef CONFIG_STACKPROTECTOR
DEFINE(TSK_STACK_CANARY, offsetof(struct task_struct, stack_canary));
+#endif
+#ifdef CONFIG_TASKS_RCU
+ DEFINE(TSK_RCU_TRAMP_NESTING, offsetof(struct task_struct, rcu_tramp_nesting));
#endif
BLANK();
DEFINE(THREAD_CPU_CONTEXT, offsetof(struct task_struct, thread.cpu_context));
diff --git a/arch/arm64/kernel/entry-ftrace.S b/arch/arm64/kernel/entry-ftrace.S
index 025140caafe74..46a102e7199ac 100644
--- a/arch/arm64/kernel/entry-ftrace.S
+++ b/arch/arm64/kernel/entry-ftrace.S
@@ -14,6 +14,33 @@
#include <asm/insn.h>
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_ARGS
+/*
+ * Tasks RCU trampoline nesting, see rcu_tasks_trampoline_enter(). The whole
+ * of ftrace_caller is treated as trampoline text by the irq-exit IP check (see
+ * arch_rcu_tasks_ip_in_trampoline()), so these only need to bracket the call
+ * out to ops->func; everything before the increment and after the decrement,
+ * including the direct-call tails that carry a BPF trampoline address in x17,
+ * is covered by that. The count is only modified by current and every nested
+ * user (interrupts) is balanced, so a plain ldr/add/str is sufficient.
+ */
+ .macro rcu_tasks_tramp_enter, tsk:req, tmp:req
+#ifdef CONFIG_TASKS_RCU
+ mrs \tsk, sp_el0
+ ldr \tmp, [\tsk, #TSK_RCU_TRAMP_NESTING]
+ add \tmp, \tmp, #1
+ str \tmp, [\tsk, #TSK_RCU_TRAMP_NESTING]
+#endif
+ .endm
+
+ .macro rcu_tasks_tramp_exit, tsk:req, tmp:req
+#ifdef CONFIG_TASKS_RCU
+ mrs \tsk, sp_el0
+ ldr \tmp, [\tsk, #TSK_RCU_TRAMP_NESTING]
+ sub \tmp, \tmp, #1
+ str \tmp, [\tsk, #TSK_RCU_TRAMP_NESTING]
+#endif
+ .endm
+
/*
* Due to -fpatchable-function-entry=2, the compiler has placed two NOPs before
* the regular function prologue. For an enabled callsite, ftrace_init_nop() and
@@ -94,6 +121,8 @@ SYM_CODE_START(ftrace_caller)
stp x29, x30, [sp, #FREGS_SIZE]
add x29, sp, #FREGS_SIZE
+ rcu_tasks_tramp_enter x12, w13
+
/* Prepare arguments for the tracer func */
sub x0, x30, #AARCH64_INSN_SIZE // ip (callsite's BL insn)
mov x1, x9 // parent_ip (callsite's LR)
@@ -111,6 +140,8 @@ SYM_INNER_LABEL(ftrace_call, SYM_L_GLOBAL)
bl ftrace_stub // func(ip, parent_ip, op, regs)
#endif
+ rcu_tasks_tramp_exit x12, w13
+
/*
* At the callsite x0-x8 and x19-x30 were live. Any C code will have preserved
* x19-x29 per the AAPCS, and we created frame records upon entry, so we need
@@ -178,6 +209,10 @@ SYM_CODE_START(ftrace_stub_direct_tramp)
SYM_CODE_END(ftrace_stub_direct_tramp)
#endif /* CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS */
+/* End of [ftrace_caller, ...) for arch_rcu_tasks_ip_in_trampoline(). */
+SYM_CODE_START(ftrace_static_tramp_end)
+SYM_CODE_END(ftrace_static_tramp_end)
+
#else /* CONFIG_DYNAMIC_FTRACE_WITH_ARGS */
/*
diff --git a/arch/arm64/kernel/ftrace.c b/arch/arm64/kernel/ftrace.c
index e1a3c0b3a0514..1b7ac2afed0df 100644
--- a/arch/arm64/kernel/ftrace.c
+++ b/arch/arm64/kernel/ftrace.c
@@ -17,6 +17,22 @@
#include <asm/insn.h>
#include <asm/text-patching.h>
+#ifdef CONFIG_RCU_TASKS_PREEMPT_QS
+extern void ftrace_static_tramp_end(void);
+
+/*
+ * See rcu_tasks_ip_in_trampoline(). ftrace_caller and ftrace_stub_direct_tramp
+ * are core kernel text but must be treated as trampolines: a task preempted in
+ * them may be carrying an ops pointer (x11) or a direct-call BPF trampoline
+ * address (x17) whose lifetime is guarded only by Tasks RCU.
+ */
+bool arch_rcu_tasks_ip_in_trampoline(unsigned long ip)
+{
+ return ip >= (unsigned long)ftrace_caller &&
+ ip < (unsigned long)ftrace_static_tramp_end;
+}
+#endif
+
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_ARGS
struct fregs_offset {
const char *name;
diff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c
index c18e005a41dbe..5c9a7bde5cc9c 100644
--- a/arch/arm64/net/bpf_jit_comp.c
+++ b/arch/arm64/net/bpf_jit_comp.c
@@ -2591,6 +2591,34 @@ static void emit_arena_arg_conv(struct jit_ctx *ctx, u8 dst, u8 src, bool nullab
emit(A64_SUB(0, dst, src, base_lo), ctx);
}
+/*
+ * Tasks RCU trampoline nesting, see rcu_tasks_trampoline_enter().
+ *
+ * mrs x10, sp_el0
+ * ldr w11, [x10, #offsetof(struct task_struct, rcu_tramp_nesting)]
+ * add/sub w11, w11, #1
+ * str w11, [x10, #...]
+ *
+ * x10/x11 are scratch in the trampoline at every point this is emitted.
+ */
+static void emit_rcu_tasks_tramp_nesting(struct jit_ctx *ctx, bool enter)
+{
+#ifdef CONFIG_TASKS_RCU
+ const int off = offsetof(struct task_struct, rcu_tramp_nesting);
+ const u8 tsk = A64_R(10), cnt = A64_R(11);
+
+ BUILD_BUG_ON(off & 3 || off >= SZ_16K); /* LDR/STR (imm12, scaled) */
+
+ emit(A64_MRS_SP_EL0(tsk), ctx);
+ emit(A64_LDR32I(cnt, tsk, off), ctx);
+ if (enter)
+ emit(A64_ADD_I(0, cnt, cnt, 1), ctx);
+ else
+ emit(A64_SUB_I(0, cnt, cnt, 1), ctx);
+ emit(A64_STR32I(cnt, tsk, off), ctx);
+#endif
+}
+
static void save_args(struct jit_ctx *ctx, int bargs_off, int oargs_off,
const struct btf_func_model *m, const struct arg_aux *a,
bool for_call_origin, bool is_struct_ops, u64 arena_base)
@@ -2854,6 +2882,13 @@ static int prepare_trampoline(struct jit_ctx *ctx, struct bpf_tramp_image *im,
emit(A64_STR64I(A64_R(19), A64_SP, regs_off), ctx);
emit(A64_STR64I(A64_R(20), A64_SP, regs_off + 8), ctx);
+ /*
+ * From here until the matching decrement in the epilogue, a preemption
+ * of this task is not a Tasks RCU quiescent state. The instructions
+ * above this point are covered by the irq-exit IP check.
+ */
+ emit_rcu_tasks_tramp_nesting(ctx, true);
+
if (flags & BPF_TRAMP_F_CALL_ORIG) {
/* for the first pass, assume the worst case */
if (!ctx->image)
@@ -2898,12 +2933,20 @@ static int prepare_trampoline(struct jit_ctx *ctx, struct bpf_tramp_image *im,
if (flags & BPF_TRAMP_F_CALL_ORIG) {
/* the original func takes kernel addresses, never converted ones */
save_args(ctx, bargs_off, oargs_off, m, a, true, is_struct_ops, 0);
+ /*
+ * The original function may run for a long time without
+ * sleeping; do not let it pin a Tasks RCU grace period. The
+ * trampoline frame above it is held by im->pcref
+ * (__bpf_tramp_enter()), not by Tasks RCU, across the call.
+ */
+ emit_rcu_tasks_tramp_nesting(ctx, false);
/* call original func */
emit(A64_LDR64I(A64_R(10), A64_SP, retaddr_off), ctx);
emit(A64_ADR(A64_LR, AARCH64_INSN_SIZE * 2), ctx);
emit(A64_RET(A64_R(10)), ctx);
/* store return value */
emit(A64_STR64I(A64_R(0), A64_SP, retval_off), ctx);
+ emit_rcu_tasks_tramp_nesting(ctx, true);
/* reserve a nop for bpf_tramp_image_put */
im->ip_after_call = ctx->ro_image + ctx->idx;
emit(A64_NOP, ctx);
@@ -2945,6 +2988,9 @@ static int prepare_trampoline(struct jit_ctx *ctx, struct bpf_tramp_image *im,
if (flags & BPF_TRAMP_F_RESTORE_REGS)
restore_args(ctx, bargs_off, a->regs_for_args);
+ /* Remaining instructions are covered by the irq-exit IP check. */
+ emit_rcu_tasks_tramp_nesting(ctx, false);
+
/* restore callee saved register x19 and x20 */
emit(A64_LDR64I(A64_R(19), A64_SP, regs_off), ctx);
emit(A64_LDR64I(A64_R(20), A64_SP, regs_off + 8), ctx);
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index 15fd9ec5ecacb..0a64270193454 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -99,6 +99,7 @@ config X86
select ARCH_HAS_PREEMPT_LAZY
select ARCH_HAS_PTDUMP
select ARCH_HAS_PTE_SPECIAL
+ select ARCH_HAS_RCU_TASKS_PREEMPT_QS if X86_64
select ARCH_HAS_HW_PTE_YOUNG
select ARCH_HAS_NONLEAF_PMD_YOUNG if PGTABLE_LEVELS > 2
select ARCH_HAS_UACCESS_FLUSHCACHE if X86_64
diff --git a/arch/x86/kernel/asm-offsets.c b/arch/x86/kernel/asm-offsets.c
index 081816888f7a0..4f3b1caa5a309 100644
--- a/arch/x86/kernel/asm-offsets.c
+++ b/arch/x86/kernel/asm-offsets.c
@@ -46,6 +46,9 @@ static void __used common(void)
#ifdef CONFIG_STACKPROTECTOR
OFFSET(TASK_stack_canary, task_struct, stack_canary);
#endif
+#ifdef CONFIG_TASKS_RCU
+ OFFSET(TASK_rcu_tramp_nesting, task_struct, rcu_tramp_nesting);
+#endif
BLANK();
OFFSET(pbe_address, pbe, address);
diff --git a/arch/x86/kernel/ftrace.c b/arch/x86/kernel/ftrace.c
index 17d6edfcb7e00..8f63cd4b543c7 100644
--- a/arch/x86/kernel/ftrace.c
+++ b/arch/x86/kernel/ftrace.c
@@ -275,6 +275,43 @@ static inline void tramp_free(void *tramp)
execmem_free(tramp);
}
+#ifdef CONFIG_RCU_TASKS_PREEMPT_QS
+extern void ftrace_static_tramp_end(void);
+extern char __return_thunk_start[], __return_thunk_end[];
+extern char __rethunk_safe_start[], __rethunk_safe_end[];
+
+/*
+ * See rcu_tasks_ip_in_trampoline(). Some core kernel text behaves like a
+ * trampoline for Tasks RCU purposes because a task executing there with
+ * rcu_tramp_nesting == 0 may still be about to enter a Tasks-RCU-protected
+ * trampoline whose address it already holds:
+ *
+ * - the static ftrace_caller / ftrace_regs_caller / ftrace_stub_direct_tramp
+ * stubs, which carry a direct-call target on the stack until their final
+ * RET, and
+ * - the return thunks that RET expands to under CONFIG_MITIGATION_RETHUNK,
+ * which run after leaving the stubs above and before landing in that
+ * target.
+ */
+bool arch_rcu_tasks_ip_in_trampoline(unsigned long ip)
+{
+ if (ip >= (unsigned long)ftrace_caller &&
+ ip < (unsigned long)ftrace_static_tramp_end)
+ return true;
+#ifdef CONFIG_MITIGATION_RETPOLINE
+ if (ip >= (unsigned long)__return_thunk_start &&
+ ip < (unsigned long)__return_thunk_end)
+ return true;
+#endif
+#ifdef CONFIG_MITIGATION_SRSO
+ if (ip >= (unsigned long)__rethunk_safe_start &&
+ ip < (unsigned long)__rethunk_safe_end)
+ return true;
+#endif
+ return false;
+}
+#endif /* CONFIG_RCU_TASKS_PREEMPT_QS */
+
/* Defined as markers to the end of the ftrace default trampolines */
extern void ftrace_regs_caller_end(void);
extern void ftrace_caller_end(void);
diff --git a/arch/x86/kernel/ftrace_64.S b/arch/x86/kernel/ftrace_64.S
index 62c1c93aa1c6a..902472c41798b 100644
--- a/arch/x86/kernel/ftrace_64.S
+++ b/arch/x86/kernel/ftrace_64.S
@@ -7,6 +7,7 @@
#include <linux/cfi_types.h>
#include <linux/linkage.h>
#include <asm/asm-offsets.h>
+#include <asm/percpu.h>
#include <asm/ptrace.h>
#include <asm/ftrace.h>
#include <asm/nospec-branch.h>
@@ -145,6 +146,27 @@ SYM_FUNC_END(ftrace_stub_graph)
#ifdef CONFIG_DYNAMIC_FTRACE
+/*
+ * Tasks RCU trampoline nesting, see rcu_tasks_trampoline_enter(). These live
+ * inside the region copied into dynamic trampolines; the %rip-relative per-CPU
+ * reference is fixed up by text_poke_apply_relocation() in create_trampoline().
+ * The increment must precede the function_trace_op load: between that load and
+ * the call, the ops pointer in %rdx is protected only by Tasks RCU.
+ */
+.macro RCU_TASKS_TRAMP_ENTER reg:req
+#ifdef CONFIG_TASKS_RCU
+ movq PER_CPU_VAR(current_task), \reg
+ incl TASK_rcu_tramp_nesting(\reg)
+#endif
+.endm
+
+.macro RCU_TASKS_TRAMP_EXIT reg:req
+#ifdef CONFIG_TASKS_RCU
+ movq PER_CPU_VAR(current_task), \reg
+ decl TASK_rcu_tramp_nesting(\reg)
+#endif
+.endm
+
SYM_FUNC_START(__fentry__)
ANNOTATE_NOENDBR
CALL_DEPTH_ACCOUNT
@@ -163,6 +185,8 @@ SYM_FUNC_START(ftrace_caller)
leaq MCOUNT_REG_SIZE+8(%rsp), %rcx
movq %rcx, RSP(%rsp)
+ RCU_TASKS_TRAMP_ENTER %rdx
+
SYM_INNER_LABEL(ftrace_caller_op_ptr, SYM_L_GLOBAL)
ANNOTATE_NOENDBR
/* Load the ftrace_ops into the 3rd parameter */
@@ -181,6 +205,8 @@ SYM_INNER_LABEL(ftrace_call, SYM_L_GLOBAL)
ANNOTATE_NOENDBR
call ftrace_stub
+ RCU_TASKS_TRAMP_EXIT %rdx
+
/* Handlers can change the RIP */
movq RIP(%rsp), %rax
movq %rax, MCOUNT_REG_SIZE(%rsp)
@@ -209,6 +235,8 @@ SYM_FUNC_START(ftrace_regs_caller)
CALL_DEPTH_ACCOUNT
+ RCU_TASKS_TRAMP_ENTER %rdx
+
SYM_INNER_LABEL(ftrace_regs_caller_op_ptr, SYM_L_GLOBAL)
ANNOTATE_NOENDBR
/* Load the ftrace_ops into the 3rd parameter */
@@ -246,6 +274,8 @@ SYM_INNER_LABEL(ftrace_regs_call, SYM_L_GLOBAL)
ANNOTATE_NOENDBR
call ftrace_stub
+ RCU_TASKS_TRAMP_EXIT %rdx
+
/* Copy flags back to SS, to restore them */
movq EFLAGS(%rsp), %rax
movq %rax, MCOUNT_REG_SIZE(%rsp)
@@ -328,6 +358,19 @@ SYM_FUNC_START(ftrace_stub_direct_tramp)
RET
SYM_FUNC_END(ftrace_stub_direct_tramp)
+/*
+ * [ftrace_caller, ftrace_static_tramp_end) is treated as trampoline text by
+ * rcu_tasks_ip_in_trampoline(): after RCU_TASKS_TRAMP_EXIT the stubs may
+ * still hold a direct-call target (a BPF trampoline) on the stack until the
+ * final RET, and that target's lifetime is guarded by Tasks RCU. With
+ * return thunks the RET itself runs elsewhere; arch_rcu_tasks_ip_in_trampoline()
+ * covers the thunk text too.
+ */
+SYM_CODE_START_NOALIGN(ftrace_static_tramp_end)
+ UNWIND_HINT_UNDEFINED
+ ANNOTATE_NOENDBR
+SYM_CODE_END(ftrace_static_tramp_end)
+
#else /* ! CONFIG_DYNAMIC_FTRACE */
SYM_FUNC_START(__fentry__)
diff --git a/arch/x86/kernel/kprobes/opt.c b/arch/x86/kernel/kprobes/opt.c
index 3f8fea52619f9..f722520bb9891 100644
--- a/arch/x86/kernel/kprobes/opt.c
+++ b/arch/x86/kernel/kprobes/opt.c
@@ -31,6 +31,7 @@
#include <asm/set_memory.h>
#include <asm/sections.h>
#include <asm/nospec-branch.h>
+#include <asm/asm-offsets.h>
#include "common.h"
@@ -101,6 +102,23 @@ static void synthesize_set_arg1(kprobe_opcode_t *addr, unsigned long val)
*(unsigned long *)addr = val;
}
+/*
+ * Tasks RCU trampoline nesting, see rcu_tasks_trampoline_enter(). The
+ * template is memcpy()d into the slot without relocation processing, so the
+ * per-CPU reference must be absolute, not %rip-relative.
+ */
+#if defined(CONFIG_TASKS_RCU) && defined(CONFIG_X86_64)
+#define OPTPROBE_RCU_TASKS_ENTER \
+ " movq %gs:current_task, %rax\n" \
+ " incl " __stringify(TASK_rcu_tramp_nesting) "(%rax)\n"
+#define OPTPROBE_RCU_TASKS_EXIT \
+ " movq %gs:current_task, %rax\n" \
+ " decl " __stringify(TASK_rcu_tramp_nesting) "(%rax)\n"
+#else
+#define OPTPROBE_RCU_TASKS_ENTER
+#define OPTPROBE_RCU_TASKS_EXIT
+#endif
+
asm (
".pushsection .rodata\n"
".global optprobe_template_entry\n"
@@ -114,6 +132,7 @@ asm (
"optprobe_template_clac:\n"
ASM_NOP3
SAVE_REGS_STRING
+ OPTPROBE_RCU_TASKS_ENTER
" movq %rsp, %rsi\n"
".global optprobe_template_val\n"
"optprobe_template_val:\n"
@@ -122,6 +141,7 @@ asm (
".global optprobe_template_call\n"
"optprobe_template_call:\n"
ASM_NOP5
+ OPTPROBE_RCU_TASKS_EXIT
/* Copy 'regs->flags' into 'regs->ss'. */
" movq 18*8(%rsp), %rdx\n"
" movq %rdx, 20*8(%rsp)\n"
diff --git a/arch/x86/kernel/vmlinux.lds.S b/arch/x86/kernel/vmlinux.lds.S
index 2438b89a4620a..e546283dc2679 100644
--- a/arch/x86/kernel/vmlinux.lds.S
+++ b/arch/x86/kernel/vmlinux.lds.S
@@ -151,7 +151,9 @@ SECTIONS
* definition.
*/
. = srso_alias_untrain_ret | (1 << 2) | (1 << 8) | (1 << 14) | (1 << 20);
+ __rethunk_safe_start = .;
*(.text..__x86.rethunk_safe)
+ __rethunk_safe_end = .;
#endif
ALIGN_ENTRY_TEXT_END
@@ -162,7 +164,9 @@ SECTIONS
SOFTIRQENTRY_TEXT
#ifdef CONFIG_MITIGATION_RETPOLINE
*(.text..__x86.indirect_thunk)
+ __return_thunk_start = .;
*(.text..__x86.return_thunk)
+ __return_thunk_end = .;
#endif
STATIC_CALL_TEXT
*(.gnu.warning)
diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index 2853e87797a78..a375c1b7bd50c 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -722,6 +722,31 @@ static void emit_indirect_jump(u8 **pprog, int bpf_reg, u8 *ip)
*pprog = prog;
}
+/*
+ * Tasks RCU trampoline nesting, see rcu_tasks_trampoline_enter().
+ *
+ * mov r11, QWORD PTR gs:[current_task]
+ * inc/dec DWORD PTR [r11 + offsetof(struct task_struct, rcu_tramp_nesting)]
+ *
+ * r11 (AUX_REG) is scratch in the trampoline at every point this is emitted.
+ */
+static void emit_rcu_tasks_tramp_nesting(u8 **pprog, bool enter)
+{
+#ifdef CONFIG_TASKS_RCU
+ u8 *prog = *pprog;
+
+ /* mov r11, gs:[abs32] */
+ EMIT2(0x65, 0x4C);
+ EMIT3(0x8B, 0x1C, 0x25);
+ EMIT((u32)(unsigned long)¤t_task, 4);
+ /* inc/dec dword ptr [r11 + disp32] */
+ EMIT3(0x41, 0xFF, enter ? 0x83 : 0x8B);
+ EMIT(offsetof(struct task_struct, rcu_tramp_nesting), 4);
+
+ *pprog = prog;
+#endif
+}
+
static void emit_return(u8 **pprog, u8 *ip)
{
u8 *prog = *pprog;
@@ -3610,6 +3635,13 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
/* mov QWORD PTR [rbp - rbx_off], rbx */
emit_stx(&prog, BPF_DW, BPF_REG_FP, BPF_REG_6, -rbx_off);
+ /*
+ * From here until the matching decrement before the final return, a
+ * preemption of this task is not a Tasks RCU quiescent state. The
+ * instructions above this point are covered by the irq-exit IP check.
+ */
+ emit_rcu_tasks_tramp_nesting(&prog, true);
+
func_meta = nr_regs;
/* Store number of argument registers of the traced function */
emit_store_stack_imm64(&prog, BPF_REG_0, -func_meta_off, func_meta);
@@ -3670,6 +3702,13 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
LOAD_TRAMP_TAIL_CALL_CNT_PTR(stack_size);
}
+ /*
+ * The original function may run for a long time without
+ * sleeping; do not let it pin a Tasks RCU grace period. The
+ * trampoline frame above it is held by im->pcref
+ * (__bpf_tramp_enter()), not by Tasks RCU, across the call.
+ */
+ emit_rcu_tasks_tramp_nesting(&prog, false);
if (flags & BPF_TRAMP_F_ORIG_STACK) {
emit_ldx(&prog, BPF_DW, BPF_REG_6, BPF_REG_FP, 8);
EMIT2(0xff, 0xd3); /* call *rbx */
@@ -3680,6 +3719,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
goto cleanup;
}
}
+ emit_rcu_tasks_tramp_nesting(&prog, true);
/* remember return value in a stack for bpf prog to access */
emit_stx(&prog, BPF_DW, BPF_REG_FP, BPF_REG_0, -8);
im->ip_after_call = image + (prog - (u8 *)rw_image);
@@ -3741,6 +3781,9 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
if (save_ret)
emit_ldx(&prog, BPF_DW, BPF_REG_0, BPF_REG_FP, -8);
+ /* Remaining instructions are covered by the irq-exit IP check. */
+ emit_rcu_tasks_tramp_nesting(&prog, false);
+
emit_ldx(&prog, BPF_DW, BPF_REG_6, BPF_REG_FP, -rbx_off);
EMIT1(0xC9); /* leave */
diff --git a/arch/x86/xen/enlighten_pv.c b/arch/x86/xen/enlighten_pv.c
index 2c64b388f6164..3d85035f5624f 100644
--- a/arch/x86/xen/enlighten_pv.c
+++ b/arch/x86/xen/enlighten_pv.c
@@ -739,7 +739,7 @@ __visible noinstr void xen_pv_evtchn_do_upcall(struct pt_regs *regs)
inhcall = get_and_clear_inhcall();
if (inhcall && !WARN_ON_ONCE(state.exit_rcu)) {
- irqentry_exit_cond_resched();
+ irqentry_exit_cond_resched(regs);
instrumentation_end();
restore_inhcall(inhcall);
} else {
diff --git a/include/linux/irq-entry-common.h b/include/linux/irq-entry-common.h
index 0bb6c03481fad..fc04725ae46b6 100644
--- a/include/linux/irq-entry-common.h
+++ b/include/linux/irq-entry-common.h
@@ -5,6 +5,7 @@
#include <linux/context_tracking.h>
#include <linux/hrtimer_rearm.h>
#include <linux/kmsan.h>
+#include <linux/rcupdate.h>
#include <linux/rseq_entry.h>
#include <linux/static_call_types.h>
#include <linux/syscalls.h>
@@ -214,6 +215,7 @@ static __always_inline void __exit_to_user_mode_validate(void)
{
/* Ensure that kernel state is sane for a return to userspace */
kmap_assert_nomap();
+ rcu_tasks_trampoline_assert_none();
lockdep_assert_irqs_disabled();
lockdep_sys_exit();
}
@@ -346,21 +348,21 @@ typedef struct irqentry_state {
*
* Conditional reschedule with additional sanity checks.
*/
-void raw_irqentry_exit_cond_resched(void);
+void raw_irqentry_exit_cond_resched(struct pt_regs *regs);
#ifdef CONFIG_PREEMPT_DYNAMIC
#if defined(CONFIG_HAVE_PREEMPT_DYNAMIC_CALL)
#define irqentry_exit_cond_resched_dynamic_enabled raw_irqentry_exit_cond_resched
#define irqentry_exit_cond_resched_dynamic_disabled NULL
DECLARE_STATIC_CALL(irqentry_exit_cond_resched, raw_irqentry_exit_cond_resched);
-#define irqentry_exit_cond_resched() static_call(irqentry_exit_cond_resched)()
+#define irqentry_exit_cond_resched(regs) static_call(irqentry_exit_cond_resched)(regs)
#elif defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY)
DECLARE_STATIC_KEY_TRUE(sk_dynamic_irqentry_exit_cond_resched);
-void dynamic_irqentry_exit_cond_resched(void);
-#define irqentry_exit_cond_resched() dynamic_irqentry_exit_cond_resched()
+void dynamic_irqentry_exit_cond_resched(struct pt_regs *regs);
+#define irqentry_exit_cond_resched(regs) dynamic_irqentry_exit_cond_resched(regs)
#endif
#else /* CONFIG_PREEMPT_DYNAMIC */
-#define irqentry_exit_cond_resched() raw_irqentry_exit_cond_resched()
+#define irqentry_exit_cond_resched(regs) raw_irqentry_exit_cond_resched(regs)
#endif /* CONFIG_PREEMPT_DYNAMIC */
/**
@@ -465,7 +467,7 @@ static inline void irqentry_exit_to_kernel_mode_preempt(struct pt_regs *regs,
return;
if (IS_ENABLED(CONFIG_PREEMPTION))
- irqentry_exit_cond_resched();
+ irqentry_exit_cond_resched(regs);
}
/**
diff --git a/include/linux/kprobes.h b/include/linux/kprobes.h
index e6de7ae55bda0..74cc48c044173 100644
--- a/include/linux/kprobes.h
+++ b/include/linux/kprobes.h
@@ -530,11 +530,17 @@ static inline bool is_kprobe_insn_slot(unsigned long addr)
}
#endif /* !CONFIG_KPROBES */
-#ifndef CONFIG_OPTPROBES
+#ifdef CONFIG_OPTPROBES
+bool kprobe_in_optimized_region(unsigned long addr);
+#else /* !CONFIG_OPTPROBES */
static inline bool is_kprobe_optinsn_slot(unsigned long addr)
{
return false;
}
+static inline bool kprobe_in_optimized_region(unsigned long addr)
+{
+ return false;
+}
#endif /* !CONFIG_OPTPROBES */
#ifdef CONFIG_KRETPROBES
diff --git a/include/linux/module.h b/include/linux/module.h
index 96cc98568eea5..ea4727f53fabf 100644
--- a/include/linux/module.h
+++ b/include/linux/module.h
@@ -521,6 +521,13 @@ struct module {
unsigned int num_ftrace_callsites;
unsigned long *ftrace_callsites;
#endif
+#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
+ /*
+ * An ftrace direct-call trampoline lives in this module's text; see
+ * rcu_tasks_ip_in_trampoline(). Sticky once set.
+ */
+ bool ftrace_direct_tramp;
+#endif
#ifdef CONFIG_KPROBES
void *kprobes_text_start;
unsigned int kprobes_text_size;
diff --git a/include/linux/rcupdate.h b/include/linux/rcupdate.h
index 44c07a66edfff..9509f99ec965f 100644
--- a/include/linux/rcupdate.h
+++ b/include/linux/rcupdate.h
@@ -173,6 +173,9 @@ static inline void rcu_nocb_flush_deferred_wakeup(void) { }
#endif /* #else #ifdef CONFIG_RCU_NOCB_CPU */
+/* Arch hook for rcu_tasks_ip_in_trampoline(); see kernel/rcu/tasks.h. */
+bool arch_rcu_tasks_ip_in_trampoline(unsigned long ip);
+
/*
* Note a quasi-voluntary context switch for RCU-tasks's benefit.
* This is a macro rather than an inline function to avoid #include hell.
@@ -180,9 +183,80 @@ static inline void rcu_nocb_flush_deferred_wakeup(void) { }
#ifdef CONFIG_TASKS_RCU_GENERIC
# ifdef CONFIG_TASKS_RCU
+
+/*
+ * Trampoline nesting: dynamically allocated text (ftrace trampolines, BPF
+ * trampoline images, kprobe optinsn slots) that relies on Tasks RCU for its
+ * lifetime brackets itself with an increment/decrement of
+ * current->rcu_tramp_nesting. While the count is non-zero the task is inside,
+ * or was called from, such text and an involuntary context switch must not be
+ * treated as a Tasks RCU quiescent state.
+ *
+ * The increment and decrement themselves live inside the trampoline, so there
+ * is a window of a few instructions at entry (before the increment) and exit
+ * (after the decrement) where the count is zero but the CPU is executing
+ * trampoline text, or text on the way into one (a static ftrace stub or a
+ * return thunk holding the trampoline's address). In that window the task
+ * cannot be preempted synchronously, only from an interrupt, so the irq-exit
+ * preemption path covers it by checking regs->ip with
+ * rcu_tasks_ip_in_trampoline() and holding the count elevated across
+ * preempt_schedule_irq() when it matches.
+ *
+ * The one non-trampoline user, kprobe jump optimization, waits for tasks
+ * preempted inside ordinary instruction bytes it is about to overwrite. A
+ * task can be parked there from before the kprobe even existed, so that
+ * cannot be decided once at preemption time: irqentry_preempt() records the
+ * interrupted IP in current->rcu_tasks_irq_ip for the duration of the
+ * preemption and rcu_tasks_irq_ip_holds() checks it at every quiescent-state
+ * decision, locally and from the grace-period kthread.
+ *
+ * With both in place, on architectures that select
+ * ARCH_HAS_RCU_TASKS_PREEMPT_QS, a preemption with rcu_tramp_nesting == 0 is
+ * a Tasks RCU quiescent state, and a CPU-bound kernel thread no longer needs
+ * to volunteer one via cond_resched_tasks_rcu_qs().
+ *
+ * Only current writes the count and only current (or an interrupt on the same
+ * CPU) reads it, so plain accesses suffice.
+ */
+static __always_inline void rcu_tasks_trampoline_enter(void)
+{
+ current->rcu_tramp_nesting++;
+ barrier();
+}
+
+static __always_inline void rcu_tasks_trampoline_exit(void)
+{
+ barrier();
+ current->rcu_tramp_nesting--;
+}
+
+/* A task must never reach userspace with a trampoline on its stack. */
+static __always_inline void rcu_tasks_trampoline_assert_none(void)
+{
+ if (IS_ENABLED(CONFIG_PROVE_RCU))
+ WARN_ON_ONCE(current->rcu_tramp_nesting);
+}
+
+bool rcu_tasks_ip_in_trampoline(unsigned long ip);
+bool rcu_tasks_irq_ip_holds(struct task_struct *t);
+
+/* Record where current is being irq-preempted; 0 once it has resumed. */
+static __always_inline void rcu_tasks_note_irq_ip(unsigned long ip)
+{
+ WRITE_ONCE(current->rcu_tasks_irq_ip, ip);
+}
+
+#ifdef CONFIG_RCU_TASKS_PREEMPT_QS
+#define rcu_tasks_preempt_is_qs(t) \
+ (!READ_ONCE((t)->rcu_tramp_nesting) && !rcu_tasks_irq_ip_holds(t))
+#else
+#define rcu_tasks_preempt_is_qs(t) false
+#endif
+
# define rcu_tasks_classic_qs(t, preempt) \
do { \
- if (!(preempt) && READ_ONCE((t)->rcu_tasks_holdout)) \
+ if (READ_ONCE((t)->rcu_tasks_holdout) && \
+ (!(preempt) || rcu_tasks_preempt_is_qs(t))) \
WRITE_ONCE((t)->rcu_tasks_holdout, false); \
} while (0)
void call_rcu_tasks(struct rcu_head *head, rcu_callback_t func);
@@ -192,6 +266,11 @@ void rcu_tasks_torture_stats_print(char *tt, char *tf);
# define rcu_tasks_classic_qs(t, preempt) do { } while (0)
# define call_rcu_tasks call_rcu
# define synchronize_rcu_tasks synchronize_rcu
+static inline void rcu_tasks_trampoline_enter(void) { }
+static inline void rcu_tasks_trampoline_exit(void) { }
+static inline void rcu_tasks_trampoline_assert_none(void) { }
+static inline bool rcu_tasks_ip_in_trampoline(unsigned long ip) { return false; }
+static inline void rcu_tasks_note_irq_ip(unsigned long ip) { }
# endif
#define rcu_tasks_qs(t, preempt) rcu_tasks_classic_qs((t), (preempt))
@@ -208,6 +287,11 @@ void exit_tasks_rcu_finish(void);
#define rcu_tasks_classic_qs(t, preempt) do { } while (0)
#define rcu_tasks_qs(t, preempt) do { } while (0)
#define rcu_note_voluntary_context_switch(t) do { } while (0)
+static inline void rcu_tasks_trampoline_enter(void) { }
+static inline void rcu_tasks_trampoline_exit(void) { }
+static inline void rcu_tasks_trampoline_assert_none(void) { }
+static inline bool rcu_tasks_ip_in_trampoline(unsigned long ip) { return false; }
+static inline void rcu_tasks_note_irq_ip(unsigned long ip) { }
#define call_rcu_tasks call_rcu
#define synchronize_rcu_tasks synchronize_rcu
static inline void exit_tasks_rcu_start(void) { }
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 8b3d47a325cca..7f0bdc81fba33 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -956,6 +956,8 @@ struct task_struct {
unsigned long rcu_tasks_nvcsw;
u8 rcu_tasks_holdout;
u8 rcu_tasks_idx;
+ int rcu_tramp_nesting;
+ unsigned long rcu_tasks_irq_ip;
int rcu_tasks_idle_cpu;
struct list_head rcu_tasks_holdout_list;
int rcu_tasks_exit_cpu;
diff --git a/include/linux/trace_recursion.h b/include/linux/trace_recursion.h
index e6ca052b2a85a..2da23a52ca4a4 100644
--- a/include/linux/trace_recursion.h
+++ b/include/linux/trace_recursion.h
@@ -153,6 +153,17 @@ static __always_inline int trace_test_and_set_recursion(unsigned long ip, unsign
current->trace_recursion = val;
barrier();
+ /*
+ * Callbacks reached from static trampoline text (return_to_handler,
+ * the rethook and kretprobe trampolines) do not maintain
+ * current->rcu_tramp_nesting themselves; they rely on this
+ * preempt_disable() to keep the task from being preempted, and thus
+ * from reporting a Tasks RCU quiescent state, while an ftrace_ops or
+ * its data is in use. If the preempt_disable() is ever removed from
+ * the recursion protection, this must rcu_tasks_trampoline_enter()
+ * here and rcu_tasks_trampoline_exit() in trace_clear_recursion()
+ * instead. See CONFIG_RCU_TASKS_PREEMPT_QS.
+ */
preempt_disable_notrace();
return bit;
diff --git a/kernel/entry/common.c b/kernel/entry/common.c
index e3d381fd3d251..b372f2670d4f7 100644
--- a/kernel/entry/common.c
+++ b/kernel/entry/common.c
@@ -134,7 +134,37 @@ static inline bool arch_irqentry_exit_need_resched(void);
static inline bool arch_irqentry_exit_need_resched(void) { return true; }
#endif
-void raw_irqentry_exit_cond_resched(void)
+/*
+ * Preempt the interrupted kernel context. If the interrupt landed in text
+ * that may be a Tasks-RCU-protected trampoline (see
+ * rcu_tasks_trampoline_enter()), hold current->rcu_tramp_nesting elevated
+ * across the context switch so that it is not mistaken for a Tasks RCU
+ * quiescent state. This closes the few-instruction windows at trampoline
+ * entry/exit where the trampoline's own increment has not yet run or its
+ * decrement already has. The interrupted IP is also recorded for the
+ * duration, for conditions that must be re-evaluated at each quiescent-state
+ * decision rather than once here (see rcu_tasks_irq_ip_holds()); nested
+ * irq-exit preemption cannot happen inside preempt_schedule_irq(), so one
+ * slot per task is enough.
+ */
+static void irqentry_preempt(struct pt_regs *regs)
+{
+ unsigned long ip = instruction_pointer(regs);
+ bool in_tramp = IS_ENABLED(CONFIG_RCU_TASKS_PREEMPT_QS) &&
+ rcu_tasks_ip_in_trampoline(ip);
+
+ if (in_tramp)
+ rcu_tasks_trampoline_enter();
+ if (IS_ENABLED(CONFIG_RCU_TASKS_PREEMPT_QS))
+ rcu_tasks_note_irq_ip(ip);
+ preempt_schedule_irq();
+ if (IS_ENABLED(CONFIG_RCU_TASKS_PREEMPT_QS))
+ rcu_tasks_note_irq_ip(0);
+ if (in_tramp)
+ rcu_tasks_trampoline_exit();
+}
+
+void raw_irqentry_exit_cond_resched(struct pt_regs *regs)
{
if (!preempt_count()) {
/* Sanity check RCU and thread stack */
@@ -142,7 +172,7 @@ void raw_irqentry_exit_cond_resched(void)
if (IS_ENABLED(CONFIG_DEBUG_ENTRY))
WARN_ON_ONCE(!on_thread_stack());
if (need_resched() && arch_irqentry_exit_need_resched())
- preempt_schedule_irq();
+ irqentry_preempt(regs);
}
}
#ifdef CONFIG_PREEMPT_DYNAMIC
@@ -150,11 +180,11 @@ void raw_irqentry_exit_cond_resched(void)
DEFINE_STATIC_CALL(irqentry_exit_cond_resched, raw_irqentry_exit_cond_resched);
#elif defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY)
DEFINE_STATIC_KEY_TRUE(sk_dynamic_irqentry_exit_cond_resched);
-void dynamic_irqentry_exit_cond_resched(void)
+void dynamic_irqentry_exit_cond_resched(struct pt_regs *regs)
{
if (!static_branch_unlikely(&sk_dynamic_irqentry_exit_cond_resched))
return;
- raw_irqentry_exit_cond_resched();
+ raw_irqentry_exit_cond_resched(regs);
}
#endif
#endif
diff --git a/kernel/fork.c b/kernel/fork.c
index 416758c8a3d43..1277603bc472f 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -1869,6 +1869,8 @@ static inline void rcu_copy_process(struct task_struct *p)
#endif /* #ifdef CONFIG_PREEMPT_RCU */
#ifdef CONFIG_TASKS_RCU
p->rcu_tasks_holdout = false;
+ p->rcu_tramp_nesting = 0;
+ p->rcu_tasks_irq_ip = 0;
INIT_LIST_HEAD(&p->rcu_tasks_holdout_list);
p->rcu_tasks_idle_cpu = -1;
INIT_LIST_HEAD(&p->rcu_tasks_exit_list);
diff --git a/kernel/kprobes.c b/kernel/kprobes.c
index 6337da5cab9e7..cf2ea278fdf5b 100644
--- a/kernel/kprobes.c
+++ b/kernel/kprobes.c
@@ -511,6 +511,48 @@ static struct kprobe *get_optimized_kprobe(kprobe_opcode_t *addr)
return NULL;
}
+/*
+ * True while kprobe_optimizer() is waiting for its Tasks RCU grace period.
+ * Only in that window can a preemption inside an optprobe's jump region
+ * matter to it, so kprobe_in_optimized_region() does no work otherwise.
+ */
+static bool kprobe_optimizer_waiting;
+
+/**
+ * kprobe_in_optimized_region - Could @addr be inside bytes a jump-optimized
+ * kprobe replaces?
+ * @addr: kernel text address, typically an interrupted instruction pointer
+ *
+ * kprobe_optimizer() relies on synchronize_rcu_tasks() to wait for tasks that
+ * were preempted on an instruction boundary inside the region about to be
+ * overwritten by the optimized jump; such a task must not report a Tasks RCU
+ * quiescent state when it is preempted (see rcu_tasks_ip_in_trampoline()).
+ * This is the lockless, conservative form of get_optimized_kprobe(): it does
+ * not care whether the kprobe found is, or ever will be, optimized. May be
+ * called from any context with preemption disabled; the kprobe hash is
+ * RCU-protected and every free path waits for a grace period after unhashing.
+ *
+ * The hash walk only runs while the optimizer is actually waiting. A
+ * preemption that does not observe kprobe_optimizer_waiting predates the
+ * grace period (its leading synchronize_rcu() publishes the store to every
+ * interrupts-disabled reader before any task is sampled as a holdout); such a
+ * task is then an ordinary preempted holdout, and the jump is not written
+ * until it has run again and left the region.
+ */
+bool kprobe_in_optimized_region(unsigned long addr)
+{
+ int i;
+
+ if (!READ_ONCE(kprobe_optimizer_waiting))
+ return false;
+
+ for (i = 1; i < MAX_OPTIMIZED_LENGTH / sizeof(kprobe_opcode_t); i++)
+ if (get_kprobe((kprobe_opcode_t *)addr - i))
+ return true;
+ return false;
+}
+NOKPROBE_SYMBOL(kprobe_in_optimized_region);
+
/* Optimization staging list, protected by 'kprobe_mutex' */
static LIST_HEAD(optimizing_list);
static LIST_HEAD(unoptimizing_list);
@@ -644,8 +686,12 @@ static void kprobe_optimizer(void)
* to 2nd-Nth byte of jump instruction. This wait is for avoiding it.
* Note that on non-preemptive kernel, this is transparently converted
* to synchronoze_sched() to wait for all interrupts to have completed.
+ * kprobe_optimizer_waiting lets Tasks RCU recognise tasks preempted
+ * in such a region while we wait, see kprobe_in_optimized_region().
*/
+ WRITE_ONCE(kprobe_optimizer_waiting, true);
synchronize_rcu_tasks();
+ WRITE_ONCE(kprobe_optimizer_waiting, false);
/* Step 3: Optimize kprobes after quiesence period */
do_optimize_kprobes();
diff --git a/kernel/rcu/Kconfig b/kernel/rcu/Kconfig
index 332df7a7a6347..8e7c943291054 100644
--- a/kernel/rcu/Kconfig
+++ b/kernel/rcu/Kconfig
@@ -94,9 +94,10 @@ config FORCE_TASKS_RCU
default n
help
This option force-enables a task-based RCU implementation
- that uses only voluntary context switch (not preemption!),
- idle, and user-mode execution as quiescent states. Not for
- manual selection in most cases.
+ that uses only voluntary context switch (not preemption, unless
+ the architecture selects ARCH_HAS_RCU_TASKS_PREEMPT_QS and the
+ task is outside any trampoline), idle, and user-mode execution
+ as quiescent states. Not for manual selection in most cases.
config NEED_TASKS_RCU
bool
@@ -107,6 +108,16 @@ config TASKS_RCU
default NEED_TASKS_RCU && PREEMPTION
select IRQ_WORK
+# Selected by architectures whose ftrace, BPF and kprobe trampolines maintain
+# current->rcu_tramp_nesting and which use the generic irqentry code, so that
+# a preemption outside any trampoline can be treated as a Tasks RCU
+# quiescent state. See rcu_tasks_trampoline_enter().
+config ARCH_HAS_RCU_TASKS_PREEMPT_QS
+ bool
+
+config RCU_TASKS_PREEMPT_QS
+ def_bool TASKS_RCU && ARCH_HAS_RCU_TASKS_PREEMPT_QS && GENERIC_IRQ_ENTRY
+
config FORCE_TASKS_RUDE_RCU
bool "Force selection of Tasks Rude RCU"
depends on RCU_EXPERT
diff --git a/kernel/rcu/rcutorture.c b/kernel/rcu/rcutorture.c
index 794937e13e7c3..df6dd708cea78 100644
--- a/kernel/rcu/rcutorture.c
+++ b/kernel/rcu/rcutorture.c
@@ -1144,11 +1144,17 @@ static struct rcu_torture_ops trivial_preempt_ops = {
static int tasks_torture_read_lock(void)
{
+ /*
+ * Model a trampoline: with CONFIG_RCU_TASKS_PREEMPT_QS a preemption is
+ * otherwise a quiescent state and rcu_read_delay() preempts on purpose.
+ */
+ rcu_tasks_trampoline_enter();
return 0;
}
static void tasks_torture_read_unlock(int idx)
{
+ rcu_tasks_trampoline_exit();
}
static void rcu_tasks_torture_deferred_free(struct rcu_torture *p)
diff --git a/kernel/rcu/tasks.h b/kernel/rcu/tasks.h
index 627295396cd91..02d2592ab7a3d 100644
--- a/kernel/rcu/tasks.h
+++ b/kernel/rcu/tasks.h
@@ -905,7 +905,10 @@ static void rcu_tasks_wait_gp(struct rcu_tasks *rtp)
//
// Simple variant of RCU whose quiescent states are voluntary context
// switch, cond_resched_tasks_rcu_qs(), user-space execution, and idle.
-// As such, grace periods can take one good long time. There are no
+// With CONFIG_RCU_TASKS_PREEMPT_QS, a preemption taken while the task is
+// not inside a trampoline (current->rcu_tramp_nesting == 0, see
+// rcu_tasks_trampoline_enter()) is a quiescent state as well; without it,
+// grace periods can take one good long time. There are no
// read-side primitives similar to rcu_read_lock() and rcu_read_unlock()
// because this implementation is intended to get the system into a safe
// state for some of the manipulations involved in tracing and the like.
@@ -1011,10 +1014,49 @@ static bool rcu_tasks_is_holdout(struct task_struct *t)
return true;
}
+#ifdef CONFIG_RCU_TASKS_PREEMPT_QS
+/* task_call_func() callback: is @t switched out with no trampoline in play? */
+static int rcu_tasks_switched_out_clean(struct task_struct *t, void *arg)
+{
+ /*
+ * With @t pinned, !task_curr() means it last left the CPU through
+ * __schedule(), so its rcu_tramp_nesting and rcu_tasks_irq_ip are
+ * stable and ordered before the rq lock we hold. A task preempted
+ * from irq exit inside trampoline text has the count held non-zero
+ * across the switch by irqentry_preempt(), so zero here means neither
+ * in nor called from a trampoline; rcu_tasks_irq_ip_holds() covers the
+ * one case that can become true after the task was switched out (a
+ * kprobe jump-optimization window). Both clear: already quiescent,
+ * whether or not it ever runs again.
+ */
+ return !task_curr(t) && !READ_ONCE(t->rcu_tramp_nesting) &&
+ !rcu_tasks_irq_ip_holds(t);
+}
+
+/* Is @t, right now, switched out somewhere that is a quiescent state? */
+static bool rcu_tasks_preempted_qs(struct task_struct *t)
+{
+ return task_call_func(t, rcu_tasks_switched_out_clean, NULL);
+}
+
+/* Make a running holdout pass through __schedule() soon, tick or no tick. */
+static void rcu_tasks_kick_running(struct task_struct *t)
+{
+ int cpu = task_cpu(t);
+
+ if (task_curr(t) && cpu_online(cpu))
+ resched_cpu(cpu);
+}
+#else
+static bool rcu_tasks_preempted_qs(struct task_struct *t) { return false; }
+static void rcu_tasks_kick_running(struct task_struct *t) { }
+#endif
+
/* Per-task initial processing. */
static void rcu_tasks_pertask(struct task_struct *t, struct list_head *hop)
{
- if (t != current && rcu_tasks_is_holdout(t)) {
+ if (t != current && rcu_tasks_is_holdout(t) &&
+ !rcu_tasks_preempted_qs(t)) {
get_task_struct(t);
t->rcu_tasks_nvcsw = READ_ONCE(t->nvcsw);
WRITE_ONCE(t->rcu_tasks_holdout, true);
@@ -1089,6 +1131,86 @@ static void rcu_tasks_postscan(struct list_head *hop)
timer_delete_sync(&tasks_rcu_exit_stall_timer);
}
+/*
+ * Architectures selecting ARCH_HAS_RCU_TASKS_PREEMPT_QS override this to flag
+ * core kernel text that must be treated like a trampoline, e.g. static ftrace
+ * entry stubs and return thunks that run with a trampoline address in hand.
+ */
+bool __weak arch_rcu_tasks_ip_in_trampoline(unsigned long ip)
+{
+ return false;
+}
+
+/**
+ * rcu_tasks_ip_in_trampoline - Could a task interrupted at @ip be a Tasks RCU reader?
+ * @ip: interrupted instruction pointer
+ *
+ * Called from the irq-exit preemption path with interrupts disabled, to decide
+ * whether the imminent preemption may be reported as a Tasks RCU quiescent
+ * state when current->rcu_tramp_nesting is zero. Returns true, meaning "do
+ * not report", when @ip is:
+ *
+ * - outside static kernel and module text, i.e. possibly in an ftrace
+ * trampoline, BPF trampoline image or program, kprobe insn/optinsn slot or
+ * other dynamically allocated text whose lifetime Tasks RCU guards. This
+ * deliberately does not consult is_ftrace_trampoline() and friends: text
+ * being torn down may already be unregistered there while a task still
+ * stands on it;
+ * - in core text the architecture flags via arch_rcu_tasks_ip_in_trampoline();
+ * - in the text of a module that hosts an ftrace direct-call trampoline,
+ * which covers the instructions before that trampoline's increment and
+ * after its decrement (see ftrace_direct_mark_module()).
+ *
+ * A false positive only defers the quiescent state to the task's next
+ * context switch.
+ */
+bool rcu_tasks_ip_in_trampoline(unsigned long ip)
+{
+ bool ret = true;
+
+ if (core_kernel_text(ip))
+ return arch_rcu_tasks_ip_in_trampoline(ip);
+
+#ifdef CONFIG_MODULES
+ scoped_guard(rcu) {
+ struct module *mod = __module_text_address(ip);
+
+#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
+ if (mod)
+ ret = READ_ONCE(mod->ftrace_direct_tramp);
+#else
+ if (mod)
+ ret = false;
+#endif
+ }
+#endif
+ return ret;
+}
+NOKPROBE_SYMBOL(rcu_tasks_ip_in_trampoline);
+
+/**
+ * rcu_tasks_irq_ip_holds - Is @t irq-preempted somewhere that must hold off Tasks RCU?
+ * @t: a task inside preempt_schedule_irq() (t->rcu_tasks_irq_ip != 0), or not
+ *
+ * Unlike trampoline text, which a task can only be interrupted in while the
+ * trampoline exists, the bytes kprobe_optimizer() is about to overwrite with a
+ * jump are ordinary text a task may have been parked in since before the
+ * kprobe was registered, and the optimizer may start waiting while the task is
+ * already switched out. So this is evaluated against the IP recorded by
+ * irqentry_preempt() at every quiescent-state decision -- each pass through
+ * __schedule() in preempt_schedule_irq()'s loop, and the grace-period
+ * kthread's scans -- rather than once at preemption time. A task switched out
+ * synchronously cannot have a resume point inside such a window (a call there
+ * returns beyond it), so only the irq-exit IP needs checking.
+ */
+bool rcu_tasks_irq_ip_holds(struct task_struct *t)
+{
+ unsigned long ip = READ_ONCE(t->rcu_tasks_irq_ip);
+
+ return ip && kprobe_in_optimized_region(ip);
+}
+NOKPROBE_SYMBOL(rcu_tasks_irq_ip_holds);
+
/* See if tasks are still holding out, complain if so. */
static void check_holdout_task(struct task_struct *t,
bool needreport, bool *firstreport)
@@ -1098,6 +1220,7 @@ static void check_holdout_task(struct task_struct *t,
if (!READ_ONCE(t->rcu_tasks_holdout) ||
t->rcu_tasks_nvcsw != READ_ONCE(t->nvcsw) ||
!rcu_tasks_is_holdout(t) ||
+ rcu_tasks_preempted_qs(t) ||
(IS_ENABLED(CONFIG_NO_HZ_FULL) &&
!is_idle_task(t) && READ_ONCE(t->rcu_tasks_idle_cpu) >= 0)) {
WRITE_ONCE(t->rcu_tasks_holdout, false);
@@ -1106,6 +1229,7 @@ static void check_holdout_task(struct task_struct *t,
return;
}
rcu_request_urgent_qs_task(t);
+ rcu_tasks_kick_running(t);
if (!needreport)
return;
if (*firstreport) {
@@ -1113,10 +1237,11 @@ static void check_holdout_task(struct task_struct *t,
*firstreport = false;
}
cpu = task_cpu(t);
- pr_alert("%p: %c%c nvcsw: %lu/%lu holdout: %d idle_cpu: %d/%d\n",
+ pr_alert("%p: %c%c nvcsw: %lu/%lu holdout: %d tramp_nesting: %d idle_cpu: %d/%d\n",
t, ".I"[is_idle_task(t)],
"N."[cpu < 0 || !tick_nohz_full_cpu(cpu)],
t->rcu_tasks_nvcsw, t->nvcsw, t->rcu_tasks_holdout,
+ data_race(t->rcu_tramp_nesting),
data_race(t->rcu_tasks_idle_cpu), cpu);
sched_show_task(t);
}
@@ -1182,8 +1307,11 @@ static void tasks_rcu_exit_stall(struct timer_list *unused)
* period elapses, in other words after all currently executing rcu-tasks
* read-side critical sections have completed. call_rcu_tasks() assumes
* that the read-side critical sections end at a voluntary context
- * switch (not a preemption!), cond_resched_tasks_rcu_qs(), entry into idle,
- * or transition to usermode execution. As such, there are no read-side
+ * switch, cond_resched_tasks_rcu_qs(), entry into idle, transition to
+ * usermode execution, or, with CONFIG_RCU_TASKS_PREEMPT_QS, a preemption
+ * taken outside any trampoline (current->rcu_tramp_nesting == 0, see
+ * rcu_tasks_trampoline_enter()); otherwise a preemption is not a
+ * quiescent state. As such, there are no read-side
* primitives analogous to rcu_read_lock() and rcu_read_unlock() because
* this primitive is intended to determine that all tasks have passed
* through a safe state, not so much for data-structure synchronization.
@@ -1205,7 +1333,8 @@ EXPORT_SYMBOL_GPL(call_rcu_tasks);
* executing rcu-tasks read-side critical sections have elapsed. These
* read-side critical sections are delimited by calls to schedule(),
* cond_resched_tasks_rcu_qs(), idle execution, userspace execution, calls
- * to synchronize_rcu_tasks(), and (in theory, anyway) cond_resched().
+ * to synchronize_rcu_tasks(), (in theory, anyway) cond_resched(), and,
+ * with CONFIG_RCU_TASKS_PREEMPT_QS, preemption outside any trampoline.
*
* This is a very specialized primitive, intended only for a few uses in
* tracing and other situations requiring manipulation of function
diff --git a/kernel/rcu/update.c b/kernel/rcu/update.c
index b62735a678842..23be7e97c3b5f 100644
--- a/kernel/rcu/update.c
+++ b/kernel/rcu/update.c
@@ -41,6 +41,8 @@
#include <linux/rcupdate_wait.h>
#include <linux/sched/isolation.h>
#include <linux/kprobes.h>
+#include <linux/kallsyms.h>
+#include <linux/module.h>
#include <linux/slab.h>
#include <linux/irq_work.h>
#include <linux/rcupdate_trace.h>
diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c
index 53d5db60bfa56..14f27b8872319 100644
--- a/kernel/trace/ftrace.c
+++ b/kernel/trace/ftrace.c
@@ -6076,6 +6076,29 @@ static void reset_direct(struct ftrace_ops *ops, unsigned long addr)
ops->trampoline = 0;
}
+/*
+ * A direct trampoline may live in module text rather than in dynamically
+ * allocated text that rcu_tasks_ip_in_trampoline() recognises on its own (see
+ * samples/ftrace/ftrace-direct*.c). The trampoline itself must hold
+ * current->rcu_tramp_nesting across its call-out (see register_ftrace_direct());
+ * marking the owning module here covers the instructions before that increment
+ * and after the decrement, where a task interrupted in the module's text must
+ * not be treated as Tasks-RCU quiescent, so that ftrace_shutdown()'s
+ * synchronize_rcu_tasks() still keeps the module text from being freed under
+ * it.
+ */
+static void ftrace_direct_mark_module(unsigned long addr)
+{
+#ifdef CONFIG_MODULES
+ struct module *mod;
+
+ guard(rcu)();
+ mod = __module_text_address(addr);
+ if (mod)
+ WRITE_ONCE(mod->ftrace_direct_tramp, true);
+#endif
+}
+
/**
* register_ftrace_direct - Call a custom trampoline directly
* for multiple functions registered in @ops
@@ -6090,6 +6113,17 @@ static void reset_direct(struct ftrace_ops *ops, unsigned long addr)
* and save the parameters of the function being traced, and restore them
* (or inject new ones if needed), before returning.
*
+ * Nothing but Tasks RCU keeps the trampoline at @addr alive while a task is
+ * executing it or is preempted in something it called. On architectures that
+ * select ARCH_HAS_RCU_TASKS_PREEMPT_QS a preemption is a Tasks RCU quiescent
+ * state unless current->rcu_tramp_nesting is non-zero, so the trampoline must
+ * increment it before calling out and decrement it before returning, as the
+ * ftrace and BPF trampolines do (see rcu_tasks_trampoline_enter() and
+ * samples/ftrace/ftrace-direct.h). The few instructions before the increment
+ * and after the decrement are covered by the irq-exit IP check: automatically
+ * for trampolines outside kernel and module text (e.g. BPF images), and via
+ * ftrace_direct_mark_module() for trampolines in module text.
+ *
* Returns:
* 0 on success
* -EINVAL - The @ops object was already registered with this call or
@@ -6169,6 +6203,7 @@ int register_ftrace_direct(struct ftrace_ops *ops, unsigned long addr)
ops->flags |= MULTI_FLAGS;
ops->trampoline = FTRACE_REGS_ADDR;
ops->direct_call = addr;
+ ftrace_direct_mark_module(addr);
err = register_ftrace_function_nolock(ops);
if (err)
@@ -6237,6 +6272,8 @@ __modify_ftrace_direct(struct ftrace_ops *ops, unsigned long addr)
lockdep_assert_held_once(&direct_mutex);
+ ftrace_direct_mark_module(addr);
+
/* Enable the tmp_ops to have the same functions as the direct ops */
ftrace_ops_init(&tmp_ops);
tmp_ops.func_hash = ops->func_hash;
@@ -6419,6 +6456,7 @@ int update_ftrace_direct_add(struct ftrace_ops *ops, struct ftrace_hash *hash)
hlist_for_each_entry(entry, &hash->buckets[i], hlist) {
if (__ftrace_lookup_ip(direct_functions, entry->ip))
goto out_unlock;
+ ftrace_direct_mark_module(entry->direct);
}
}
@@ -6702,6 +6740,7 @@ int update_ftrace_direct_mod(struct ftrace_ops *ops, struct ftrace_hash *hash, b
tmp = __ftrace_lookup_ip(direct_hash, entry->ip);
if (!tmp)
continue;
+ ftrace_direct_mark_module(entry->direct);
tmp->direct = entry->direct;
}
}
diff --git a/samples/ftrace/ftrace-direct-modify.c b/samples/ftrace/ftrace-direct-modify.c
index 164d9dd6fd921..eb8230fa4242b 100644
--- a/samples/ftrace/ftrace-direct-modify.c
+++ b/samples/ftrace/ftrace-direct-modify.c
@@ -2,6 +2,7 @@
#include <linux/module.h>
#include <linux/kthread.h>
#include <linux/ftrace.h>
+#include "ftrace-direct.h"
#if !defined(CONFIG_ARM64) && !defined(CONFIG_PPC32)
#include <asm/asm-offsets.h>
#endif
@@ -73,7 +74,9 @@ asm (
" pushq %rbp\n"
" movq %rsp, %rbp\n"
CALL_DEPTH_ACCOUNT
+ RCU_TASKS_TRAMP_ENTER
" call my_direct_func1\n"
+ RCU_TASKS_TRAMP_EXIT
" leave\n"
ASM_RET
" .size my_tramp1, .-my_tramp1\n"
@@ -85,7 +88,9 @@ asm (
" pushq %rbp\n"
" movq %rsp, %rbp\n"
CALL_DEPTH_ACCOUNT
+ RCU_TASKS_TRAMP_ENTER
" call my_direct_func2\n"
+ RCU_TASKS_TRAMP_EXIT
" leave\n"
ASM_RET
" .size my_tramp2, .-my_tramp2\n"
@@ -141,11 +146,13 @@ asm (
" .globl my_tramp1\n"
" my_tramp1:"
" hint 34\n" // bti c
+ RCU_TASKS_TRAMP_ENTER
" sub sp, sp, #16\n"
" stp x9, x30, [sp]\n"
" bl my_direct_func1\n"
" ldp x30, x9, [sp]\n"
" add sp, sp, #16\n"
+ RCU_TASKS_TRAMP_EXIT
" ret x9\n"
" .size my_tramp1, .-my_tramp1\n"
@@ -153,11 +160,13 @@ asm (
" .globl my_tramp2\n"
" my_tramp2:"
" hint 34\n" // bti c
+ RCU_TASKS_TRAMP_ENTER
" sub sp, sp, #16\n"
" stp x9, x30, [sp]\n"
" bl my_direct_func2\n"
" ldp x30, x9, [sp]\n"
" add sp, sp, #16\n"
+ RCU_TASKS_TRAMP_EXIT
" ret x9\n"
" .size my_tramp2, .-my_tramp2\n"
" .popsection\n"
diff --git a/samples/ftrace/ftrace-direct-multi-modify.c b/samples/ftrace/ftrace-direct-multi-modify.c
index b03766c6217bc..c8f1062e5d1ad 100644
--- a/samples/ftrace/ftrace-direct-multi-modify.c
+++ b/samples/ftrace/ftrace-direct-multi-modify.c
@@ -2,6 +2,7 @@
#include <linux/module.h>
#include <linux/kthread.h>
#include <linux/ftrace.h>
+#include "ftrace-direct.h"
#if !defined(CONFIG_ARM64) && !defined(CONFIG_PPC32)
#include <asm/asm-offsets.h>
#endif
@@ -77,10 +78,12 @@ asm (
" pushq %rbp\n"
" movq %rsp, %rbp\n"
CALL_DEPTH_ACCOUNT
+ RCU_TASKS_TRAMP_ENTER
" pushq %rdi\n"
" movq 8(%rbp), %rdi\n"
" call my_direct_func1\n"
" popq %rdi\n"
+ RCU_TASKS_TRAMP_EXIT
" leave\n"
ASM_RET
" .size my_tramp1, .-my_tramp1\n"
@@ -92,10 +95,12 @@ asm (
" pushq %rbp\n"
" movq %rsp, %rbp\n"
CALL_DEPTH_ACCOUNT
+ RCU_TASKS_TRAMP_ENTER
" pushq %rdi\n"
" movq 8(%rbp), %rdi\n"
" call my_direct_func2\n"
" popq %rdi\n"
+ RCU_TASKS_TRAMP_EXIT
" leave\n"
ASM_RET
" .size my_tramp2, .-my_tramp2\n"
@@ -154,6 +159,7 @@ asm (
" .globl my_tramp1\n"
" my_tramp1:"
" hint 34\n" // bti c
+ RCU_TASKS_TRAMP_ENTER
" sub sp, sp, #32\n"
" stp x9, x30, [sp]\n"
" str x0, [sp, #16]\n"
@@ -162,6 +168,7 @@ asm (
" ldp x30, x9, [sp]\n"
" ldr x0, [sp, #16]\n"
" add sp, sp, #32\n"
+ RCU_TASKS_TRAMP_EXIT
" ret x9\n"
" .size my_tramp1, .-my_tramp1\n"
@@ -169,6 +176,7 @@ asm (
" .globl my_tramp2\n"
" my_tramp2:"
" hint 34\n" // bti c
+ RCU_TASKS_TRAMP_ENTER
" sub sp, sp, #32\n"
" stp x9, x30, [sp]\n"
" str x0, [sp, #16]\n"
@@ -177,6 +185,7 @@ asm (
" ldp x30, x9, [sp]\n"
" ldr x0, [sp, #16]\n"
" add sp, sp, #32\n"
+ RCU_TASKS_TRAMP_EXIT
" ret x9\n"
" .size my_tramp2, .-my_tramp2\n"
" .popsection\n"
diff --git a/samples/ftrace/ftrace-direct-multi.c b/samples/ftrace/ftrace-direct-multi.c
index 3fe6ddaf0b69f..bc6a88dd4ffcb 100644
--- a/samples/ftrace/ftrace-direct-multi.c
+++ b/samples/ftrace/ftrace-direct-multi.c
@@ -3,6 +3,7 @@
#include <linux/mm.h> /* for handle_mm_fault() */
#include <linux/ftrace.h>
+#include "ftrace-direct.h"
#include <linux/sched/stat.h>
#if !defined(CONFIG_ARM64) && !defined(CONFIG_PPC32)
#include <asm/asm-offsets.h>
@@ -56,10 +57,12 @@ asm (
" pushq %rbp\n"
" movq %rsp, %rbp\n"
CALL_DEPTH_ACCOUNT
+ RCU_TASKS_TRAMP_ENTER
" pushq %rdi\n"
" movq 8(%rbp), %rdi\n"
" call my_direct_func\n"
" popq %rdi\n"
+ RCU_TASKS_TRAMP_EXIT
" leave\n"
ASM_RET
" .size my_tramp, .-my_tramp\n"
@@ -101,6 +104,7 @@ asm (
" .globl my_tramp\n"
" my_tramp:"
" hint 34\n" // bti c
+ RCU_TASKS_TRAMP_ENTER
" sub sp, sp, #32\n"
" stp x9, x30, [sp]\n"
" str x0, [sp, #16]\n"
@@ -109,6 +113,7 @@ asm (
" ldp x30, x9, [sp]\n"
" ldr x0, [sp, #16]\n"
" add sp, sp, #32\n"
+ RCU_TASKS_TRAMP_EXIT
" ret x9\n"
" .size my_tramp, .-my_tramp\n"
" .popsection\n"
diff --git a/samples/ftrace/ftrace-direct-too.c b/samples/ftrace/ftrace-direct-too.c
index bf2411aa6fd7a..247e418644a29 100644
--- a/samples/ftrace/ftrace-direct-too.c
+++ b/samples/ftrace/ftrace-direct-too.c
@@ -3,6 +3,7 @@
#include <linux/mm.h> /* for handle_mm_fault() */
#include <linux/ftrace.h>
+#include "ftrace-direct.h"
#if !defined(CONFIG_ARM64) && !defined(CONFIG_PPC32)
#include <asm/asm-offsets.h>
#endif
@@ -61,6 +62,7 @@ asm (
" pushq %rbp\n"
" movq %rsp, %rbp\n"
CALL_DEPTH_ACCOUNT
+ RCU_TASKS_TRAMP_ENTER
" pushq %rdi\n"
" pushq %rsi\n"
" pushq %rdx\n"
@@ -70,6 +72,7 @@ asm (
" popq %rdx\n"
" popq %rsi\n"
" popq %rdi\n"
+ RCU_TASKS_TRAMP_EXIT
" leave\n"
ASM_RET
" .size my_tramp, .-my_tramp\n"
@@ -110,6 +113,7 @@ asm (
" .globl my_tramp\n"
" my_tramp:"
" hint 34\n" // bti c
+ RCU_TASKS_TRAMP_ENTER
" sub sp, sp, #48\n"
" stp x9, x30, [sp]\n"
" stp x0, x1, [sp, #16]\n"
@@ -119,6 +123,7 @@ asm (
" ldp x0, x1, [sp, #16]\n"
" ldp x2, x3, [sp, #32]\n"
" add sp, sp, #48\n"
+ RCU_TASKS_TRAMP_EXIT
" ret x9\n"
" .size my_tramp, .-my_tramp\n"
" .popsection\n"
diff --git a/samples/ftrace/ftrace-direct.c b/samples/ftrace/ftrace-direct.c
index 5368c8c39cbb4..9e1964baf28b4 100644
--- a/samples/ftrace/ftrace-direct.c
+++ b/samples/ftrace/ftrace-direct.c
@@ -3,6 +3,7 @@
#include <linux/sched.h> /* for wake_up_process() */
#include <linux/ftrace.h>
+#include "ftrace-direct.h"
#if !defined(CONFIG_ARM64) && !defined(CONFIG_PPC32)
#include <asm/asm-offsets.h>
#endif
@@ -54,9 +55,11 @@ asm (
" pushq %rbp\n"
" movq %rsp, %rbp\n"
CALL_DEPTH_ACCOUNT
+ RCU_TASKS_TRAMP_ENTER
" pushq %rdi\n"
" call my_direct_func\n"
" popq %rdi\n"
+ RCU_TASKS_TRAMP_EXIT
" leave\n"
ASM_RET
" .size my_tramp, .-my_tramp\n"
@@ -97,6 +100,7 @@ asm (
" .globl my_tramp\n"
" my_tramp:"
" hint 34\n" // bti c
+ RCU_TASKS_TRAMP_ENTER
" sub sp, sp, #32\n"
" stp x9, x30, [sp]\n"
" str x0, [sp, #16]\n"
@@ -104,6 +108,7 @@ asm (
" ldp x30, x9, [sp]\n"
" ldr x0, [sp, #16]\n"
" add sp, sp, #32\n"
+ RCU_TASKS_TRAMP_EXIT
" ret x9\n"
" .size my_tramp, .-my_tramp\n"
" .popsection\n"
diff --git a/samples/ftrace/ftrace-direct.h b/samples/ftrace/ftrace-direct.h
new file mode 100644
index 0000000000000..d0313f33f47f8
--- /dev/null
+++ b/samples/ftrace/ftrace-direct.h
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _SAMPLES_FTRACE_DIRECT_H
+#define _SAMPLES_FTRACE_DIRECT_H
+
+#include <linux/stringify.h>
+
+/*
+ * A direct-call trampoline is entered with no lock, refcount or RCU marker
+ * held; only Tasks RCU keeps it (and, for a module, its text) alive while a
+ * task is inside it or preempted in something it called. On architectures
+ * that select ARCH_HAS_RCU_TASKS_PREEMPT_QS a preemption is a Tasks RCU
+ * quiescent state unless current->rcu_tramp_nesting is non-zero, so the
+ * trampoline must raise it before calling out and drop it afterwards, exactly
+ * like the ftrace and BPF trampolines do. See rcu_tasks_trampoline_enter()
+ * and register_ftrace_direct(). The instructions before the increment and
+ * after the decrement are covered by ftrace_direct_mark_module().
+ *
+ * These expand to instruction strings for use inside the samples' asm()
+ * trampolines. The scratch register is caller-saved and not an argument
+ * register, so it is dead on entry to and exit from an fentry trampoline.
+ *
+ * The generated asm-offsets.h is only pulled in on the architectures that need
+ * it here: it is not generally safe to include from C (e.g. PPC32's TASK_SIZE
+ * and arm64's TRAMP_VALIAS clash with the C definitions), which is why the
+ * samples themselves guard their own include of it.
+ */
+#if defined(CONFIG_TASKS_RCU) && defined(CONFIG_X86_64)
+
+#include <asm/asm-offsets.h>
+
+#define RCU_TASKS_TRAMP_ENTER \
+ " movq %gs:current_task(%rip), %r11\n" \
+ " incl " __stringify(TASK_rcu_tramp_nesting) "(%r11)\n"
+#define RCU_TASKS_TRAMP_EXIT \
+ " movq %gs:current_task(%rip), %r11\n" \
+ " decl " __stringify(TASK_rcu_tramp_nesting) "(%r11)\n"
+
+#elif defined(CONFIG_TASKS_RCU) && defined(CONFIG_ARM64)
+
+/* arm64's asm-offsets.h redefines TRAMP_VALIAS from <asm/fixmap.h>. */
+#pragma push_macro("TRAMP_VALIAS")
+#undef TRAMP_VALIAS
+#include <asm/asm-offsets.h>
+#pragma pop_macro("TRAMP_VALIAS")
+
+#define RCU_TASKS_TRAMP_ENTER \
+ " mrs x12, sp_el0\n" \
+ " ldr w13, [x12, #" __stringify(TSK_RCU_TRAMP_NESTING) "]\n" \
+ " add w13, w13, #1\n" \
+ " str w13, [x12, #" __stringify(TSK_RCU_TRAMP_NESTING) "]\n"
+#define RCU_TASKS_TRAMP_EXIT \
+ " mrs x12, sp_el0\n" \
+ " ldr w13, [x12, #" __stringify(TSK_RCU_TRAMP_NESTING) "]\n" \
+ " sub w13, w13, #1\n" \
+ " str w13, [x12, #" __stringify(TSK_RCU_TRAMP_NESTING) "]\n"
+
+#else
+
+#define RCU_TASKS_TRAMP_ENTER
+#define RCU_TASKS_TRAMP_EXIT
+
+#endif
+
+#endif /* _SAMPLES_FTRACE_DIRECT_H */
|
| 19/3 | 2026/09/11 14:20 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 27380 output: 21 thoughts: 802 Thoughts: **KMSAN Evaluation of RCU Tasks Preempt Quiescent State Optimization** Okay, let's break this down. The core task is deciding if this patch series warrants a dedicated KMSAN fuzzing session, beyond the KASAN coverage we already have. First, I need to understand what the patch is *doing*. It's focused on optimizing Tasks RCU, specifically allowing involuntary context switches (preemption) to be counted as Tasks-RCU quiescent states on x86_64 and arm64. It seems to hinge on new fields within `struct task_struct`: `rcu_tramp_nesting` and `rcu_tasks_irq_ip`. These are used to track trampoline nesting and interrupted IPs, respectively. The patch touches ftrace, BPF JIT, optprobes, direct calls, and interrupt handling to manage these new fields. Now, the critical question: are there KMSAN-relevant concerns? Let's go through the checklist. 1. **Kernel data going to userspace?** No. This patch doesn't introduce any new kernel-to-user copies, which is a major win. 2. **Conditional logic based on uninitialized variables?** The new `rcu_tramp_nesting` and `rcu_tasks_irq_ip` fields are added to `struct task_struct`, but critically, they *are* initialized in `rcu_copy_process` during task creation (`fork.c`) and are initialized to zero statically for the `init_task`. This is exactly what I was looking for. No immediate uninitialized memory exposure here. 3. **Allocation or initialization of complex data structures?** No issues. The new fields are part of an existing structure and the code is very clear about initializing them. 4. **Bounds checks exposing uninitialized data?** Nothing. No buffer handling changes. 5. **What's the overall risk profile?** The main risks are concurrency issues, incorrect RCU grace period completion, use-after-free scenarios if quiescence is misreported, and potential locking or architecture-specific corruption. However, these are all *precisely* the types of bugs that KASAN and general kernel debugging are designed to catch. KMSAN's focus is on uninitialized memory reads and leaks to userspace. To be extra cautious, I'm going to quickly scan the code to ensure `init_task` is initialized correctly everywhere. Double-checking `task_struct` allocations and initializations is always a good practice. **Conclusion:** Based on this analysis, I'm confident that this patch series does *not* introduce KMSAN-relevant risks. The new fields are initialized correctly, and the core risk profile falls squarely within the scope of KASAN's detection capabilities. No dedicated KMSAN session is needed for this patch. I think we are good to go. |
| 20/3 | 2026/09/11 14:20 | tool | grepper |
0mArgs:{
"Expression": "rcu_tasks_holdout"
}Results: {
"Output": "Documentation/RCU/Design/Data-Structures/Data-Structures.rst=1089=the ``task_struct`` structure:\n--\nDocumentation/RCU/Design/Data-Structures/Data-Structures.rst-1100- 8 unsigned long rcu_tasks_nvcsw;\nDocumentation/RCU/Design/Data-Structures/Data-Structures.rst:1101: 9 bool rcu_tasks_holdout;\nDocumentation/RCU/Design/Data-Structures/Data-Structures.rst:1102: 10 struct list_head rcu_tasks_holdout_list;\nDocumentation/RCU/Design/Data-Structures/Data-Structures.rst-1103- 11 int rcu_tasks_idle_cpu;\n--\nDocumentation/RCU/Design/Data-Structures/Data-Structures.rst=1117=switches that this task had undergone at the beginning of the current\nDocumentation/RCU/Design/Data-Structures/Data-Structures.rst:1118:tasks-RCU grace period, ``-\u003ercu_tasks_holdout`` is set if the current\nDocumentation/RCU/Design/Data-Structures/Data-Structures.rst-1119-tasks-RCU grace period is waiting on this task,\nDocumentation/RCU/Design/Data-Structures/Data-Structures.rst:1120:``-\u003ercu_tasks_holdout_list`` is a list element enqueuing this task on\nDocumentation/RCU/Design/Data-Structures/Data-Structures.rst-1121-the holdout list, and ``-\u003ercu_tasks_idle_cpu`` tracks which CPU this\n--\ninclude/linux/rcupdate.h=244=static __always_inline void rcu_tasks_note_irq_ip(unsigned long ip)\n--\ninclude/linux/rcupdate.h-257-\tdo {\t\t\t\t\t\t\t\t\\\ninclude/linux/rcupdate.h:258:\t\tif (READ_ONCE((t)-\u003ercu_tasks_holdout) \u0026\u0026\t\t\\\ninclude/linux/rcupdate.h-259-\t\t (!(preempt) || rcu_tasks_preempt_is_qs(t)))\t\t\\\ninclude/linux/rcupdate.h:260:\t\t\tWRITE_ONCE((t)-\u003ercu_tasks_holdout, false);\t\\\ninclude/linux/rcupdate.h-261-\t} while (0)\n--\ninclude/linux/sched.h=835=struct task_struct {\n--\ninclude/linux/sched.h-956-\tunsigned long\t\t\trcu_tasks_nvcsw;\ninclude/linux/sched.h:957:\tu8\t\t\t\trcu_tasks_holdout;\ninclude/linux/sched.h-958-\tu8\t\t\t\trcu_tasks_idx;\n--\ninclude/linux/sched.h-961-\tint\t\t\t\trcu_tasks_idle_cpu;\ninclude/linux/sched.h:962:\tstruct list_head\t\trcu_tasks_holdout_list;\ninclude/linux/sched.h-963-\tint\t\t\t\trcu_tasks_exit_cpu;\n--\ninit/init_task.c=105=struct task_struct init_task __aligned(L1_CACHE_BYTES) = {\n--\ninit/init_task.c-202-#ifdef CONFIG_TASKS_RCU\ninit/init_task.c:203:\t.rcu_tasks_holdout = false,\ninit/init_task.c:204:\t.rcu_tasks_holdout_list = LIST_HEAD_INIT(init_task.rcu_tasks_holdout_list),\ninit/init_task.c-205-\t.rcu_tasks_idle_cpu = -1,\n--\nkernel/fork.c=1862=static inline void rcu_copy_process(struct task_struct *p)\n--\nkernel/fork.c-1870-#ifdef CONFIG_TASKS_RCU\nkernel/fork.c:1871:\tp-\u003ercu_tasks_holdout = false;\nkernel/fork.c-1872-\tp-\u003ercu_tramp_nesting = 0;\nkernel/fork.c-1873-\tp-\u003ercu_tasks_irq_ip = 0;\nkernel/fork.c:1874:\tINIT_LIST_HEAD(\u0026p-\u003ercu_tasks_holdout_list);\nkernel/fork.c-1875-\tp-\u003ercu_tasks_idle_cpu = -1;\n--\nkernel/rcu/tasks.h=964=static void rcu_tasks_pregp_step(struct list_head *hop)\n--\nkernel/rcu/tasks.h-974-\t * This synchronize_rcu() also dispenses with the need for a\nkernel/rcu/tasks.h:975:\t * memory barrier on the first store to t-\u003ercu_tasks_holdout,\nkernel/rcu/tasks.h-976-\t * as it forces the store to happen after the beginning of the\n--\nkernel/rcu/tasks.h=1056=static void rcu_tasks_pertask(struct task_struct *t, struct list_head *hop)\n--\nkernel/rcu/tasks.h-1061-\t\tt-\u003ercu_tasks_nvcsw = READ_ONCE(t-\u003envcsw);\nkernel/rcu/tasks.h:1062:\t\tWRITE_ONCE(t-\u003ercu_tasks_holdout, true);\nkernel/rcu/tasks.h:1063:\t\tlist_add(\u0026t-\u003ercu_tasks_holdout_list, hop);\nkernel/rcu/tasks.h-1064-\t}\n--\nkernel/rcu/tasks.h=1071=static void rcu_tasks_postscan(struct list_head *hop)\n--\nkernel/rcu/tasks.h-1107-\t\tlist_for_each_entry_safe(t, t1, \u0026rtpcp-\u003ertp_exit_list, rcu_tasks_exit_list) {\nkernel/rcu/tasks.h:1108:\t\t\tif (list_empty(\u0026t-\u003ercu_tasks_holdout_list))\nkernel/rcu/tasks.h-1109-\t\t\t\trcu_tasks_pertask(t, hop);\n--\nkernel/rcu/tasks.h=1215=static void check_holdout_task(struct task_struct *t,\n--\nkernel/rcu/tasks.h-1219-\nkernel/rcu/tasks.h:1220:\tif (!READ_ONCE(t-\u003ercu_tasks_holdout) ||\nkernel/rcu/tasks.h-1221-\t t-\u003ercu_tasks_nvcsw != READ_ONCE(t-\u003envcsw) ||\n--\nkernel/rcu/tasks.h-1225-\t !is_idle_task(t) \u0026\u0026 READ_ONCE(t-\u003ercu_tasks_idle_cpu) \u003e= 0)) {\nkernel/rcu/tasks.h:1226:\t\tWRITE_ONCE(t-\u003ercu_tasks_holdout, false);\nkernel/rcu/tasks.h:1227:\t\tlist_del_init(\u0026t-\u003ercu_tasks_holdout_list);\nkernel/rcu/tasks.h-1228-\t\tput_task_struct(t);\n--\nkernel/rcu/tasks.h-1242-\t\t \"N.\"[cpu \u003c 0 || !tick_nohz_full_cpu(cpu)],\nkernel/rcu/tasks.h:1243:\t\t t-\u003ercu_tasks_nvcsw, t-\u003envcsw, t-\u003ercu_tasks_holdout,\nkernel/rcu/tasks.h-1244-\t\t data_race(t-\u003ercu_tramp_nesting),\n--\nkernel/rcu/tasks.h=1250=static void check_all_holdout_tasks(struct list_head *hop,\n--\nkernel/rcu/tasks.h-1254-\nkernel/rcu/tasks.h:1255:\tlist_for_each_entry_safe(t, t1, hop, rcu_tasks_holdout_list) {\nkernel/rcu/tasks.h-1256-\t\tcheck_holdout_task(t, needreport, firstreport);\n--\nkernel/rcu/tasks.h=1262=static void rcu_tasks_postgp(struct rcu_tasks *rtp)\n--\nkernel/rcu/tasks.h-1272-\t *\nkernel/rcu/tasks.h:1273:\t * This synchronize_rcu() also confines all -\u003ercu_tasks_holdout\nkernel/rcu/tasks.h-1274-\t * accesses to be within the grace period, avoiding the need for\nkernel/rcu/tasks.h:1275:\t * memory barriers for -\u003ercu_tasks_holdout accesses.\nkernel/rcu/tasks.h-1276-\t *\n"
} |
| 21/3 | 2026/09/11 14:21 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 31041 output: 300 thoughts: 231 Thoughts: **Analyzing the RCU Patch Set** Okay, let's break this down systematically. This patch series is focused on updating the Tasks RCU logic. Specifically, it seems designed to enable involuntary context switches, or preemptions, outside of trampolines. The goal is to make these preemptions function as quiescent states on the x86_64 and arm64 architectures. Key changes I'm seeing include: adding `rcu_tramp_nesting` and `rcu_tasks_irq_ip` fields to `struct task_struct`, which is expected. These are initialized at fork. Also, entry code, assembly trampolines in ftrace, BPF JIT, and optprobes are being updated to account for this nesting. That all seems logical. Critically, there are no changes to any user-space copy/serialization interfaces. I'm also seeing that there are no dynamic allocations that utilize buffers containing uninitialized content, and there's no conditional branching being done based on uninitialized data. That's a huge win in terms of safety. Potential problems? Synchronization errors, deadlocks, and race conditions in RCU grace period detection leading to premature free/use-after-free, or crashes/hangs. These would be, hopefully, caught by KASAN. Given all of this, my immediate assessment is that "NeedsKMSAN" should be `false`. I'm ready to move forward and prepare the call to `set-results` and formulate a concise explanation to justify my decision. |
| 22/3 | 2026/09/11 14:21 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch series implements CONFIG_ARCH_HAS_RCU_TASKS_PREEMPT_QS, allowing involuntary context switches outside trampolines to be treated as Tasks-RCU quiescent states. It introduces tracking fields (rcu_tramp_nesting, rcu_tasks_irq_ip) to struct task_struct, updates ftrace, BPF JIT, kprobe optprobes, and irqentry code across x86 and arm64 architectures.\n\nNone of the changes introduce uninitialized memory risks or info-leaks:\n1. No user-space data copying (copy_to_user, netlink, ioctl, etc.) or info-leak channels are modified or created.\n2. The newly added fields in task_struct are explicitly initialized to zero in fork (rcu_copy_process()) and statically initialized for init_task.\n3. No variable-length buffer handling, uninitialized stack variables, or uninitialized heap allocations are read or exposed.\n\nAny potential bugs in this patch would relate to RCU grace period synchronization, CPU preemption handling, or premature trampoline reclamation leading to use-after-free or memory corruption, which are covered by KASAN and standard kernel debugging facilities, not KMSAN. Therefore, a dedicated KMSAN fuzzing session is not required.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch series implements CONFIG_ARCH_HAS_RCU_TASKS_PREEMPT_QS, allowing involuntary context switches outside trampolines to be treated as Tasks-RCU quiescent states. It introduces tracking fields (rcu_tramp_nesting, rcu_tasks_irq_ip) to struct task_struct, updates ftrace, BPF JIT, kprobe optprobes, and irqentry code across x86 and arm64 architectures.\n\nNone of the changes introduce uninitialized memory risks or info-leaks:\n1. No user-space data copying (copy_to_user, netlink, ioctl, etc.) or info-leak channels are modified or created.\n2. The newly added fields in task_struct are explicitly initialized to zero in fork (rcu_copy_process()) and statically initialized for init_task.\n3. No variable-length buffer handling, uninitialized stack variables, or uninitialized heap allocations are read or exposed.\n\nAny potential bugs in this patch would relate to RCU grace period synchronization, CPU preemption handling, or premature trampoline reclamation leading to use-after-free or memory corruption, which are covered by KASAN and standard kernel debugging facilities, not KMSAN. Therefore, a dedicated KMSAN fuzzing session is not required.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|