
يحتوي هذا المستودع على استغلال (exploit) لثغرة في البرنامج الثابت الخاص بـ SEV. يتيح الاستغلال فك تشفير ذاكرة اعتباطية من ضيف SEV-SNP قيد التشغيل.
تم اختباره على الإصدار 1.55.16 (أحدث إصدار وقت كتابة هذا النص).
يمكن استخدام حقل nv_paddr الخاص بأمر SEV_INIT_EX للتبرع بجزء من الذاكرة إلى البرنامج الثابت، بحيث يمكن استخدامه بدلاً من الفلاش الدائم. إذا كان SEV-SNP مفعّلاً، فيجب أن تكون هذه الذاكرة في حالة FIRMWARE. يتحقق البرنامج الثابت من ذلك مرة واحدة أثناء تنفيذ أمر SEV_INIT_EX. ومنذ تلك النقطة فصاعداً، يفترض البرنامج الثابت أن هذه الذاكرة في حالة FIRMWARE ويكتب إليها دون أي تحقق إضافي.
الافتراض بأن الذاكرة ما تزال في حالة FIRMWARE ليس صحيحاً دائماً؛ فلا شيء يمنع المضيف (host) من إعادة الحالة إلى HYPERVISOR باستخدام أمر SNP_PAGE_RECLAIM. وبمجرد أن تصبح الصفحات في حالة HYPERVISOR يمكن نقلها إلى حالات أخرى مثل CONTEXT. وعلى الرغم من أن الصفحات لم تعد في حالة FIRMWARE، فإن البرنامج الثابت سيكتب إلى تلك الصفحات، مما يكسر السلامة التي تتطلبها حالات معينة من الصفحات.
يمكننا استغلال فساد الذاكرة هذا من خلال استهداف صفحات CONTEXT. تُعد صفحات CONTEXT هدفاً قوياً، لكن توجد بعض المشاكل:
CONTEXT مشفرة بمفتاح مختلف عن بقية الذاكرة. ونتيجة لذلك، ليس من السهل التحكم في النص الصريح (plaintext) حتى لو تمكنا من التحكم في النص المشفر (ciphertext).يملأ فساد الذاكرة صفحة CONTEXT فعلياً ببيانات عشوائية، لذا ليس من السهل إفساد صفحات CONTEXT بطريقة تفيد المهاجم. وللتغلب على ذلك، يمكننا تكرار تشغيل الثغرة لإحداث الفساد واستخدام أمر SNP_GUEST_STATUS لقراءة الحقول ذات الصلة من صفحة CONTEXT التالفة حتى نلاحظ قيماً مفيدة.
يمكن استخدام أمر SNP_DBG_DECRYPT لفك تشفير ذاكرة ضيف SEV-SNP مع تفعيل سياسة DEBUG. إذا تمكنا من صياغة CONTEXT بحيث تكون فيه علامة DEBUG مضبوطة ويحتوي على ASID الخاص بضيف آخر، فيمكننا استخدامه لفك تشفير ذاكرة الضيف الآخر حتى لو لم تكن سياسة DEBUG مفعّلة لديه.
اتضح أن SNP_DBG_DECRYPT يتجاهل معظم الحقول في صفحة CONTEXT؛ فهو يتحقق فقط من gctx->guest.asid وgctx->guest.policy_snp وgctx->guest.guest_flags. إن احتمال صحة هذه الحقول بعد فساد الذاكرة ليس مرتفعاً، لكنه ليس مستبعداً تماماً أيضاً. والخبر الجيد أيضاً أن بإمكاننا قراءة كل تلك الحقول باستخدام أمر GUEST_STATUS.
في الختام، يمكننا استغلال الثغرة عبر الخطوات التالية:
nv_paddr إلى حالة FIRMWARE باستخدام تعليمة rmpupdate.SEV_INIT_EX.nv_paddr إلى حالة HYPERVISOR باستخدام أمر SNP_RECLAIM_PAGE.CONTEXT واحدة أو أكثر عند nv_paddr.nv_paddr باستخدام أمر SEV_PDH_GEN. يؤدي هذا إلى إفساد صفحات CONTEXT.GUEST_STATUS للتحقق مما إذا كان SNP_DBG_DECRYPT سينجح، وإذا لم ينجح فالعودة إلى الخطوة 5. العقبة الرئيسية هنا هي أن ASIDs تُخزَّن في عدد صحيح 32-بت، لكن عدد ASIDs الصالحة أقل بكثير (509 أو 1006 حسب المعالج)، لذا سيتطلب الأمر محاولات كثيرة جداً لتحقيق ذلك.يقضي الاستغلال معظم وقته في الخطوتين 5 و6. احتمالات توافر جميع الشروط الصحيحة تبلغ حوالي 1/20,000,000 على معالج EPYC Milan، ويمكننا تنفيذ حوالي 100 محاولة في الثانية، لذا نتوقع بلوغ الشروط الصحيحة مرة واحدة كل يومين تقريباً (تحذير: الحسابات تقريبية فقط وقد أكون أخطأت في شيء ما، لكن من واقع التجربة، مرة كل يومين تبدو صحيحة تقريباً). يمكننا تسريع ذلك بعدم مهاجمة صفحة CONTEXT واحدة فقط في كل مرة، بل مهاجمة ثلاث صفحات CONTEXT عند nv_paddr وnv_paddr+4096 وnv_paddr+8192 (سيفسد SEV_PDG_GEN ثلاث صفحات). ومن الملائم أن هذه الخطوات يمكن تنفيذها قبل إطلاق الضيف الضحية، ولا يلزم نجاحها إلا مرة واحدة لمهاجمة أي عدد من الضيوف (لاحظ أن الإثبات المفاهيمي (PoC) يهاجم حالياً ضيفاً واحداً فقط).
على الرغم من أنني لم أتمكن من اختبار هذا بعد، إلا أنني أعتقد أنه بمجرد أن يستخدم المهاجم هذه الثغرة لتسريب مفاتيح التواصل مع منصة الآلة الافتراضية الخاصة بالضيف، فسيتسنى له إرسال رسائل الضيف إلى البرنامج الثابت نيابةً عن الضيف واستخدام ذلك لطلب تقارير الإثبات. وهذا ينتهك مبدأً أساسياً في SEV-SNP يقضي بأن يكون الضيف وحده قادراً على طلب تقارير الإثبات.
ينبغي لعدد من الأوامر (مثل SNP_RECLAIM_PAGE وSNP_GCTX_CREATE وRING_BUFFER، وربما المزيد؟ ربما كلها احتياطاً؟) التي تقبل صفحة بحالة FIRMWARE أن تتحقق مما إذا كانت تتداخل مع nv_paddr وأن تفشل إذا كان الأمر كذلك.
لدّي قلق إضافي واحد، لست متأكداً من صحته وسأحب سماع رأيكم فيه: إذا ما فهمتُ الأمر بشكل صحيح، يمكن ترقية البرنامج الثابت لـ SEV دون مقاطعة الضيوف قيد التشغيل. وهذا يوحي لي بإمكانية نقل صفحة CONTEXT التالفة من إصدار قديم هشّ من البرنامج الثابت إلى إصدار جديد مُصلَح. هل سيكون من الممكن البدء بإصدار قديم هشّ، وتنفيذ الاستغلال الموصوف أعلاه، ثم ترقية البرنامج الثابت المُصلَح الجديد وتثبيته، وإطلاق الضيف باستخدام البرنامج الثابت الجديد (بحيث لا يظهر إصدار البرنامج الثابت القديم في تقرير الإثبات)؟ ثم استخدام صفحة CONTEXT التالفة التي أُنشئت باستخدام البرنامج الثابت القديم لمهاجمة الضيف الذي أُنشئ باستخدام الإصدار الجديد؟ هل سيكون بمقدور مستهلك تقارير الإثبات التي أنشأها الضيف الجديد معرفة أن إصدار البرنامج الثابت القديم كان يعمل في وقت ما قبل إطلاق الضيف الجديد؟ وإذا لم يكن الأمر كذلك، فهل ثمة تخفيفات إضافية مطلوبة لمنع حدوث ذلك؟
طبّق التصحيحات الموجودة في مجلد linux-patches على آخر نسخة من https://github.com/AMDESE/linux/commits/snp-host-v10. ثم ابنِ النواة وثبّتها وشغّلها.
شغّل الإثبات المفاهيمي (PoC).
root@server:~/sev-exploit# cargo run --release
Finished release [optimized] target(s) in 0.12s
Running `target/release/sev-exploit`
Corrupt guest context page so that ASID is in range 1..510
Smallest ASID: 0x0000001f iterations: 14052175 zeros: 10539628 unique asids: 31500727 elapsed time: 1d 19h 20m 31s
Creating VM with same ASID
[03, 00, 00, 00, 00, 00, 00, 00, 11, 0f, a0, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, f0, 51, a5, 03, 3f, 69, 6b, 93, e8, d8, 61, 0d, 2e, 5a, 45, f1, ea, 6d, bf, 49, fe, e4, a9, 2d, 8d, af, 76, 5e, 2e, 56, e0, fa, a9, b3, a7, e0, bc, 09, d9, 4f, 28, 5c, 9f, 84, d2, 7e, 34, eb, ea, 3f, 29, 88, 30, 01, 28, 65, 8b, 73, 3c, 84, 00, ae, 4a, 74, a2, 7a, d1, c7, 4f, 63, 7f, 72, 7b, 3b, 2f, 08, b3, 1a, 8c, 99, 1b, ad, b5, 1d, 42, 0b, 4d, 98, d4, 7d, c1, 0b, d6, 2f, b4, 6c, 6b, 51, a2, 92, 17, 3b, 01, e8, 82, 11, 1e, cb, cb, a2, 8f, c9, b0, 52, 1d, 1d, b7, d2, 25, 8d, 32, a9, 7a, 6f, 86, e4, 40, 44, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 80, 00, 88, 00, 00, 00, 00, ee, ff, 00, 00, f0, ff, ff, ff, ff, ff, ff, ff, ff, 3f, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, <cut off zeros>]
thread 'main' panicked at src/main.rs:170:13:
not yet implemented: use the leaked secrets to send guest messages
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
لاحظ أنه من الطبيعي أن يستغرق الاستغلال وقتاً طويلاً (في حدود الساعات إذا كنت محظوظاً، وأيام إذا لم تكن كذلك). التشغيل على معالج EPYC Genoa سيكون أسرع على الأرجح لأن عدد ASIDs الصالحة يبلغ ضعف العدد تقريباً.
خلال الخطوتين 5 و6 يعرض الإثبات المفاهيمي (PoC) بعض المقاييس:
CONTEXT في حالة يعتبرها البرنامج الثابت غير معيّن لها ASID بعد. في تلك الحالات، سيرجع SNP_GUEST_STATUS القيمة 0 في حقل ASID.في معظم الحالات، يتسبب إلغاء تخصيص صفحات CONTEXT التالفة في تعطل البرنامج الثابت (على الأرجح هنا). وعلى حد علمي، يتسبب تعطل البرنامج الثابت في إعادة تشغيل النظام بأكمله. ولتجنب هذه الأعطال، تمنع تصحيحات النواة إلغاء تخصيص صفحات CONTEXT. ومن عيوب ذلك أن وحدة النواة ccp لا يمكن إلغاء تحميلها. وبمجرد بدء تشغيل الإثبات المفاهيمي (PoC)، يجب إعادة تشغيل النظام بالكامل قبل إمكانية تشغيله مرة أخرى (بغض النظر عما إذا نجح أم أُجهض).
CONTEXT التالفة، مع تتبع صفحة الأسرار. هذا ممكن لأن البرنامج الثابت لـ SEV يتتبع ASIDs النشطة داخلياً ولا يفحص صفحات CONTEXT النشطة للتحقق من التكرار.CONTEXT التالفة لتنفيذ SNP_DBG_DECRYPT على صفحة أسرار الضيف الضحية.