
Exploit di escalation dei privilegi locale che prende di mira la CVE-2026-43499 per Xiaomi 17T Pro (warhol) su Android 16 con SoC MediaTek MT6993.
CVE-2026-43499 (IonStack) elevazione dei privilegi locale, portata su Xiaomi 17T Pro (warhol) — MediaTek MT6993, Android 16.
Solo GKI 6.12 / android16. L'overlay del waiter, il layout dello stack di
pselecte ogni offset di struct sono ancorati a quel ramo, quindi nulla qui è trasferibile a 6.6, 6.1 o 5.10 — quelli richiedono una base costruita per loro.generate_target.pyrifiuta qualsiasi altro banner piuttosto che emettere un header che farebbe fault sul dispositivo.
Stato: funzionante. Verificato su dispositivo il 2026-07-26 da un avvio pulito, con entrambe le varianti del preloader —
uid=0(root) context=u:r:kernel:s0, SELinux permissive,suinstallato. Il root non è persistente: riesegui la rigaLD_PRELOADdopo ogni avvio. La race dipselectnon è al 100%: un'esecuzione fallita può causare un panic del telefono, e un nuovo tentativo dopo il riavvio è normale. VediWARHOL_PORT.mdper il registro completo.
Questo dispositivo richiede la correzione kernel-MTE. La popsicle upstream originale non riesce a ottenere root — resta bloccata in
kernel page retry N/12all'infinito. Vedi Kernel MTE.
Le build sono disponibili in due varianti di preloader, PRELOADER=retail (predefinita) e
PRELOADER=eng. Selezionano directory target diverse, così nessuna può sovrascrivere gli
indirizzi dell'altra — vedi Variante del preloader.
Le informazioni seguenti sono state lette dal pacchetto OTA completo retail — metadati, il
manifest del payload A/B e le immagini boot/vendor_boot/dtbo estratte da payload.bin.
| Voce | Valore | Sorgente |
|---|---|---|
| Codename | warhol | pre-device=warhol |
| Build fingerprint | Xiaomi/warhol_global/warhol:16/BP2A.250605.031.A3/OS3.0.304.0.WPSJPXM:user/release-keys | post-build |
| Incremental | OS3.0.304.0.WPSJPXM (JP globale) | post-build-incremental |
| Android | 16, SDK 36 | post-sdk-level=36 |
| Patch di sicurezza | 2026-05-01 | post-security-patch-level |
| Tipo OTA | A/B (payload.bin, CrAU, 39 partizioni) | ota-type=AB |
| SoC | MediaTek MT6993 (Dimensity 9500) | compatibili mediatek,mt6993-* nel DTB di vendor_boot; cmdline bootopt=64S3,32N2,64N2 |
| Kernel | 6.12.38-android16-5-g1d46253471dd-ab15048002-4k, clang 19.0.1 | banner letto dalla boot.img estratta |
| boot image | header v4, kernel 18.898.125 B, compresso LZ4-legacy, nessun ramdisk | tools/bootinfo.py |
Il kernel conta più del SoC qui: warhol si trova sullo stesso ramo GKI della popsicle
upstream (android16-5, pagine 4K), a un patchlevel di distanza — 6.12.38 rispetto alla
verificata 6.12.23 della popsicle. Gli offset delle struct vanno comunque rigenerati per
ogni build; solo la forma dell'exploit si trasferisce. Una build OS3.0.x diversa richiede
il proprio target.h.
La catena dell'exploit è bloccata alla versione del ramo GKI, non al SoC. Il layout del
waiter di rt_mutex, l'overlay dello stack di pselect e gli offset della struct
pipe_buffer seguono tutti il kernel, quindi una base 6.12/android16 batte una base dello
stesso vendor su un ramo più vecchio.
x-spy/CVE-2026-43499-popsicle è l'unica implementazione pubblica verificata sulla linea
GKI 6.12/android16 (Xiaomi 17 / Pro / Ultra, kernel 6.12.23-android16-5), quindi source/
è presa da essa e mantenuta il più vicino possibile all'upstream — a parte due righe
(vedi Kernel MTE, di cui questo dispositivo ha bisogno per funzionare del
tutto).
La maggior parte della differenza MediaTek è confinata alla generazione del target a build-time, in due parti:
p0_phys_offset e p0_kernel_phys_load da una partizione Qualcomm
xbl_config, che warhol non ha. generate_target.py --dtb legge invece la base della
DRAM dal nodo /memory del FDT di vendor_boot, arrotondandola per difetto come fa
arm64_memblock_init. L'indirizzo fisico di caricamento del kernel viene letto dalla
prenotazione mb_kernel del preloader con --preloader; il delta risulta 0 per questo
dispositivo, e lk rifiuta di avviare un kernel posizionato altrove.boot.img invece che come
Image arm64 nuda, quindi il generatore lo decompressa prima dell'analisi.WARHOL_PORT.md §2 contiene la derivazione completa.
L'lk di MediaTek non carica il kernel a una costante di compilazione — cerca una prenotazione
DRAM chiamata mb_kernel e verifica che sia finita esattamente lì. Quella prenotazione è
fatta dal preloader, ed è per questo che la build del preloader in esecuzione sul telefono
è un input di build: è l'unica cosa al di fuori di boot.img che può spostare
P0_KERNEL_PHYS_LOAD, e un valore sbagliato lì significa un alias sbagliato della mappa
lineare e un telefono morto, non un exploit fallito.
Da qui due varianti, mantenute come directory target separate:
PRELOADER= | Preloader sul telefono | Directory target | Stato |
|---|---|---|---|
retail (predefinito) | il preloader_<device>.bin distribuito, byte-identico al preloader_raw.img della ROM fastboot | targets/warhol-OS3.0.304.0.WPSJPXM/ | ✅ verificato su dispositivo 2026-07-26 |
eng | la build engineering, preloader_<device>_eng.bin | targets/warhol-OS3.0.304.0.WPSJPXM-eng/ | ✅ verificato su dispositivo 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
Entrambi gli artefatti sono mantenuti affiancati con nomi distinti. Su questo firmware risultano byte-identici — questo è il risultato dell'analisi sotto, non una scorciatoia, quindi i due sono comunque compilati e nominati separatamente.
Leggi tu stesso le tabelle del memory layout dei due preloader con:
python3 tools/preloader_memlayout.py <preloader.bin> --diff <preloader_eng.bin>
Per questo firmware le due tabelle sono identiche, mb_kernel.start = 0x80000000 in
entrambe, quindi il target.h eng risulta byte-identico a quello retail ed entrambe le build
producono lo stesso preload.so.
Ciò che arriva davvero all'exploit è la differenza
P0_KERNEL_PHYS_LOAD − P0_PHYS_OFFSET, e i suoi due termini non poggiano su prove
ugualmente solide:
P0_KERNEL_PHYS_LOAD — misurato dall'immagine eng, come mb_kernel.start sopra.P0_PHYS_OFFSET — è stato dedotto più che misurato, poiché è memstart_addr, che il
kernel prende dal nodo /memory che lk emette a runtime da ciò che il preloader riporta
dopo l'inizializzazione della DRAM, non dal FDT statico che il generatore legge.
L'argomento era che le due build condividono l'intero percorso DRAM — stesse sorgenti
dramc/emi/mblock/memory_layout, nessun codice di memory layout tra le stringhe
solo-eng — e che l'unica prenotazione extra del preloader eng, un security_fe_rsv
dinamico da 3 MiB con mapping=1, non può né spostare una voce a indirizzo fisso né
scavare un buco in fondo alla DRAM. L'esecuzione sul dispositivo lo ha risolto: gli
alias della mappa lineare sono finiti sotto eng, quindi il termine è corretto.Derivazione completa in
targets/warhol-OS3.0.304.0.WPSJPXM-eng/NOTES.md.
Note dall'esecuzione sul dispositivo: