
PoC for CVE-2026-53360: KVM SEV-SNP पेज स्टेट चेंज (PSC) हैंडलिंग में अतिथि-प्रेरित हीप आउट-ऑफ-बाउंड्स रीड/राइट।
KVM के SEV-SNP पेज स्टेट चेंज (PSC) हैंडलिंग में हीप आउट-ऑफ-बाउंड्स रीड और राइट का प्रूफ ऑफ कॉन्सेप्ट। एक दुर्भावनापूर्ण SEV-SNP अतिथि होस्ट कर्नेल को उसके स्लैब आवंटन के अंत से एक PSC एंट्री ऐरे को चलाने पर मजबूर करता है। इससे पड़ोसी kmalloc-cg-32 ऑब्जेक्ट्स का लेआउट लीक हो जाता है और उनमें एक नियंत्रित छोटा मान लिखा जाता है, और अतिथि जितनी बार चाहे इसे दोहरा सकता है।
पूर्ण विवरण: https://cyberstan.co.uk/sev-snp-oob/
| CVE | CVE-2026-53360 |
| घटक | KVM SNP होस्ट समर्थन, arch/x86/kvm/svm/sev.c |
| प्रस्तुत | 9b54e248d264 (पहला KVM SNP PSC हैंडलिंग, मई 2024, ~v6.10) |
| सुधारित | db3f219 (मेनलाइन, मई 2026, Cc: stable), टैग Fixes: 4af663c |
| रिपोर्ट की गई | [email protected], 8 अप्रैल 2026 |
| प्रभावित | केवल SEV-SNP होस्ट पथ। KVM सादे SEV-ES अतिथियों के लिए PSC सक्षम नहीं करता। |
कोई भी SEV-SNP अतिथि होस्ट कर्नेल के हीप को दूषित कर सकता है और एक गलत PSC अनुरोध भेजकर उसके लेआउट के बारे में जानकारी वापस पढ़ सकता है। यह अतिथि से होस्ट दिशा है: SEV-SNP अतिथि को एक अविश्वसनीय होस्ट से बचाने के लिए बनाया गया है, लेकिन होस्ट को अभी भी एक दुर्भावनापूर्ण अतिथि के खिलाफ अपनी रक्षा करनी होती है, और यह हैंडलर ऐसा नहीं करता।
इसके लिए वास्तविक SEV-SNP हार्डवेयर चाहिए। इसे Intel पर पुन: उत्पन्न नहीं किया जा सकता, और नेस्टेड वर्ट आपको SNP अतिथि नहीं देगा।
हार्डवेयर:
*.metal, Hetzner AX, और इसी प्रकार)।होस्ट कर्नेल:
CONFIG_KASAN=y
CONFIG_KASAN_GENERIC=y
CONFIG_KVM=y
CONFIG_KVM_AMD=y
CONFIG_KVM_AMD_SEV=y
CONFIG_CRYPTO_DEV_SP_PSP=y
kvm_amd.sev=1 kvm_amd.sev_es=1 kvm_amd.sev_snp=1 kasan_multi_shot
cat /sys/module/kvm_amd/parameters/sev_snp # Y
ls /dev/sev # /dev/sev
होस्ट यूज़रस्पेस:
git clone https://github.com/AMDESE/qemu.git
cd qemu && git checkout snp-latest
mkdir build && cd build
../configure --target-list=x86_64-softmmu && make -j$(nproc)
अतिथि:
build-essential और linux-headers-$(uname -r) स्थापित हों ताकि आप उसमें मॉड्यूल बना सकें।SEV-SNP अतिथि GHCB के माध्यम से होस्ट से बात करते हैं, जो एक 4 KB साझा पेज है। एक PSC अनुरोध SW_EXITCODE को SVM_VMGEXIT_PSC (0x80000010) पर सेट करता है, SW_SCRATCH को एक डिस्क्रिप्टर की ओर इंगित करता है, और डिस्क्रिप्टर की लंबाई SW_EXITINFO2 में डालता है।
डिस्क्रिप्टर एक struct psc_buffer है: एक 8-बाइट हेडर जिसके बाद 8-बाइट एंट्री का ऐरे है। कोई स्पष्ट गणना फ़ील्ड नहीं है। होस्ट hdr->cur_entry से hdr->end_entry तक एंट्री संसाधित करता है, दोनों अतिथि-नियंत्रित।
struct psc_hdr {
u16 cur_entry;
u16 end_entry;
u32 reserved;
} __packed; /* 8 bytes */
struct psc_entry {
u64 cur_page : 12;
u64 gfn : 40;
u64 operation : 4;
u64 pagesize : 1;
u64 reserved : 7;
} __packed; /* 8 bytes */
GHCB v2+ अतिथि को अपने स्क्रैच क्षेत्र को GHCB के 2032-बाइट साझा बफर के अंदर रखना चाहिए, ताकि होस्ट अपने मौजूदा मैपिंग का पुन: उपयोग कर सके। (2032 - 8) / 8 = 253 एंट्री वहाँ फिट होती है, जहाँ से प्रोटोकॉल अधिकतम VMGEXIT_PSC_MAX_COUNT (253) आता है। यह संख्या केवल तब समझ में आती है जब बफर वास्तव में साझा बफर हो।
यदि अतिथि स्क्रैच क्षेत्र को GHCB के बाहर इंगित करता है, तो होस्ट अपनी मैपिंग का उपयोग नहीं कर सकता, इसलिए setup_vmgexit_scratch() अतिथि द्वारा मांगे गए आकार का एक अलग बफर आवंटित करता है। SNP को कभी भी यह पथ नहीं लेना चाहिए, लेकिन इसे रोकने वाला कुछ नहीं है:
scratch_va = kvzalloc(len, GFP_KERNEL_ACCOUNT); /* len == exit_info_2, guest-controlled */
len सीधे अतिथि से आता है, और GFP_KERNEL_ACCOUNT आवंटन को cgroup-गणना वाले kmalloc-cg-N कैश में रखता है। exit_info_2 = 24 मांगें और आपको 32-बाइट kmalloc-cg-32 स्लॉट में 24-बाइट का आवंटन मिलता है: हेडर के लिए जगह और दो एंट्री। entries[1] से आगे सब कुछ किसी दूसरी वस्तु की मेमोरी है।
फिर snp_begin_psc() एंट्री गणना को प्रोटोकॉल स्थिरांक के विरुद्ध जांचता है, न कि उस बफर के विरुद्ध जो वास्तव में आवंटित किया गया था:
idx_end = hdr->end_entry;
if (idx_end >= VMGEXIT_PSC_MAX_COUNT) { /* checks 253, NOT the buffer size */
snp_complete_psc(svm, ...);
return 1;
}
for (idx = idx_start; idx <= idx_end; idx++) {
entry_start = entries[idx]; /* OOB once idx >= 2 */
...
}
24-बाइट बफर में केवल दो एंट्री मौजूद हैं, लेकिन जांच end_entry को 252 तक अनुमति देती है। इसे 252 पर सेट करें और लूप आवंटन से लगभग 2 KB आगे चलता है, पड़ोसी स्लैब ऑब्जेक्ट्स के पार।
प्रत्येक चरण अंत के बाद स्लैब मेमोरी के अगले 8 बाइट को psc_entry के रूप में पुनर्व्याख्या करता है और इसे PSC कोड के माध्यम से चलाता है। यह तीन चीजें देता है:
entry.gfn और entry.operation को मेमोरी से खींचता है जो बफर के पास कभी नहीं थी। यह वह स्लैब-आउट-ऑफ-बाउंड्स रीड है जिसे KASAN पकड़ता है।KVM_HC_MAP_GPA_RANGE के रूप में भेजी जाती है, तो पूर्णता कोड उसी OOB स्लॉट में वापस लिखता है: entries[idx].cur_page = entry.pagesize ? 512 : 1। एक शब्द के निचले 12 बिट्स में दो छोटे मानों में से एक, दोहराने योग्य।SW_EXITINFO2 में प्रतिक्रिया उस इंडेक्स को रिपोर्ट करती है जिस पर वह रुकी थी। end_entry को एक-एक करके बढ़ाने से लीक होता है, स्लॉट दर स्लॉट, क्या पड़ोसी मेमोरी नो-ऑप में डिकोड हुई या कुछ ऐसी जो असफल रही, जो ऑब्जेक्ट सीमाएँ खोजने और शून्य बनाम गैर-शून्य बताने के लिए पर्याप्त है।प्रत्येक VMGEXIT स्क्रैच बफर को पुनः आवंटित करता है, इसलिए बार-बार अनुरोध अलग-अलग फ्रीलिस्ट स्लॉट में उतरते हैं और अतिथि को एक के साथ अटके रहने के बजाय पड़ोसियों में स्वीप करने देते हैं। एक साथ रखने पर यह हीप लेआउट प्रकटीकरण, उपरोक्त संयमित राइट, और अनुरोधों के बीच उपयोग-पश्चात-मुक्त देता है।
trigger.c एक अतिथि कर्नेल मॉड्यूल है। इसे SEV-SNP अतिथि के अंदर लोड करें और यह एकल insmod से होस्ट के विरुद्ध चार चरण चलाता है:
cur_page लिखकर और पुष्टि करके कि बाद का अनुरोध इसे छोड़ देता है।end_entry=200 के साथ एक अनुरोध भेजता है और मापता है कि OOB रीड गैर-शून्य डेटा से टकराने से पहले कितनी दूर तक पहुँचता है।entries[3..10] आउट-ऑफ-बाउंड्स हैं, जिनमें से प्रत्येक होस्ट पर KASAN रिपोर्ट ट्रिगर करता है।मॉड्यूल एक पेज आवंटित करता है, इसे set_memory_decrypted() से डिक्रिप्टेड चिह्नित करता है, इसे स्क्रैच क्षेत्र के रूप में उपयोग करता है, और GHCB PSC अनुरोध हाथ से बनाता है। यह -EAGAIN के साथ बाहर निकलता है ताकि यह लोड न रहे।
अपने सेटअप के अनुसार OVMF और डिस्क पथ समायोजित करें: