
Анализ UAF в ядре Linux для CVE-2026-64560 с PoC, срабатывающим по гонке, разбором патча, матрицей затронутых версий LTS/Android и самопроверкой пропатченных устройств.
Reproducer / PoC (проверка срабатывания): Linux и Android (NDK) Данный репозиторий предназначен исключительно для проверки статуса исправления на собственном тестовом оборудовании и исследовательских целей, не содержит никаких примитивов повышения привилегий/эксплуатации.
| Поле | Значение |
|---|---|
| CVE ID | CVE-2026-64560 |
| Название | posix-cpu-timers: Prevent UAF caused by non-leader exec() race |
| Тип | Use-After-Free (CWE-416), состояние гонки |
| 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, по состоянию на 2026-08) |
| CISA KEV | не включена |
| Дата публикации | 2026-07-29 |
| Исправляющий коммит (mainline) | 920f893f735e92ba3a1cd9256899a186b161928d |
| Коммит, внёсший проблему (Fixes:) | 55e8c8eb2c7b (v5.7, 2020) — "posix-cpu-timers: Store a reference to a pid not a task" |
| Автор исправления | Thomas Gleixner <[email protected]> |
| Автор отчёта | Wongi Lee <[email protected]>, Jungwoo Lee <[email protected]> |
| Затрагиваемые файлы | kernel/exit.c, kernel/signal.c, kernel/time/posix-cpu-timers.c |
Уязвимость внесена в v5.7 (2020-05), исправление перенесено во все stable-ветки:
Ядра Android GKI основаны на LTS-ветках 5.10 / 5.15 / 6.1 / 6.6 / 6.12, все они в зоне поражения. Поскольку исправление в mainline было опубликовано 2026-07-29, устройства Android с SPL (Security Patch Level) 2026-08-01 и ранее в основном не содержат этого исправления. Проверить версию ядра и SPL на устройстве можно командами adb shell cat /proc/version и getprop ro.build.version.security_patch.
POSIX CPU-таймеры (timer_create(CLOCK_PROCESS_CPUTIME_ID, ...) / timer_create(CLOCK_THREAD_CPUTIME_ID, ...)) обслуживаются в ядре файлом kernel/time/posix-cpu-timers.c. Каждый k_itimer запоминает целевой task через it.cpu.pid; при операциях с таймером требуется получить sighand->siglock этого task через lock_task_sighand(p, &flags) для защиты timerqueue.
Коммит 2020 года 55e8c8eb2c7b заменил указатель на task, кэшируемый в таймере, на ссылку на pid (для исправления проблемы, внесённой workaround 2010 года e0a70217107e), добавив перед каждой операцией поиск через pid_task(pid, type). Это изменение и оставило окно гонки, ставшее причиной данного CVE.
Когда execve() выполняется потоком, не являющимся лидером, de_thread() → switch_leader() переносит TGID со старого лидера на нового, а старый лидер проходит release_task() → __exit_signal(), где old_leader->sighand = NULL и выполняется unhash_task(old_leader).
В то же самое время sys_timer_delete() → posix_cpu_timer_del() на другом CPU:
sys_timer_delete() exec()
posix_cpu_timer_del()
// наблюдает старого лидера
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 освобождается
Найденный posix_cpu_timer_del() p — это старый лидер, у которого p->sighand == NULL; функция считает, что "task завершается, путь exit сам снимет таймер с цепочки", поэтому ничего не делает и возвращает успех. Затем free_posix_timer() освобождает k_itimer.
Ключевой момент: exec() отличается от exit() — при exec() TGID не меняется, и armed-таймеры, висящие на уровне процесса (p->signal->cpu_timers), наследуются и остаются в очереди. Тогда:
run_posix_cpu_timers() (обходит timerqueue в тике) обращается к timerqueue_node уже освобождённого объекта → UAF чтение/запись;Аналогичная проблема существует и в:
posix_cpu_timer_set(): обычный таймер просто временно возвращает -ESRCH; однако внутренний do_cpu_nanosleep() использует k_itimer, выделенный на стеке, — тот же UAF.posix_cpu_timer_rearm(): тихий сбой rearm, таймер больше не срабатывает (функциональный баг).Frederic Weisbecker указал: запись tsk->sighand = NULL в __exit_signal() — обычное сохранение, и на слабоупорядоченных архитектурах вроде ARM64, когда posix_cpu_timer_del() наблюдает sighand == NULL, не гарантируется, что он также увидит запись снятия с цепочки, выполненную до posix_cpu_timers*_exit(), из-за чего WARN_ON_ONCE(timer_queued(tmr)) может ложно срабатывать.
__exit_signal() заменено на smp_store_release(&tsk->sighand, NULL);!sighand в lock_task_sighand() добавлен smp_acquire__after_ctrl_dep();timer_lock_sighand(): поиск task + захват sighand; если sighand == NULL — не возвращаться, а повторить поиск — в сценарии exec будет найден новый лидер, в сценарии exit поиск завершается неудачей;_del / _set / _rearm) переведены на этот helper.Полный diff — в patches/920f893f735e.patch.
В poc/ находится триггер гонки: два потока в интенсивном цикле выполняют
timer_create(CLOCK_PROCESS_CPUTIME_ID) → arm (очень короткое начальное время срабатывания) → busy-wait до срабатывания → timer_delete();fork() → в дочернем процессе создаётся не-лидер поток, который вызывает execve() (exec не-лидером — обязательное условие этой уязвимости), родитель немедленно делает waitpid() для утилизации.Когда timer_delete() точно попадает в гонку с de_thread()/__exit_signal(), на непропатченном ядре освобождённый k_itimer остаётся висеть на rbtree в signal->cpu_timers, после чего run_posix_cpu_timers() или другие операции с timerqueue обращаются к висячему узлу. В сочетании с KASAN-ядром стабильно наблюдаются отчёты вида BUG: KASAN: use-after-free in run_posix_cpu_timers / timerqueue_del; без KASAN обычно проявляется как случайное предупреждение ядра или panic.
Примечание о сути: это чистый C-триггер гонки, не содержащий ни heap spray, ни подмены объектов, ни управления RIP или иных примитивов эксплуатации. Превращение его в эксплойт для повышения привилегий требует большой дополнительной работы (heap feng shui, объекты-заполнители в slab cache, где живёт
k_itimer, обход KASLR/CFI и т.д.) и сильно зависит от конкретной сборки ядра. Этот репозиторий намеренно не содержит такой части.
├── README.md ← этот файл
├── patches/
│ └── 920f893f735e.patch ← полный текст исправления из mainline
└── poc/
├── cve_2026_64560_poc.c ← исходный код триггера (общий для Linux/Android)
├── Makefile ← кросс-компиляция Linux / NDK
└── Android.mk ← NDK ndk-build (опционально)
cd poc
make # создаст cve_2026_64560_poc
sudo ./cve_2026_64560_poc -d 60
# наблюдать dmesg: sudo dmesg -wH | grep -iE 'kasan|use-after|BUG|WARNING'
cd poc
export ANDROID_NDK_HOME=/path/to/ndk
make android # создаст cve_2026_64560_poc_arm64 (static, pie)
adb push cve_2026_64560_poc_arm64 /data/local/tmp/cvepoc
adb shell chmod 755 /data/local/tmp/cvepoc
adb shell /data/local/tmp/cvepoc -d 120
# наблюдать журнал ядра:
adb shell su 0 dmesg -w | grep -iE 'kasan|use-after|BUG|WARNING|timer'
# без root после срабатывания паники можно посмотреть: adb shell cat /sys/fs/pstore/console-ramoops*
Требования к устройству:
CLOCK_PROCESS_CPUTIME_ID нацелен на TGID → висит в signal->cpu_timers, наследуется после exec — это предпосылка UAF (CLOCK_THREAD_CPUTIME_ID не подходит);switch_leader() приводит к тому, что у возвращённого через pid_task(TGID) старого лидера сразу же sighand = NULL — это необходимое условие гонки;timer_create/arm/delete в сочетании с частыми fork/exec параллельно максимизируют вероятность попадания posix_cpu_timer_del() в окно; обработка срабатывания таймера (run_posix_cpu_timers) сама задевает висячий узел, отдельного триггера не нужно.# Android:
adb shell cat /proc/version # версия ядра >= версии исправления из таблицы выше?
adb shell getprop ro.build.version.security_patch # SPL > 2026-08?
# Linux:
uname -r
# или напрямую проверить, есть ли в исходниках timer_lock_sighand:
grep -r timer_lock_sighand /usr/src/linux/kernel/time/posix-cpu-timers.c
Если PoC несколько минут работает без каких-либо KASAN/panic и ядро ≥ версии исправления — можно считать, что устройство исправлено (сам PoC также имеет режим --check для лёгкой smoke-проверки).
Данный репозиторий предназначен только для исследований в области безопасности и проверки защитных мер. Запускайте только на устройствах, которыми вы владеете или на которые имеете письменное разрешение. PoC может вызывать нестабильность ядра и даже panic — не запускайте его на рабочих устройствах. Автор не несёт ответственности за последствия любого неправомерного использования.
| Ветка | Затронута | Версия исправления (≥) | Stable-коммит исправления |
|---|
| 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 |