
अविशेषाधिकार प्राप्त proof-of-concept और x86_64 स्थानीय विशेषाधिकार वृद्धि (local privilege escalation) Linux कर्नेल Binder use-after-free (CVE-2026-64468) के लिए, KASAN प्रयोगशाला और संवेदनशील/सुधारित (vulnerable/fixed) अंतर (differential) के साथ।
binder_free_transaction() प्रक्रिया-जीवनकाल उपयोग-के-बाद-मुक्त
इस रिपॉजिटरी में, Linux कर्नेल Binder उपयोग-के-बाद-मुक्त के लिए, जिसे अपस्ट्रीम कमिट
f223d27a546c1e1f48d38fd67760e78f068fe8c4 द्वारा ठीक किया गया है, निम्नलिखित शामिल हैं:
binder_chain_64468.c — एक विशेषाधिकार-रहित प्रूफ ऑफ कॉन्सेप्ट जो बग तक पहुँचता है और कर्नेल को इसे साबित करने देता है, एक KASAN प्रयोगशाला और एक कमजोर/पैच किया हुआ अंतर (lab/, run.sh, verify.sh) के साथ।exploit.c — x86_64 के लिए एक स्व-निहित स्थानीय विशेषाधिकार वृद्धि। यह gcc -O2 -pthread -o exploit exploit.c के साथ संकलित होता है, एक सामान्य उपयोगकर्ता के रूप में चलता है, और रूट शेल में समाप्त होता है।demo/ — एक प्रयोगशाला जो बिना पैच वाले कर्नेल पर एक वास्तविक Debian 13 यूजरलैंड बूट करती है, ताकि एक्सप्लॉइट को लक्ष्य के अपने gcc द्वारा संकलित किया जा सके और उस मशीन पर चलाया जा सके जिसे वह फिर अपने कब्जे में ले लेता है।सब कुछ एक सामान्य उपयोगकर्ता (uid/gid 1000, कोई क्षमताएँ नहीं, कोई नेमस्पेस नहीं) के रूप में बिना किसी प्रकार के पैच वाले स्टॉक अपस्ट्रीम कर्नेल के विरुद्ध चलता है।
चेतावनी
यह कोड जानबूझकर कर्नेल ऑब्जेक्ट जीवनकाल के साथ रेस करता है और फिर कर्नेल नियंत्रण प्रवाह को हाईजैक करता है। एक खोई हुई रेस कर्नेल हीप स्थिति को दूषित करती है और मशीन को पैनिक या हैंग कर सकती है। इसे केवल एक पृथक, डिस्पोजेबल VM में चलाएँ जो आपका अपना हो। इसे होस्ट पर न चलाएँ।
binder_free_transaction() t->lock के अंतर्गत लेनदेन से लक्ष्य प्रक्रिया को पढ़ता है, उस लॉक को छोड़ता है, और फिर लक्ष्य का आंतरिक लॉक प्राप्त करता है:```c
spin_lock(&t->lock);
target_proc = t->to_proc;
spin_unlock(&t->lock);
if (target_proc) {
binder_inner_proc_lock(target_proc); /* use after free */
कुछ भी `target_proc` को उस अंतराल में जीवित नहीं रखता। एक प्रक्रिया जिसे समानांतर में
नीचे गिराया जा रहा है, वह बीच में `binder_proc_dec_tmpref() -> kfree()` तक पहुँच सकती है, इसलिए
लॉक मुक्त की गई मेमोरी पर लिया जाता है। अपस्ट्रीम फिक्स `t->to_thread` को पिन करता है जबकि
`t->lock` अभी भी धारित है, जो आंतरिक लॉक के उपयोग और रिलीज़ होने तक स्वामी प्रक्रिया को जीवित रखता है।
यह भेद्यता **एलिस रायल** द्वारा रिपोर्ट की गई थी और **कार्लोस
लामास** द्वारा ठीक की गई थी, दोनों Google से।
[मूल रिपोर्ट](https://lore.kernel.org/all/[email protected]/)
KASAN ट्रेस का संदर्भ रखती है।
### भेद्य एक्सेस तक पहुँचना
एकमात्र कॉलर जो *विदेशी* `to_proc` तक पहुँच सकता है, वह है
`binder_send_failed_reply()`, और यह केवल `t->from_parent` तक चलता है जब
`t->from` `NULL` होता है:```c
target_thread = binder_get_txn_from_and_acq_inner(t);
if (target_thread) { ...; binder_free_transaction(t); return; }
next = t->from_parent;
binder_free_transaction(t);
t = next;
from_parent ठीक एक ही स्थान पर निर्दिष्ट किया गया है, और यह निर्दिष्टीकरण कुछ पंक्तियाँ पहले बाइंडर के खराब ट्रांज़ैक्शन स्टैक जाँच द्वारा सुरक्षित है: एक थ्रेड केवल तभी सिंक्रोनस ट्रांज़ैक्शन भेज सकता है जब उसके स्टैक का शीर्ष एक ट्रांज़ैक्शन हो जिसे वह प्राप्त कर रहा है। इसलिए, उस श्रृंखला की हर कड़ी के लिए, चाइल्ड का भेजने वाला और पैरेंट का प्राप्त करने वाला एक ही थ्रेड होता है।
इसका एक गहरा परिणाम है। binder_thread_release() मरते हुए थ्रेड के स्टैक को proc->inner_lock धारण करके चलता है, और निम्नलिखित दोनों को लिखता है:```
iteration j : [holds child->lock ] child->from = NULL ; unlock child->lock
spin_lock(parent->lock)
iteration j+1 : [holds parent->lock] parent->to_proc = NULL
in **एक ही वॉक के सन्निकट पुनरावृत्तियों** में, प्रत्येक अपने-अपने
`t->lock` के अंतर्गत होता है। एक वॉकर `child->from == NULL` केवल तभी जानता है जब वॉक ने
`child->lock` जारी कर दिया हो, और उसे अपने स्नैपशॉट के लिए `parent->lock` की आवश्यकता होती है। इसलिए पूरा
अवसर उस वॉक के `spin_unlock(&child->lock)` और उसके
`spin_lock(&parent->lock)` के बीच का अंतराल है — कुछ निर्देश। रिलीज़ वॉक एक
स्पिनलॉक रखता है और वहाँ प्रीएम्प्ट नहीं हो सकता; केवल एक इंटरप्ट इसे विलंबित कर सकता है।
यही कारण है कि रेस संकीर्ण है और क्यों प्रूफ ऑफ कॉन्सेप्ट और
एक्सप्लॉइट दोनों संभाव्य हैं।
### प्रूफ ऑफ कॉन्सेप्ट क्या बनाता है
`binder_chain_64468.c` सबसे छोटी श्रृंखला बनाता है जो
कमजोर एक्सेस तक पहुँचती है, ताकि उस अंतराल के भीतर वॉकर का काम उतना ही छोटा हो जितना बाइंडर
अनुमति देता है — दो `t->lock` अधिग्रहण और एक `kfree()`, बिना किसी विदेशी
`inner_proc_lock`, बिना `wake_up` और बिना रिप्लाई डिलीवरी के:```
B thread i --e2 (sync, code 0x4442414b)--> P thread Y_i
P thread Y_i --e1 (sync, code 0x54414c4c)--> B thread i (nested target)
B thread i stack: [ e2 outgoing , e1 incoming (top) ]
P thread Y_i stack: [ e2 incoming , e1 outgoing (top) ]
P फिर अपना binder fd छोड़ देता है, इसलिए binder_deferred_release() Y_1..Y_K को रिलीज़ करता है और अंततः binder_proc को मुक्त कर देता है, जबकि हर B थ्रेड समवर्ती रूप से BINDER_THREAD_EXIT जारी करता है:```
binder_thread_release(B_i) nulls e1->to_proc (own proc) and e2->from
binder_send_failed_reply(e1) e1->from == NULL once Y_i was released
-> binder_free_transaction(e1) target_proc already NULL, kfree(e1)
-> binder_free_transaction(e2) target_proc == P <-- vulnerable access
एक तीसरी प्रक्रिया बाइंडर कॉन्टेक्स्ट मैनेजर है, जिसका उपयोग केवल उन हैंडल्स को बांटने के लिए किया जाता है जिनकी बाकी दोनों को आवश्यकता होती है। `exploit.c` इसी सटीक निर्माण का पुन: उपयोग करता है।
### दो स्वतंत्र स्थितियाँ
KASAN रिपोर्ट के लिए दोनों की आवश्यकता होती है:
* **कमजोर एक्सेस** — वॉकर को उपरोक्त संकीर्ण विंडो के अंदर `parent->lock` लेना चाहिए, ताकि वह अभी भी जीवित `to_proc` का स्नैपशॉट ले सके; और
* **विंडो के अंदर फ्री लैंडिंग** — वॉकर को फिर `t->lock` छोड़ने और पीड़ित के आंतरिक लॉक को लेने के बीच अपना CPU खोना चाहिए, और तब तक उससे दूर रहना चाहिए जब तक कि विलंबित रिलीज़ समाप्त न हो जाए और `binder_proc` मुक्त न हो जाए।
पहली स्थिति का अपना स्वयं का ओरेकल है जिसे KASAN की आवश्यकता नहीं है: बाइंडर प्रिंट करता है```
binder: binder_free_proc: Unexpected outstanding_txns -1
जब भी ऐसा होता है, क्योंकि वॉकर और binder_thread_release() दोनों फिर एक ही लेनदेन के लिए एक ही काउंटर को घटाते हैं। ध्यान दें कि यह अपने आप में कोई कमज़ोर/फिक्स्ड अंतर नहीं है — फिक्स फ्री को रोकता है, दूसरे घटाव को नहीं — इसलिए यह दोनों कर्नेल पर दिखाई देता है। इसका उपयोग यहाँ केवल यह दिखाने के लिए किया गया है कि कमज़ोर कोड पथ का उपयोग हो रहा है।
प्रूफ ऑफ कॉन्सेप्ट मुक्त की गई मेमोरी के 4-बाइट घटाव पर रुक जाता है। उसे uid 0 में बदलने के लिए चार चीज़ों की आवश्यकता होती है, और उनमें से कोई भी बग से नहीं आती: बग कुछ भी लीक नहीं करता।
struct binder_proc 648 बाइट्स का है और इसे सादे GFP_KERNEL kzalloc के साथ आवंटित किया जाता है — नहीं __GFP_ACCOUNT। इसलिए यह kmalloc-1k में पहुँचता है, उस आकार के हर अन्य अनअकाउंटेड आवंटन के साथ, और kmalloc-cg-* के पीछे अलग नहीं होता। यही एक तथ्य वस्तु को पुनः प्राप्त करने योग्य बनाता है।
kfree() का क्षण userspace से देखने योग्य नहीं है, और न ही वह क्षण जब वॉकर वस्तु को फिर से छूता है, इसलिए समय देने के लिए कुछ भी नहीं है। इसलिए स्प्रे एक पंप के रूप में चलता है: यह kmalloc-1k वस्तुओं को लगातार आवंटित और मुक्त करता है, उस CPU से जिसने विलंबित रिलीज़ चलाई थी, जब तक वॉकर चल रहे हैं।
इसके लिए System V संदेशों का उपयोग किया जाता है। alloc_msg() एक सादा अनअकाउंटेड kmalloc है जिसमें 48-बाइट हेडर और पेलोड होता है, इसलिए 976-बाइट संदेश 1024-बाइट आवंटन है; कोटा प्रति कतार है न कि प्रति uid; और msgrcv() समकालिक रूप से मुक्त करता है। मापा गया थ्रूपुट: ~198,000 आवंटन प्रति सेकंड, शून्य विफलताएँ।
add_key/user_key_payload पहले आज़माया गया और यह एक जाल है। इसका पेलोड प्रति-uid बाइट कोटा (kernel.keys.maxbytes, डिफ़ॉल्ट रूप से 20000) के विरुद्ध लगाया जाता है जो केवल तब जारी होता है जब कुंजी कचरा संग्राहक कुंजी को नष्ट करता है, इसलिए एक तंग आवंटन/मुक्त लूप इसे मिलीसेकंड में समाप्त कर देता है: मापा गया 2,121,009 विफलताओं के विरुद्ध 27,151 सफल आवंटन — 98.7% स्प्रे चुपचाप कुछ नहीं कर रहा, जो बिल्कुल ऐसे स्प्रे जैसा दिखता है जो कभी स्लॉट नहीं जीतता। KEYCTL_INVALIDATE ने इसे और खराब कर दिया (4,775 सफलताएँ), क्योंकि यह GC कार्य को कतारबद्ध करता है।
वॉकर मुक्त किए गए binder_proc के चार फ़ील्ड छूता है (लक्ष्य बिल्ड पर pahole के साथ मापे गए ऑफसेट):
outstanding_txns == 1 और is_frozen == 1 के साथ, घटाव शून्य तक पहुँचता है और वॉकर wake_up_interruptible_all(&proc->freeze_wait) को कॉल करता है। __wake_up_common फिर curr = head.next - 24 की गणना करता है और *(head.next - 8) को कॉल करता है: एक फ़ंक्शन पॉइंटर जो जहाँ भी head.next इंगित करता है वहाँ से पढ़ा जाता है, जो एक मान है जो पुनः प्राप्त वस्तु प्रदान करती है।
उस पॉइंटर को उस मेमोरी तक पहुँचना होता है जिसे हमलावर नियंत्रित करता है, एक कर्नेल पते पर, और बग कुछ भी लीक नहीं करता। दोनों पते इसके बजाय प्रीफेच टाइमिंग से आते हैं — वही चैनल जैसा KASLD (Brendan Coles, MIT), जिसके कार्यान्वयन से यह व्युत्पन्न है:
कर्नेल टेक्स्ट। मैप किए गए कर्नेल पते का प्रीफेच पेज टेबल वॉक में हल होता है और अनमैप किए गए पते की तुलना में मापने योग्य रूप से तेज़ी से समाप्त होता है, भले ही एक्सेस कभी आर्किटेक्चरल रूप से दृश्यमान न हो। टेक्स्ट रेंज के 2 MiB स्लॉट्स को स्कैन करने से छवि तेज़ स्लॉट्स की एक श्रृंखला के रूप में दिखाई देती है; इसका पहला स्लॉट _text है।
डायरेक्ट मैप। CONFIG_RANDOMIZE_MEMORY के साथ डायरेक्ट मैप 1 GiB इकाइयों में यादृच्छिक होता है, इसलिए इसे भी स्थित करना पड़ता है। टेक्स्ट के विपरीत यह सभी RAM को कवर करता है, इसलिए यह मैप किए गए स्लॉट्स की सबसे लंबी सतत श्रृंखला है। इसे उपयोगी बनाने के लिए दो परिशोधन आवश्यक थे:
रन विश्वसनीय रूप से जिससे शुरू होता है वह page_offset_base स्वयं नहीं है बल्कि पहला स्लॉट है जिसे कर्नेल 1 GiB पेज के साथ मैप कर सकता था — वह जो भौतिक 4 GiB को कवर करता है। उसके नीचे, PCI होल और फर्मवेयर आरक्षण 2 MiB पेजों को मजबूर करते हैं जिनका लंबा वॉक यहाँ अनमैप किए गए से अलग नहीं है। वह स्लॉट ठीक वही है जो स्प्रे को चाहिए, इसलिए इसका उपयोग किया जाता है।
फिर ~60% भौतिक मेमोरी एक तैयार 4 KiB पेज की प्रतियों से भरी जाती है, ताकि उस एंकर से एक निश्चित ऑफसेट तैयार पेज द्वारा समर्थित हो चाहे लेआउट कुछ भी निकले।
freeze_wait.head.next -> entry1 (in the sprayed page)
entry1.func = mov 0x28(%rdi),%rdi ; mov 0x18(%rdi),%rax ; jmp *0x58(%rax) RDI <- entry1+0x28 = &cred RAX <- cred+0x18 jmp *(RAX+0x58) = commit_creds -> commit_creds(cred)
entry2.func = mov $-1,%rax ; ret __wake_up_common breaks out on ret < 0
कोई स्टैक पिवट नहीं और कोई `iretq` नहीं: हाईजैक किया गया थ्रेड अपने `ioctl()` से
सामान्य रूप से लौटता है और बस रूट के रूप में यूज़रस्पेस में वापस आ जाता है।
### जाली क्रेड, और वह बग जो इसे बर्बाद कर देता
डिस्पैचर गैजेट अपना `RAX` `cred+0x18` से लेता है, और `cred+0x18`
`euid`/`egid` है, इसलिए चेन के तुरंत बाद `euid` एक कर्नेल पॉइंटर का निचला आधा हिस्सा होता है। यह कॉस्मेटिक है। जो कॉस्मेटिक नहीं है वह यह है कि `prepare_creds()` — जिसे **हर बाद का `fork()` और `execve()` कॉल करता है** — बिना किसी NULL चेक के तीन फ़ील्ड्स को डीरेफ़रेंस करता है:```c
get_group_info(new->group_info); /* refcount_inc(&gi->usage) */
get_uid(new->user); /* refcount_inc(&u->__count) */
new->ucounts = get_ucounts(new->ucounts);
एक जाली क्रेड जो उन्हें NULL छोड़ देता है, uid 0 देता है और फिर पहले execve पर मशीन को पैनिक कर देता है — यानी, एक्सप्लॉइट सफलता की रिपोर्ट करेगा और तुरंत बॉक्स को नष्ट कर देगा। इसलिए चेन user, ucounts और group_info को वास्तविक कर्नेल ग्लोबल्स root_user, init_ucounts और init_groups पर इंगित करती है, जिनके पते उसी _text बेस से आते हैं। इन्हें सेट करने के बाद, रूटेड थ्रेड init_user_ns पर CAP_SETUID रखता है, इसलिए यह setresuid(0,0,0) कॉल करता है और कर्नेल जाली क्रेड के ऊपर एक साफ, कर्नेल-आवंटित रूट क्रेड स्थापित करता है। उसके बाद ही कुछ और किया जाता है।
विशेषाधिकार माता-पिता को एक्सप्लॉइट बाइनरी की setuid-root प्रति के माध्यम से दिया जाता है, जो शेल को exec करने से पहले स्वयं को अनलिंक कर लेती है, इसलिए रूट शेल मुख्य प्रक्रिया में एक साफ टर्मिनल पर चलता है और पीछे कुछ भी setuid नहीं बचता है।
uname -r पर्याप्त नहीं है। वेंडर कर्नेल नियमित रूप से संबंधित मेनलाइन संस्करण को बदले बिना binder फिक्स बैकपोर्ट करते हैं; स्रोत या पैकेज चेंजलॉग का निरीक्षण करें।
फिक्स Cc: stable टैग किया गया है, इसलिए स्थिर और वेंडर शाखाओं को बैकपोर्ट प्राप्त होते हैं।
ये अलग-अलग प्रश्न हैं, और दूसरा वह है जो प्रभाव तय करता है।
कमजोर कोड वहाँ संकलित होता है जहाँ CONFIG_ANDROID_BINDER_IPC सेट है। इसमें सामान्य-उद्देश्य वितरण शामिल हैं — लेकिन उन सभी पर जिनका सर्वेक्षण किया गया, ड्राइवर एक मॉड्यूल है जो डिफ़ॉल्ट रूप से लोड नहीं होता, और जब यह लोड होता भी है, तो init_binder_device() miscdev.mode सेट किए बिना misc डिवाइस पंजीकृत करता है, इसलिए devtmpfs /dev/binder को 0600 root:root के रूप में बनाता है। Android पर यह ueventd है जो इसे 0666 पर खोलता है, जो वास्तव में कारण है कि बग वहाँ मायने रखता है और यहाँ अधिकतर नहीं।
वितरणों के अपने स्वयं के शिप किए गए कर्नेल पैकेजों से पढ़े गए कॉन्फ़िगरेशन:
डेस्कटॉप वितरण पर व्यावहारिक जोखिम इसलिए अप्रत्यक्ष है: जो कुछ भी binder लोड करता है और उसे खोलता है — Waydroid, Anbox, एक Android एमुलेटर या कंटेनर रनटाइम — उस मशीन पर ठीक Android पहुंच क्षमता को फिर से पेश करता है जिसके कर्नेल में अभी भी बग है।
ये कमजोरी को प्रभावित नहीं करते; ये इस एक्सप्लॉइट को प्रभावित करते हैं।
दोनों कर्नेल स्टॉक अपस्ट्रीम ट्री हैं। कुछ भी पैच नहीं किया गया है।
पहली प्रयोगशाला में CONFIG_KASAN_GENERIC एक डिटेक्टर है, सक्षमकर्ता नहीं: रेस इसके बिना समान है। CONFIG_PREEMPT एक वास्तविक पूर्व-शर्त है, और यही Android शिप करता है।
आर्किटेक्चर बग के लिए एक कारक नहीं है — यह आर्किटेक्चर-स्वतंत्र C में एक लाइफटाइम त्रुटि है। यह एक्सप्लॉइट के लिए बहुत अधिक कारक है: गैजेट्स, प्रीफेच चैनल और डायरेक्ट-मैप लेआउट सभी x86_64 हैं।
आवश्यकताएँ: clang, lld, make, cpio, gzip, qemu-system-x86_64, docker (केवल Debian rootfs को इकट्ठा करने के लिए), Linux git ट्री की एक स्थानीय क्लोन, और gcc।
gcc -O2 -pthread -o exploit exploit.c ./exploit
कर्नेल के लिए `demo/` में मौजूद कर्नेल के अलावा किसी अन्य कर्नेल के लिए, पहले उसके ऑफसेट निकालें:```sh
./mkoffsets.sh /path/to/vmlinux > offsets.h
gcc -O2 -pthread -DEXPLOIT_OFFSETS='"offsets.h"' -o exploit exploit.c
ट्यूनेबल्स, सभी वैकल्पिक, सभी पर्यावरण से पढ़े जाते हैं:
CVE64468_SECONDS, CVE64468_THREADS, CVE64468_SPRAY_PERCENT,
CVE64468_CALL_OFFSET_MB, CVE64468_KASLR_ATTEMPTS, CVE64468_DELAY_MAX_US,
CVE64468_DELAY_STEP_US, CVE64468_STAGGER_US, CVE64468_VERBOSE,
CVE64468_SHELL.
LINUX_GIT=/path/to/linux ./lab/build.sh # x86_64 LINUX_GIT=/path/to/linux TARGET_ARCH=arm64 ./lab/build.sh # arm64 ./run.sh vulnerable ./verify.sh 600 16
The script creates two detached worktrees at the commits above, refuses to run
if either worktree is dirty, verifies that the vulnerable tree lacks the fix and
the fixed tree carries it, builds both kernels, and packs the proof of concept
into an initramfs.
### The exploitation laboratory```sh
./demo/build-kernel.sh # vulnerable kernel, KASAN off, hardening on
./demo/build-rootfs.sh # Debian 13 userland + gcc, as an initramfs
./demo/run-demo.sh # boot it; this is what the recording shows
./demo/verify.sh logs/ # unattended reliability run, one guest per trial
demo/run-demo.sh अतिथि को बूट करता है और कंसोल को uid 1000 को सौंप देता है, जो
अतिथि के अपने gcc के साथ exploit.c को संकलित करता है और उसे चलाता है।
कर्नेल इमेज, वर्कट्री, रूटफ्स ट्री और इनिट्रैम्फ्स प्रयोगशाला कलाकृतियाँ हैं और इन्हें संस्करणित नहीं किया जाता है।
docs/example-output.txt वास्तविक कमजोर-कर्नेल ट्रांसक्रिप्ट है, जिसमें
KASAN रिपोर्ट शामिल है; docs/patched-negative-output.txt फिक्स्ड-कर्नेल
नियंत्रण है; docs/e2e-results.json मशीन-पठनीय परिणाम है।
रिपोर्ट की गई कॉल श्रृंखला अपस्ट्रीम रिपोर्ट से बिल्कुल मेल खाती है:``` BUG: KASAN: slab-use-after-free in queued_spin_lock_slowpath+0x62/0x6a0 Read of size 4 at addr ffff888100282270 by task cve64468-B/91 CPU: 6 UID: 1000 PID: 91 Comm: cve64468-B Not tainted 7.2.0-rc1+ #2 PREEMPT _raw_spin_lock+0x55/0x60 binder_free_transaction+0x80/0x1f0 binder_send_failed_reply+0x98/0x340 binder_thread_release+0x528/0x5e0 binder_ioctl+0x1ca/0xeb0 __x64_sys_ioctl+0x8c9/0xcf0
Allocated by task 93: binder_open+0xb5/0x7b0
Freed by task 9: kfree+0x113/0x310 binder_deferred_func+0x104b/0x1180 process_scheduled_works+0x69f/0xbb0
`UID: 1000` प्रूफ-ऑफ-कॉन्सेप्ट की अपनी अन-प्रिविलेज्ड पहचान है: पीड़ित
`binder_proc` को `binder_open()` द्वारा आवंटित किया जाता है, binder deferred
workqueue द्वारा मुक्त किया जाता है, और मुक्त होने के बाद walker द्वारा पढ़ा जाता है।
रिकॉर्ड किया गया रन, 2026-08-16, `./verify.sh 1200 16 3` — प्रति वैरिएंट तीन समवर्ती QEMU/KVM
गेस्ट, प्रत्येक में 10 vCPU, 16 थ्रेड, प्रति वैरिएंट 1200 s, **किसी भी तरफ कोई kernel
पैच नहीं**:
| | कमज़ोर `114a116aaa5f` | फिक्स्ड `f223d27a546c` |
| --- | --- | --- |
| प्रयास | 158,384 | 158,471 |
| Walks | 2,534,144 | 2,535,536 |
| सेटअप विफलताएँ | 0 | 0 |
| कमज़ोर एक्सेस (`Unexpected outstanding_txns -1`) | 1,127 | 934 |
| **KASAN `slab-use-after-free`** | **8** | **0** |
यही अंतर है: समान वर्कलोड, समान प्रयास संख्या 0.06% के भीतर,
और use-after-free केवल अन-पैच्ड कमज़ोर kernel पर।
कमज़ोर एक्सेस *दोनों* kernels पर दिखाई देता है, और यह अपेक्षित है — फिक्स
प्रक्रिया को विंडो के अंदर मुक्त होने से रोकता है, न कि `outstanding_txns` के दूसरे
घटाव को। यही कारण है कि उस लाइन का उपयोग केवल एक सस्ते oracle के रूप में किया जाता है
और कभी भी अंतर के रूप में नहीं।
### Privilege escalation

`docs/lpe-output.txt` एक डेमोन्स्ट्रेशन गेस्ट से एक वास्तविक ट्रांसक्रिप्ट है, और
`docs/lpe-demo.cast` पूरी, अन-एडिटेड Asciinema रिकॉर्डिंग है जिससे ऊपर का एनीमेशन
रेंडर किया गया था (`asciinema play docs/lpe-demo.cast` इसे पूरी तरह से रीप्ले करता है)।
एनीमेशन उस रिकॉर्डिंग के लंबे रेसिंग मध्य भाग को हटा देता है — गेस्ट का सीरियल
कंसोल पूरे ~24 मिनट की रेस के लिए binder डीबग स्ट्रीम करता है, जो दसियों मेगाबाइट
स्क्रॉलिंग लॉग में रेंडर होगा — Debian प्रस्तावना और रूट शेल को बनाए रखते हुए;
exploit की अपनी `hit after 26661 attempts ... in 1437s` लाइन ठीक वही बताती है जो
हटाया गया था। दोनों एकल रन हैं: एक गेस्ट जो अपने बजट के भीतर नहीं जीतता वह बंद हो
जाता है, और रिकॉर्डिंग को जीत में संपादित करने के बजाय बस दोहराया जाता है।
मापी गई सफलता दर के लिए नीचे *Reliability* देखें।
## Reliability
रेस दोनों मामलों में संभाव्य है, इसलिए एक असफल रन कभी-कभी अपेक्षित
व्यवहार है, न कि एक टूटा हुआ exploit।
### Memory safety
कमज़ोर kernel पर व्युत्पन्न दरें, ऊपर दिए गए रन से:
| मात्रा | मान |
| --- | --- |
| प्रयास दर | ~44 प्रयास/s प्रति गेस्ट, तीनों में ~132/s |
| कमज़ोर एक्सेस | 7.1e-3 प्रति प्रयास |
| एक्सेस दिए जाने पर विंडो के अंदर मुक्त लैंडिंग | 7.1e-3 |
| KASAN रिपोर्ट | ~1 प्रति 20,000 प्रयास, यानी इस दर पर लगभग हर 2.5 मिनट में एक |
### Privilege escalation
प्रत्येक गेस्ट एक स्वतंत्र परीक्षण है: इसका अपना KASLR, इसका अपना direct-map
रैंडमाइज़ेशन, और एक गेस्ट जो जीतता है वह रेसिंग बंद कर देता है। इसलिए आंकड़ा एक
**प्रति बूट सफलता दर** है, न कि प्रति प्रयास।
रिकॉर्ड किया गया अभियान, 2026-08-16 23:32 UTC, `GUESTS=6 CPUS=8 MEMORY_MB=9216 ./demo/verify.sh`
— छह समवर्ती QEMU/KVM गेस्ट, अन-पैच्ड `114a116aaa5f`, Debian 13 userland,
60 मिनट का बजट प्रत्येक, exploit गेस्ट में संकलित, uid 1000 के रूप में शुरू:
| | |
| --- | --- |
| गेस्ट जो uid 0 तक पहुँचे | **6 में से 2** |
| रूट तक का समय | 623 s और 1,344 s |
| जीतने वाली रेस में प्रयास | 13,839 और 31,125 |
| प्रत्येक गेस्ट द्वारा प्रयास जो नहीं जीता | पूरे 3,600 s में ~95,000 |
| अभियान में कुल प्रयास | 427,676 (6.8 M स्टैक walks) |
| देखे गए कमज़ोर एक्सेस (`Unexpected outstanding_txns -1`) | 256 |
| **Kernel क्रैश, oopses या panics** | **0**, 9 गेस्ट-घंटों में |
उस तालिका से दो चीज़ें पढ़ने लायक हैं।
**अनिवार्य रूप से हर जीती गई रेस रूट बन गई।** KASAN प्रयोगशाला मापती है कि
कमज़ोर एक्सेस दिए जाने पर मुक्त विंडो के अंदर उतरने की संभावना
7.1e-3 है। यहाँ देखे गए 256 एक्सेस पर लागू करने पर यह अभियान में ~1.8
use-after-frees की भविष्यवाणी करता है — और 2 रूट प्राप्त हुए। रिक्लेम,
एड्रेस डिस्कवरी और चेन बाधा नहीं हैं; रेस है।
**कुछ भी क्रैश नहीं हुआ।** किसी भी गेस्ट ने नौ गेस्ट-घंटों में oops नहीं लिया, जिसमें
वे चार भी शामिल हैं जो कभी नहीं जीते। या तो चेन सही ढंग से स्थित, स्प्रे किए गए
पेज के खिलाफ फायर करती है, या यह कभी फायर नहीं करती — जिसके लिए स्टेज 2 में
बहुमत-वोट अस्वीकृति है।
वह अस्वीकृति व्यवहार में फायर करती है। पहले से लोडेड होस्ट पर एक साथ छह गेस्ट बूट करने
से एक ऐसा उत्पन्न हुआ जिसने स्टेज 2 पर हार मान ली```
[*] direct map not found; refusing to fire at an unverified address
और बिना दौड़ के बाहर निकल गया। यही इच्छित व्यवहार है: एक बर्बाद बूट सही परिणाम है जब टाइमिंग चैनल आम सहमति तक नहीं पहुंच सकता, और यह उस विकल्प से कहीं बेहतर है जिसमें चेन को ऐसे पते पर फायर किया जाए जिसकी कभी पुष्टि नहीं हुई।
docs/lpe-results.json मशीन-पठनीय रूप रखता है, जिसमें उपयोग किए गए सटीक कर्नेल और initramfs का SHA-256 शामिल है।
गेस्टों को समवर्ती रूप से चलाना केवल समानांतरता के बारे में नहीं है। एक व्यस्त होस्ट पर, KVM गेस्ट vCPU को डीशेड्यूल करता है, और यही वह देरी है जिसकी दूसरी शर्त को आवश्यकता है: वॉकर को t->lock छोड़ने और पीड़ित के आंतरिक लॉक को लेने के बीच अपना CPU खोना पड़ता है। एक निष्क्रिय होस्ट पर एक अकेला गेस्ट तीन समवर्ती गेस्टों की तुलना में लगभग बीसवें हिस्से की कमजोर-पहुंच दर पर मापा गया।
हालाँकि, एक ऊपरी सीमा है। 32-थ्रेड होस्ट पर 8 vCPU वाले आठ गेस्टों पर, प्रति गेस्ट ~5 GiB डायरेक्ट-मैप स्प्रे के साथ, होस्ट स्वैप में चला गया और आठ में से तीन गेस्टों ने कोई प्रगति नहीं की। छह शिप किया गया डिफ़ॉल्ट है।
वैकल्पिक इन-गेस्ट प्रीएम्प्शन हेल्पर थ्रेड्स को हिट दर में सुधार किए बिना प्रयास दर का लगभग तीन गुना खर्च करते हुए मापा गया, और उपयोग नहीं किए जाते हैं।
एक पर्यावरणीय कारक उम्मीद से अधिक मायने रखता निकला: binder का अपना डिबग आउटपुट। binder.debug_mask अपने डिफ़ॉल्ट पर होने पर ड्राइवर दौड़ के दौरान बड़ी मात्रा में दर-सीमित pr_info ट्रैफ़िक उत्सर्जित करता है, और printk और कंसोल-लॉक दबाव जो वह बनाता है, उस सटीक प्रीएम्प्शन विंडो को लंबा करता है जिसकी दूसरी शर्त को आवश्यकता है। रिकॉर्डिंग के लिए एक साफ़ कंसोल के लिए स्पष्ट काम — binder.debug_mask=0 के साथ इसे शांत करना — परीक्षण में हिट दर को मापने योग्य रूप से कम करता है: गेस्ट दोनों विजेताओं की प्रयास संख्या से कहीं आगे बिना हिट के चले। इसलिए demo/run-demo.sh binder डिबग को उसके डिफ़ॉल्ट पर छोड़ देता है, और एक शांत कंसोल एक स्पष्ट ऑप्ट-इन है। यह प्रयोगशाला का एक गुण है, शोषण का नहीं — लेकिन यह एक अच्छा उदाहरण है कि यह दौड़ सिस्टम-व्यापी टाइमिंग जिटर पर कितना निर्भर करती है, न कि शोषण स्वयं जो नियंत्रित करता है उस पर।
/dev/binder खोलता है, binder ट्रांज़ैक्शन भेजता है और binder थ्रेड्स से बाहर निकलता है। यह कुछ भी इंस्टॉल नहीं करता और कुछ भी पीछे नहीं छोड़ता।panic=1 oops=panic के साथ बूट होती है ताकि एक रन भ्रष्ट स्थिति पर जारी रहने के बजाय समाप्त हो जाए। हमेशा एक साफ़ बूट से पुनः आरंभ करें।<[email protected]> (ट्विटर:
@aramosf)2026-08-16 पर SearchSploit (स्थानीय Exploit-DB प्रति) और CVE-2026-64468, binder_free_transaction और
f223d27a546c के लिए वेब खोज के साथ जाँच की गई। इस CVE के लिए कोई सार्वजनिक शोषण या प्रूफ ऑफ कॉन्सेप्ट नहीं मिला;
SearchSploit केवल असंबंधित, पुराने Android binder प्रविष्टियाँ लौटाता है। यह एक बिंदु-समय की जाँच है, स्थायी गारंटी नहीं।
| स्थिति | दावा |
|---|
| पुष्टि | कमजोरी वास्तविक है, एक विशेषाधिकार-रहित प्रक्रिया से पहुँच योग्य है, और अपस्ट्रीम फिक्स इसे हटा देता है। |
| प्रदर्शित | मरते हुए binder_proc का कमजोर डीरेफरेंस बिना पैच वाले कर्नेल पर स्वाभाविक रूप से और बार-बार पहुँचा जाता है, और पैच किए गए कर्नेल पर कभी नहीं। |
| प्रदर्शित | बिना पैच वाले कर्नेल पर KASAN द्वारा रिपोर्ट किया गया पूर्ण उपयोग-के-बाद-मुक्त। |
| प्रदर्शित | हमलावर-नियंत्रित बाइट्स के साथ मुक्त किए गए binder_proc का पुनः अधिग्रहण, कर्नेल नियंत्रण-प्रवाह हाईजैक, और x86_64 पर एक विशेषाधिकार-रहित उपयोगकर्ता से uid 0 तक विशेषाधिकार वृद्धि। |
| दावा नहीं | किसी विशिष्ट विक्रेता या Android डिवाइस पर कोई परिणाम। केवल नीचे सूचीबद्ध अपस्ट्रीम कर्नेल का x86_64 पर परीक्षण किया गया। |
| दावा नहीं | कि शिप किया गया एक्सप्लॉइट बिना संशोधन के डिस्ट्रीब्यूशन कर्नेल के विरुद्ध काम करता है। कौन से सिस्टम प्रभावित हैं देखें: इसे प्रति-कर्नेल ऑफसेट की आवश्यकता होती है, और सर्वेक्षण किए गए हर सामान्य-उद्देश्य डिस्ट्रीब्यूशन पर binder डिवाइस पहले स्थान पर किसी विशेषाधिकार-रहित उपयोगकर्ता के लिए पहुँच योग्य नहीं है। |
| ऑफसेट | फ़ील्ड | वॉकर क्या करता है |
|---|
| 108 | int outstanding_txns | इसे घटाता है |
| 113 | bool is_frozen | इसे पढ़ता है |
| 120 | wait_queue_head_t freeze_wait | इसे चलाता है यदि outstanding_txns == 0 && is_frozen |
| 624 | spinlock_t inner_lock | इसे लेता और जारी करता है |
| स्थिति | कमिट | नोट्स |
|---|
| परिचय वंशावली | a370003cc301 | अपस्ट्रीम Fixes: टैग द्वारा नामित |
| मान्य किया गया कमजोर | 114a116aaa5f | फिक्स का प्रत्यक्ष माता-पिता; पड़ोसी CVE-2026-64469 फिक्स रखता है, इसलिए यह जोड़ी CVE-2026-64468 को अकेले अलग करती है |
| सही किया गया मेनलाइन | f223d27a546c | परीक्षण के अधीन फिक्स |
| वितरण | कर्नेल | ANDROID_BINDER_IPC | डिवाइस | SLAB_BUCKETS | RANDOM_KMALLOC_CACHES | अनप्रिविलेज्ड पहुंच योग्य? |
|---|
| Debian 13 (trixie) | 6.12.101 | m | ANDROID_BINDER_DEVICES="binder", BINDERFS बंद | y | बंद | नहीं — मॉड्यूल लोड नहीं; /dev/binder 0600 है |
| Debian 12 (bookworm) | 6.1.0 | m | ANDROID_BINDER_DEVICES="binder", BINDERFS बंद | n/a (6.11 से पहले) | n/a (6.6 से पहले) | नहीं — वही |
| Ubuntu 24.04 LTS | 6.8.0 | m | BINDERFS=m, ANDROID_BINDER_DEVICES="" | n/a (6.11 से पहले) | y | नहीं — mount -t binder के लिए root चाहिए |
| Ubuntu 22.04 LTS | 5.15.0 | m | BINDERFS=m, ANDROID_BINDER_DEVICES="" | n/a | n/a | नहीं — वही |
| Android (AOSP / वेंडर) | 6.1, 6.6, 6.12 GKI | y | /dev/binder, /dev/hwbinder, /dev/vndbinder | — | — | हाँ — ड्राइवर प्लेटफ़ॉर्म IPC है और विश्व-सुलभ है |
| विकल्प | यहाँ प्रभाव |
|---|
CONFIG_SLAB_BUCKETS (6.11+) | इस रिक्लेम के लिए घातक। यह msg_msg को अपने स्वयं के kmalloc बकेट में अलग करता है, इसलिए पंप कभी भी binder_proc के स्लॉट में नहीं उतर सकता। Debian 13 इसे सेट करता है। एक और बेहिसाब 1 KiB आवंटन खोजना होगा। कर्नेल 6.6, जो रुचि के Android डिवाइस चलाते हैं, इसे पूरी तरह से पूर्व-दिनांकित करता है। |
CONFIG_RANDOM_KMALLOC_CACHES (6.6+) | kmalloc-1k को कॉल साइट द्वारा कई कैश में विभाजित करता है, इसलिए पंप को उसी एक को हिट करना होता है; रिक्लेम पर 1-इन-16 कर, दीवार नहीं। Ubuntu इसे सेट करता है, Debian नहीं। |
पेज टेबल आइसोलेशन (nopti उपयोग नहीं) | पता खोज के लिए घातक। PTI सक्रिय होने पर प्रीफेच कर्नेल टेक्स्ट नहीं देख सकता, और एक्सप्लॉइट इसे पहचानकर रुक जाता है। PTI हर उपरोक्त वितरण पर संकलित है, लेकिन CPU तय करता है कि यह सक्रिय है या नहीं: यह उन हार्डवेयर पर बंद है जो Meltdown से प्रभावित नहीं हैं, जहाँ ये परिणाम मापे गए थे। |
CONFIG_SLAB_FREELIST_RANDOM, ..._HARDENED | प्रयोगशाला में सक्षम, जैसा कि वितरण उन्हें शिप करते हैं। कोई मापने योग्य प्रभाव नहीं: पंप फ्रीलिस्ट क्रम की भविष्यवाणी नहीं करता, यह केवल बड़ी संख्या में ऑब्जेक्ट आवंटित करता है। |
KASLR (RANDOMIZE_BASE, RANDOMIZE_MEMORY) | सक्षम। प्रीफेच चरणों द्वारा पराजित; कोई nokaslr नहीं। |
| प्रति-कर्नेल ऑफसेट | चेन को commit_creds, तीन क्रेड-संबंधित ग्लोबल्स और दो गैजेट्स की आवश्यकता होती है, _text से ऑफसेट के रूप में। mkoffsets.sh उन्हें लक्ष्य vmlinux से निकालता है; उनके बिना एक्सप्लॉइट गलत पतों पर फायर करता है। यह किसी भी कर्नेल एक्सप्लॉइट की प्रति-बिल्ड संपत्ति है, बचाव नहीं। |
मेमोरी-सुरक्षा प्रयोगशाला (lab/) | शोषण प्रयोगशाला (demo/) |
|---|
| कर्नेल संस्करण | 7.2.0-rc1+ | 7.2.0-rc1+ |
| कमजोर कमिट | 114a116aaa5f0295376cdf12da743c5bce3b20ce | वही |
| फिक्स्ड कमिट | f223d27a546c1e1f48d38fd67760e78f068fe8c4 | — (शोषण केवल कमजोर कर्नेल पर मापा जाता है) |
| आर्किटेक्चर | x86_64 (KVM) और arm64 (TCG) | x86_64 (KVM) |
| कंपाइलर | Ubuntu clang 21.1.8 / LLD 21.1.8 | वही |
| KASAN | चालू — यह डिटेक्टर है | बंद — यह स्लैब लेआउट बदलता है और किसी भी रिक्लेम को अप्रतिनिधित्वपूर्ण बना देगा |
| स्लैब हार्डनिंग | — | SLAB_FREELIST_RANDOM, SLAB_FREELIST_HARDENED चालू; SLAB_BUCKETS, RANDOM_KMALLOC_CACHES बंद |
| KASLR | — | RANDOMIZE_BASE, RANDOMIZE_MEMORY चालू |
| यूज़रलैंड | न्यूनतम initramfs | Debian GNU/Linux 13 (trixie), वितरण के अपने स्वयं के gcc के साथ |
| प्रारंभिक पहचान | uid 1000, gid 1000, कोई क्षमता नहीं, कोई नेमस्पेस नहीं | वही |
| बूट कमांड लाइन | console=ttyS0 rdinit=/init panic=1 oops=panic kasan_multi_shot=1 | console=ttyS0 loglevel=4 rdinit=/init — कोई nopti नहीं, कोई nokaslr नहीं, कोई mitigations=off नहीं |