
리눅스 커널 UAF 분석: CVE-2026-64560에 대한 경쟁 조건(race) 트리거 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 커널은 5.10 / 5.15 / 6.1 / 6.6 / 6.12 LTS를 기반으로 하며 모두 영향을 받는 범위에 속합니다. 메인라인 수정 커밋이 2026-07-29에 공개되었으므로, SPL(보안 패치 수준)이 2026-08-01 이하인 Android 기기는 대부분 이 수정 사항이 반영되지 않았습니다. 기기에서 adb shell cat /proc/version 및 getprop ro.build.version.security_patch로 커널 버전과 SPL을 확인할 수 있습니다.
POSIX CPU 타이머(timer_create(CLOCK_PROCESS_CPUTIME_ID, ...) / timer_create(CLOCK_THREAD_CPUTIME_ID, ...))는 커널에서 kernel/time/posix-cpu-timers.c에 의해 관리됩니다. 각 k_itimer는 it.cpu.pid를 통해 대상 태스크를 기억하며, 타이머를 조작할 때는 lock_task_sighand(p, &flags)로 해당 태스크의 sighand->siglock을 획득하여 timerqueue를 보호해야 합니다.
2020년 커밋 55e8c8eb2c7b는 타이머에 캐시되던 task 포인터를 pid 참조로 교체했고(2010년 워크어라운드 e0a70217107e가 도입한 문제를 수정하기 위해), 매 작업 전에 pid_task(pid, type) 조회를 수행합니다. 이 변경으로 인해 본 CVE의 경쟁 조건 창(race window)이 남게 되었습니다.
execve()가 non-leader 스레드에 의해 실행되면, de_thread() → switch_leader()가 TGID를 기존 leader에서 새 leader로 이전하고, 기존 leader는 release_task() → __exit_signal() 경로를 거치며, 이 과정에서 old_leader->sighand = NULL로 설정되고 unhash_task(old_leader)가 호출됩니다.
한편, 다른 CPU에서 실행 중이던 sys_timer_delete() → posix_cpu_timer_del():
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 被释放
posix_cpu_timer_del()이 조회한 p는 기존 leader이며, 이때 p->sighand == NULL이므로 함수는 "태스크가 종료 중이고 exit 경로가 연결 해제(unlink)를 처리할 것"이라고 판단하여 아무것도 하지 않고 성공을 반환합니다. 이후 free_posix_timer()가 k_itimer를 해제합니다.
핵심 포인트: exec()는 exit()와 다릅니다. exec() 시 TGID는 변경되지 않으므로 프로세스 레벨(p->signal->cpu_timers)에 걸려 있는 armed 타이머는 상속되어 여전히 큐에 남아 있습니다. 따라서:
run_posix_cpu_timers()(tick에서 timerqueue를 순회)가 해제된 객체의 timerqueue_node에 접근 → UAF 읽기/쓰기;동일한 유형의 문제는 다음에도 존재합니다:
posix_cpu_timer_set(): 일반 타이머는 단지 일시적으로 -ESRCH를 반환할 뿐입니다. 그러나 커널 내부의 do_cpu_nanosleep()은 스택에 할당된 k_itimer를 사용하므로 동일한 UAF가 발생합니다.posix_cpu_timer_rearm(): rearm이 조용히 실패하여 타이머가 더 이상 만료되지 않습니다(기능적 버그).Frederic Weisbecker가 지적했듯이, __exit_signal()에서 tsk->sighand = NULL은 일반(ordinary) 저장이므로 ARM64와 같은 약한 메모리 순서 아키텍처에서는 posix_cpu_timer_del()이 sighand == NULL을 관찰하더라도 posix_cpu_timers*_exit() 이전의 연결 해제 쓰기를 관찰할 수 있음이 보장되지 않아, WARN_ON_ONCE(timer_queued(tmr))가 오탐(false positive)을 낼 수 있습니다.
__exit_signal()에서 smp_store_release(&tsk->sighand, NULL)로 변경;lock_task_sighand()의 !sighand 경로에 smp_acquire__after_ctrl_dep() 추가;timer_lock_sighand() 도입: task 조회 + sighand 잠금. sighand == NULL이면 반환하지 않고 조회를 재시도 — exec 시나리오에서는 새 leader를 찾게 되고, exit 시나리오에서는 조회가 실패해야 비로소 포기;_del / _set / _rearm)가 모두 이 helper를 사용하도록 통일.전체 diff는 patches/920f893f735e.patch를 참조하세요.
poc/ 아래에 경쟁 트리거(race trigger) 가 제공됩니다. 두 스레드가 각각 고강도로 다음을 반복합니다:
timer_create(CLOCK_PROCESS_CPUTIME_ID) → arm(매우 짧은 초기 만료 시간) → busy-wait로 만료 대기 → timer_delete()를 반복;fork()를 반복 → 자식 프로세스에서 non-leader 스레드를 만든 뒤 해당 스레드가 execve() 호출(non-leader exec는 이 취약점의 필요 조건), 부모 프로세스는 즉시 waitpid()로 회수.timer_delete()가 마침 de_thread()/__exit_signal()과의 경쟁에 적중하면, 패치되지 않은 커널에서는 해제된 k_itimer가 여전히 signal->cpu_timers의 rbtree에 남아 있게 되고, 이후 run_posix_cpu_timers()나 다른 timerqueue 작업이 댕글링 노드에 접근하게 됩니다. KASAN 커널과 함께 사용하면 BUG: KASAN: use-after-free in run_posix_cpu_timers / timerqueue_del 등의 보고를 안정적으로 관찰할 수 있으며, KASAN이 없을 때는 보통 산발적인 커널 경고나 panic으로 나타납니다.
성격 설명: 이것은 순수 C로 작성된 race 트리거이며, heap spray, 객체 점유, RIP 제어 등 어떤 익스플로잇 프리미티브도 포함하지 않습니다. 이를 권한 상승 익스플로잇으로 만들려면 추가로 많은 작업(힙 풍수(heap feng shui),
k_itimer가 속한 slab cache의 점유 객체, 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)가 반환한 기존 leader가 곧바로 sighand = NULL이 됨 — 이것이 경쟁 조건의 필요 조건;timer_create/arm/delete + 고빈도 fork/exec 병렬 실행으로 posix_cpu_timer_del()이 경쟁 창(window) 안에 들어갈 확률을 극대화. 타이머 만료 처리(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 모드가 있어 가벼운 스모크 테스트를 수행합니다).
본 저장소는 보안 연구 및 방어 검증만을 위한 것입니다. 소유한 기기 또는 서면 승인을 받은 기기에서만 실행하세요. 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 |