
Exploit do kernel Android GKI 6.12 para CVE-2026-43499, encadeando um bug de rollback do rt_mutex com sobrescrita de pilha do pselect para obter root em dispositivos Samsung e Pixel.
Um exploit de kernel Linux para a linha GKI 6.12 do Android (dispositivos
Samsung e Pixel) visando CVE-2026-43499: um rollback defeituoso de
remove_waiter() em rt_mutex_start_proxy_lock() que usa current em vez de
waiter::task, deixando o pi_blocked_on de um waiter apontando para seu
rt_mutex_waiter na pilha do kernel (posteriormente removido). Combinado com
uma sobrescrita de pilha de fd_set via pselect() e uma travessia de rb_tree
falsa controlada por sched_setattr do consumidor, isso produz uma primitiva
de escrita determinística no kernel e root completo.
O exploit roda como uma biblioteca compartilhada LD_PRELOAD (preload.so) e
instala um daemon su root mais o wallpaper como artefatos pós-root.
Status: desenvolvimento ativo. O alvo m1q (ZF1) é o foco do bring-up. O bootstrap pipei tmp_page-uname é a rota ativa atual: a travessia está comprovadamente limpa no dispositivo (build #33: regiões rt_mutex semeadas por filho) e a varredura completa de 168 candidatos está em andamento. A rota configfs CFI é um beco sem saída no m1q (Rust ashmem — sem slot fops injetável) e permanece a rota para alvos C-ashmem. Os offsets por alvo variam por dispositivo — verifique contra o binário real do kernel antes de confiar neles.
Quando FUTEX_CMP_REQUEUE_PI reenfileira um waiter e a travessia da cadeia de
detecção de deadlock do requeue retorna -EDEADLK, __rt_mutex_start_proxy_lock()
faz rollback via remove_waiter() (rtmutex.c:1535). O rollback desenfileira
corretamente o waiter da árvore de espera, mas limpa o pi_blocked_on do
chamador do requeue em vez de waiter->task->pi_blocked_on. O pi_blocked_on
do WAITER permanece pendente em seu rt_mutex_waiter na pilha, que é removido
quando o futex expira.
Após o timeout, a região da pilha do kernel do waiter é reutilizada:
core_sys_select() copia os três fd_sets para esse buffer de pilha (o caminho
de pilha nfds < 344 no ZF1). Um array de palavras fd_set forjado
re-materializa um rt_mutex_waiter falso / task falsa / rt_mutex falso (com
uma raiz rb_tree cujos ponteiros são controlados). Uma thread consumidora então
chama sched_setattr_tid(waiter) → rt_mutex_adjust_pi() → rb_erase_cached,
o que produz uma escrita em endereço arbitrário de um valor controlado.
A primitiva inteira requer o ciclo da cadeia PI: o owner mantém
f_pi_target e também bloqueia em f_pi_chain (mantido pelo waiter), então a
travessia da cadeia atinge owner → chain → waiter → target → owner e falha com
-EDEADLK. Remover o ciclo faz o requeue retornar sucesso com efeito zero no
kernel.
sched_blocked_reason (primária no m1q, comprovada no
dispositivo): o PC de retorno salvo de um kworker bloqueado
(stack_trace_save_tsk) é lido do ring buffer e comparado contra o
offset worker_thread compilado. Roda antes de qualquer palavra da rota
boot_id ser usada, para que os alvos de escrita de data-alias
(data_addr() = alias p0 + slide_p0_offset) permaneçam corretos em um
slide diferente de zero.SLIDE_LOGGERS_0_1 nos dados do sysctl boot_id via um
alias de mapa linear; o valor vazado reconstrói stext. Estacionada no
m1q: seu alvo W1 (SLIDE_RANDOM_BOOT_ID_DATA_OFF, RAM física baixa)
tem gravabilidade dependente de página — o slide move o alvo através de
limites de página a cada boot, então ~6/7 execuções falham. Só é
exercitada explicitamente via SLIDE_FORCE_BOOTID (teste de mecanismo)
com tree_pc/tree_left redirecionados para a página sprayada.mm_struct e uso de uma
colisão de hash de futex para localizar um mm_struct no heap, vazando um
endereço de página do heap do kernel usado como base do spray de objetos
falsos. Os waiters são desanexados (não unidos) na limpeza para evitar o OOM
da pilha do kernel que derrubou builds antes da #26.write_iter injetável e o objeto de heap ASHMEM_SET_NAME é
um KVec cujo layout não se alinha com o private_data de
configfs_bin_write_iter. O m1q pivota para uma escrita kmalloc: recupera o
bloco mm order-3 vazado como objetos pipe_inode_info e usa a escrita
pselect W1 (*(tree_left) = tree_pc) para sobrescrever o slot tmp_page de
um candidato (+0x90) com a página do namespace UTS (init_uts_ns). Uma
escrita fanout planta um nome marcador no offset sysname e uname()
reporta "CatOS" — uma escrita de kernel verificável sem dependência de
objeto estático. Alvos C-ashmem mantêm o caminho configfs.preload.c instala o daemon su embutido (montado em tmpfs em
/apex/com.android.virt/bin, mais variantes de namespace adbd e local) e
troca o wallpaper.As palavras fd_set do pselect re-materializam um rt_mutex_waiter falso na
pilha do kernel do waiter; o sched_setattr_tid(waiter) de uma thread
consumidora o percorre via rt_mutex_adjust_pi(). Mapa de palavras do ZF1
(verificado por disasm): PSELECT_WAITER_WORD_SHIFT = 0, palavra 12 = task
(@+0x50), palavra 13 = lock (@+0x58), palavra 14 = wake_state (@+0x60 = 3).
Com um rt_mutex falso semeado, a travessia completa limpa: as W1
*(tree_left)=tree_pc / W2 *(tree_pc&~3+8)=tree_left do rb_erase de [7] são
as únicas escritas no kernel; [11] (setprio / dequeue_pi) e o wake de [9] são
ambos ignorados (waiter falso local em prio 100 mantém o nó da pilha fora do
top-waiter). Toda conclusão que entra na travessia desde a build #19 sobrevive
no dispositivo; os crashes determinísticos anteriores eram um erro de
posicionamento de payload de 8 bytes (SKB_DATA_DELTA), não uma falha do corpo
da travessia.
Fase opcional STAGE3=1 que faz fork de um filho ponte, troca seu
mm->pgd por uma tabela de páginas falsa preparada (3 níveis: PGD→L1→L2, folha
de loop RX + folha de pilha RW) e deixa o filho executar um blob de assembly
somente de registradores que faz patch de seu próprio cred através de uma
janela de varredura física de 2MB — phys R/W de toda a RAM sem as primitivas
configfs/pipe. Atualmente conectado apenas ao alvo m1q e não foi executado
no dispositivo.
41 alvos em src/targets/<codename>-<build>/, cada um exigindo no mínimo um
target.h (offsets de símbolos do kernel, KIMAGE_TEXT_BASE, base do
direct-map, offsets de structs). Alvos Pixel (comet, tokay, tegu,
caiman, komodo, frankel, mustang, rango, stallion, blazer)
sobrescrevem fontes compartilhadas; alvos Samsung (m1q-*) adicionam lógica
específica do dispositivo.
make list-projects # full list
Os layouts de structs do kernel diferem por dispositivo — os offsets devem ser verificados contra o binário real do kernel / kallsyms para cada alvo.
Saída: build/<PROJECT>/bin/preload.so (biblioteca compartilhada carregada via
LD_PRELOAD).
# Default project
CC=clang make
# Specific device target (default: blazer-CP2A.260605.012)
CC=clang make PROJECT=m1q-BP4A.251205.006