
Exploit du noyau Android GKI 6.12 pour CVE-2026-43499, enchaînant un bug de rollback rt_mutex avec un écrasement de pile pselect pour obtenir les droits root sur les appareils Samsung et Pixel.
Un exploit du noyau Linux pour la branche Android GKI 6.12 (appareils Samsung et Pixel)
ciblant CVE-2026-43499 : un rollback défectueux de remove_waiter() dans
rt_mutex_start_proxy_lock() qui utilise current au lieu de waiter::task,
laissant le pi_blocked_on d'un waiter pointer vers sa pile noyau (ensuite dépilée)
rt_mutex_waiter. Combiné à un écrasement de pile fd_set via pselect() et à un
parcours rb_tree factice piloté par sched_setattr côté consommateur, cela produit
une primitive d'écriture noyau déterministe et un root complet.
L'exploit s'exécute en tant que bibliothèque partagée LD_PRELOAD (preload.so) et installe
un démon su root ainsi qu'un fond d'écran comme artefacts post-root.
Statut : développement actif. La cible m1q (ZF1) est le point focal du bring-up. Le bootstrap pipei tmp_page-uname est la route active actuelle : le parcours est prouvé propre sur l'appareil (build #33 : régions rt_mutex seedées par enfant) et le balayage complet des 168 candidats est en cours. La route configfs CFI est une impasse sur m1q (ashmem Rust — aucun slot fops injectable) et reste la route pour les cibles C-ashmem. Les offsets par cible varient selon l'appareil — vérifiez-les par rapport au binaire noyau réel avant de leur faire confiance.
Lorsque FUTEX_CMP_REQUEUE_PI remet un waiter en file et que le parcours de
détection d'interblocage de la requeue renvoie -EDEADLK, __rt_mutex_start_proxy_lock()
effectue un rollback via remove_waiter() (rtmutex.c:1535). Le rollback retire correctement
le waiter de l'arbre d'attente mais efface le pi_blocked_on de l'appelant de la requeue
au lieu de waiter->task->pi_blocked_on. Le pi_blocked_on du WAITER reste
donc pendant sur son rt_mutex_waiter de pile, qui est dépilé une fois le futex expiré.
Après le timeout, la région de pile noyau du waiter est réutilisée : core_sys_select()
copie les trois fd_sets dans ce tampon de pile (le chemin de pile nfds < 344 sur
ZF1). Un tableau de mots fd_set forgé re-matérialise un faux rt_mutex_waiter /
fausse task / faux rt_mutex (avec une racine rb_tree dont les pointeurs sont contrôlés).
Un thread consommateur appelle alors sched_setattr_tid(waiter) →
rt_mutex_adjust_pi() → rb_erase_cached, ce qui produit une écriture à une adresse
arbitraire d'une valeur contrôlée.
Toute la primitive nécessite le cycle de chaîne PI : le propriétaire détient f_pi_target
et bloque également sur f_pi_chain (détenu par le waiter), de sorte que le parcours de chaîne atteint
owner → chain → waiter → target → owner et échoue avec -EDEADLK. Supprimer le
cycle fait que la requeue renvoie un succès sans aucun effet noyau.
sched_blocked_reason (m1q principal, prouvé sur l'appareil) : le PC de retour sauvegardé
d'un kworker bloqué (stack_trace_save_tsk) est lu depuis
le ring buffer et comparé à l'offset worker_thread compilé en dur. S'exécute avant
que les mots de la route boot_id ne soient utilisés, afin que les cibles d'écriture d'alias de données
(data_addr() = alias p0 + slide_p0_offset) restent correctes à un slide non nul.SLIDE_LOGGERS_0_1 dans les données sysctl boot_id via un alias de
linear-map ; la valeur fuitée reconstruit stext. Mise en pause sur m1q : sa cible W1
(SLIDE_RANDOM_BOOT_ID_DATA_OFF, RAM physique basse) a une écriturabilité dépendante de la page —
le slide déplace la cible à travers les frontières de page à chaque boot, donc ~6/7 exécutions
plantent. Exercée uniquement explicitement via SLIDE_FORCE_BOOTID (test de mécanisme)
avec tree_pc/tree_left redirigés vers la page sprayée.mm_struct et utilisation d'une collision
de hash de futex pour localiser un mm_struct sur le tas, fuitant une adresse de page de tas noyau
utilisée comme base de spray d'objets factices. Les waiters sont détachés (non
joints) au nettoyage pour éviter l'OOM de pile noyau qui a fait planter les builds avant
#26.write_iter statique injectable et l'objet tas ASHMEM_SET_NAME est
un KVec dont la disposition ne s'aligne pas avec le private_data de
configfs_bin_write_iter. m1q pivote vers une écriture kmalloc à la place : récupérer le bloc
mm d'ordre 3 fuité en tant qu'objets pipe_inode_info et utiliser l'écriture pselect W1
(*(tree_left) = tree_pc) pour écraser le slot tmp_page d'un candidat
(+0x90) avec la page du namespace UTS (init_uts_ns). Une écriture fanout plante
un nom marqueur à l'offset sysname et uname() rapporte "CatOS" — une
écriture noyau vérifiable sans dépendance à un objet statique. Les cibles C-ashmem
conservent le chemin configfs.preload.c installe le démon su embarqué (monté en tmpfs dans
/apex/com.android.virt/bin, plus les variantes adbd-namespace et locales) et
échange le fond d'écran.Les mots fd_set de pselect re-matérialisent un faux rt_mutex_waiter sur la
pile noyau du waiter ; le sched_setattr_tid(waiter) d'un thread consommateur le parcourt
via rt_mutex_adjust_pi(). Carte des mots ZF1 (vérifiée par désassemblage) :
PSELECT_WAITER_WORD_SHIFT = 0, mot 12 = task (@+0x50), mot 13 = lock
(@+0x58), mot 14 = wake_state (@+0x60 = 3). Avec un faux rt_mutex seedé,
le parcours se termine proprement : les rb_erase W1 *(tree_left)=tree_pc / W2
*(tree_pc&~3+8)=tree_left de [7] sont les seules écritures noyau ; [11] (setprio /
dequeue_pi) et le wake [9] sont tous deux ignorés (le faux waiter local à prio 100
garde le nœud de pile hors du top-waiter). Chaque complétion entrant dans le parcours depuis
le build #19 survit sur l'appareil ; les plantages déterministes antérieurs étaient une
erreur de placement de payload de 8 octets (SKB_DATA_DELTA), pas une faute du corps du parcours.
Phase optionnelle STAGE3=1 qui fork un enfant pont, échange son mm->pgd
contre une table de pages factice préparée (3 niveaux : PGD→L1→L2, feuille boucle RX + feuille pile RW),
et laisse l'enfant exécuter un blob assembleur registres uniquement qui patche son propre cred
via une fenêtre de scan physique de 2 Mo — phys R/W sur toute la RAM sans les primitives
configfs/pipe. Actuellement câblée uniquement dans la cible m1q et n'a pas été exécutée
sur l'appareil.
41 cibles dans src/targets/<codename>-<build>/, chacune nécessitant au minimum un
target.h (offsets de symboles noyau, KIMAGE_TEXT_BASE, base de direct-map, offsets de struct).
Les cibles Pixel (comet, tokay, tegu, caiman, komodo,
frankel, mustang, rango, stallion, blazer) surchargent les sources partagées ;
les cibles Samsung (m1q-*) ajoutent une logique spécifique à l'appareil.
make list-projects # liste complète
Les dispositions des structs noyau diffèrent selon l'appareil — les offsets doivent être vérifiés par rapport au binaire noyau réel / kallsyms pour chaque cible.
Sortie : build/<PROJECT>/bin/preload.so (bibliothèque partagée chargée via LD_PRELOAD).