
Pesquisa sobre CVE-2026-43499 (GhostLock) no HUAWEI MatePad Pro 11 GOT-W29
Registro de pesquisa de escalonamento de privilégios para CVE-2026-43499 (Linux rtmutex/futex-PI UAF, "GhostLock") no HUAWEI MatePad Pro 11 GOT-W29 (Qualcomm kona / Snapdragon 870, HarmonyOS 4.x, kernel 4.19.157-perf+).
| Item | Valor |
|---|---|
| Modelo | HUAWEI MatePad Pro 11 GOT-W29 (tablet) |
| SoC | Qualcomm kona (SM8250, Snapdragon 870) |
| Sistema | HarmonyOS 4.2 (104.2.0.237C00); de fábrica: 4.0 (104.0.0.136) |
| Kernel | 4.19.157-perf+ (build de 2025-10-13) |
| VA | 39-bit, 4K pages, KASLR on |
CVE-2026-43499: o remove_waiter() em kernel/locking/rtmutex.c, no caminho de rollback de rt_mutex_start_proxy_lock(), faz a limpeza com current em vez de waiter->task, resultando em pi_blocked_on pendente (UAF na pilha). Afeta 2.6.39 ~ 7.1 (este kernel está dentro do intervalo). Correção upstream: commit 3bfdc63936dd.
Confirmado neste dispositivo: código-fonte rtmutex.c:1110-1112, decompilação do boot.elf e acionamento em dispositivo real — todos validados.
No shell (uid 2000) com perf_event_paranoid=-1, perf_event_open(PERF_SAMPLE_IP, exclude_user=1) amostra o cluster de endereços do texto do kernel; o slide é obtido pelo alinhamento com os deslocamentos de símbolos conhecidos.
samples=27651 kernel_ips=1685 lo=0xffffff948728176c hi=0xffffff9488ebfc7c
KASLR slide=0x147f200000 (40/40 IP 映射进内核文本区验证)
runtime _stext=0xffffff9487280800
Ferramenta: tools/perf_kaslr.c. Pré-requisitos de execução: shell (Shizuku rish), sem interceptação de seccomp.
Cria um ciclo de PI para fazer FUTEX_CMP_REQUEUE_PI retornar -EDEADLK; o rollback dispara o bug do remove_waiter.
Disposição-chave: o futex alvo do requeue é mantido pelo waiter que está sendo requeueado (futex2 = waiter_tid) → em task_blocks_on_rt_mutex, owner == task → -EDEADLK.
[M] CMP_REQUEUE_PI ret=-1 errno=35 (EDEADLK!)
[W] WAIT_REQUEUE_PI ret=-1 errno=110 (ETIMEDOUT) ← waiter 返回
[M] waiter_returned=1 ← 留下悬空 pi_blocked_on
Ferramenta: tools/edeadlk_probe.c (variante 8+2+1 = 11, ou 27).
O passo [7] de rt_mutex_adjust_prio_chain executa rb_erase (caminho de filho único à esquerda) no waiter falso: *(tree_left) = tree_pc (value→target) + escrita incremental em __rb_change_child. Todos os deslocamentos em target.h foram medidos na prática via decompilação do boot.elf.
Consulte target/got_w29_target.h. Pontos principais:
A decompilação de task_blocks_on_rt_mutex a partir do boot.elf é a prova cabal: o kernel deste dispositivo possui uma verificação antecipada de owner==task em 0x3808-0x3868 (cmp owner,task; b.eq -> -EDEADLK), que retorna antes da gravação de task->pi_blocked_on (0x38d4 str x21,[x20,#0xa90]). O acionador antigo do GOT-W29 fazia o waiter ter auto-posse via futex2=waiter_tid (self-own) → caía exatamente nessa verificação antecipada → nunca definia pi_blocked_on → nenhum ponteiro pendente. As observações no dispositivo (sem crash + boot_id inalterado) são totalmente consistentes com "sem ponteiro pendente" — a colocação do overlay foi um diagnóstico errado.
Acionamento correto (referência do smt878u, já implementado): ciclo de PI — o owner mantém o alvo do requeue via FUTEX_LOCK_PI(target); o waiter mantém o futex da cadeia (chain); o owner então bloqueia na cadeia (ciclo: waiter→target→owner→chain→waiter). Durante o requeue, a caminhada da cadeia detecta rt_mutex_owner(chain)==top_task (rtmutex step[6]) → -EDEADLK → no rollback, remove_waiter usa o current do requeuer para limpar a pessoa errada → o pi_blocked_on do waiter fica pendente. O owner precisa ter a prioridade reduzida (nice=10) para que, após o boost, a prio seja diferente de owner_waiter->prio; caso contrário, rt_mutex_waiter_equal sai antecipadamente.
Com shift=12, as words 6-7 (task/lock) do waiter falso caem em res_in[3..4] (região zerada pelo kernel). Aproveitando a semântica do do_select: res_in[i] = in[i] & POLLIN-ready. SLIDE_INIT_TASK / fake_lock são gravados em in[3]/in[4], e todos os fds correspondentes são duplicados com dup2 para a "extremidade de leitura de um pipe com dados" (sempre EPOLLIN-ready) → res_in[3]=init_task e res_in[4]=fake_lock são codificados com precisão. As words 3-5 (pi_tree) e 8-10 podem ser zero (o caminho de ownerless-lock não usa pi_tree; prio/deadline são sobrescritos pelo kernel no passo [7]). Como o pselect retorna imediatamente por causa do fd pronto, o waiter fica em busy-wait no espaço do usuário (sinais desativados, zero syscalls, impedindo que a reutilização da pilha do kernel apague o waiter falso) até o consumer concluir o disparo. A tabela HW_FUTEX_PI de 11 words, as duas classes de fd e o timeout de espera do processo pai já estão implementados (git diff).
O valor vazado de boot_id é um alias constante de direct-map (*(boot_id)=DM(loggers[0][1])); stext=leaked-p0_alias_image_offset(NFULNL_LOGGER) tem um off-by em relação a DM(_stext); na fase root, se todo o caminho for via physmap (espaço DM), é autoconsistente; caso contrário, é necessário usar o slide de runtime do perf_event_open (disponível sob rish).
Formato de escrita: o smt878u usa pi_tree (dequeue_pi); o caminho ownerless do GOT-W29 usa apenas tree (rt_mutex_dequeue) — esta correção usa o formato tree (tree_pc=LOGGERS, tree_left=BOOT_ID).
tools/cycle_probe (já compilado): validação de baixo custo do acionamento do ciclo EDEADLK; se, após o EDEADLK, um sched_setattr no waiter disparar um oops no consumer = ponteiro pendente presente + overlay aplicado.build_tools/deploy_test.sh e observar slide-kaslr-ok ou oops no consumer.tools/ 验证工具(perf KASLR, EDEADLK 探针, overlay 测试, kaslr.json)
target/ 全部实测偏移
exploit/ 移植的 slide.c(含 EDEADLK 触发改动)