
PoC и GDB-скрипт для срабатывания CVE-2025-38352
Воспроизводимый Proof-of-Concept (PoC) и скрипт автоматизированной оркестрации GDB для CVE-2025-38352 — состояния гонки Time-of-Check to Time-of-Use (TOCTOU) в подсистеме POSIX CPU-таймеров ядра Linux (kernel/time/posix-cpu-timers.c), приводящего к Use-After-Free (UAF).
Отказ от ответственности: Этот проект предназначен исключительно для образовательных, защитных и исследовательских целей в области безопасности. Все тестирование и воспроизведение проводились в изолированной виртуальной машине ARM64 QEMU.
Процесс A создаёт дочерний процесс B.
Процесс B создаёт CPU-таймер и начинает завершение, переходя в состояние зомби. В момент завершения B на его ядре CPU приходит прерывание таймера. Ядро входит в контекст прерывания для обработки таймера, но временно освобождает свою блокировку (unlock_task_sighand), чтобы избежать взаимоблокировок.
В этот же момент на другом ядре процесс A пожинает завершающийся B, очищая его обработчик сигналов (sighand = NULL) и удаляя его PID из хеш-таблицы.
Другой поток в B вызывает удаление таймера (posix_cpu_timer_del). Он пытается найти задачу по её PID и проверить её обработчик сигналов, но обнаруживает, что они уже очищены / равны NULL. Ошибочно полагая, что задача полностью мертва и никакие таймеры не могут срабатывать, он считает, что всё безопасно, и освобождает таймер из памяти.
Тем временем обработчик прерывания таймера на первом ядре возобновляет работу, чтобы запустить истёкший таймер. Поскольку таймер только что был освобождён на шаге 4, обращение к нему вызывает Use-After-Free и приводит к падению ядра.
poc_gdb_script.py)Поскольку GDB-стаб QEMU не поддерживает set non-stop on (остановка одного vCPU останавливает все vCPU), стандартная многопоточная синхронизация не может быть достигнута с помощью обычных точек останова.
Этот репозиторий решает проблему с помощью патчинга инструкций в памяти:
Этап 1 (барьер CPU):
exit_notify (CPU 2)kernel_wait4 (CPU 0)__arm64_sys_timer_delete (CPU 1)handle_posix_cpu_timers (CPU 2)$pc опкодом самоперехода ARM64 0x14000000 (b . — бесконечный цикл) и возобновляет выполнение.Этап 2 (последовательная оркестрация):
set scheduler-locking on) и продвигает каждый vCPU вперёд последовательно:
handle_posix_cpu_timers за unlock_task_sighand().Скачайте или клонируйте дерево исходников Android Common Kernel на коммите 1bf1aa362e6b9573a310fcd14f35bc875b42ba83:
# Вариант A: Прямая загрузка tarball
curl -LO https://android.googlesource.com/kernel/common/+archive/1bf1aa362e6b9573a310fcd14f35bc875b42ba83.tar.gz
mkdir -p kernel-cve && tar -xzf 1bf1aa362e6b9573a310fcd14f35bc875b42ba83.tar.gz -C kernel-cve
cd kernel-cve
# Вариант B: Клонирование репозитория
git clone https://android.googlesource.com/kernel/common
cd common
git checkout 1bf1aa362e6b9573a310fcd14f35bc875b42ba83
run_posix_cpu_timers()Откройте kernel/time/posix-cpu-timers.c, найдите функцию run_posix_cpu_timers() (примерно строки 1435–1448) и закомментируйте строки патча:
void run_posix_cpu_timers(void)
{
struct task_struct *tsk = current;
lockdep_assert_irqs_disabled();
/*
* Ensure that release_task(tsk) can't happen while
* handle_posix_cpu_timers() is running. Otherwise, a concurrent
* posix_cpu_timer_del() may fail to lock_task_sighand(tsk) and
* miss timer->it.cpu.firing != 0.
*/
// if (tsk->exit_state)
// return;
CONFIG_POSIX_CPU_TIMERS_TASK_WORKПримечание: Когда
CONFIG_POSIX_CPU_TIMERS_TASK_WORK=y, CPU-таймеры выполняются из контекстаtask_work, а не из IRQ таймера, что обходит это состояние гонки. Поэтому для воспроизведения уязвимости его необходимо отключить.
Поскольку CONFIG_POSIX_CPU_TIMERS_TASK_WORK не имеет строки приглашения в upstream Kconfig, он по умолчанию равен y и не может быть переключён напрямую в menuconfig. Вы можете сделать его доступным следующим образом:
kernel/time/Kconfig (примерно строка 56) добавьте строку приглашения и измените значение по умолчанию:
config POSIX_CPU_TIMERS_TASK_WORK
bool "POSIX CPU timers task work"
default n
ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- make defconfig
scripts/config --disable POSIX_CPU_TIMERS_TASK_WORK
ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- make olddefconfig
.config:
grep POSIX_CPU_TIMERS_TASK_WORK .config
# Ожидаемый вывод: # CONFIG_POSIX_CPU_TIMERS_TASK_WORK is not set
Скомпилируйте образ ядра ARM64:
time ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- make -j$(nproc) Image
Кросс-компилируйте poc.c:
aarch64-linux-gnu-gcc -ggdb3 ./poc.c -o poc
Запустите вашу виртуальную машину QEMU с 4 vCPU (-smp 4) и включите GDB-стаб (флаг -s, порт 1234):
qemu-system-aarch64 \
-M virt \
-cpu cortex-a57 \
-smp 4 \
-m 2G \
-kernel arch/arm64/boot/Image \
-append "console=ttyAMA0 root=/dev/vda oops=panic panic_on_warn=1" \
... \
-s
Из терминала вашего хоста подключите GDB к QEMU и загрузите скрипт оркестрации:
gdb-multiarch vmlinux
pwndbg> target remote :1234
pwndbg> source poc_gdb_script.py
pwndbg> continue
Внутри гостевой оболочки QEMU запустите скомпилированный бинарник:
./poc
[!TIP] Тайминг и устранение неполадок:
Если таймер срабатывает преждевременно, до того как целевой поток достиг состояния зомби, GDB выведет в лог:[!] ERROR: handle_posix_cpu_timers fired before exit_state == EXIT_ZOMBIE
- Вариант 1: Просто перезапустите
./pocи несколько раз загрузите скрипт в GDB.- Вариант 2: Увеличьте
TIMER_FIRE_MSвpoc.c(например, с#define TIMER_FIRE_MS 10до20), перекомпилируйтеpoc.cи запустите снова. Это даст потоку дополнительное время, чтобы достичьexit_notify()и перейти вEXIT_ZOMBIEдо истечения таймера.
https://github.com/user-attachments/assets/11481b5c-25f8-46c3-be3a-7b79a06a4948
kernel_wait4tsk->sighand = NULLtimer_delete до вызова release_posix_timer() (освобождающего sigq).