
Exploração Dirty Pagetable para CVE-2025-8045
Um exploit de escalonamento local de privilégios para CVE-2025-8045, um use-after-free no
driver de kernel do GPU Arm Mali (kbase). Um processo local sem privilégios disputa o
caminho de dump da fila KCPU do CSF para liberar e recuperar um cpu_queue.buffer, transforma a
alocação órfã em uma tabela de páginas de sua propriedade ("dirty pagetable") e usa
essa primitiva para obter leitura/escrita física arbitrária do kernel — terminando em um shell
root com SELinux em modo permissivo.
Explorado no Pixel 7 Pro (build
BP3A.250905.014). Os offsets fixados no código emexploit.csão calibrados para este alvo; outros dispositivos/builds precisam ter os offsets recalculados (veja Ajuste e portabilidade).
CVE-2025-8045 é um use-after-free nos drivers de kernel Arm Valhall e Arquitetura de GPU de 5ª Geração (r53p0–r54p1). Um processo local e sem privilégios
pode conduzir operações de processamento da GPU de modo que o driver continue operando em
memória já liberada, abrindo as portas para divulgação, corrupção e
execução de código na memória do kernel.
Este exploit alcança a falha por meio da interface de fila KCPU do CSF exposta por
/dev/mali0:
CQS_WAIT insatisfazível seguido por um
FENCE_SIGNAL. A espera trava a fila para que o timer da fence dispare mais tarde, em um
limite previsível de bucket da roda de timers.KBASE_IOCTL_CS_CPU_QUEUE_DUMP aciona kbase_csf_cpu_queue_dump_buffer().
A disputa entre a transição de estado da fila (ISSUED → COMPLETE/PENDING) contra o
caminho de dump libera cpu_queue.buffer enquanto ele ainda está referenciado — o UAF.O exploit é um exploit de timing: primeiro ele calibra o deadline, depois prevê a cada rodada e dispara uma "onda" de chamadas de dump inofensivas para alargar a janela de corrida em torno do momento da liberação.
Como uma página acaba sendo ao mesmo tempo uma alocação Mali e uma tabela de páginas de CPU viva, o processo permanece parqueado para sempre em vez de sair — liberá-la seria um double-free e causaria pânico no kernel.
# Android / arm64 (NDK)
aarch64-linux-android31-clang -O2 -o exploit exploit.c
./exploit
Em caso de sucesso, você obtém um shell root (uid=0) com SELinux permissivo. Exemplo de fluxo:
[*] 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
Se ele executar todas as rodadas sem sucesso, cpu_queue.c:71 no dmesg indica que a
corrida está atingindo o alvo, mas a recuperação falhou — ajuste os parâmetros de recuperação/onda.
Os valores em exploit.c são calibrados para o Pixel 7 Pro, build
BP3A.250905.014. Os offsets e as constantes são específicos do dispositivo/kernel e devem
ser recalculados para qualquer outro alvo:
KERNEL_PA_BASE, SEL_READ_ENFORCE, INIT_CRED, COMMIT_CREDS,
SELINUX_STATE — offsets de símbolos/físicos do kernel.ARM64_PTE_FLAGS — modelo de descritor de último nível para a configuração de MMU do alvo.WAVE_*, SWEEP_*, BUCKET_NS, WAIT_NS, tamanhos de spray — timing da corrida e
parâmetros de grooming de heap.Atualize para a versão corrigida do driver disponibilizada pela Arm para remediar.
Este código é publicado estritamente para pesquisa defensiva de segurança, engenharia de detecção e educação. Não o execute contra sistemas que você não possui ou para os quais não tenha autorização explícita por escrito para testar. O autor não aceita nenhuma responsabilidade por uso indevido ou por qualquer dano causado. Usá-lo contra sistemas sem permissão é ilegal.
| Estágio | O que acontece |
|---|
| Calibrar | A rodada 1 mede a borda ISSUED→COMPLETE para descobrir a fase do bucket da roda de timers. |
| Prever + varrer | As rodadas seguintes preveem o deadline e varrem o offset de leitura em ±ms para acertar exatamente o momento da liberação. |
| Onda | 64 threads empilham chamadas CS_CPU_QUEUE_DUMP em csf.lock para cercar o deadline e esticar a janela. |
| Recuperar | Esvaziar previamente as listas livres UNMOVABLE e depois pulverizar páginas de GPU carimbadas para que o bloco liberado volte sob nosso controle. |
| Dirty pagetable | Liberar novamente e imediatamente pulverizar tabelas de páginas da CPU na mesma página; a alocação da GPU agora se tornou um alias para uma tabela viva de último nível. |
| R/W arbitrário | Um PTE de janela nunca reutilizado é apontado para páginas físicas do kernel; a TLB é forçada a ser invalidada via um round-trip com PROT_NONE em um grande mapeamento temporário. |
| Root | Limpar selinux_state.enforcing, patchear sel_read_enforce com commit_creds(init_cred) e liberar um filho parqueado que executa um shell root. |