CVE-2026-98530: kprobes: Fix permanent hang when flushing the kprobe optimizer
In the Linux kernel, the following vulnerability has been resolved:
kprobes: Fix permanent hang when flushing the kprobe optimizer
Writing 0 to /proc/sys/debug/kprobes-optimization while a kprobe is jump-optimized never returns. The writer sleeps in D state forever with kprobesysctlmutex held, so any later read or write of that sysctl hangs as well. For example, with vfsread+9 as an optimizable address in this build:
# cd /sys/kernel/tracing # echo 'p:myprobe vfsread+9' >> kprobeevents # echo 1 > events/kprobes/myprobe/enable # # wait until /sys/kernel/debug/kprobes/list shows [OPTIMIZED] # echo 0 > /proc/sys/debug/kprobes-optimization
INFO: task sh:246 blocked for more than 10 seconds. Call Trace: <TASK> schedule+0x1176/0x4f70 schedule+0xdc/0x2c0 scheduletimeout+0x17b/0x260 waitforcompletion+0x173/0x3c0 waitforkprobeoptimizerlocked+0xbc/0x130 prockprobesoptimizationhandler+0x156/0x1b0 procsyscallhandler+0x324/0x490 vfswrite+0x52d/0xfe0 ksyswrite+0xff/0x200 dosyscall64+0x106/0x630 entrySYSCALL64afterhwframe+0x77/0x7f </TASK> ... INFO: task cat:265 is blocked on a mutex likely owned by task sh:246.
waitforkprobeoptimizerlocked() reinitializes optimizercompletion, asks the optimizer thread to flush and sleeps in waitforcompletion(). The thread drains the (un)optimizing lists, but calls complete() only if completiondone() is true, i.e. if the completion is already done, which never happens while someone waits. disarmallkprobes() and kprobetraceselftestsinit() wait the same way.
Calling complete() unconditionally would not be enough: the waiter drops kprobemutex while it sleeps, and nothing else serializes the sysctl handler against the debugfs "enabled" file. A second flusher that still finds the lists non-empty, e.g. because a disabled probe is queued for unoptimizing, reinitializes the completion under the first:
sysctl write debugfs "enabled" write unoptimizeallkprobes() waitforkprobeoptimizerlocked() initcompletion(c) mutexunlock(&kprobemutex) waitforcompletion(c) disarmallkprobes() waitforkprobeoptimizerlocked() initcompletion(c) // c->wait is reset, the first // waiter is off the queue mutexunlock(&kprobemutex) waitforcompletion(c) kprobeoptimizer() complete(c) // wakes the debugfs writer only
where c is &optimizercompletion. Lining up the two writes during an optimizer pass loses the sysctl writer this way.
Replace the completion with a counter of optimizer passes, bumped at the end of each pass and signalled with wakeupvarlocked(), both under kprobemutex. A flusher samples the count and waits with waitvareventmutex(), which drops kprobemutex only while sleeping, so a new count means a whole pass ran in the meantime. Nothing is reinitialized, so several flushers can sleep in the wait at once.