
Investigación de LPE en el kernel de Android para CVE-2026-98260 (UAF en posix-cpu-timers): PoC de activación, cadenas de shell con uid-0 mediante credenciales falsas y credenciales reales, además de una app de arranque para Shizuku/KernelSU.
English | 简体中文
Investigación de escalada local de privilegios en Android para un use-after-free en la ruta de salida de posix-cpu-timers. El repositorio incluye un PoC de solo activación, un exploit de verificación con credenciales falsas, una etapa de shell uid-0 con credenciales reales, una app KernelSU basada en Shizuku y una precomprobación de vulnerabilidad.
Rama
magicos-11.0.0.109-6.12.58: esta rama es de doble perfil. El APK y los binarios nativos seleccionan un perfil a partir deluname -rexacto en tiempo de ejecución: MagicOS 10.0.0 / 6.12.38 es el perfil verificado, mientras que MagicOS 11.0.0.109 / 6.12.58 está adaptado estáticamente y su validación en tiempo de ejecución en el dispositivo aún está pendiente. El APK incluye ambas tablasko-symsde 226 símbolos y selecciona la correspondiente.
| Elemento | Valor |
|---|---|
| CVE | CVE-2026-98260 |
| Clase | LPE (use-after-free, borrado del rb-tree de timerqueue) |
| Objetivo | Honor MagicPad3 Pro 13.3 YLP-W00, MagicOS 10.0.0 / 11.0.0.109 (Android 16), rama 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) |
| Estado del perfil | doble perfil; 6.12.38 verificado, validación en tiempo de ejecución de 6.12.58 pendiente |
| SELinux | Enforcing |
| Parches | ausentes en la compilación probada |
| Mitigaciones | KASLR activado, SMEP/SMAP/PAN activados, KASAN compilado pero deshabilitado |
| Bootloader | bloqueado y no desbloqueable; sin escrituras en particiones |
| Componente | Estado | Resultado |
|---|---|---|
src/poc.c | verificado en 6.12.38 | activador destructivo; el reap provoca panic en cleanup_timerqueue |
src/exp.c | verificado en 6.12.38 | credenciales falsas; getuid()==0 && geteuid()==0; no puede hacer exec |
src/uid0.c | verificado en 6.12.38 | credenciales reales; shell uid-0 en u:r:shell:s0 |
app/ | verificado en 6.12.38 | arranque de Shizuku + KernelSU; uid 0 en u:r:ksu:s0 |
src/vuln_precheck.h | añadido | comprobación de perfil/entorno; --check / --force |
| Perfil 6.12.58 | adaptado | sitios, selinux_state y su tabla ko-syms de 226 símbolos rederivados; ejecución en dispositivo pendiente |
| APK de doble perfil | probado en 6.12.38 | selección de perfil y ko-syms mediante uname -r; ruta live-KSU de 6.12.38 superada |
Versión actual: v1.4.3-app.
La preversión v1.4.2-app fue retirada el 2026-10-30; su etiqueta se conserva solo por
motivos históricos. Consulte docs/release-notes.md para el
registro de versiones y las carencias conocidas.
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
Un hilo no líder que creó un temporizador posix CLOCK_THREAD_CPUTIME_ID y
luego falla en execve() después de de_thread() mantiene el nodo del temporizador en cola. El
SIGKILL que interrumpe el exec hace que el hilo salga a través de
exit_itimers(), que no puede desarmar un temporizador cuya tarea ya está saliendo.
El k_itimer se libera mientras su timerqueue_node sigue enlazado en la
cola posix_cputimers de la tarea. Cuando el padre recoge el zombi,
release_task() -> posix_cpu_timers_exit() -> cleanup_timerqueue() llama a
timerqueue_del()/rb_erase() sobre el nodo liberado.
El borrado es la primitiva de escritura: rb_erase() almacena un valor
__rb_parent_color controlado en un objetivo rb_left controlado. El exploit
coreografía la reclamación de slab/página para que el nodo liberado quede en una página llena con
el payload del atacante, y luego elige el objetivo y el valor.
Consulta docs/analysis.md para ver el rastro de pánico observado, los
offsets de YLP-W00, las fugas basadas en tracefs, la coreografía SLUB/RCU, la
cadena de credenciales falsas en dos etapas y la cadena de credenciales reales de una sola escritura.
Requisitos: Android NDK r29 en /opt/android-ndk-r29 (sobrescribir con
NDK=). Los objetivos son aarch64, API 24 por defecto. La aplicación además necesita
el Android SDK (build-tools 37.0.0, platform 37), un JDK y los jars de Shizuku
en ~/.local/share/android-cve-env/cve98260-app (sobrescribir con
APP_DEPS=). Los artefactos de compilación permanecen en build/ y están ignorados por git. Los
jars de Shizuku son proporcionados por el usuario; scripts/fetch-deps.sh los verifica contra
deps.lock (SHA-256) y nunca descarga nada.
| Objetivo | Salida / acción |
|---|---|
make / make poc | build/poc |
make exp | build/exp |
make uid0 | build/uid0 (estático; la etapa de shell se re-ejecuta a sí misma) |
make build-all | compilar poc + exp + uid0 + apk |
make host | build/poc.static para validación con qemu en el host |
make emu | ejecutar build/poc.static bajo qemu (con --force) |
make apk | app/CVE98260Root.apk |
make clean | eliminar build/ |
make help | listar objetivos |
poc, exp y uid0 llaman a una compuerta de perfil antes de tocar el kernel. Esta
acepta una versión de perfil exacta (6.12.38 verificada, 6.12.58 adaptada),
requiere arm64 y páginas de 4 KiB, y para exp/uid0 también comprueba la
disponibilidad de tracefs/kmem y (para la cadena de credenciales falsas) panic_on_warn=0. Cualquier otra
versión no está confirmada y el programa sale con código 2:
build/uid0 --check # print the verdict, do not trigger
build/uid0 --check --force # same, overriding an unconfirmed environment
Use --force o CVE98260_FORCE=1 solo después de verificar el kernel de forma independiente. --force relaja únicamente las comprobaciones del entorno; exp, uid0 y la app siguen requiriendo un perfil exacto de kernel_profile.h. La compuerta deliberadamente no es una sonda en tiempo de ejecución: una sonda que alcanzara el bug dejaría por sí misma un nodo de timerqueue obsoleto.
En YLP-W00 un shell sin privilegios no puede leer /proc/sys/kernel/panic_on_warn ni /proc/cmdline; para un perfil conocido, la compuerta asume el valor 0 validado para 6.12.38. La misma suposición se usa para el perfil adaptado 6.12.58 hasta que se complete su validación en tiempo de ejecución. Las versiones desconocidas permanecen bloqueadas.
El exploit se inicia como un proceso de shell ordinario (uid 2000, SELinux u:r:shell:s0) y no utiliza su. No lo inicie mediante su: la condición probada es un inicio sin privilegios.
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