CVE-2026-64560
Gravedad:
Pendiente de análisis
Tipo:
No Disponible / Otro tipo
Fecha de publicación:
29/07/2026
Última modificación:
29/07/2026
Descripción
*** Pendiente de traducción *** In the Linux kernel, the following vulnerability has been resolved:<br />
<br />
posix-cpu-timers: Prevent UAF caused by non-leader exec() race<br />
<br />
Wongi and Jungwoo decoded and reported a non-leader exec() related race<br />
which can result in an UAF:<br />
<br />
sys_timer_delete() exec()<br />
posix_cpu_timer_del()<br />
// Observes old leader<br />
p = pid_task(pid, pid_type); de_thread()<br />
switch_leader();<br />
release_task(old_leader)<br />
__exit_signal(old_leader)<br />
sighand = lock(old_leader, sighand);<br />
posix_cpu_timers*_exit();<br />
sighand = lock_task_sighand(p) unhash_task(old_leader);<br />
sh = lock(p, sighand) old_leader->sighand = NULL;<br />
unlock(sighand);<br />
(p->sighand == NULL)<br />
unlock(sh)<br />
return NULL;<br />
<br />
// Returns without action<br />
if(!sighand)<br />
return 0;<br />
free_posix_timer();<br />
<br />
This is "harmless" unless the deleted timer was armed and enqueued in<br />
p->signal because on exec() a TGID targeted timer is inherited.<br />
<br />
As sys_timer_delete() freed the underlying posix timer object<br />
run_posix_cpu_timers() or any timerqueue related add/delete operations on<br />
other timers will access the freed object&#39;s timerqueue node, which results<br />
in an UAF.<br />
<br />
There is a similar problem vs. posix_cpu_timer_set(). For regular posix<br />
timers it just transiently returns -ESRCH to user space, but for the use<br />
case in do_cpu_nanosleep() it&#39;s the same UAF just that the k_itimer is<br />
allocated on the stack.<br />
<br />
Also posix_cpu_timer_rearm() fails to rearm the timer, which means it stops<br />
to expire.<br />
<br />
While debating solutions Frederic pointed out another problem:<br />
<br />
posix_cpu_timer_del(tmr)<br />
__exit_signal(p)<br />
posix_cpu_timers*_exit(p);<br />
unhash_task(p);<br />
p->sighand = NULL;<br />
sh = lock_task_sighand(p)<br />
sighand = p->sighand;<br />
if (!sighand)<br />
return NULL;<br />
lock(sighand);<br />
<br />
if (!sh)<br />
WARN_ON_ONCE(timer_queued(tmr));<br />
<br />
On weakly ordered architectures it is not guaranteed that<br />
posix_cpu_timer_del() will observe the stores in posix_cpu_timers*_exit()<br />
when p->sighand is observed as NULL, which means the WARN() can be a false<br />
positive.<br />
<br />
Solve these issues by:<br />
<br />
1) Changing the store in __exit_signal() to smp_store_release().<br />
<br />
2) Adding a smp_acquire__after_ctrl_dep() into the !sighand path<br />
of lock_task_sighand().<br />
<br />
3) Creating a helper function for looking up the task and locking sighand<br />
which does not return when sighand == NULL. Instead it retries the<br />
task lookup and only if that fails it gives up.<br />
<br />
4) Using that helper in the three affected functions.<br />
<br />
#1/#2 ensures that the reader side which observes sighand == NULL also<br />
observes all preceeding stores, i.e. the stores in posix_cpu_timers*_exit()<br />
and the ones in unhash_task().<br />
<br />
#3 ensures that the above described non-leader exec() situation is handled<br />
gracefully. When the task lookup returns the old leader, but sighand ==<br />
NULL then it retries. In the non-leader exec() case the subsequent task<br />
lookup will observe the new leader due to #1/#2. In normal exit() scenarios<br />
the subsequent lookup fails.<br />
<br />
When the task lookup fails, the function also checks whether the timer is<br />
still enqueued and issues a warning if that&#39;s the case. Unfortunately there<br />
is nothing which can be done about it, but as the task is already not<br />
longer visible the timer should not be accessed anymore. This check also<br />
requires memory ordering, which is not provided when the first lookup<br />
fails. To achieve that the check is preceeded by a smp_rmb() which pairs<br />
with the smp_wmb() in write_seqlock() in __exit_signal(). That ensures that<br />
the stores in posix_cpu_timers*_exit() are visible.<br />
<br />
The history of the non-leader exec() issue goes back to the early days of<br />
posix CPU timers, which stored a pointer to the group leader task in the<br />
timer. That obviously fails when a non-leader exec() switches the leader.<br />
commit e0a70217107e ("posix-cpu-timers: workaround to suppress the problems<br />
with mt exec") added a temporary workaround for that in 2010 which surv<br />
---truncated---



