
प्रूफ-ऑफ-कॉन्सेप्ट एक्सप्लॉइट CVE-2022-22706 के लिए: Mali GPU कर्नेल ड्राइवर की पेज-कैश लेखन दोष का शोषण करके /etc/passwd को मेमोरी में संशोधित करता है और रूट शेल प्राप्त करता है।
Arm Mali GPU ड्राइवर userspace को उन पेजों का CPU-लिखने-योग्य मैपिंग सौंपता है जिन्हें उसने रीड-ओनली पिन किया था, जिससे एक अन-प्रिविलेज्ड प्रोसेस को उस फ़ाइल के पेज कैश का writable alias मिल जाता है जिसे वह केवल O_RDONLY के साथ खोल सकता है।
exploit.c /etc/passwd पेज कैश में root के पासवर्ड फ़ील्ड को खाली कर देता है और su root को exec करता है। डिस्क पर मौजूद फ़ाइल कभी संशोधित नहीं होती।
इस रेपो में मौजूद ड्राइवर ट्री और QEMU टारगेट के लिए PoC (
mali_kbaser35p0-01eac0,CONFIG_MALI_NO_MALI=y, x86_64 GKI 5.15)। इसे VM में चलाएँ।
CPU मैपिंग की लिखने की क्षमता KBASE_REG_CPU_WR से आती है, लेकिन पिन केवल KBASE_REG_GPU_WR के आधार पर लिखने की अनुमति माँगता है:
/* mali_kbase_mem.c */
pinned_pages = pin_user_pages_remote(
mm, address, alloc->imported.user_buf.nr_pages,
reg->flags & KBASE_REG_GPU_WR ? FOLL_WRITE : 0, pages, NULL, NULL);
CPU_WR सेट और GPU_WR क्लियर के साथ इम्पोर्ट करें और आपको एक साथ दोनों चीज़ें मिलती हैं: इम्पोर्ट का writable CPU मैपिंग, और बिना FOLL_WRITE के लिया गया get_user_pages पिन। FOLL_WRITE के बिना, get_user_pages रीड-ओनली फ़ाइल मैपिंग पर COW को तोड़ता नहीं है, वह पेज-कैश पेज ही लौटा देता है। फिर ड्राइवर उन्हीं पेजों को writable के रूप में userspace पर मैप कर देता है।
इसे 5381ff7 ("GPUCORE-32592 Fix userbuf imports to respect RO memory") द्वारा ठीक किया गया, जो लिखने की पहुँच केवल GPU_WR के बजाय KBASE_REG_CPU_WR | KBASE_REG_GPU_WR से प्राप्त करता है।
sequenceDiagram
participant U as unprivileged process
participant K as mali_kbase
participant PC as page cache
U->>U: mmap /etc/passwd O_RDONLY, PROT_READ
U->>K: MEM_IMPORT(anon page, CPU_RD|CPU_WR|GPU_RD)
Note over K: address recorded, nothing pinned yet
U->>U: munmap(anon) + mremap file mapping onto that VA
U->>K: mmap(import cookie) → writable CPU mapping
U->>K: JOB_SUBMIT(EXTERNAL_RESOURCES)
K->>PC: pin_user_pages_remote() without FOLL_WRITE
U->>PC: memcpy() through the writable mapping
U->>U: execl("/bin/su", "su", "root")MEM_IMPORT केवल एक एड्रेस रिकॉर्ड करता है; पिन बाद में JOB_SUBMIT पर होता है। यही अंतराल anonymous पेज को बीच में फ़ाइल मैपिंग से बदलने देता है।
यह संपादन लंबाई-संरक्षित है, इसलिए root की पंक्ति के बाद कुछ भी स्थानांतरित नहीं होता:
root:x:0:0:root:/root:/bin/sh ← before
root::0:0:rootx:/root:/bin/sh ← after (empty password)
busybox su पासवर्ड फ़ील्ड खाली होने पर प्रॉम्प्ट करने से पहले CHECKPASS_PW_HAS_EMPTY_PASSWORD लौटाता है, और /etc/shadow केवल तभी पढ़ता है जब वह बिल्कुल x हो।
gcc -static -o exploit exploit.c
इसे VM में कॉपी करें और एक अन-प्रिविलेज्ड उपयोगकर्ता के रूप में चलाएँ:
$ ./exploit

QEMU टारगेट में user (uid 1000) के रूप में SSH के ज़रिए रिकॉर्ड किया गया। ./exploit /etc/passwd पेज कैश में root की पंक्ति को पैच करता है और su root को exec करता है, जो बिना प्रॉम्प्ट किए सीधे रूट शेल में पहुँचा देता है।
उस शेल से बाहर निकलने और su को दोबारा चलाने पर भी root मिलता है: पेज कैश में बना रहता है, इसलिए /etc/passwd का हर बाद का open()/read() पैच किए गए बाइट्स को ही देखता है।
जब echo 1 > /proc/sys/vm/drop_caches पेज को हटा देता है और फ़ाइल स्टोरेज से दोबारा पढ़ी जाती है, तो su फिर से पासवर्ड माँगता है। डिस्क पर मौजूद बाइट्स कभी छुए नहीं गए।