
# Exploit Dirty Pagetable pour CVE-2025-8045
Un exploit d'élévation locale de privilèges pour CVE-2025-8045, un use-after-free dans le
pilote noyau du GPU Arm Mali (kbase). Un processus local non privilégié entre en course avec le
chemin de dump de la file KCPU CSF pour provoquer la libération d'un cpu_queue.buffer et sa
récupération, transforme l'allocation pendante en table de pages qu'il possède (« dirty
pagetable ») et utilise cette primitive pour obtenir une lecture/écriture physique arbitraire du
noyau — aboutissant à un shell root avec SELinux en mode permissif.
Exploité sur Pixel 7 Pro (build
BP3A.250905.014). Les offsets codés en dur dansexploit.csont calibrés pour cette cible ; les autres appareils/builds nécessitent de redériver les offsets (voir Tuning & portability).
La CVE-2025-8045 est un use-after-free dans les pilotes noyau Arm Valhall et
architecture GPU de 5e génération (r53p0–r54p1). Un processus local non
privilégié peut piloter des opérations de traitement GPU d'une manière qui laisse
le pilote opérer sur de la mémoire déjà libérée, ouvrant la porte à la divulgation
de mémoire du noyau, à la corruption et à l'exécution de code.
Cet exploit atteint le bug via l'interface CSF des files KCPU exposée par
/dev/mali0 :
CQS_WAIT insatisfaisable suivi d'un
FENCE_SIGNAL. L'attente bloque la file afin que la minuterie de fence se
déclenche plus tard, sur une limite de compartiment prévisible de la timer-wheel.KBASE_IOCTL_CS_CPU_QUEUE_DUMP déclenche kbase_csf_cpu_queue_dump_buffer().
Mettre en course la transition d'état de la file (ISSUED → COMPLETE/PENDING)
avec le chemin de dump libère cpu_queue.buffer alors qu'il est encore
référencé — l'UAF.Il s'agit d'un exploit temporel : il commence par calibrer l'échéance, puis la prédit à chaque tour et déclenche une « vague » d'appels de dump inoffensifs pour élargir la fenêtre de course autour du moment de la libération.
Comme une page finit par être à la fois une allocation Mali et une table de pages CPU vivante, le processus reste bloqué à jamais au lieu de se terminer — la libérer serait un double-free et provoquerait un panic du noyau.
# Android / arm64 (NDK)
aarch64-linux-android31-clang -O2 -o exploit exploit.c
./exploit
En cas de succès, vous obtenez un shell root (uid=0) avec SELinux en mode
permissif. Exemple de déroulement :
[*] 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
S'il parcourt tous les tours sans succès, cpu_queue.c:71 dans dmesg indique
que la course se déclenche bien mais que le reclaim a échoué — ajustez les
paramètres de reclaim/wave.
Les valeurs dans exploit.c sont calibrées pour le Pixel 7 Pro, build
BP3A.250905.014. Les offsets et constantes sont spécifiques à
l'appareil/au noyau et doivent être redérivés pour toute autre cible :
KERNEL_PA_BASE, SEL_READ_ENFORCE, INIT_CRED, COMMIT_CREDS,
SELINUX_STATE — offsets de symboles et d'adresses physiques du noyau.ARM64_PTE_FLAGS — modèle de descripteur de dernier niveau pour la
configuration MMU cible.WAVE_*, SWEEP_*, BUCKET_NS, WAIT_NS, tailles de spray — réglages de
timing de course et de grooming du tas.Mettez à jour vers la version corrigée du pilote d'Arm pour remédier à la vulnérabilité.
Ce code est publié strictement à des fins de recherche en sécurité défensive, d'ingénierie de détection et d'éducation. Ne l'exécutez pas contre des systèmes que vous ne possédez pas ou que vous n'avez pas l'autorisation écrite explicite de tester. L'auteur décline toute responsabilité en cas d'utilisation abusive ou de dommages causés. L'utiliser contre des systèmes sans autorisation est illégal.
| Étape | Ce qui se passe |
|---|
| Calibrage | Le tour 1 mesure le passage ISSUED→COMPLETE pour déterminer la phase du compartiment de la timer-wheel. |
| Prédiction + balayage | Les tours suivants prédisent l'échéance et balayent l'offset de lecture sur ±ms pour tomber exactement sur la libération. |
| Vague | 64 threads empilent des appels CS_CPU_QUEUE_DUMP sur csf.lock pour encadrer l'échéance et étirer la fenêtre. |
| Reclaim | Drainer au préalable les listes libres UNMOVABLE, puis pulvériser des pages GPU estampillées pour que le bloc libéré revienne sous notre contrôle. |
| Dirty pagetable | Libérer à nouveau et pulvériser immédiatement des tables de pages CPU dans la même page ; l'allocation GPU est désormais en alias avec une table de dernier niveau vivante. |
| R/W arbitraire | Un PTE de fenêtre jamais réutilisé est pointé vers des pages physiques du noyau ; la TLB est vidée de force via un aller-retour PROT_NONE sur un grand mapping de brouillon. |
| Root | Effacer selinux_state.enforcing, patcher sel_read_enforce avec commit_creds(init_cred), libérer un enfant bloqué qui exécute un shell root. |