
Pesquisa de LPE no kernel Android para CVE-2026-98260 (UAF em posix-cpu-timers): PoC de acionamento, cadeias de shell uid-0 com credenciais falsas e credenciais reais, além de um app de bootstrap Shizuku/KernelSU.
Português | 简体中文
Pesquisa de escalada de privilégios local no Android para um use-after-free no caminho de saída do posix-cpu-timers. O repositório inclui um PoC apenas de disparo, um exploit de verificação de credenciais falsas, um estágio de shell uid-0 com credenciais reais, um app KernelSU baseado em Shizuku e uma pré-verificação de vulnerabilidade.
Branch
magicos-11.0.0.109-6.12.58: esta branch é de perfil duplo. O APK e os binários nativos selecionam um perfil a partir douname -rexato em tempo de execução: MagicOS 10.0.0 / 6.12.38 é o perfil verificado, enquanto MagicOS 11.0.0.109 / 6.12.58 é adaptado estaticamente e sua validação de execução no dispositivo ainda está pendente. O APK inclui ambas as tabelasko-symsde 226 símbolos e seleciona a correspondente.
| Item | Valor |
|---|---|
| CVE | CVE-2026-98260 |
| Classe | LPE (use-after-free, remoção da rb-tree do timerqueue) |
| Alvo | Honor MagicPad3 Pro 13.3 YLP-W00, MagicOS 10.0.0 / 11.0.0.109 (Android 16), branch magicos-11.0.0.109-6.12.58 |
| Kernel | 6.12.38-android16-5-gfde7767f6ef6-abogki481467632-4k (verificado)6.12.58-android16-6-g3a7ac1674240-abogki541502914-4k (adaptado) |
| Status do perfil | perfil duplo; 6.12.38 verificado, validação em execução do 6.12.58 pendente |
| SELinux | Enforcing |
| Patches | ausentes na build testada |
| Mitigações | KASLR ativado, SMEP/SMAP/PAN ativados, KASAN compilado mas desativado |
| Bootloader | bloqueado e não desbloqueável; sem gravações em partições |
| Componente | Status | Resultado |
|---|---|---|
src/poc.c | verificado no 6.12.38 | disparo destrutivo; o reap causa panic em cleanup_timerqueue |
src/exp.c | verificado no 6.12.38 | credencial falsa; getuid()==0 && geteuid()==0; não consegue executar |
src/uid0.c | verificado no 6.12.38 | credencial real; shell uid-0 em u:r:shell:s0 |
app/ | verificado no 6.12.38 | bootstrap Shizuku + KernelSU; uid 0 em u:r:ksu:s0 |
src/vuln_precheck.h | adicionado | gate de perfil/ambiente; --check / --force |
| Perfil 6.12.58 | adaptado | sites, selinux_state e sua tabela ko-syms de 226 símbolos rederivados; execução no dispositivo pendente |
| APK de perfil duplo | testado no 6.12.38 | seleção de perfil e ko-syms por uname -r; caminho KSU em execução no 6.12.38 aprovado |
Versão atual: v1.4.3-app.
A pré-versão v1.4.2-app foi retirada em 2026-10-30; sua tag é mantida apenas
por histórico. Consulte docs/release-notes.md para o
registro de versões e lacunas conhecidas.
src/poc.c trigger only; the reap panics on the tested kernel
src/exp.c fake-cred chain; verifies uid 0 via getuid/geteuid
src/uid0.c real-cred chain; zeroes cred->uid/gid and execs sh
src/kernel_profile.h per-release call sites, symbols and structure offsets
src/trace_ids.h runtime kmem trace event IDs; unprivileged probe
src/vuln_precheck.h shared profile/environment gate
app/ Android root app (Shizuku + KernelSU bootstrap)
docs/analysis.md root cause, exploit chain and device notes
scripts/dump_device.sh root device dump: all .ko/modinfo, kernel/config/policy, perf/tracefs, vendor mount, harness, active boot.img
Makefile single build entry (NDK cross-compile, app APK)
README.zh-CN.md Chinese version of this document
Uma thread que não é líder criou um timer posix CLOCK_THREAD_CPUTIME_ID e
depois falha em execve() após de_thread() mantém o nó do timer na fila. O
SIGKILL que interrompe o exec faz a thread sair através de
exit_itimers(), que não consegue desarmar um timer cuja tarefa já está saindo.
O k_itimer é liberado enquanto seu timerqueue_node ainda está vinculado na
fila posix_cputimers da tarefa. Quando o pai colhe o zumbi,
release_task() -> posix_cpu_timers_exit() -> cleanup_timerqueue() chama
timerqueue_del()/rb_erase() no nó liberado.
A remoção é a primitiva de escrita: rb_erase() armazena um valor
__rb_parent_color controlado em um alvo rb_left controlado. O exploit
coreografa a recuperação de slab/page para que o nó liberado fique em uma página
preenchida com o payload do atacante, e então escolhe o alvo e o valor.
Veja docs/analysis.md para o trace de panic observado, os
offsets do YLP-W00, os vazamentos baseados em tracefs, a coreografia SLUB/RCU, a
cadeia de credenciais falsas em dois estágios e a cadeia de credenciais reais de
escrita única.
Requisitos: Android NDK r29 em /opt/android-ndk-r29 (sobrescreva com
NDK=). Os alvos são aarch64, API 24 por padrão. O app também precisa do
Android SDK (build-tools 37.0.0, platform 37), um JDK e os jars do Shizuku
em ~/.local/share/android-cve-env/cve98260-app (sobrescreva com
APP_DEPS=). Os artefatos de build permanecem em build/ e são ignorados pelo git. Os
jars do Shizuku são fornecidos pelo usuário; scripts/fetch-deps.sh os verifica contra
deps.lock (SHA-256) e nunca baixa nada.
| Alvo | Saída / ação |
|---|---|
make / make poc | build/poc |
make exp | build/exp |
make uid0 | build/uid0 (estático; o estágio de shell se reexecuta) |
make build-all | compila poc + exp + uid0 + apk |
make host | build/poc.static para validação qemu no host |
make emu | executa build/poc.static sob qemu (com --force) |
make apk | app/CVE98260Root.apk |
make clean | remove build/ |
make help | lista os alvos |
poc, exp e uid0 chamam um gate de perfil antes de tocar no kernel. Ele
aceita apenas a release exata do perfil (6.12.38 verificada, 6.12.58 adaptada),
exige arm64 e páginas de 4 KiB, e para exp/uid0 também verifica a
disponibilidade de tracefs/kmem e (para a cadeia de credenciais falsas) panic_on_warn=0. Qualquer outra
release não é confirmada e o programa sai com código 2:
build/uid0 --check # print the verdict, do not trigger
build/uid0 --check --force # same, overriding an unconfirmed environment
Use --force ou CVE98260_FORCE=1 apenas após verificar o kernel de forma independente. --force relaxa apenas as verificações de ambiente; exp, uid0 e o app ainda exigem um perfil exato de kernel_profile.h. O gate deliberadamente não é uma sondagem em tempo de execução: uma sondagem que alcançasse o bug deixaria, ela própria, um nó timerqueue obsoleto para trás.
No YLP-W00, um shell sem privilégios não consegue ler /proc/sys/kernel/panic_on_warn nem /proc/cmdline; para um perfil conhecido, o gate assume o valor 0 validado para 6.12.38. A mesma suposição é usada para o perfil adaptado 6.12.58 até que sua validação em tempo de execução seja concluída. Versões desconhecidas permanecem bloqueadas.
O exploit inicia como um processo de shell comum (uid 2000, SELinux u:r:shell:s0) e não usa su. Não o inicie através de su: a condição testada é uma inicialização sem privilégios.
make uid0
adb push build/uid0 /data/local/tmp/uid0
adb shell chmod 755 /data/local/tmp/uid0
adb shell /data/local/tmp/uid0 # runs id and prints the context
adb shell /data/local/tmp/uid0 shell # interactive sh -i
Antes da cadeia completa, use os modos não destrutivos:
adb shell /data/local/tmp/uid0 --probe-ids # discover kmem trace event IDs
adb shell /data/local/tmp/uid0 leak # timer/cred leak only; no UAF/reap