
Exploit del kernel Android GKI 6.12 para CVE-2026-43499, que encadena un fallo de reversión de rt_mutex con una sobrescritura de pila en pselect para obtener root en dispositivos Samsung y Pixel.
Un exploit del kernel de Linux para la línea GKI 6.12 de Android (dispositivos
Samsung y Pixel) dirigido a CVE-2026-43499: un rollback defectuoso de
remove_waiter() en rt_mutex_start_proxy_lock() que usa current en lugar de
waiter::task, dejando el pi_blocked_on de un waiter apuntando a su
rt_mutex_waiter de la pila del kernel (que luego se saca). Combinado con una
sobrescritura de la pila del fd_set de pselect() y un recorrido falso de
rb_tree impulsado por sched_setattr del consumidor, produce una primitiva de
escritura en el kernel determinista y root completo.
El exploit se ejecuta como una biblioteca compartida LD_PRELOAD (preload.so)
e instala un demonio su root más un fondo de pantalla como artefactos
posteriores al root.
Estado: desarrollo activo. El objetivo m1q (ZF1) es el foco de la puesta en marcha. El bootstrap pipei tmp_page-uname es la ruta activa actual: el recorrido está probado limpio en el dispositivo (build #33: regiones rt_mutex sembradas por hijo) y el barrido completo de 168 candidatos está en curso. La ruta configfs CFI es un callejón sin salida en m1q (Rust ashmem — sin slot fops inyectable) y sigue siendo la ruta para objetivos C-ashmem. Los offsets por objetivo varían según el dispositivo — verifíquelos contra el binario real del kernel antes de confiar en ellos.
Cuando FUTEX_CMP_REQUEUE_PI reencola un waiter y el recorrido de la cadena de
detección de deadlock del reencolado devuelve -EDEADLK,
__rt_mutex_start_proxy_lock() hace rollback mediante remove_waiter()
(rtmutex.c:1535). El rollback desencola correctamente el waiter del árbol de
espera pero limpia el pi_blocked_on del llamador del reencolado en lugar
de waiter->task->pi_blocked_on. El pi_blocked_on del WAITER queda colgando
en su rt_mutex_waiter de la pila, que se saca una vez que el futex expira.
Tras el timeout, la región de la pila del kernel del waiter se reutiliza:
core_sys_select() copia los tres fd_sets en ese búfer de pila (la ruta de pila
nfds < 344 en ZF1). Un array de palabras fd_set manipulado vuelve a
materializar un rt_mutex_waiter falso / task falsa / rt_mutex falso (con una
raíz de rb_tree cuyos punteros están controlados). Un hilo consumidor entonces
llama a sched_setattr_tid(waiter) → rt_mutex_adjust_pi() → rb_erase_cached,
lo que produce una escritura en dirección arbitraria de un valor controlado.
Toda la primitiva requiere el ciclo de la cadena PI: el propietario mantiene
f_pi_target y también se bloquea en f_pi_chain (mantenido por el waiter), de
modo que el recorrido de la cadena llega a owner → chain → waiter → target →
owner y falla con -EDEADLK. Eliminar el ciclo hace que el reencolado devuelva
éxito con efecto nulo en el kernel.
sched_blocked_reason (primaria en m1q, probada en
dispositivo): el PC de retorno guardado de un kworker bloqueado
(stack_trace_save_tsk) se lee del ring buffer y se compara con el offset
de worker_thread compilado. Se ejecuta antes de usar cualquier palabra de
la ruta boot_id para que los objetivos de escritura de alias de datos
(data_addr() = alias p0 + slide_p0_offset) sigan siendo correctos con
un slide distinto de cero.SLIDE_LOGGERS_0_1 en los datos del sysctl boot_id mediante
un alias de mapa lineal; el valor filtrado reconstruye stext.
Aparcada en m1q: su objetivo W1 (SLIDE_RANDOM_BOOT_ID_DATA_OFF, RAM
física baja) tiene escriturabilidad dependiente de la página — el slide
mueve el objetivo a través de los límites de página en cada arranque, por
lo que ~6/7 ejecuciones fallan. Solo se ejercita explícitamente mediante
SLIDE_FORCE_BOOTID (prueba de mecanismo) con tree_pc/tree_left
redirigidos a la página rociada.mm_struct y usar una
colisión de hash de futex para localizar un mm_struct en el heap, filtrando
una dirección de página del heap del kernel usada como base del rociado de
objetos falsos. Los waiters se desasocian (no se unen) en la limpieza para
evitar el OOM de la pila del kernel que hizo fallar builds anteriores al #26.write_iter inyectable y el objeto de heap
ASHMEM_SET_NAME es un KVec cuyo diseño no se alinea con el private_data de
configfs_bin_write_iter. m1q pivota a una escritura kmalloc en su lugar:
reclamar el bloque de orden 3 de mm filtrado como objetos pipe_inode_info y
usar la escritura pselect W1 (*(tree_left) = tree_pc) para sobrescribir el
slot tmp_page de un candidato (+0x90) con la página del namespace UTS
(init_uts_ns). Una escritura de abanico planta un nombre marcador en el
offset sysname y uname() informa "CatOS" — una escritura en el kernel
verificable sin dependencia de objetos estáticos. Los objetivos C-ashmem
mantienen la ruta configfs.preload.c instala el demonio su embebido (montado en tmpfs en
/apex/com.android.virt/bin, más variantes de namespace adbd y locales) y
cambia el fondo de pantalla.Las palabras fd_set de pselect vuelven a materializar un rt_mutex_waiter falso
en la pila del kernel del waiter; el sched_setattr_tid(waiter) de un hilo
consumidor lo recorre mediante rt_mutex_adjust_pi(). Mapa de palabras ZF1
(verificado por disasm): PSELECT_WAITER_WORD_SHIFT = 0, palabra 12 = task
(@+0x50), palabra 13 = lock (@+0x58), palabra 14 = wake_state (@+0x60 = 3).
Con un rt_mutex falso sembrado el recorrido se completa limpiamente: las
rb_erase W1 *(tree_left)=tree_pc / W2 *(tree_pc&~3+8)=tree_left de [7] son
las únicas escrituras en el kernel; [11] (setprio / dequeue_pi) y el wake de [9]
se omiten ambos (el waiter falso local con prio 100 mantiene el nodo de pila
fuera del top-waiter). Cada finalización que entra al recorrido desde el build
#19 sobrevive en el dispositivo; los fallos deterministas anteriores eran un
error de colocación de payload de 8 bytes (SKB_DATA_DELTA), no un fallo del
cuerpo del recorrido.
Fase opcional STAGE3=1 que bifurca un hijo puente, cambia su mm->pgd a
una tabla de páginas falsa preparada (3 niveles: PGD→L1→L2, hoja de bucle RX +
hoja de pila RW), y deja que el hijo ejecute un blob de ensamblador solo de
registros que parchea su propio cred a través de una ventana de escaneo físico
de 2MB — lectura/escritura física de toda la RAM sin las primitivas
configfs/pipe. Actualmente solo está conectada al objetivo m1q y no se ha
ejecutado en dispositivo.
41 objetivos en src/targets/<codename>-<build>/, cada uno requiere como mínimo
un target.h (offsets de símbolos del kernel, KIMAGE_TEXT_BASE, base del mapa
directo, offsets de structs). Los objetivos Pixel (comet, tokay, tegu,
caiman, komodo, frankel, mustang, rango, stallion, blazer)
sobrescriben fuentes compartidas; los objetivos Samsung (m1q-*) añaden lógica
específica del dispositivo.
make list-projects # full list