
CVE-2025-38352 트리거를 지원하는 PoC 및 GDB 스크립트
CVE-2025-38352에 대한 재현 가능한 Proof-of-Concept(PoC) 및 자동화된 GDB 오케스트레이션 스크립트로, 이는 Linux 커널의 POSIX CPU 타이머 서브시스템(kernel/time/posix-cpu-timers.c)에서 발생하는 Time-of-Check to Time-of-Use(TOCTOU) 경쟁 조건으로 인해 Use-After-Free(UAF)가 발생하는 취약점입니다.
면책 조항: 이 프로젝트는 교육, 방어 및 보안 연구 목적으로만 사용됩니다. 모든 테스트와 재현은 격리된 ARM64 QEMU 가상 머신에서 수행되었습니다.
프로세스 A가 자식 프로세스 B를 생성합니다.
프로세스 B가 CPU 타이머를 생성하고 종료를 시작하여 좀비 상태에 진입합니다. B가 종료되는 순간, 해당 CPU 코어에 타이머 인터럽트가 도착합니다. 커널은 타이머를 처리하기 위해 인터럽트 컨텍스트에 진입하지만, 교착 상태를 피하기 위해 일시적으로 락을 해제합니다(unlock_task_sighand).
동시에 다른 코어에서 프로세스 A가 종료 중인 B를 수확(reap)하여 시그널 핸들러를 정리(sighand = NULL)하고 PID를 언해시(unhash)합니다.
B의 다른 스레드가 타이머 삭제(posix_cpu_timer_del)를 호출합니다. 이 스레드는 PID를 통해 태스크를 조회하고 시그널 핸들러를 확인하려 하지만, 이미 정리되었거나 NULL임을 발견합니다. 태스크가 완전히 죽었고 타이머가 발화할 가능성이 없다고 오해하여 안전하다고 가정하고 메모리에서 타이머를 해제합니다.
한편, 첫 번째 코어의 타이머 인터럽트 핸들러가 재개되어 만료된 타이머를 발화합니다. 4단계에서 타이머가 방금 해제되었기 때문에, 이에 접근하면 Use-After-Free가 발생하고 커널이 충돌합니다.
poc_gdb_script.py)QEMU의 GDB 스텁은 set non-stop on을 지원하지 않기 때문에(하나의 vCPU를 중단하면 모든 vCPU가 중단됨), 일반적인 브레이크포인트를 통한 표준 멀티스레드 동기화는 달성할 수 없습니다.
이 저장소는 인메모리 명령어 패칭(In-Memory Instruction Patching) 을 통해 문제를 해결합니다:
1단계 (CPU 배리어):
exit_notify (CPU 2)kernel_wait4 (CPU 0)__arm64_sys_timer_delete (CPU 1)handle_posix_cpu_timers (CPU 2)$pc를 ARM64 자기 분기(self-branch) opcode 0x14000000(b . 무한 루프)으로 패치하고 실행을 재개합니다.2단계 (순차적 오케스트레이션):
set scheduler-locking on) 각 vCPU를 순차적으로 전진시킵니다:
handle_posix_cpu_timers를 unlock_task_sighand() 이후로 전진시킵니다.kernel_wait4를 이후로 전진시킵니다.커밋 1bf1aa362e6b9573a310fcd14f35bc875b42ba83에서 Android Common Kernel 소스 트리를 다운로드하거나 클론합니다:
# Option A: Download tarball directly
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
# Option B: Clone repository
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 타이머는 타이머 IRQ 대신task_work컨텍스트에서 실행되어 이 경쟁 조건을 우회합니다. 따라서 취약점을 재현하려면 이 옵션을 비활성화해야 합니다.
CONFIG_POSIX_CPU_TIMERS_TASK_WORK는 업스트림 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
# Expected output: # 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
4개의 vCPU(-smp 4)로 QEMU 가상 머신을 시작하고 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:
poc.c의TIMER_FIRE_MS를 늘리고(예:#define TIMER_FIRE_MS 10에서20으로),poc.c를 재컴파일한 후 다시 실행합니다. 이렇게 하면 타이머가 만료되기 전에 스레드가exit_notify()에 도달하여EXIT_ZOMBIE로 전환할 추가 시간이 주어집니다.
https://github.com/user-attachments/assets/11481b5c-25f8-46c3-be3a-7b79a06a4948
tsk->sighand = NULLtimer_delete를 release_posix_timer() 호출(sigq 해제)까지 전진시킵니다.