
Investigación sobre CVE-2026-43499 (GhostLock) en HUAWEI MatePad Pro 11 GOT-W29
Registro de investigación de escalada de privilegios para CVE-2026-43499 (Linux rtmutex/futex-PI UAF, "GhostLock") en HUAWEI MatePad Pro 11 GOT-W29
(Qualcomm kona / Snapdragon 870, HarmonyOS 4.x, kernel
4.19.157-perf+).
| Campo | 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+ (2025-10-13 build) |
| VA | 39-bit, 4K pages, KASLR on |
CVE-2026-43499: remove_waiter() en kernel/locking/rtmutex.c en la
ruta de reversión de rt_mutex_start_proxy_lock() limpia con current en lugar de waiter->task,
lo que provoca un pi_blocked_on colgante (UAF de pila). Afecta a 2.6.39 ~ 7.1 (este kernel está dentro del rango).
Corrección upstream: commit 3bfdc63936dd.
Confirmado en este dispositivo: código fuente rtmutex.c:1110-1112, descompilación de boot.elf y disparo en hardware real, todo verificado.
Con shell (uid 2000) y perf_event_paranoid=-1, perf_event_open(PERF_SAMPLE_IP, exclude_user=1) muestrea un clúster de direcciones del texto del kernel; alineando con los desplazamientos de símbolos conocidos se obtiene el slide.
samples=27651 kernel_ips=1685 lo=0xffffff948728176c hi=0xffffff9488ebfc7c
KASLR slide=0x147f200000 (40/40 IP 映射进内核文本区验证)
runtime _stext=0xffffff9487280800
Herramienta: tools/perf_kaslr.c. Requisito de ejecución: shell (Shizuku rish), sin intercepción de seccomp.
Crea un ciclo PI para que FUTEX_CMP_REQUEUE_PI devuelva -EDEADLK; la reversión dispara el bug de remove_waiter.
Disposición clave: el futex objetivo del requeue está en manos del waiter que se va a requeue (futex2 = waiter_tid) → en 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
Herramienta: tools/edeadlk_probe.c (variant 8+2+1 = 11, o 27).
En el paso [7] de rt_mutex_adjust_prio_chain, se hace rb_erase sobre un fake waiter (ruta de un solo hijo izquierdo):
*(tree_left) = tree_pc (value→target) + escritura incremental con __rb_change_child. En target.h,
todos los desplazamientos se midieron mediante desensamblado de boot.elf.
Ver target/got_w29_target.h. Puntos clave:
El desensamblado de task_blocks_on_rt_mutex en boot.elf lo confirma: este kernel de dispositivo tiene en 0x3808-0x3868
una comprobación anticipada de owner==task (cmp owner,task; b.eq -> -EDEADLK), que
retorna antes de la escritura de task->pi_blocked_on (0x38d4 str x21,[x20,#0xa90]).
El disparador antiguo de GOT-W29 hacía que el waiter se auto-retuviera futex2=waiter_tid (self-own) → justo caía en esa comprobación
anticipada → nunca se establecía pi_blocked_on → sin puntero colgante. La observación en el dispositivo (sin crash + boot_id sin cambios)
coincide plenamente con "sin colgante": la colocación del overlay era un diagnóstico erróneo.
Disparo correcto (referencia smt878u, ya implementado): ciclo PI — el owner retiene FUTEX_LOCK_PI(target) (el objetivo
del requeue); el waiter retiene el futex de la cadena; el owner se bloquea a su vez en la cadena (ciclo: waiter→target→
owner→chain→waiter). Al hacer el requeue, la cadena recorre la detección rt_mutex_owner(chain)==top_task
(rtmutex step[6]) → -EDEADLK → la reversión de remove_waiter limpia a la persona equivocada usando el current del requeuer
→ el pi_blocked_on del waiter queda colgante. El owner necesita reducir su prioridad (nice=10) para que, tras el boost,
su prio sea distinto de owner_waiter->prio; de lo contrario, rt_mutex_waiter_equal sale antes de tiempo.
Con shift=12, las palabras 6-7 (task/lock) del fake waiter caen en res_in[3..4] (zona que el kernel pone a cero).
Aprovechando la semántica de do_select: res_in[i] = in[i] & POLLIN-ready. Se escribe SLIDE_INIT_TASK /
fake_lock en in[3]/in[4], y todos los fd correspondientes se hacen dup2 al "extremo de lectura de una tubería con datos" (siempre
EPOLLIN-ready) → res_in[3]=init_task, res_in[4]=fake_lock quedan codificados con precisión. Las palabras 3-5
(pi_tree) y 8-10 pueden ser cero (la ruta ownerless-lock no usa pi_tree; prio/deadline los sobrescribe el kernel en
step[7]). pselect regresa inmediatamente por el fd ready → el waiter hace espera ocupada en modo usuario (señales deshabilitadas, cero
syscalls, para evitar que la reutilización de la pila del kernel borre el fake waiter) hasta que el consumer complete el disparo. La tabla 11-word HW_FUTEX_PI
y la clase de doble fd y el tiempo de espera del proceso padre ya están implementados (git diff).
*(boot_id)=DM(loggers[0][1]));
stext=leaked-p0_alias_image_offset(NFULNL_LOGGER) tiene un off-by con DM(_stext);
si la fase root usa physmap (espacio DM) en todo momento es autoconsistente; de lo contrario, hay que usar el slide en tiempo de ejecución
de perf_event_open (disponible bajo rish).tools/cycle_probe (ya compilado): verificación barata del disparo de EDEADLK por ciclo; si tras EDEADLK se hace sched_setattr al waiter
y se provoca un oops del consumer = existe el colgante + el overlay aterrizó.build_tools/deploy_test.sh y observar slide-kaslr-ok o consumer oops.tools/ 验证工具(perf KASLR, EDEADLK 探针, overlay 测试, kaslr.json)
target/ 全部实测偏移
exploit/ 移植的 slide.c(含 EDEADLK 触发改动)