
Exploit del kernel Android GKI 6.12 per CVE-2026-43499, che concatena un bug di rollback di rt_mutex con la sovrascrittura dello stack di pselect per ottenere i privilegi di root su dispositivi Samsung e Pixel.
Un exploit del kernel Linux per la linea GKI 6.12 di Android (dispositivi
Samsung e Pixel) che prende di mira CVE-2026-43499: un rollback difettoso
di remove_waiter() in rt_mutex_start_proxy_lock() che usa current invece
di waiter::task, lasciando il pi_blocked_on di un waiter puntato al suo
(later-popped) kernel stack rt_mutex_waiter. Combinato con una
sovrascrittura dello stack fd_set di pselect() e una camminata fake rb_tree
guidata da sched_setattr del consumer, produce una primitiva di scrittura
kernel deterministica e root completo.
L'exploit viene eseguito come libreria condivisa LD_PRELOAD (preload.so) e
installa un demone su root piu un wallpaper come artefatti post-root.
Stato: sviluppo attivo. Il target m1q (ZF1) e il focus del bring-up. Il bootstrap pipei tmp_page-uname e la rotta attiva corrente: la camminata e provata pulita on-device (build #33: regioni rt_mutex seedate per-child) e la scansione completa dei 168 candidati e in corso. La rotta configfs CFI e un vicolo cieco su m1q (Rust ashmem — nessuno slot fops iniettabile) e resta la rotta per i target C-ashmem. Gli offset per-target variano da dispositivo a dispositivo — verificarli rispetto al binario del kernel effettivo prima di fidarsene.
Quando FUTEX_CMP_REQUEUE_PI riaccoda un waiter e la camminata della catena
di rilevamento deadlock del requeue restituisce -EDEADLK,
__rt_mutex_start_proxy_lock() esegue il rollback tramite remove_waiter()
(rtmutex.c:1535). Il rollback rimuove correttamente il waiter dall'albero di
attesa ma azzera il pi_blocked_on del chiamante del requeue invece di
waiter->task->pi_blocked_on. Il pi_blocked_on del WAITER resta pendente sul
suo rt_mutex_waiter nello stack, che viene rimosso una volta che il futex va
in timeout.
Dopo il timeout, la regione dello stack kernel del waiter viene riutilizzata:
core_sys_select() copia i tre fd_set in quel buffer dello stack (il percorso
stack nfds < 344 su ZF1). Un array di parole fd_set artigianale
ri-materializza un finto rt_mutex_waiter / finto task / finto rt_mutex (con
una radice rb_tree i cui puntatori sono controllati). Un thread consumer
chiama poi sched_setattr_tid(waiter) → rt_mutex_adjust_pi() →
rb_erase_cached, che produce una scrittura su indirizzo arbitrario di un
valore controllato.
L'intera primitiva richiede il ciclo della catena PI: l'owner detiene
f_pi_target e si blocca anche su f_pi_chain (detenuto dal waiter), quindi
la camminata della catena incontra owner → chain → waiter → target → owner e
fallisce con -EDEADLK. Rimuovere il ciclo fa si che il requeue ritorni
successo con effetto kernel nullo.
sched_blocked_reason (primaria su m1q, provata on-device):
il return PC salvato di un kworker bloccato (stack_trace_save_tsk) viene
letto dal ring buffer e confrontato con l'offset worker_thread compilato
nel binario. Viene eseguita prima che venga usata qualsiasi parola della
rotta boot_id, cosi i target di scrittura data-alias (data_addr() = alias
p0 + slide_p0_offset) restano corretti a uno slide non nullo.SLIDE_LOGGERS_0_1 nei dati sysctl di boot_id
tramite un alias linear-map; il valore leakato ricostruisce stext.
Parcheggiata su m1q: il suo target W1
(SLIDE_RANDOM_BOOT_ID_DATA_OFF, RAM fisica bassa) ha scrivibilita
dipendente dalla pagina — lo slide sposta il target attraverso i confini
di pagina a ogni boot, quindi ~6/7 esecuzioni vanno in fault. Viene
esercitata esplicitamente solo tramite SLIDE_FORCE_BOOTID (test del
meccanismo) con tree_pc/tree_left reindirizzati alla pagina spruzzata.mm_struct e uso
di una collisione futex-hash per localizzare un mm_struct sull'heap,
leakando un indirizzo di pagina heap del kernel usato come base dello spray
di oggetti fake. I waiter vengono distaccati (non joinati) durante la
pulizia per evitare l'OOM dello stack kernel che faceva crashare le build
prima della #26.write_iter iniettabile e l'oggetto heap
ASHMEM_SET_NAME e un KVec il cui layout non si allinea con il
private_data di configfs_bin_write_iter. m1q passa a una scrittura
kmalloc: recupera il blocco mm order-3 leakato come oggetti
pipe_inode_info e usa la scrittura pselect W1 (*(tree_left) = tree_pc)
per sovrascrivere lo slot tmp_page di un candidato (+0x90) con la pagina
del namespace UTS (init_uts_ns). Una scrittura fanout pianta un nome
marker all'offset sysname e uname() riporta "CatOS" — una scrittura
kernel verificabile senza dipendenza da oggetti statici. I target C-ashmem
mantengono il percorso configfs.preload.c installa il demone su incorporato (montato su tmpfs in
/apex/com.android.virt/bin, piu varianti adbd-namespace e locali) e
scambia il wallpaper.Le parole fd_set di pselect ri-materializzano un finto rt_mutex_waiter sullo
stack kernel del waiter; il sched_setattr_tid(waiter) di un thread consumer
lo percorre tramite rt_mutex_adjust_pi(). Mappa parole ZF1 (verificata via
disasm): PSELECT_WAITER_WORD_SHIFT = 0, parola 12 = task (@+0x50), parola
13 = lock (@+0x58), parola 14 = wake_state (@+0x60 = 3). Con un finto
rt_mutex seedato la camminata si completa pulitamente: le W1
*(tree_left)=tree_pc / W2 *(tree_pc&~3+8)=tree_left di rb_erase di [7]
sono le uniche scritture kernel; [11] (setprio / dequeue_pi) e il wake di [9]
vengono entrambi saltati (il finto waiter locale a prio 100 tiene il nodo
dello stack fuori dal top-waiter). Ogni completamento entrato nella camminata
dalla build #19 sopravvive on-device; i crash deterministici precedenti erano
un errore di posizionamento del payload di 8 byte (SKB_DATA_DELTA), non un
fault del corpo della camminata.
Fase opzionale STAGE3=1 che fork-a un figlio bridge, scambia il suo
mm->pgd con una tabella di pagine fake preparata (3 livelli: PGD→L1→L2,
foglia RX loop + foglia RW stack) e lascia che il figlio esegua un blob
assembly di soli registri che patcha le proprie cred attraverso una finestra
di scansione fisica da 2MB — phys R/W su tutta la RAM senza le primitive
configfs/pipe. Attualmente cablata solo nel target m1q e non e stata
eseguita on-device.
41 target in src/targets/<codename>-<build>/, ognuno richiede come minimo un
target.h (offset dei simboli del kernel, KIMAGE_TEXT_BASE, base della
direct-map, offset delle struct). I target Pixel (comet, tokay, tegu,
caiman, komodo, frankel, mustang, rango, stallion, blazer)
sovrascrivono i sorgenti condivisi; i target Samsung (m1q-*) aggiungono
logica specifica del dispositivo.
make list-projects # full list
I layout delle struct del kernel differiscono per dispositivo — gli offset devono essere verificati rispetto al binario del kernel effettivo / kallsyms per ogni target.
Output: build/<PROJECT>/bin/preload.so (libreria condivisa caricata via
LD_PRELOAD).