
Exploit d'élévation locale de privilèges ciblant CVE-2026-43499 pour le Xiaomi 17T Pro (warhol) sous Android 16 avec le SoC MediaTek MT6993.
CVE-2026-43499 (IonStack) — élévation de privilèges locale, portée sur le Xiaomi 17T Pro (warhol) — MediaTek MT6993, Android 16.
GKI 6.12 / android16 uniquement. L'overlay waiter, la disposition de pile
pselectet chaque offset de structure sont épinglés à cette branche, donc rien ici ne se transfère vers 6.6, 6.1 ou 5.10 — ceux-ci nécessitent une base construite pour eux.generate_target.pyrefuse tout autre banner plutôt que d'émettre un en-tête qui planterait sur l'appareil.
Statut : fonctionnel. Vérifié sur l'appareil le 2026-07-26 depuis un démarrage propre, avec les deux variantes de preloader —
uid=0(root) context=u:r:kernel:s0, SELinux permissif,suinstallé. Le root n'est pas persistant : relancez la ligneLD_PRELOADaprès chaque démarrage. La course pselect n'est pas fiable à 100 % : un échec peut faire paniquer le téléphone, et une nouvelle tentative après le redémarrage est normale. VoirWARHOL_PORT.mdpour le registre complet.
Cet appareil nécessite le correctif kernel-MTE. Le popsicle upstream standard ne peut pas le rooter — il boucle dans
kernel page retry N/12indéfiniment. Voir MTE du kernel.
Les builds existent en deux variantes de preloader, PRELOADER=retail (défaut) et PRELOADER=eng. Elles sélectionnent des répertoires cibles différents afin que l'une ne puisse pas écraser les adresses de l'autre — voir Variante de preloader.
Les informations ci-dessous ont été lues dans le package full-OTA retail — métadonnées, manifeste de charge utile A/B, et images boot/vendor_boot/dtbo extraites de payload.bin.
| Objet | Valeur | Source |
|---|---|---|
| Nom de code | warhol | pre-device=warhol |
| Empreinte build | Xiaomi/warhol_global/warhol:16/BP2A.250605.031.A3/OS3.0.304.0.WPSJPXM:user/release-keys | post-build |
| Incrémentale | OS3.0.304.0.WPSJPXM (JP global) | post-build-incremental |
| Android | 16, SDK 36 | post-sdk-level=36 |
| Correctif de sécurité | 2026-05-01 | post-security-patch-level |
| Type OTA | A/B (payload.bin, CrAU, 39 partitions) | ota-type=AB |
| SoC | MediaTek MT6993 (Dimensity 9500) | compatibles mediatek,mt6993-* dans le DTB vendor_boot ; cmdline bootopt=64S3,32N2,64N2 |
| Kernel | 6.12.38-android16-5-g1d46253471dd-ab15048002-4k, clang 19.0.1 | bannière lue depuis le boot.img extrait |
| image boot | en-tête v4, kernel 18 898 125 B, compressé LZ4-legacy, pas de ramdisk | tools/bootinfo.py |
Le kernel importe plus que le SoC ici : warhol se trouve sur la même branche GKI que popsicle upstream (android16-5, pages 4K), à un niveau de patch près — 6.12.38 contre le 6.12.23 vérifié de popsicle. Les offsets de structure doivent quand même être régénérés pour chaque build ; seule la forme de l'exploit se transmet. Un build OS3.0.x différent nécessite son propre target.h.
La chaîne d'exploit est verrouillée en version sur la branche GKI, pas sur le SoC. La disposition du waiter rt_mutex, l'overlay de pile pselect et les offsets de la structure pipe_buffer suivent tous le kernel, donc une base 6.12/android16 bat une base du même fournisseur sur une branche plus ancienne.
x-spy/CVE-2026-43499-popsicle est la seule implémentation publique vérifiée sur la ligne GKI 6.12/android16 (Xiaomi 17 / Pro / Ultra, kernel 6.12.23-android16-5), donc source/ en est tiré et conservé aussi proche d'upstream que possible — deux lignes mises à part (voir MTE du kernel, dont cet appareil a besoin pour fonctionner tout court).
L'essentiel du delta MediaTek se limite à la génération de cible au moment du build, en deux morceaux :
p0_phys_offset et p0_kernel_phys_load dans une partition Qualcomm xbl_config, que warhol ne possède pas. generate_target.py --dtb lit la base DRAM depuis le nœud /memory du FDT vendor_boot à la place, en l'arrondissant à l'inférieur comme le fait arm64_memblock_init. L'adresse de chargement physique du kernel est lue dans la réservation mb_kernel du preloader avec --preloader ; le delta sort à 0 pour cet appareil, et lk refuse de démarrer un kernel placé ailleurs.boot.img plutôt que comme une image arm64 Image nue, donc le générateur le décompresse avant analyse.WARHOL_PORT.md §2 contient la dérivation complète.
lk de MediaTek ne charge pas le kernel vers une constante de compilation — il cherche une réservation DRAM nommée mb_kernel et exige qu'elle atterrisse exactement dessus. Cette réservation est faite par le preloader, c'est pourquoi le build de preloader que le téléphone exécute est une entrée de build ici : c'est la seule chose en dehors de boot.img qui puisse déplacer P0_KERNEL_PHYS_LOAD, et une mauvaise valeur là signifie un mauvais alias de carte linéaire et un téléphone mort plutôt qu'un exploit échoué.
D'où deux variantes, conservées comme des répertoires cibles séparés :
PRELOADER= | Preloader sur le téléphone | Répertoire cible | Statut |
|---|---|---|---|
retail (défaut) | le preloader_<device>.bin livré, identique octet pour octet au preloader_raw.img de la ROM fastboot | targets/warhol-OS3.0.304.0.WPSJPXM/ | ✅ vérifié sur l'appareil le 2026-07-26 |
eng | le build engineering, preloader_<device>_eng.bin | targets/warhol-OS3.0.304.0.WPSJPXM-eng/ | ✅ vérifié sur l'appareil le 2026-07-26 |
make preload # PRELOADER=retail -> out/preload-<device>.so
make PRELOADER=eng preload # -> out/preload-<device>-eng.so
make both # both of the above, and print their sha256
Les deux artefacts sont conservés côte à côte sous des noms distincts. Sur ce firmware, ils sortent identiques octet pour octet — c'est le résultat de l'analyse ci-dessous, pas un raccourci pour contourner cette analyse, donc les deux sont toujours construits et nommés séparément.
Lisez vous-même les tables de disposition mémoire des deux preloaders avec :
python3 tools/preloader_memlayout.py <preloader.bin> --diff <preloader_eng.bin>
Pour ce firmware, les deux tables sont identiques, mb_kernel.start = 0x80000000 dans les deux, donc le target.h eng sort identique octet pour octet à celui retail et les deux builds produisent le même preload.so.
Ce qui atteint réellement l'exploit, c'est la différence P0_KERNEL_PHYS_LOAD − P0_PHYS_OFFSET, et ses deux termes ne reposent pas sur des preuves d'égale solidité :
P0_KERNEL_PHYS_LOAD — mesuré depuis l'image eng, comme mb_kernel.start ci-dessus.P0_PHYS_OFFSET — a été argumenté plutôt que mesuré, puisque c'est memstart_addr, que le kernel prend depuis le nœud /memory que lk émet à l'exécution à partir de ce que le preloader rapporte après l'init DRAM, pas depuis le FDT statique que le générateur lit. L'argument était que les deux builds partagent tout le chemin DRAM — mêmes sources dramc/emi/mblock/memory_layout, aucun code de disposition mémoire parmi les chaînes propres à eng — et que la seule réservation supplémentaire du preloader eng, une security_fe_rsv dynamique de 3 Mio avec mapping=1, ne peut ni déplacer une entrée à adresse fixe ni creuser un trou en bas de la DRAM. L'exécution sur l'appareil a tranché : les alias de carte linéaire ont atterri pour la variante eng, donc le terme est correct.Dérivation complète dans targets/warhol-OS3.0.304.0.WPSJPXM-eng/NOTES.md.
Notes de l'exécution sur l'appareil :