
Exploit de escalonamento de privilégio local direcionado à CVE-2026-43499 para o Xiaomi 17T Pro (warhol) no Android 16 com SoC MediaTek MT6993.
Escalada de privilégios local CVE-2026-43499 (IonStack), portada para o Xiaomi 17T Pro
(warhol) — MediaTek MT6993, Android 16.
Apenas GKI 6.12 / android16. O overlay do waiter, o layout da pilha do
pselecte todos os offsets de struct estão fixados a esse branch, então nada aqui é transferível para 6.6, 6.1 ou 5.10 — esses precisam de uma base construída para eles.generate_target.pyrecusa qualquer outro banner em vez de emitir um header que causaria fault no dispositivo.
Status: funcionando. Verificado no dispositivo em 2026-07-26 a partir de uma inicialização limpa, com ambas as variantes de preloader —
uid=0(root) context=u:r:kernel:s0, SELinux permissivo,suinstalado. O root não é persistente: execute novamente a linhaLD_PRELOADapós cada boot. A corrida do pselect não é 100%: uma execução com falha pode causar panic no telefone, e uma nova tentativa após o reboot é normal. ConsulteWARHOL_PORT.mdpara o registro completo.
Este dispositivo precisa da correção do kernel-MTE. O popsicle upstream padrão não consegue fazer root nele — ele fica em loop em
kernel page retry N/12para sempre. Consulte Kernel MTE.
Os builds vêm em duas variantes de preloader, PRELOADER=retail (padrão) e PRELOADER=eng.
Elas selecionam diretórios de target diferentes para que nenhuma possa sobrescrever os endereços da outra —
consulte Variante de preloader.
Os fatos abaixo foram lidos do pacote full-OTA retail — metadados, o manifesto do payload
A/B e as imagens boot/vendor_boot/dtbo extraídas do payload.bin.
Aqui o kernel importa mais do que o SoC: o warhol está no mesmo branch GKI do
popsicle upstream (android16-5, páginas de 4K), com um patchlevel de diferença — 6.12.38 contra
o 6.12.23 verificado do popsicle. Os offsets de struct ainda precisam ser regenerados por build; apenas
o formato do exploit é reaproveitado. Um build OS3.0.x diferente precisa do seu próprio target.h.
A cadeia do exploit está travada por versão ao branch GKI, não ao SoC. O layout do waiter
do rt_mutex, o overlay de pilha do pselect e os offsets da struct pipe_buffer acompanham o
kernel, então uma base 6.12/android16 supera uma base do mesmo fornecedor em um branch mais antigo.
x-spy/CVE-2026-43499-popsicle é a única implementação pública verificada na linha
GKI 6.12/android16 (Xiaomi 17 / Pro / Ultra, kernel 6.12.23-android16-5), então
source/ foi retirada dela e mantida o mais próxima possível do upstream — com exceção de duas linhas
(consulte Kernel MTE, sem a qual este dispositivo simplesmente não funciona).
A maior parte do delta da MediaTek está limitada à geração de target em tempo de build, em duas partes:
p0_phys_offset e p0_kernel_phys_load de uma partição xbl_config
da Qualcomm, que o warhol não possui. generate_target.py --dtb lê a base da DRAM
do nó /memory do FDT do vendor_boot em vez disso, arredondando para baixo da mesma forma
que arm64_memblock_init faz. O endereço de carga física do kernel é lido da
reserva mb_kernel do preloader com --preloader; o delta resulta em 0 para este
dispositivo, e o lk se recusa a dar boot em um kernel colocado em qualquer outro lugar.boot.img, em vez de um
Image arm64 puro, então o gerador o descompacta antes da análise.WARHOL_PORT.md §2 tem a derivação completa.
O lk da MediaTek não carrega o kernel para uma constante de tempo de compilação — ele procura uma
reserva de DRAM chamada mb_kernel e valida que ela caiu exatamente nela. Essa reserva é feita
pelo preloader, e é por isso que o build do preloader que o telefone está executando é uma
entrada de build aqui em primeiro lugar: é a única coisa fora do boot.img que pode mover
P0_KERNEL_PHYS_LOAD, e um valor errado ali significa um alias errado do mapa linear e um telefone
morto, em vez de um exploit com falha.
Daí duas variantes, mantidas como diretórios de target separados:
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
Ambos os artefatos são mantidos lado a lado com nomes distintos. Neste firmware eles saem idênticos byte a byte — esse é o resultado da análise abaixo, não um atalho para contorná-la, então os dois ainda são compilados e nomeados separadamente.
Leia as tabelas de layout de memória dos dois preloaders você mesmo com:
python3 tools/preloader_memlayout.py <preloader.bin> --diff <preloader_eng.bin>
Neste firmware as duas tabelas são idênticas, mb_kernel.start = 0x80000000 em ambas,
então o target.h do eng sai idêntico byte a byte ao do retail e ambos os builds produzem o
mesmo preload.so.
O que de fato chega ao exploit é a diferença
P0_KERNEL_PHYS_LOAD − P0_PHYS_OFFSET, e seus dois termos não se apoiam em evidências
igualmente sólidas:
P0_KERNEL_PHYS_LOAD — medido a partir da imagem eng, como mb_kernel.start acima.P0_PHYS_OFFSET — foi argumentado em vez de medido, já que é memstart_addr,
que o kernel obtém do nó /memory que o lk emite em tempo de execução a partir do que o
preloader reporta após a inicialização da DRAM, não do FDT estático que o gerador lê.
O argumento foi que os dois builds compartilham todo o caminho da DRAM — mesmas fontes
dramc/emi/mblock/memory_layout, nenhum código de layout de memória entre as strings
exclusivas do eng — e que a única reserva extra do preloader eng, uma security_fe_rsv
dinâmica de 3 MiB com mapping=1, não pode deslocar uma entrada de endereço fixo nem abrir
um buraco no fundo da DRAM. A execução no dispositivo resolveu a questão: os aliases do
mapa linear bateram com o eng, então o termo está correto.Derivação completa em targets/warhol-OS3.0.304.0.WPSJPXM-eng/NOTES.md.
Notas da execução no dispositivo:
ro.boot.verifiedbootstate=green e flash.locked=1 inalterados. Essa era uma questão em
aberto até ser testada; ela é decidida pelo hash da chave raiz gravado no efuse, e este build é
assinado com as mesmas chaves do retail.%s META DIS é uma
string exclusiva do retail, enquanto o eng carrega uma implementação META completa que o retail não tem
(Enable fastmeta., FAST META GPIO: %d, META_COM PORT: %d, read_meta_proinfo, …).
Aqui ele iniciou direto no Android, mas se um telefone gravado com o eng algum dia cair em outro lugar,
essa é a causa provável e nenhum valor de target.h está envolvido.mb_kernel, os dois targets divergem — mantê-los como diretórios
separados é o que impede que isso passe despercebido.Este kernel executa KASAN_HW_TAGS — /proc/cmdline carrega kasan.stack_ring_size=524288
— então ponteiros de slab carregam uma tag de alocação nos bits 59:56. O popsicle upstream assume ponteiros
sem tag e, portanto, não consegue fazer root neste dispositivo de forma alguma: ele fica em loop em
[-] kernel page retry N/12 mode=1 indefinidamente, sem nada no log explicando o motivo.
Duas linhas em source/ corrigem isso, e ambas estão em warhol-mte-fix.patch:
Apenas as verificações removem a tag. Os ponteiros em si permanecem com tag, porque a tag é a correta para a memória para a qual apontam — que é exatamente o que uma dereferência deles pelo kernel precisa.
Não consulte arm64.memtag.bootctl para isso: essa propriedade é o controle de MTE do userspace
e não diz nada sobre se o kernel está aplicando tags às próprias alocações.
Este repositório é um port. O crédito pela cadeia do exploit pertence ao 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)
Nada é compilado até que um target.h exista — o Makefile falha ruidosamente em vez de emitir um
binário contra o kernel errado.
# 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
Sem uma imagem de preloader em mãos, --kernel-phys-delta 0 é a forma mais antiga e gera o
mesmo header para este firmware — mas é uma afirmação em vez de uma leitura, então prefira
--preloader. Passar ambos faz o gerador fazer a verificação cruzada e falhar em caso de divergência.
--base 5081 é o offset do header local do payload.bin para este OTA específico; leia-o
de ota-property-files em META-INF/com/android/metadata para qualquer outro pacote.
Requer um 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 o estado do SELinux são alterados em memória, então isso precisa ser repetido após cada
boot. Não é necessário desbloquear o bootloader.
Para pesquisa em hardware que você possui. Executar isso pode causar bootloop ou brickar um dispositivo e invalidará a garantia. Nenhuma garantia de qualquer tipo.
Os projetos upstream são licenciados por seus respectivos autores; este port carrega seus termos.
| Item | Valor | Fonte |
|---|
| Codinome | warhol | pre-device=warhol |
| Fingerprint do build | 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 global) | post-build-incremental |
| Android | 16, SDK 36 | post-sdk-level=36 |
| Patch de segurança | 2026-05-01 | post-security-patch-level |
| Tipo de OTA | A/B (payload.bin, CrAU, 39 partições) | ota-type=AB |
| SoC | MediaTek MT6993 (Dimensity 9500) | compatíveis mediatek,mt6993-* no DTB do vendor_boot; cmdline bootopt=64S3,32N2,64N2 |
| Kernel | 6.12.38-android16-5-g1d46253471dd-ab15048002-4k, clang 19.0.1 | banner lido do boot.img extraído |
| Imagem de boot | header v4, kernel 18,898,125 B, compactado com LZ4-legacy, sem ramdisk | tools/bootinfo.py |
PRELOADER= | Preloader no telefone | Diretório de target | Status |
|---|
retail (padrão) | o preloader_<device>.bin de fábrica, byte idêntico ao preloader_raw.img da ROM de fastboot | targets/warhol-OS3.0.304.0.WPSJPXM/ | ✅ verificado no dispositivo em 2026-07-26 |
eng | o build de engenharia, preloader_<device>_eng.bin | targets/warhol-OS3.0.304.0.WPSJPXM-eng/ | ✅ verificado no dispositivo em 2026-07-26 |
| Arquivo | Alteração | Motivo |
|---|
src/util.c | kernelsnitch_setup(..., mte_enabled=1) | com 0, a busca do mm_struct só tenta candidatos sem tag e nunca alcança um ponteiro com tag — um erro estrutural, não azar |
src/util.c | is_kernel_ptr() / is_direct_ptr() removem a tag antes da verificação de intervalo | um ponteiro com tag lido da memória do kernel é, caso contrário, rejeitado como fora do mapa linear (direct-entry-fatal reason=bad-task-or-cpu) |
src/kernelsnitch/kernelsnitch.h | varredura de tag < 15 → < 16 | a tag 0xf é o ponteiro sem tag/que casa com tudo, então a varredura agora também cobre um kernel sem MTE — mte_enabled=1 é seguro de qualquer forma |
| Repositório | Papel aqui |
|---|
| https://github.com/MobiusM/CVE-2026-43499 | PoC / disparador de crash original do CVE-2026-43499 |
| https://github.com/x-spy/CVE-2026-43499-popsicle | Base deste port. Xiaomi 17 Pro Max (popsicle), kernel 6.12.23-android16-5; source/ e tools/generate_target.py vêm daqui |
| https://github.com/Kananosa/CVE-2026-43499-For-Xiaomi-17T-chagall | Dispositivo irmão mais próximo (Xiaomi 17T, chagall, também MediaTek). Derivado do popsicle, traz um preload.so pré-compilado — suas constantes embutidas confirmaram o offset de carga física |
| https://github.com/MiCode/Xiaomi_Kernel_OpenSource | Fontes do kernel da Xiaomi, para verificação cruzada do layout das structs |