Commit 0be7a931c5e4 for kernel
commit 0be7a931c5e41dcfdaf297f5cd34def2696b0a7e
Author: Mani Tofigh <mt4022@columbia.edu>
Date: Thu Nov 5 15:32:12 2026 -0500
kprobes: Disarm disabled optprobe on forced unoptimization
A disabled optimized probe can be freed with its int3 still in the
text. It waits on the unoptimizing list for the optimizer to remove its
jump. If another probe is armed on one of the bytes that jump replaced,
unoptimize_kprobe() forces the unoptimization and moves the disabled
probe straight to a cooling list without removing its int3:
Task A (tracefs) Task B (optimizer)
disable k1
/* k1 queued to unoptimize */
/* still on an earlier batch */
enable k2 on a byte k1's jump replaced
unoptimize_kprobe(k1, true)
/* int3 back, k1 on a cooling list */
remove k1
/* k1 freed, int3 left in the text */
The next call into the function then oopses:
[ 9.924313] Oops: int3: 0000 [#1] SMP KASAN NOPTI
[ 9.925627] CPU: 0 UID: 0 PID: 68 Comm: kprobeopt Tainted: G W 7.3.0-17087-gd865f2677c44 #1 PREEMPT
[ 9.930933] RIP: 0010:__do_sys_sysinfo+0x8/0xe0
...
[ 9.949348] Call Trace:
[ 9.949890] <TASK>
[ 9.950404] do_syscall_64+0xfb/0x590
[ 9.952027] entry_SYSCALL_64_after_hwframe+0x77/0x7f
...
[ 9.966436] </TASK>
...
[ 9.968009] Kernel panic - not syncing: Fatal exception in interrupt
Disarm a disabled probe on that path before it goes to the freeing or
cooling list, like do_unoptimize_kprobes() does. With the fix, the int3
is gone by the time the probe is freed.
This bug was identified and confirmed with the help of a deterministic
hypervisor currently under development. The C reproducer below however
hits it within about 10s.
Link: https://lore.kernel.org/all/20261105-kprobes-optprobe-state-v2-1-7a2f5290baa0@columbia.edu/
Fixes: 06e0d3b89da1 ("kprobes: Make optprobe optimizer multi-generational and asynchronous")
Signed-off-by: Mani Tofigh <mt4022@columbia.edu>
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
diff --git a/kernel/kprobes.c b/kernel/kprobes.c
index 0b41d69540ae..2df6f9894c3e 100644
--- a/kernel/kprobes.c
+++ b/kernel/kprobes.c
@@ -1002,9 +1002,10 @@ static void unoptimize_kprobe(struct kprobe *p, bool force)
* for cooling down (or freeing if unused).
*/
force_unoptimize_kprobe(op);
+ /* Disarm probes if marked disabled and not gone */
+ if (kprobe_disabled(&op->kp) && !kprobe_gone(&op->kp))
+ arch_disarm_kprobe(&op->kp);
if (kprobe_unused(&op->kp)) {
- if (!kprobe_gone(&op->kp))
- arch_disarm_kprobe(&op->kp);
hlist_del_init_rcu(&op->kp.hlist);
list_move(&op->list, &cur->freeing_list);
} else {