
CVE-2026-43499 exploit root GhostLock pour Chromecast avec Google TV (sabrina)
Exploit root pour Chromecast with Google TV (sabrina) via CVE-2026-43499 -- une utilisation après libération (use-after-free) dans le sous-système futex PI (héritage de priorité) du noyau Linux.
Obtient root sur un appareil à bootloader verrouillé exécutant Android 14 avec le noyau 5.15.170 (PGO+BOLT+LTO, clang 17).
GhostLock exploite un bug dans remove_waiter() appelé depuis le chemin de restauration -EDEADLK dans rt_mutex_start_proxy_lock(). La fonction efface current->pi_blocked_on (celui du requeueur, déjà NULL) au lieu de celui de la tâche en attente. Le pi_blocked_on de la tâche en attente n'est jamais effacé, laissant un pointeur pendant vers un rt_mutex_waiter libéré sur la pile du noyau.
Un sched_setattr ultérieur déclenche rt_mutex_adjust_pi -> rt_mutex_adjust_prio_chain, qui suit le pointeur pendant et parcourt une chaîne PI sur des données contrôlées par l'attaquant sur une page de tas récupérée -- offrant une primitive d'écriture arbitraire via rb_erase.
| Étape | Technique |
|---|---|
| Fuite KASLR | perf_event_open avec PERF_SAMPLE_IP (filtré par TID) — l'IP noyau minimal se trouve dans .entry.text (+0x10000), _text est aligné sur 2 Mo et le décalage KASLR est un multiple de 2 Mo, donc min_ip & ~0x1fffff récupère exactement le _text à l'exécution |
| Fuite de la structure de tâche | perf_event_open avec PERF_SAMPLE_REGS_INTR (filtré par TID, mode des adresses de la carte linéaire) |
| Validation de la table de symboles | perf-échantillonne &init_user_ns à partir des arguments de registre de security_capable() pendant une rafale de setpriority(-20) et le compare à kaslr_base + off_init_user_ns — prouve que la disposition .data/.bss du noyau en cours d'exécution correspond à la table d'offsets avant toute écriture aveugle |
| Fuite de mm_struct | KernelSnitch -- canal auxiliaire temporel par collision de hachage futex |
| Pulvérisation du tas | Chorégraphie de rejet SLUB (fermeture memfd, débordement partiel CPU) + récupération de pages d'ordre 2 via io_uring_setup(256) |
| Superposition de pile | AF_UNIX SOCK_SEQPACKET sendmsg -- move_addr_to_kernel copie un sockaddr de 128 octets sur la pile du noyau, recouvrant les champs task/lock/prio du waiter pendant |
| Déclenchement du parcours | sched_setattr avec échelle nice monotone (7 -> 14 -> 19) déclenche le parcours de la chaîne PI, un par tour de superposition |
| Primitive d'écriture | rb_erase Cas 1 : `{pc=(TARGET-8) |
| Désactivation SELinux (parcours 0) | Zéro sur 8 octets à selinux_state efface enforcing, checkreqprot, initialized et policycap[0..4] — avc_denied() ne refuse jamais (enforcing=0) et security_compute_av() court-circuite vers tout-autoriser (!initialized), donc le SID noyau post-échange (u:r:kernel) cesse d'avoir de l'importance |
| Échange de cred (parcours 1+2) | task->cred = fake_cred et task->real_cred = fake_cred (uid=0, caps FULL, faux user_namespace autonome sur la page de pulvérisation ; les deux écritures sont requises — commit_creds() à l'exécution déclenche un BUG_ON sauf si cred == real_cred) |
| Batterie de tests root | sondes en appels système bruts uniquement (uid/SELinux//proc/1//dev/kmsg/wifi/paquets//data/data/kallsyms) vers /data/local/tmp/.ghostlock_out |
| Shell root | execve("/system/bin/sh") — commit_creds dans le chemin d'exécution copie fake_cred dans un credential slab propre ; conditionné à ce que toutes les effacements prévus aient abouti |
rb_right = 0 effectue un unique store propre de 8 octets à zéro à une adresse noyau arbitraire (aucun store latéral, aucun déréférencement sauvage) ; mettre à zéro le premier qword de selinux_state donne un double permissif (enforcing=0 + !initialized → allow-all), ce qui débloque toutes les E/S de fichiers post-root malgré le SID du noyau&init_user_ns via perf depuis le chemin actif security_capable() et bascule vers le flux cred-only sauf si cela correspond à kaslr_base + off_init_user_nsucounts = NULL -- cap_capable correspond dès la première itération et inc_rlimit_ucounts se termine après une boucle, éliminant le besoin de faire fuiter &init_user_nsPERF_SAMPLE_TID garantissent que seuls les instantanés de registres du thread appelant sont comptés, empêchant les services système actifs de dominer le vote de mode5.15.170-android14-11-gf4a1f03072af (aarch64, PGO+BOLT+LTO, clang 17.0.2)CONFIG_FUTEX_PI=y, CONFIG_IO_URING=y, perf_event_paranoid=-1, SELinux enforcing, panic_on_oops=1, pas de namespaces utilisateurexport ANDROID_NDK_HOME=/path/to/android-ndk
make -j$(nproc)
Nécessite le Android NDK (testé avec r27). Produit un binaire aarch64 lié statiquement.
adb push ghostlock /data/local/tmp/
adb shell "cd /data/local/tmp && ./ghostlock --cred"
L'exploit prend environ 25 secondes (fuites KASLR/task/layout + heap spray + bruteforce KernelSnitch + trois tours d'overlay de 3 secondes). En cas de succès, il écrit la batterie root dans /data/local/tmp/.ghostlock_out, le marqueur dans /data/local/tmp/.ghostlock_root, et exécute /system/bin/sh avec uid=0 sous un noyau permissif.
Paramètres d'environnement :
GHOST_SELINUX=0 — ignore l'écriture SELinux, exécute la route cred-only éprouvée (mode 6)GHOST_SELINUX_FORCE=1 — arme l'écriture SELinux même si la validation de la disposition des symboles de l'appareil échoueGHOST_EXEC=0 — écrit la batterie et fait exit(99) au lieu d'exécuter un shellCRED_ATTEMPTS=n — nombre de nouvelles tentatives de full-spray lorsqu'une exécution rate la récupération (par défaut 3)Cet exploit a été porté et développé par Claude Opus 4.6 (Anthropic) avec GLM-5.3 (Z.ai) comme consultant en exploitation de noyau.