
Recherche sur CVE-2026-43499 (GhostLock) concernant HUAWEI MatePad Pro 11 GOT-W29
Notes de recherche sur l'élévation de privilèges de CVE-2026-43499 (UAF rtmutex/futex-PI sous Linux, « GhostLock ») sur HUAWEI MatePad Pro 11 GOT-W29 (Qualcomm kona / Snapdragon 870, HarmonyOS 4.x, kernel 4.19.157-perf+).
| Champ | Valeur |
|---|---|
| Modèle | HUAWEI MatePad Pro 11 GOT-W29 (tablet) |
| SoC | Qualcomm kona (SM8250, Snapdragon 870) |
| Système | HarmonyOS 4.2 (104.2.0.237C00), version d'usine 4.0 (104.0.0.136) |
| Kernel | 4.19.157-perf+ (build du 2025-10-13) |
| VA | 39-bit, pages 4K, KASLR activé |
CVE-2026-43499 : remove_waiter() dans kernel/locking/rtmutex.c nettoie avec current au lieu de waiter->task dans le chemin de rollback de rt_mutex_start_proxy_lock(), ce qui conduit à un pi_blocked_on pendant (UAF de pile). Impacte 2.6.39 ~ 7.1 (ce noyau se trouve dans la plage concernée). Correctif en amont : commit 3bfdc63936dd.
Confirmé sur cet appareil : code source rtmutex.c:1110-1112, décompilation boot.elf, déclenchement sur appareil réel — tout vérifié.
Sous shell (uid 2000) avec perf_event_paranoid=-1, perf_event_open(PERF_SAMPLE_IP, exclude_user=1) échantillonne des clusters d'adresses du texte noyau ; l'alignement sur les décalages de symboles connus donne le slide.
samples=27651 kernel_ips=1685 lo=0xffffff948728176c hi=0xffffff9488ebfc7c
KASLR slide=0x147f200000 (40/40 IP 映射进内核文本区验证)
runtime _stext=0xffffff9487280800
Outil : tools/perf_kaslr.c. Prérequis d'exécution : shell (Shizuku rish), sans interception seccomp.
Créer un cycle PI pour que FUTEX_CMP_REQUEUE_PI renvoie -EDEADLK ; le rollback déclenche le bug remove_waiter.
Disposition clé : le futex cible du requeue est détenu par le waiter en cours de requeue (futex2 = waiter_tid) → owner == task dans task_blocks_on_rt_mutex → -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
Outil : tools/edeadlk_probe.c (variante 8+2+1 = 11, ou 27).
rt_mutex_adjust_prio_chain step[7] effectue un rb_erase sur le fake waiter (chemin à enfant gauche unique) : *(tree_left) = tree_pc (value→target) + écriture incrémentale __rb_change_child. Tous les décalages dans target.h proviennent de mesures par désassemblage de boot.elf.
Voir target/got_w29_target.h. Points clés :
Le désassemblage boot.elf de task_blocks_on_rt_mutex le confirme : le noyau de cet appareil contient une vérification anticipée owner==task à 0x3808-0x3868 (cmp owner,task; b.eq -> -EDEADLK), qui retourne avant l'écriture de task->pi_blocked_on (0x38d4 str x21,[x20,#0xa90]). L'ancien déclenchement GOT-W29 faisait que le waiter se possédait lui-même futex2=waiter_tid (self-own) → il atteignait exactement cette vérification anticipée → pi_blocked_on jamais défini → aucun pointeur pendant. Les observations sur l'appareil (pas de crash + boot_id inchangé) correspondent parfaitement à « aucun pointeur pendant » — le placement de l'overlay était un mauvais diagnostic.
Déclenchement correct (référence smt878u, déjà implémenté) : cycle PI — l'owner détient la cible du requeue via FUTEX_LOCK_PI(target) ; le waiter détient le futex chain ; l'owner se bloque ensuite sur chain (cycle : waiter→target→owner→chain→waiter). Lors du requeue, la remontée de chaîne détecte rt_mutex_owner(chain)==top_task (rtmutex step[6]) → -EDEADLK → le rollback de remove_waiter utilise le current du requeuer et nettoie la mauvaise cible → le pi_blocked_on du waiter reste pendant. L'owner doit réduire sa priorité (nice=10) pour que la prio après boost diffère de owner_waiter->prio, sinon rt_mutex_waiter_equal sort prématurément.
Avec shift=12, les mots 6-7 (task/lock) du fake waiter tombent dans res_in[3..4] (zone mise à zéro par le noyau). En exploitant la sémantique de do_select : res_in[i] = in[i] & POLLIN-ready. Écrire SLIDE_INIT_TASK / fake_lock dans in[3]/in[4], puis dup2 tous les fd correspondants vers des « extrémités de lecture de pipe contenant des données » (toujours EPOLLIN-ready) → res_in[3]=init_task, res_in[4]=fake_lock encodés précisément. Les mots 3-5 (pi_tree) et 8-10 peuvent être nuls (le chemin ownerless-lock n'utilise pas pi_tree ; prio/deadline sont écrasés par le noyau à l'étape step[7]). pselect retourne immédiatement grâce aux fd prêts → le waiter fait une attente active en espace utilisateur (signaux bloqués, zéro syscall, pour empêcher la réutilisation de la pile noyau d'effacer le fake waiter) jusqu'à ce que le consumer ait terminé le déclenchement. La table HW_FUTEX_PI à 11 mots, les deux classes de fd et le délai d'attente du processus parent sont tous implémentés (git diff).
*(boot_id)=DM(loggers[0][1])) ; stext=leaked-p0_alias_image_offset(NFULNL_LOGGER) présente un off-by par rapport à DM(_stext) ; si la phase root passe entièrement par physmap (espace DM), c'est cohérent, sinon il faut utiliser à la place le slide d'exécution de perf_event_open (disponible sous rish).tools/cycle_probe (déjà compilé) : vérification peu coûteuse du déclenchement du cycle EDEADLK ; si, après EDEADLK, un sched_setattr sur le waiter déclenche un oops du consumer = pointeur pendant présent + overlay en place.build_tools/deploy_test.sh, observer slide-kaslr-ok ou un oops du consumer.tools/ 验证工具(perf KASLR, EDEADLK 探针, overlay 测试, kaslr.json)
target/ 全部实测偏移
exploit/ 移植的 slide.c(含 EDEADLK 触发改动)