
Dirty Pagetable Exploit for CVE-2025-8045
CVE-2025-8045, Arm Mali GPU 커널 드라이버(kbase)의 use-after-free 취약점에 대한
로컬 권한 상승 익스플로잇입니다. 비권한 로컬 프로세스가 CSF KCPU 큐 덤프
경로를 레이스하여 cpu_queue.buffer를 해제하고 다시 확보한 다음, 댕글링 할당을
자체가 소유한 페이지 테이블("dirty pagetable")로 바꾸고, 이 프리미티브를
사용하여 커널 물리 메모리 임의 읽기/쓰기를 얻습니다. 최종적으로 SELinux가
permissive로 설정된 루트 셸을 획득합니다.
Pixel 7 Pro에서 검증됨 (빌드
BP3A.250905.014). 하드코딩된 오프셋은exploit.c에서 이 타겟에 맞게 보정되어 있으며, 다른 기기/빌드는 오프셋을 다시 도출해야 합니다 (튜닝 및 포팅 참조).
CVE-2025-8045는 Arm Valhall 및 5세대 GPU 아키텍처 커널
드라이버(r53p0–r54p1)의 use-after-free 취약점입니다. 로컬의 비권한
프로세스가 GPU 처리 연산을 조작하여 드라이버가 이미 해제된 메모리를 계속
사용하게 만들 수 있으며, 이를 통해 커널 메모리 노출, 변조, 코드 실행이
가능해집니다.
이 익스플로잇은 /dev/mali0가 노출하는 CSF KCPU 큐 인터페이스를 통해 이
버그에 도달합니다:
CQS_WAIT**이 설정되고 이어서
FENCE_SIGNAL이 따라옵니다. 대기는 큐를 정지시켜 펜스 타이머가 나중에,
예측 가능한 타이머 휠 버킷 경계에서 실행되게 합니다.KBASE_IOCTL_CS_CPU_QUEUE_DUMP는 kbase_csf_cpu_queue_dump_buffer()를 구동합니다.
큐의 상태 전이(ISSUED → COMPLETE/PENDING)를 덤프 경로와 레이스하면
cpu_queue.buffer가 여전히 참조되는 동안 해제됩니다 — 이것이 UAF입니다.이 익스플로잇은 타이밍 익스플로잇입니다. 먼저 데드라인을 **보정(calibrate)**한 다음, 매 라운드마다 이를 예측하고, 해제가 발생하는 순간 주변의 레이스 윈도우를 넓히기 위해 무해한 덤프 호출의 "웨이브"를 발사합니다.
| 단계 | 내용 |
|---|---|
| 보정(Calibrate) | 1라운드에서 ISSUED→COMPLETE 경계를 측정하여 타이머 휠 버킷 위상을 파악합니다. |
| 예측 + 스윕(Predict + sweep) | 이후 라운드에서는 데드라인을 예측하고 읽기 오프셋을 ±ms 범위로 스윕하여 해제 시점에 정확히 맞춥니다. |
| 웨이브(Wave) | 64개 스레드가 csf.lock에 CS_CPU_QUEUE_DUMP 호출을 쏟아 부어 데드라인을 감싸고 윈도우를 늘립니다. |
| 리클레임(Reclaim) | UNMOVABLE 프리 리스트를 미리 비운 다음, 스탬프가 찍힌 GPU 페이지를 스프레이하여 해제된 블록이 우리의 통제 아래로 돌아오게 합니다. |
| Dirty pagetable | 다시 해제한 후 즉시 동일한 페이지에 CPU 페이지 테이블을 스프레이합니다. 이제 GPU 할당이 활성 최하위 레벨 테이블과 에일리어싱됩니다. |
| 임의 R/W(Arbitrary R/W) | 재사용되지 않는 윈도우 PTE를 커널 물리 페이지에 연결합니다. 대형 스크래치 매핑에 대한 PROT_NONE 왕복으로 TLB를 강제 플러시합니다. |
| 루트(Root) | selinux_state.enforcing을 클리어하고 commit_creds(init_cred)로 sel_read_enforce를 패치한 다음, 대기 중인 자식 프로세스를 풀어 루트 셸을 exec합니다. |
하나의 페이지가 Mali 할당이면서 동시에 활성 CPU 페이지 테이블이 되므로, 프로세스는 종료하지 않고 **영원히 대기(park)**합니다. 해제하면 이중 해제가 되어 커널이 패닉에 빠지기 때문입니다.
# Android / arm64 (NDK)
aarch64-linux-android31-clang -O2 -o exploit exploit.c
./exploit
성공하면 SELinux가 permissive인 루트 셸(uid=0)을 얻습니다. 샘플 흐름:
[*] pid=... cpus=8 wave=64 harvest cpu=7 rounds=256
[*] round 1 calibrate deadline t0+... ms phase ... ms
[*] round N read +... us
[+] UAF page: spray region R page P @ ..., 1 live descriptor
[+] entry 0 = ... -- a CPU page table, writable through the GPU
[+] alias: dirty pagetable
[+] selinux_state.enforcing = 0 @ phys ...
[+] sel_read_enforce patched @ phys ...
[+] root shell: uid=0 euid=0
모든 라운드를 승리 없이 마치면, dmesg의 cpu_queue.c:71은 레이스가 적중했지만
리클레임이 빗나갔음을 나타냅니다 — 리클레임/웨이브 파라미터를 조정하세요.
exploit.c의 값은 Pixel 7 Pro, 빌드 BP3A.250905.014에 맞게 보정되어
있습니다. 오프셋과 상수는 기기/커널별로 다르므로 다른 모든 타겟에서
반드시 다시 도출해야 합니다:
KERNEL_PA_BASE, SEL_READ_ENFORCE, INIT_CRED, COMMIT_CREDS,
SELINUX_STATE — 커널 심볼/물리 오프셋.ARM64_PTE_FLAGS — 타겟 MMU 구성용 최하위 레벨 디스크립터 템플릿.WAVE_*, SWEEP_*, BUCKET_NS, WAIT_NS, 스프레이 크기 — 레이스 타이밍
및 힙 그루밍 튜닝 파라미터.Arm에서 배포한 수정된 드라이버 릴리스로 업데이트하여 조치하십시오.
이 코드는 방어적 보안 연구, 탐지 엔지니어링, 교육 목적으로만 게시됩니다. 소유하지 않았거나 명시적인 서면 테스트 승인을 받지 않은 시스템에 대해 실행하지 마십시오. 저자는 오용 또는 발생한 어떠한 손해에 대해서도 책임을 지지 않습니다. 허가 없이 시스템에 대해 사용하는 것은 불법입니다.