
Exploit de Dirty Pagetable para CVE-2025-8045
Un exploit de escalada de privilegios local para CVE-2025-8045, un use-after-free en el controlador del kernel de la GPU Arm Mali (kbase). Un proceso local sin privilegios hace una carrera contra la ruta de volcado de la cola KCPU del CSF para liberar y reclamar un cpu_queue.buffer, convierte la asignación colgante en una tabla de páginas que él posee ("dirty pagetable"), y usa esa primitiva para obtener lectura/escritura física arbitraria del kernel, terminando en una shell root con SELinux en modo permisivo.
Explotado en Pixel 7 Pro (build
BP3A.250905.014). Los offsets hardcodeados enexploit.cestán calibrados para este objetivo; otros dispositivos/builds necesitan recalcular los offsets (ver Ajuste y portabilidad).
CVE-2025-8045 es un use-after-free en los controladores del kernel Valhall y Arquitectura GPU de 5.ª generación de Arm (r53p0–r54p1). Un proceso local sin privilegios puede dirigir operaciones de procesamiento de GPU de forma que el controlador opere sobre memoria ya liberada, abriendo la puerta a la divulgación de memoria del kernel, corrupción y ejecución de código.
Este exploit alcanza el bug a través de la interfaz de cola KCPU del CSF expuesta por /dev/mali0:
CQS_WAIT insatisfacible seguido de un FENCE_SIGNAL. La espera detiene la cola para que el temporizador de fence se dispare más tarde, en un límite predecible de cubeta del timer-wheel.KBASE_IOCTL_CS_CPU_QUEUE_DUMP invoca a kbase_csf_cpu_queue_dump_buffer(). Hacer una carrera entre la transición de estado de la cola (ISSUED → COMPLETE/PENDING) y la ruta de volcado libera cpu_queue.buffer mientras aún está referenciado: el UAF.El exploit es de timing: primero calibra el deadline, luego lo predice en cada ronda y dispara una «ola» de llamadas de volcado inofensivas para ampliar la ventana de carrera alrededor del momento de la liberación.
Dado que una página acaba siendo a la vez una asignación de Mali y una tabla de páginas de CPU viva, el proceso se estaciona para siempre en lugar de salir; liberarla sería un double-free y provocaría un pánico en el kernel.
# Android / arm64 (NDK)
aarch64-linux-android31-clang -O2 -o exploit exploit.c
./exploit
Con éxito obtienes una shell root (uid=0) con SELinux permisivo. Flujo de ejemplo:
[*] 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
Si ejecuta todas las rondas sin conseguir la victoria, cpu_queue.c:71 en dmesg indica que la carrera está aterrizando pero la reclamación falló; ajusta los parámetros de reclamación/ola.
Los valores en exploit.c están calibrados para el Pixel 7 Pro, build BP3A.250905.014. Los offsets y constantes son específicos del dispositivo/kernel y deben recalcularse para cualquier otro objetivo:
KERNEL_PA_BASE, SEL_READ_ENFORCE, INIT_CRED, COMMIT_CREDS, SELINUX_STATE — offsets de símbolos/físicos del kernel.ARM64_PTE_FLAGS — plantilla de descriptor de último nivel para la configuración de MMU del objetivo.WAVE_*, SWEEP_*, BUCKET_NS, WAIT_NS, tamaños de spray — parámetros de sincronización de la carrera y de preparación del heap.Actualice a la versión corregida del controlador de Arm para remediarlo.
Este código se publica estrictamente para investigación defensiva de seguridad, ingeniería de detección y educación. No lo ejecute contra sistemas que no le pertenezcan o para los que no tenga autorización escrita explícita de prueba. El autor no acepta responsabilidad alguna por el mal uso o por cualquier daño causado. Usarlo contra sistemas sin permiso es ilegal.
| Etapa | Qué ocurre |
|---|
| Calibrar | La ronda 1 mide el borde ISSUED→COMPLETE para aprender la fase de la cubeta del timer-wheel. |
| Predecir + barrer | Las rondas posteriores predicen el deadline y barren el offset de lectura ±ms para caer exactamente en la liberación. |
| Ola | 64 hilos apilan llamadas CS_CPU_QUEUE_DUMP sobre csf.lock para enmarcar el deadline y estirar la ventana. |
| Reclamar | Drenar previamente las listas libres UNMOVABLE y luego rociar páginas GPU selladas para que el bloque liberado vuelva a nuestro control. |
| Dirty pagetable | Liberar de nuevo e inmediatamente rociar tablas de páginas de CPU en la misma página; la asignación de GPU ahora es alias de una tabla viva de último nivel. |
| R/W arbitraria | Un PTE de ventana nunca reutilizado apunta a páginas físicas del kernel; la TLB se vacía a la fuerza mediante un viaje de ida y vuelta con PROT_NONE sobre un mapeo scratch grande. |
| Root | Limpiar selinux_state.enforcing, parchear sel_read_enforce con commit_creds(init_cred), liberar un hijo estacionado que ejecuta una shell root. |