
Исследование CVE-2026-43499 (GhostLock) на HUAWEI MatePad Pro 11 GOT-W29
Запись исследования повышения привилегий для CVE-2026-43499 (Linux rtmutex/futex-PI UAF, «GhostLock»)
на HUAWEI MatePad Pro 11 GOT-W29 (Qualcomm kona / Snapdragon 870, HarmonyOS 4.x, ядро
4.19.157-perf+).
| Параметр | Значение |
|---|---|
| Модель | HUAWEI MatePad Pro 11 GOT-W29 (планшет) |
| SoC | Qualcomm kona (SM8250, Snapdragon 870) |
| Система | HarmonyOS 4.2 (104.2.0.237C00), заводская 4.0 (104.0.0.136) |
| Ядро | 4.19.157-perf+ (сборка 2025-10-13) |
| VA | 39-bit, 4K pages, KASLR on |
CVE-2026-43499: remove_waiter() в kernel/locking/rtmutex.c на пути отката
rt_mutex_start_proxy_lock() выполняет очистку с помощью current вместо waiter->task,
что приводит к висячему pi_blocked_on (стековый UAF). Затрагивает 2.6.39 ~ 7.1 (данное ядро в диапазоне).
Исправление в апстриме: commit 3bfdc63936dd.
Подтверждено на данном устройстве: исходный код rtmutex.c:1110-1112, декомпиляция boot.elf, срабатывание на реальном устройстве — всё проверено.
В shell (uid 2000) при perf_event_paranoid=-1 perf_event_open(PERF_SAMPLE_IP, exclude_user=1) сэмплирует кластеры адресов в тексте ядра; выравнивание по известным смещениям символов даёт slide.
samples=27651 kernel_ips=1685 lo=0xffffff948728176c hi=0xffffff9488ebfc7c
KASLR slide=0x147f200000 (40/40 IP 映射进内核文本区验证)
runtime _stext=0xffffff9487280800
Инструмент: tools/perf_kaslr.c. Условия запуска: shell (Shizuku rish), без перехвата seccomp.
Создание PI-цикла, заставляющего FUTEX_CMP_REQUEUE_PI вернуть -EDEADLK; откат запускает баг remove_waiter.
Ключевая комбинация: целевой futex requeue удерживается самим перемещаемым waiter'ом (futex2 = waiter_tid) → в 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
Инструмент: tools/edeadlk_probe.c (variant 8+2+1 = 11 или 27).
rt_mutex_adjust_prio_chain, step[7], выполняет rb_erase для fake waiter (путь с одним левым потомком):
*(tree_left) = tree_pc (value→target) + инкрементальная запись через __rb_change_child. Все смещения
в target.h измерены по дизассемблированному boot.elf.
См. target/got_w29_target.h. Основное:
Декомпиляция boot.elf для task_blocks_on_rt_mutex подтверждает: ядро данного устройства по адресам 0x3808-0x3868
содержит преждевременную проверку owner==task (cmp owner,task; b.eq -> -EDEADLK), которая
возвращает управление до записи task->pi_blocked_on (0x38d4 str x21,[x20,#0xa90]).
Старый триггер GOT-W29 заставлял waiter удерживать собственный futex2=waiter_tid (self-own) → ровно попадал в эту
преждевременную проверку → pi_blocked_on никогда не устанавливался → висячего указателя нет. Наблюдения на устройстве (нет краха + boot_id не меняется)
полностью согласуются с «нет висячего указателя» — размещение overlay было ошибочным диагнозом.
Правильный триггер (по образцу smt878u, реализован): PI-цикл — owner через FUTEX_LOCK_PI(target) удерживает
цель requeue; waiter удерживает chain futex; owner затем блокируется на chain (цикл: waiter→target→
owner→chain→waiter). При requeue цепочка доходит до проверки rt_mutex_owner(chain)==top_task
(rtmutex step[6]) → -EDEADLK → при откате remove_waiter, используя current requeuer'а,
очищает не тот task → pi_blocked_on у waiter остаётся висячим. Owner необходимо понизить в приоритете (nice=10), чтобы после boost
его prio отличался от owner_waiter->prio, иначе rt_mutex_waiter_equal выйдет преждевременно.
При shift=12 слова 6-7 (task/lock) fake waiter попадают в res_in[3..4] (область, обнуляемую ядром).
Используется семантика do_select: res_in[i] = in[i] & POLLIN-ready. SLIDE_INIT_TASK /
fake_lock записываются в in[3]/in[4], а все соответствующие fd дублируются через dup2 на «читающий конец pipe с данными» (всегда
EPOLLIN-ready) → res_in[3]=init_task, res_in[4]=fake_lock кодируются точно. Слова 3-5
(pi_tree) и 8-10 могут быть нулевыми (путь ownerless-lock не использует pi_tree; prio/deadline перезаписываются ядром на
step[7]). pselect немедленно возвращается из-за ready fd → waiter выполняет busy-wait в пользовательском режиме (запрещены сигналы, ноль
syscall — чтобы повторное использование стекового фрейма ядром не затёрло fake waiter), пока consumer не завершит срабатывание. Таблица HW_FUTEX_PI
из 11 слов, класс двойного fd, ожидание родителя с таймаутом — всё реализовано (git diff).
*(boot_id)=DM(loggers[0][1]));
stext=leaked-p0_alias_image_offset(NFULNL_LOGGER) имеет off-by относительно DM(_stext);
если на этапе root весь путь идёт через physmap (пространство DM), всё самосогласовано, иначе нужно использовать runtime slide из
perf_event_open (доступен под rish).tools/cycle_probe (скомпилирован): дешёвая проверка срабатывания цикла EDEADLK;
если после EDEADLK вызвать sched_setattr для waiter и это вызовет consumer oops —
висячий указатель существует + overlay применился.build_tools/deploy_test.sh, смотреть slide-kaslr-ok или consumer oops.tools/ 验证工具(perf KASLR, EDEADLK 探针, overlay 测试, kaslr.json)
target/ 全部实测偏移
exploit/ 移植的 slide.c(含 EDEADLK 触发改动)