
Linux kernel UAF analysis for CVE-2026-64560 with race-triggering PoC, patch review, affected LTS/Android version matrix, and self-check for patched devices.
Reproducer / PoC (trigger verification) : Linux & Android (NDK) This repository is intended solely for verifying patch status and for research/learning on your own test devices. It does not contain any privilege escalation/exploitation primitive.
| Field | Details |
|---|---|
| CVE ID | CVE-2026-64560 |
| Title | posix-cpu-timers: Prevent UAF caused by non-leader exec() race |
| Type | Use-After-Free (CWE-416), race condition |
| CNA | kernel.org (Linux CNA) |
| CVSS v3.1 | 7.8 High — CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| CVSS v4.0 (SUSE) | 8.5 High — CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N |
| EPSS | ~0.12% (2nd percentile, as of 2026-08) |
| CISA KEV | Not listed |
| Public disclosure date | 2026-07-29 |
| Fix commit (mainline) | 920f893f735e92ba3a1cd9256899a186b161928d |
| Introduced-by commit (Fixes:) | 55e8c8eb2c7b (v5.7, 2020) — "posix-cpu-timers: Store a reference to a pid not a task" |
| Fixed by | Thomas Gleixner <[email protected]> |
| Reported by | Wongi Lee <[email protected]>, Jungwoo Lee <[email protected]> |
| Affected files | kernel/exit.c, kernel/signal.c, kernel/time/posix-cpu-timers.c |
The vulnerability was introduced in v5.7 (2020-05), and the fix has been backported to the following stable branches:
| Branch | Affected | Fixed version (≥) | Stable fix commit |
|---|---|---|---|
| 5.10 LTS | 5.7 ~ 5.10.261 | 5.10.262 | 67aa823e3e8c |
| 5.15 LTS | ~ 5.15.212 | 5.15.213 | d8bcb28abad8 |
| 6.1 LTS | ~ 6.1.179 | 6.1.180 | cc35ddbc4973 |
| 6.6 LTS | ~ 6.6.146 | 6.6.147 | 12a891c773ae |
| 6.12 LTS | ~ 6.12.99 | 6.12.100 | e74443f5db00 |
| 6.18 | ~ 6.18.40 | 6.18.41 | 6a7ecc25abe6 |
| 7.1 | ~ 7.1.4 | 7.1.5 | ad1cafa1bdaa |
| mainline | < 7.2-rc3 | 7.2-rc3 | 920f893f735e |
Android GKI kernels are based on 5.10 / 5.15 / 6.1 / 6.6 / 6.12 LTS, all of which are affected. Since the mainline fix commit was published on 2026-07-29, Android devices with an SPL (Security Patch Level) of 2026-08-01 or earlier have almost certainly not incorporated this fix. On the device, confirm the kernel version and SPL with adb shell cat /proc/version and getprop ro.build.version.security_patch.
POSIX CPU timers (timer_create(CLOCK_PROCESS_CPUTIME_ID, ...) / timer_create(CLOCK_THREAD_CPUTIME_ID, ...)) are managed in the kernel by kernel/time/posix-cpu-timers.c. Each k_itimer remembers its target task via it.cpu.pid; timer operations require lock_task_sighand(p, &flags) to acquire that task's sighand->siglock in order to protect the timerqueue.
The 2020 commit 55e8c8eb2c7b replaced the task pointer cached in timers with a pid reference (to fix a problem introduced by the 2010 workaround e0a70217107e), performing a pid_task(pid, type) lookup before each operation. This change left the race window behind this CVE.
When execve() is issued by a non-leader thread, de_thread() → switch_leader() moves the TGID from the old leader to the new leader; the old leader then goes through release_task() → __exit_signal(), which sets old_leader->sighand = NULL and calls unhash_task(old_leader).
At the same time, sys_timer_delete() → posix_cpu_timer_del() executes on another CPU:
sys_timer_delete() exec()
posix_cpu_timer_del()
// 观察到旧 leader
p = pid_task(pid, pid_type); de_thread()
switch_leader();
release_task(old_leader)
__exit_signal(old_leader)
sighand = lock(old_leader, sighand);
posix_cpu_timers*_exit();
sighand = lock_task_sighand(p) unhash_task(old_leader);
sh = lock(p, sighand) old_leader->sighand = NULL;
unlock(sighand);
(p->sighand == NULL)
unlock(sh)
return NULL;
// 直接返回,没有摘链!
if (!sighand)
return 0;
free_posix_timer(); // ← k_itimer 被释放
The p found by posix_cpu_timer_del() is the old leader. At this point p->sighand == NULL, so the function assumes "the task is exiting, and the exit path will take care of unlinking", and therefore does nothing and returns success. Afterwards, free_posix_timer() frees the k_itimer.
Key point: exec() differs from exit() — during exec(), the TGID does not change, so armed timers attached at the process level (p->signal->cpu_timers) are inherited and remain queued. As a result:
run_posix_cpu_timers() (walks the timerqueue on each tick) accesses the freed object's timerqueue_node → UAF read/write;Similar issues also exist in:
posix_cpu_timer_set(): for ordinary timers it only returns -ESRCH temporarily; however, the kernel-internal do_cpu_nanosleep() uses a stack-allocated k_itimer, which is the same UAF.posix_cpu_timer_rearm(): silent rearm failure; the timer never expires again (functional bug).Frederic Weisbecker pointed out that tsk->sighand = NULL in __exit_signal() is a plain store. On weakly-ordered architectures such as ARM64, when posix_cpu_timer_del() observes sighand == NULL, it is not guaranteed to observe the unlink writes that happened before posix_cpu_timers*_exit(), which can cause WARN_ON_ONCE(timer_queued(tmr)) to spuriously fire.
__exit_signal() to use smp_store_release(&tsk->sighand, NULL);smp_acquire__after_ctrl_dep() to the !sighand path of lock_task_sighand();timer_lock_sighand(): it looks up the task and locks the sighand. If sighand == NULL, it does not return but retries the lookup — in the exec scenario it finds the new leader, and in the exit scenario it gives up only when the lookup fails;_del / _set / _rearm) to use this helper.See patches/920f893f735e.patch for the full diff.
Under poc/ there is a race trigger: two threads each loop at high intensity
timer_create(CLOCK_PROCESS_CPUTIME_ID) → arm (very short initial expiration time) → busy-wait for expiration → timer_delete();fork() → in the child process, create a non-leader thread that calls execve() (non-leader exec is a necessary condition for this vulnerability); the parent immediately reaps it with waitpid().