
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.
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:
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:
ro.boot.verifiedbootstate=green e flash.locked=1 invariati. Era una questione aperta
finché non è stato provato; è decisa dall'hash della root key bruciata nell'efuse, e questa
build è firmata con le stesse chiavi della retail.%s META DIS è una stringa solo retail, mentre eng porta un'intera implementazione META che retail
non ha (Enable fastmeta., FAST META GPIO: %d, META_COM PORT: %d,
read_meta_proinfo, …). Qui si è avviato direttamente ad Android, ma se un telefono con
preloader eng finisse da qualche altra parte, questa è la causa probabile e nessun valore di
target.h è coinvolto.mb_kernel, i due target divergono — tenerli come
directory separate è ciò che impedisce che questo accada in silenzio.Questo kernel esegue KASAN_HW_TAGS — /proc/cmdline contiene
kasan.stack_ring_size=524288 — quindi i puntatori slab portano un tag di allocazione nei
bit 59:56. La popsicle upstream assume puntatori senza tag e quindi non può ottenere root su
questo dispositivo in alcun modo: resta in loop in [-] kernel page retry N/12 mode=1
all'infinito, senza che il log dica perché.
Due righe in source/ lo risolvono, ed entrambe sono in warhol-mte-fix.patch:
Solo i controlli rimuovono il tag. I puntatori stessi restano taggati, perché il tag è quello corretto per la memoria a cui puntano — che è esattamente ciò di cui ha bisogno una dereferenziazione da kernel.
Non basarti su arm64.memtag.bootctl per questo: quella proprietà è il controllo MTE di
userspace e non dice nulla sul fatto che il kernel stia taggando le proprie allocazioni.
Questo repository è un port. Il credito per la catena dell'exploit va all'upstream.
source/ exploit core (upstream popsicle + the kernel-MTE fix; see warhol-mte-fix.patch)
src/ main.c slide.c fops.c pipe.c util.c preload.c su_daemon.c + kernelsnitch/
targets/ per-build generated target.h, one dir per fingerprint x preloader variant
warhol-OS3.0.304.0.WPSJPXM/ PRELOADER=retail
warhol-OS3.0.304.0.WPSJPXM-eng/ PRELOADER=eng
tools/
payload_dump.py extract partitions from an A/B payload.bin (verifies size+sha256)
kernel_banner.py read the Linux banner out of a boot.img
bootinfo.py dump boot / vendor_boot header fields
generate_target.py target.h generator; upstream's, MediaTek-only, plus kernel decompression
preloader_memlayout.py dump/diff a MediaTek preloader's static DRAM reservation table
lz4legacy.py LZ4 legacy-frame decompressor (kernel images)
out/ build artifacts (gitignored)
Nulla viene compilato finché non esiste un target.h — il Makefile fallisce rumorosamente
piuttosto che emettere un binario contro il kernel sbagliato.
# 1. extract boot.img straight out of the OTA zip (payload.bin is stored uncompressed,
# so --base seeks into the zip; no need to unpack 7.6 GB first)
python3 tools/payload_dump.py <ota.zip> --base 5081 -p boot,vendor_boot,dtbo -o <dir>
# 2. confirm the kernel banner — every offset downstream is pinned to it
python3 tools/kernel_banner.py <dir>/boot.img
python3 tools/bootinfo.py <dir>/boot.img
# 3. generate the target header. --preloader reads the kernel's physical load address
# out of the preloader's mb_kernel reservation; pass the preloader the phone runs.
python3 tools/generate_target.py \
--boot <dir>/boot.img --dtb <dir>/vendor_boot.img --preloader <preloader.bin> \
-o targets/warhol-OS3.0.304.0.WPSJPXM/target.h
# 4. build
make preload # DEVICE=warhol-OS3.0.304.0.WPSJPXM, PRELOADER=retail by default
Senza avere a disposizione un'immagine del preloader, --kernel-phys-delta 0 è la forma più
vecchia e produce lo stesso header per questo firmware — ma è un'asserzione piuttosto che una
lettura, quindi preferisci --preloader. Passarli entrambi fa sì che il generatore li
incroci e fallisca in caso di discrepanza.
--base 5081 è l'offset del local header di payload.bin per questa specifica OTA; leggilo
da ota-property-files in META-INF/com/android/metadata per qualsiasi altro pacchetto.
Richiede un Android NDK (NDK_ROOT / ANDROID_NDK_HOME) e llvm-objdump.
# <device> is the target name: <fingerprint> for PRELOADER=retail, <fingerprint>-eng for eng
adb push out/preload-<device>.so /data/local/tmp/preload.so
adb shell chmod 0644 /data/local/tmp/preload.so
adb shell "LD_PRELOAD=/data/local/tmp/preload.so /system/bin/true"
adb shell "/data/local/tmp/su -c id"
cred e lo stato di SELinux vengono patchati in memoria, quindi questo va ripetuto dopo ogni
avvio. Non è richiesto lo sblocco del bootloader.
Per ricerca su hardware di tua proprietà. Eseguirlo può causare bootloop o brick del dispositivo e invaliderà la garanzia. Nessuna garanzia di alcun tipo.
I progetti upstream sono licenziati dai rispettivi autori; questo port mantiene i loro termini.
| 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 |
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 |
| File | Modifica | Perché |
|---|
src/util.c | kernelsnitch_setup(..., mte_enabled=1) | con 0, la ricerca di mm_struct prova solo candidati senza tag e non può mai raggiungere un puntatore con tag — un errore strutturale, non sfortuna |
src/util.c | is_kernel_ptr() / is_direct_ptr() rimuovono il tag prima del controllo dell'intervallo | un puntatore con tag letto dalla memoria del kernel altrimenti viene rifiutato come non-in-linear-map (direct-entry-fatal reason=bad-task-or-cpu) |
src/kernelsnitch/kernelsnitch.h | sweep dei tag < 15 → < 16 | il tag 0xf è il puntatore senza tag/match-all, quindi lo sweep ora copre anche un kernel senza MTE — mte_enabled=1 è sicuro in entrambi i casi |
| Repository | Ruolo qui |
|---|
| https://github.com/MobiusM/CVE-2026-43499 | PoC / trigger di crash originale di CVE-2026-43499 |
| https://github.com/x-spy/CVE-2026-43499-popsicle | Base di questo port. Xiaomi 17 Pro Max (popsicle), kernel 6.12.23-android16-5; source/ e tools/generate_target.py vengono da qui |
| https://github.com/Kananosa/CVE-2026-43499-For-Xiaomi-17T-chagall | Dispositivo sibling più vicino (Xiaomi 17T, chagall, anch'esso MediaTek). Derivato da popsicle, include un preload.so precompilato — le sue costanti compilate hanno confermato l'offset del caricamento fisico |
| https://github.com/MiCode/Xiaomi_Kernel_OpenSource | Sorgenti del kernel Xiaomi, per il cross-check del layout delle struct |