
Android कर्नेल CVE विश्लेषण और MediaTek ION आवंटनकर्ता टाइप कन्फ्यूज़न के लिए PoC, जिसमें रूट-कॉज़ डिफिंग, अनप्रिविलेज्ड ट्रिगर और एक्सप्लॉइटेबिलिटी आकलन शामिल है।
सारांश। CVE-2023-20768 MediaTek के ION एलोकेटर में एक टाइप कन्फ्यूज़न (CWE-843) है। मैंने जाँचा कि क्या यह जुलाई 2022 फ़र्मवेयर चलाने वाले सैमसंग SM-M325F (Galaxy M32, Helio G80) पर वास्तव में शोषण योग्य है। संवेदनशील कोड मौजूद है, और मैंने साबित किया कि यह इस डिवाइस पर तब चलता है जब इसे एक अविशेषाधिकार प्राप्त प्रक्रिया द्वारा ट्रिगर किया जाता है। मैं इसे शोषण में नहीं बदल सका। ioctl पथ इनपुट सत्यापन द्वारा सीमित है, और जिस पथ में वास्तविक सीमा-से-बाहर रीड है वह केवल असली ION बफ़र को ही संसाधित करता है, क्योंकि dma_buf_ops पॉइंटर पर दूसरी जाँच उस किसी भी चीज़ को अस्वीकार कर देती है जिसे मैं जालसाज़ी कर सकता था। यह लेख बताता है कि मैं उस निष्कर्ष तक कैसे पहुँचा, जिसमें एक मध्यवर्ती निष्कर्ष भी शामिल है जो ग़लत निकला।
CVE सार्वजनिक है और पैच किया जा चुका है। सभी परीक्षण मेरे अपने डिवाइस पर किए गए थे, जो Magisk से रूट किया गया है।
बग MediaTek के ION कोड में है, न कि अपस्ट्रीम Linux में या Samsung द्वारा लिखी गई किसी चीज़ में। MediaTek अपने चिप्स पर निर्माण करने वाले हर विक्रेता को दिए जाने वाले BSP में Android ION एलोकेटर का अपना फ़ोर्क भेजता है। M32 Helio G80 का उपयोग करता है, इसलिए उसे यह कोड मिलता है। Exynos SoC वाला वही फ़ोन इससे बिल्कुल प्रभावित नहीं होगा।
मैंने 2022 (संवेदनशील) और 2023 (पैच किए गए) vmlinux इमेज निकालीं और उन्हें IDA में diff किया। दो ION फ़ंक्शन बदले:
| फ़ंक्शन | 2022 | 2023 |
|---|---|---|
ion_drv_file_to_buffer | strstr(name, "dmabuf") | is_dma_buf_file() |
_ion_ioctl | strcmp(name, "ion") | is_dma_buf_file() |
is_dma_buf_file 2022 इमेज में मौजूद नहीं है। यह 2023 वाली में दिखता है। अतः दोनों फ़ंक्शन एक नाम देखकर तय कर रहे थे कि struct file dma_buf है या नहीं, और फ़िक्स ने उसे वास्तविक प्रकार-जाँच से बदल दिया। यहाँ किसी ऑब्जेक्ट को भ्रमित करने का अर्थ है कि कर्नेल एक गैर-dma_buf को ऐसे पढ़ता है जैसे वह dma_buf हो।
दोनों में से, _ion_ioctl वह है जिस तक एक अविशेषाधिकार प्राप्त प्रक्रिया पहुँच सकती है:
open("/dev/ion")
-> ion_ioctl (.unlocked_ioctl)
-> ION_IOC_CUSTOM (0xC0104906)
-> ion_custom_ioctl
-> _ion_ioctl
-> case 0: ION_SYS_CACHE_SYNC
-> find_vma(user_VA) (call site at _ion_ioctl+0x9f0)
-> strcmp(vma->vm_file...name, "ion")
अनुरोध एक ion_custom_data { u32 cmd = 0; u64 arg; } है जो 120 बाइट के ion_sys_data की ओर इंगित करता है:
spoof.c इसे बनाता है।
मैं इसे आसानी से ट्रेस नहीं कर सका। डिवाइस Samsung की SELinux पॉलिसी और कर्नेल हार्डनिंग के माध्यम से kprobe_events, set_ftrace_filter और function_graph को ब्लॉक करता है, और ION के printks डीबग-गेटेड हैं इसलिए dmesg शांत रहता है।
इसलिए मैंने इसके बजाय रिटर्न कोड को ओरेकल के रूप में इस्तेमाल किया। चार अनुरोध, और जो वापस मिलता है उसका पैटर्न बताता है कि निष्पादन कहाँ गया:
C का सफलता लौटाना मतलब है कि switch वास्तव में sys_cmd पर डिस्पैच कर रहा है। A और B का अलग होना मतलब है कि VA संसाधित किया जा रहा है, जो निष्पादन को find_vma के अंदर ले जाता है। यही संवेदनशील पथ है, जो बिना रूट के पहुँचा जा सकता है।
जाँच तक पहुँचना उसे पार करने के समान नहीं है। मैंने परीक्षण किया कि strcmp वास्तव में क्या स्वीकार करता है:
ION_IOC_SHARE के माध्यम से मैप किया गया एक असली ION बफ़र फिर mmap पास हो जाता है, 0 लौटाता है।memfd:ion नाम का एक memfd विफल हो जाता है, -EFAULT।ion है, वह भी विफल हो जाती है, -EFAULT।अतः vm_file+0x60 पर जिस फ़ील्ड की तुलना हो रही है वह फ़ाइलनाम नहीं है। यह dma_buf के आंतरिक है, लगभग निश्चित रूप से dma_buf->exp_name, जिसे ION "ion" पर सेट करता है। यह जाँच डिज़ाइन से ही त्रुटिपूर्ण है, लेकिन मैं यूज़रस्पेस से जो कुछ भी बना सकता हूँ वह उस फ़ील्ड को सेट नहीं कर सकता।
फिर मैंने पथ को फ़ज़ किया: sync_type 0 से 7, आकार {0, 1, 0x1000, 0x100000, 0xffffffff}, VA {असली ion बफ़र, memfd, 0}, 120 मामले, साथ ही use-after-free के लिए एक मुक्त-हैंडल प्रोब। कोई क्रैश नहीं, डिवाइस चालू रहा। अत्यधिक बड़े आकार find_vma से पहले बाहर निकल जाते हैं और 0 लौटाते हैं। sync_type 3 से 5 m4u पथ पर पहुँचते हैं और -EPERM लौटाते हैं। 5 से ऊपर कुछ भी -EINVAL लौटाता है। एक मुक्त हैंडल -EINVAL लौटाता है, इसलिए ION इसे मान्य करता है और वहाँ कोई UAF नहीं है।
वास्तविक सीमा-से-बाहर का रीड ion_drv_file_to_buffer में है। यह ldr [private_data+0x28] करता है, यानी एक गैर-dma_buf के private_data को ऐसे पढ़ता है जैसे वह dma_buf हो। वहाँ ops == &ion_dma_buf_ops की तुलना है (टेबल 0xFFFFFF800A097F18 पर है), लेकिन यह उस रीड के बाद होती है, इसलिए यह उसे रोकती नहीं है। डाउनस्ट्रीम में, __do_dump_share_fd लौटाए गए बफ़र से +0x28, +0x48, +0x50, +0xb8, +0xe4 पर मौजूद फ़ील्ड पढ़ता है और उन्हें प्रिंट करता है, और ldr x8, [buf+0x28]; ldr [x8+0x30] भ्रमित ऑब्जेक्ट के लिए एक अनियंत्रित पॉइंटर डेरेफ़रेंस है।
ट्रिगर ion_dump_all_share_fds है, जो हर ION क्लाइंट प्रोसेस के फ़ाइल डिस्क्रिप्टर को स्कैन करने के लिए iterate_fd का उपयोग करता है। मेरा पहला निष्कर्ष था कि यह केवल तब चलता है जब आप ION debugfs नोड पढ़ते हैं, और इस कर्नेल में CONFIG_DEBUG_FS अनसेट है। मैंने इसे तीन तरीकों से सत्यापित किया: /proc/config.gz, /proc/filesystems से debugfs का गायब होना, और mount -t debugfs का ENODEV लौटाना। मैंने उस पथ को संरचनात्मक रूप से अगम्य मानकर खारिज कर दिया।
वह ग़लत था। dump_header, जो OOM किलर का मेमोरी डंप है, में 0xffffff8008204b9c पर एक सीधा bl ion_mm_heap_memory_detail है, और dump_header को out_of_memory और oom_kill_process से कॉल किया जाता है। किसी debugfs की ज़रूरत नहीं है।
memcg_oom.c इसकी पुष्टि करता है। यह /dev/memcg के अंतर्गत एक cgroup बनाता है, memory.limit_in_bytes और memory.memsw.limit_in_bytes दोनों को 8MB पर सीमित करता है (केवल पहले को सीमित करने से चाइल्ड zram स्वैप में भाग सकता है), और एक चाइल्ड फ़ोर्क करता है जो मरने तक आवंटन करता है। dmesg फिर दिखाता है:
dump_header <- oom_kill_process <- out_of_memory <- mem_cgroup_oom_synchronize
उसके बाद पूरा ion_mm_heap_memory_detail और __do_dump_share_fd आउटपुट आता है जो असली gralloc dma_bufs को हल करता है। अतः संवेदनशील फ़ंक्शन किसी ऐसी चीज़ के दौरान निष्पादित होता है जिसे कोई भी अविशेषाधिकार प्राप्त प्रक्रिया उत्पन्न कर सकती है। एक तीसरा ट्रिगर भी है, MediaTek वॉचडॉग में hang_detect_dump_thread से ShowStatus।
मैंने memfd:dmabuf नाम के 32 memfd खोले रखे और वही memcg OOM ट्रिगर किया। यदि मेरा कोई एक ion_drv_file_to_buffer तक पहुँचाया गया होता, तो strstr पास हो जाता, private_data NULL होता, और कर्नेल KERN_ERR पर [ION]ion_drv_file_to_buffer warnning, dmabuf is NULL प्रिंट करता। वह पंक्ति कभी नहीं आई। केवल ग्राफ़िक्स और gralloc क्लाइंट्स डंप हुए। या तो एक साधारण /dev/ion क्लाइंट की fd तालिका वह नहीं है जिसे iterate_fd यहाँ स्कैन करता है, या memfd प्रिंट से पहले चुपचाप विफल हो जाता है।
किसी भी तरह, OOM डंप केवल सिस्टम के वैध dma_bufs को ही संभालता है, जो सफाई से पास होते हैं। memfd एकमात्र ऐसा fd प्रकार भी है जिसका नाम मैं इतना नियंत्रित कर सकता हूँ कि उसमें "dmabuf" शामिल हो, और यह फॉल्ट नहीं कर सकता।
भेद्यता मौजूद है। संवेदनशील कोड सुलभ है और इस बिल्ड पर वास्तव में निष्पादित होता है, बिना रूट के ट्रिगर किया जा सकता है। इसे यहाँ यूज़रस्पेस से शोषण में नहीं बदला जा सकता। ioctl पथ हैंडल सत्यापन, एक आकार जाँच और access_ok द्वारा सीमित है। डंप पथ केवल असली ION बफ़र देखता है, और जालसाज़ी ops == &ion_dma_buf_ops द्वारा अवरुद्ध है। आगे बढ़ने के लिए एक नियंत्रण योग्य गैर-ION ऑब्जेक्ट की आवश्यकता होगी जिसका exp_name "ion" हो, या कोई भिन्न प्रिमिटिव जैसे ION बफ़र UAF, या एक TOCTOU रेस।
spoof.c — कैश-सिंक ioctl PoC और विभेदक ओरेकलmemcg_oom.c — डंप पथ के लिए memcg OOM ट्रिगरtrigger.c, oom_trigger.c — पहले के ट्रिगर प्रयासboot_images/ — निकाली गई कर्नेल इमेज (2022 और 2023) और 2022 बिल्ड के लिए IDA डेटाबेस| ऑफ़सेट | फ़ील्ड |
|---|
+0x00 | sys_cmd = 0 |
+0x08 | ion हैंडल (पहले एक आवंटित करें, heap_id_mask = 0x1 काम करता है) |
+0x10 | यूज़र वर्चुअल एड्रेस |
+0x18 | निचला आधा = आकार, ऊपरी आधा = {0,1,2} में sync_type |
| केस | अनुरोध | परिणाम |
|---|
| A | sys_cmd=0, स्पूफ़ किया गया VA | -EFAULT |
| B | sys_cmd=0, VA = 0 | 0 |
| C | sys_cmd=4 | 0 |
| D | sys_cmd=99 | -EFAULT |