
Ricerca su CVE-2026-43499 (GhostLock) per HUAWEI MatePad Pro 11 GOT-W29
Ricerca di escalation dei privilegi per CVE-2026-43499 (rtmutex/futex-PI UAF, "GhostLock") su GOT-W29 (HarmonyOS 4.0, kernel 4.19.157-perf+).
Conclusioni principali:
sysctl_bootid del kernel con assistenza KPM, derivazione dello slide KASLR).| Elemento | Valore |
|---|---|
| Modello | HUAWEI MatePad Pro 11 GOT-W29 |
| SoC | Qualcomm kona (SM8250, Snapdragon 870) |
| Sistema | HarmonyOS 4.0 (104.0.0.136) |
| Kernel | 4.19.157-perf+ |
| VA | 39-bit, pagine 4K, KASLR attivo |
remove_waiter() in kernel/locking/rtmutex.c usa current invece di waiter->task per la pulizia nel percorso di rollback di rt_mutex_start_proxy_lock(), causando un pi_blocked_on pendente (UAF dello stack). Interessa 2.6.39 ~ 7.1 (questo kernel è nel range). Commit di fix upstream 3bfdc63936dd.
Confermato su questo dispositivo: sorgente rtmutex.c:1110-1112, decompilazione di boot.elf, trigger su hardware reale tutti verificati.
Creare un ciclo PI per far sì che FUTEX_CMP_REQUEUE_PI restituisca -EDEADLK; il rollback attiva il bug di remove_waiter, lasciando un pi_blocked_on pendente (che punta al rt_waiter sullo stack del kernel del thread waiter).
Il vecchio trigger faceva sì che il waiter possedesse il futex target del requeue (self-own), colpendo esattamente il controllo anticipato owner==task di task_blocks_on_rt_mutex di questo kernel (boot.elf 0x3808-0x3868), che restituisce prima di scrivere pi_blocked_on → non viene mai generato un puntatore pendente → il posizionamento dell'overlay era una diagnosi errata (nessun crash + boot_id invariato).
Ciclo PI: l'owner FUTEX_LOCK_PI(target) detiene il target del requeue; il waiter detiene il futex chain; l'owner si blocca poi sul chain (ciclo: waiter→target→owner→chain→waiter). Durante il requeue, la camminata della catena rileva rt_mutex_owner(chain)==top_task → -EDEADLK → il rollback pulisce il pi_blocked_on della persona sbagliata → il pi_blocked_on del waiter resta pendente. L'owner deve abbassare la priorità (nice=10) affinché la prio dopo il boost sia diversa da owner_waiter->prio, altrimenti rt_mutex_waiter_equal esce anticipatamente.
Con shell (uid 2000) e perf_event_paranoid=-1, perf_event_open(PERF_SAMPLE_IP, exclude_user=1) campiona cluster di indirizzi del testo del kernel; allineandoli agli offset dei simboli noti si ottiene lo slide.
samples=27651 kernel_ips=1685 lo=0xffffff948728176c hi=0xffffff9488ebfc7c
KASLR slide=0x147f200000 (40/40 IP mappati nella regione di testo del kernel verificati)
runtime _stext=0xffffff9487280800
Strumento: tools/perf_kaslr.c. Prerequisito di esecuzione: shell (Shizuku rish), nessuna intercettazione seccomp.
[M] CMP_REQUEUE_PI ret=-1 errno=35 (EDEADLK!)
[W] WAIT_REQUEUE_PI ret=-1 errno=110 (ETIMEDOUT) ← il waiter ritorna
[M] waiter_returned=1 ← lascia pi_blocked_on pendente
Strumento: tools/edeadlk_probe.c (variante 8+2+1 = 11, oppure 27).
rt_mutex_adjust_prio_chain step[7] esegue rb_erase sul fake waiter (percorso a singolo figlio sinistro): *(tree_left) = tree_pc + scrittura incrementale __rb_change_child. Tutti gli offset in target.h sono misurati dalla decompilazione di boot.elf.
Vedi exploit/ghostlock-source/src/target.h. Punti chiave:
Un modulo KPM scritto da me (tools/kpm-debug/rtmutex-dbg.c, KernelPatch 0.13.5 inline-hook) ricostruisce l'overlay (tree/task/lock) durante la fake walk e riscrive il parametro next_lock con empty_zero_page (KernelPatch _transit8 chiama la funzione originale con i fargs modificati), facendo sì che rt_mutex_adjust_prio_chain [3] next_lock==waiter->lock passi, [5] il trylock su lock zero abbia successo, [6] sia ownerless, [7] rt_mutex_dequeue (rb_erase a singolo figlio sinistro) venga eseguito — sysctl_bootid viene riscritto con &loggers[0][1], slide-kaslr-ok.
REPAIR3 waiter=0xffffff801ecdbc00 lock=0xffffffa7c4950000
slide boot_id_leaked_nfulnl_logger value=ffffffa7c4612320
slide-kaslr-ok base=ffffffa7c1280000 slide=00000027b9200000
La geometria dello stack è confermata dalle dimensioni dei frame di boot.elf: in __arm64_sys_futex(0x70) + do_futex(0x60+0x1a0) rt_waiter è a sp+0xc0 → profondità 0x1b0; nel percorso pselect stack_fds[0] è a profondità 0x210, differenza 0x60 = 12 word. Quindi word_i cade in stack_fds[12+i]:
le word 0-2 sono nella zona di input ex[2..4] (direttamente controllabili), le word 6-7 (task/lock) in res_in[3..4] (codificate con in[3..4] + fd POLLIN-ready), le word 3-5/8-10 restano 0.
pselect ritorna immediatamente a causa del fd ready → il waiter fa busy-wait in user space (segnali disabilitati, zero syscall) finché il consumer non completa.
Le word task/lock dell'overlay cadono in res_in[3]/[4] (codificate da fd ready). Misurato: res_in[4] (lock) viene deterministicamente sovrascritto nel percorso di ritorno di pselect (residuo del frame rt_sigreturn), e non è mai uguale al fake_lock del payload; res_in[3] (task) è occasionalmente intatto. L'elusione in user space (operazioni FP + sched_yield) può solo ridurre il tasso di trigger di do_notify_resume a ~21%, ma la sovrascrittura del lock è quasi certa.
Altri due fatti correlati:
rt_mutex_adjust_pi() ha if (!owner) return 0;, quindi senza owner nel fake_lock non viene chiamato adjust_prio_chain. popsicle (6.12) non ha questo controllo. Il payload con owner è stato portato (fake_lock owner=fake_task|1), ma poiché la word lock viene sempre sovrascritta non può essere attivato in modo affidabile.Dalla decompilazione di boot.elf di __arm64_sys_ppoll / do_sys_poll è confermato: pollfd è una struttura di 16 byte (fd 4B + events 4B + revents 4B + pad 4B), che non può trasportare le word task/lock a 64 bit del fake waiter — il valore fd è limitato (deve essere un fd reale), events ha solo 4 byte e non è contiguo, revents è scritto dal kernel (non controllabile).
smt878u / popsicle possono essere completamente compromessi: la loro geometria dello stack consente alle word di cadere nelle zone in/out/ex controllabili dall'utente (3 fd_set di pselect). La posizione del waiter su GOT-W29 (bits+0x60 → task/lock in res_in[3]/[4]) non ha questa finestra. È una differenza di geometria dello stack del kernel, non un difetto di implementazione.
Il dominio app (untrusted_app) non ha canali KASLR disponibili (perf/kallsyms/pagemap/dmesg tutti negati); CMP_REQUEUE_PI restituisce 1 (requeue riuscito) e non passa dal rollback EDEADLK; il major_only del cpuset non-root cortocircuita duramente lo step[6] della camminata della catena. L'unico ingresso per cambiare QOS, /dev/iaware_qos_ctrl, è negato da SELinux. Quindi i permessi di una normale app non possono attivare questa CVE.
Modulo KernelPatch scritto da me (rtmutex-dbg) per l'osservazione su hardware reale del fake waiter e della primitive di scrittura, compilato sulla base degli header di LyraVoid/KernelPatch 0.13.5 (stessa origine di FolkPatch).
cd <KernelPatch>/kpms/rtmutex-dbg
make TARGET_COMPILE=aarch64-linux-android- \
CC=$PREFIX/bin/aarch64-linux-android-clang \
LD=$PREFIX/bin/aarch64-linux-android-ld
Produce rtmutex_dbg.kpm. I flag di compilazione devono includere
-fno-pic -fno-pie -fno-asynchronous-unwind-tables -fno-unwind-tables
(già nel Makefile): il PIC predefinito di clang produce rilocazioni GOT, e genera .eh_frame di default (R_AARCH64_PREL32), nessuno dei quali è supportato dal loader KPM → caricamento fallito -1.
La superkey di FolkPatch è su (non il KernelPatch predefinito di APatch). Usa lo strumento sc_kpm_load (sorgente sc_kpm_load.c):
adb shell /data/local/tmp/sc_kpm_load su /sdcard/Download/rtmutex_dbg.kpm # carica
adb shell /data/local/tmp/sc_kpm_load unload rtmutex-dbg su # scarica
adb shell /data/local/tmp/sc_kpm_load ctl rtmutex-dbg counts su # contatori
run_rtmdbg_test.sh (lato dispositivo /data/local/tmp/ghostlock-test/): esegue GhostLock come shell (GOT_SLIDE_NO_RT=1 percorso reale), ciclo di sync da 0.5s per evitare perdita di log, dmesg -w su file, raccolta automatica dopo 90s (SIGSTOP per evitare soft-lock e riavvio). Prima del test:
adb shell 'su -c "sh /sdcard/ghostlock-test/set_debug_no_reboot.sh"' # evita il riavvio
[RTMDBG])REPAIR3: ricostruzione dell'overlay e riscrittura del parametro next_lock durante la verifica della primitive di scritturaFAKEWALK skip: la fake walk coperta viene saltata (nessuna scrittura, nessun crash)prio_chain[N] / prio_chain_ret: chiamate della walk e valori di ritorno (0=completata; 4294967261=-EDEADLK)do_select n=320: res_in[3]/[4] lato kernelfutex op=13/14: CMP_REQUEUE_PI / WAIT_REQUEUE_PILo stack completo di oops/panic di Huawei è registrato in /data/log/bbox/history.log.
tools/ strumenti di verifica (perf KASLR, sonda EDEADLK, toolchain KPM)
target/ tutti gli offset misurati
exploit/ slide.c portato (con modifiche per il trigger EDEADLK)
| hook | scopo |
|---|
rt_mutex_adjust_pi | registra le regolazioni PI; skip quando l'overlay è coperto (pulisce pi_blocked_on) |
rt_mutex_adjust_prio_chain | fake walk skip incondizionato; dump completo del waiter |
__arm64_sys_pselect6 / __arm64_sys_ppoll | pulisce _TIF_WORK_MASK nel percorso di ritorno; osservazione fd_set |
do_select | lettura lato kernel di res_in[3]/[4] |
__arm64_sys_futex | traccia WAIT_REQUEUE_PI / CMP_REQUEUE_PI |
rt_mutex_dequeue | conferma l'esecuzione della primitive di scrittura dello step[7] e la forma dell'albero |