
تحليل تقني وإثبات مفهوم يوضح تجاوز إجراءات تخفيف Spectre-BTI في مساحة المستخدم لنواة لينكس عبر prctl و seccomp، مع شرح على مستوى الكود ونتائج الاختبار.
José Oliveira (esoj)
Rodrigo Branco (BSDaemon)
عند اختبار معدل نجاح هجمات Spectre-BTI، اكتشفنا نمطًا غريبًا عند استخدام واجهة برمجة تطبيقات kernel كإجراء تخفيف[^1]. أظهرت اختباراتنا أن نواة Linux تفشل في تخفيف الهجوم بشكل صحيح، مما يترك العملية مكشوفة لفترة قصيرة بعد استدعاء النظام.
أظهرت التحقيقات الإضافية أن النواة لا تصدر IBPB فورًا أثناء استدعاء النظام. تقوم وظيفة ib_prctl_set[^2] بتحديث أعلام معلومات الخيط (TIFs) للمهمة وتحديث MSR SPEC_CTRL في الوظيفة __speculation_ctrl_update [^3]، ولكن يتم إصدار IBPB فقط في الجدولة التالية، عندما يتم التحقق من بتات TIF. وهذا يترك الضحية عرضة للقيم التي تم حقنها بالفعل في BTB، قبل استدعاء prctl. يتم تصحيح السلوك فقط بعد إعادة جدولة المهمة. علاوة على ذلك، فإن دخول النواة (بسبب استدعاء النظام نفسه)، لا يصدر IBPB في السيناريوهات الافتراضية (أي عندما تحمي النواة نفسها عبر retpoline أو eIBRS).
تنفيذ prctl لتخفيف هجمات spectre-BTI باستخدام:
prctl(PR_SET_SPECULATION_CTRL, PR_SPEC_INDIRECT_BRANCH, PR_SPEC_FORCE_DISABLE, 0, 0);
يؤدي إلى وظيفة ib_prctl_set[^2] على kernel 5.15. عند استخدام الخيار SPEC_DISABLE، يتم تعيين بت TIF لـ task_set_spec_ib_disable ويتم استدعاء task_update_spec_tif:
static int ib_prctl_set(struct task_struct *task, unsigned long ctrl)
[...]
case PR_SPEC_FORCE_DISABLE:
/*
* Indirect branch speculation is always allowed when
* mitigation is force disabled.
*/
if (spectre_v2_user_ibpb == SPECTRE_V2_USER_NONE &&
spectre_v2_user_stibp == SPECTRE_V2_USER_NONE)
return -EPERM;
if (!is_spec_ib_user_controlled())
return 0;
task_set_spec_ib_disable(task);
if (ctrl == PR_SPEC_FORCE_DISABLE)
task_set_spec_ib_force_disable(task);
task_update_spec_tif(task);
break;
task_set_spec_ib_disable تستدعي set_tsk_thread_flag(tsk, TIF_SPEC_FORCE_UPDATE); وإذا كانت المهمة المستهدفة هي المهمة الحالية، فإنها تستدعي speculation_ctrl_update_current();
static void task_update_spec_tif(struct task_struct *tsk)
{
/* Force the update of the real TIF bits */
set_tsk_thread_flag(tsk, TIF_SPEC_FORCE_UPDATE);
/*
* Immediately update the speculation control MSRs for the current
* task, but for a non-current task delay setting the CPU
* mitigation until it is scheduled next.
*
* This can only happen for SECCOMP mitigation. For PRCTL it's
* always the current task.
*/
if (tsk == current)
speculation_ctrl_update_current();
}
بعد غلاف speculation_ctrl_update، تنفذ speculation_ctrl_update_current الدالة __speculation_ctrl_update مع tifp = ~tifp، هنا يتم تنفيذ تحديث wrmsr لتعيين STIBP ولكن لا يتم إصدار IBPB:
static __always_inline void __speculation_ctrl_update(unsigned long tifp,
unsigned long tifn)
{
unsigned long tif_diff = tifp ^ tifn;
u64 msr = x86_spec_ctrl_base;
bool updmsr = false;
lockdep_assert_irqs_disabled();
/* Handle change of TIF_SSBD depending on the mitigation method. */
if (static_cpu_has(X86_FEATURE_VIRT_SSBD)) {
if (tif_diff & _TIF_SSBD)
amd_set_ssb_virt_state(tifn);
} else if (static_cpu_has(X86_FEATURE_LS_CFG_SSBD)) {
if (tif_diff & _TIF_SSBD)
amd_set_core_ssb_state(tifn);
} else if (static_cpu_has(X86_FEATURE_SPEC_CTRL_SSBD) ||
static_cpu_has(X86_FEATURE_AMD_SSBD)) {
updmsr |= !!(tif_diff & _TIF_SSBD);
msr |= ssbd_tif_to_spec_ctrl(tifn);
}
/* Only evaluate TIF_SPEC_IB if conditional STIBP is enabled. */
if (IS_ENABLED(CONFIG_SMP) &&
static_branch_unlikely(&switch_to_cond_stibp)) {
updmsr |= !!(tif_diff & _TIF_SPEC_IB);
msr |= stibp_tif_to_spec_ctrl(tifn);
}
if (updmsr)
wrmsrl(MSR_IA32_SPEC_CTRL, msr);
}
يستخدم استدعاء نظام seccomp أيضًا ib_prctl_set[^2] كإجراء تخفيف، داخل arch_seccomp_spec_mitigate[^4] لذلك من المتوقع نفس النتيجة مع seccomp.
بينما نحن متأكدون من خلال تحليل الكود من وجود نافذة للاستغلال، لم يكن واضحًا ما إذا كانت كبيرة بما يكفي لتحميل الضحية للأسرار وتسريبها من قبل المهاجم (حيث من المتوقع ألا تكون الأسرار موجودة في مساحة عنوان الضحية حتى يتم استدعاء prctl). تم تنفيذ الاختبارات على جهاز فعلي يدعم إجراءات التخفيف من الأجهزة، مع تثبيت ubuntu 22.04.1 LTS:
Kernel is Linux 5.15.0-56-generic #62-Ubuntu SMP Tue Nov 22 19:54:14 UTC 2022 x86_64
CPU is Intel(R) Core(TM) i7-4790 CPU @ 3.60GHz
* Hardware support (CPU microcode) for mitigation techniques
* Indirect Branch Restricted Speculation (IBRS)
* SPEC_CTRL MSR is available: YES
* CPU indicates IBRS capability: YES (SPEC_CTRL feature bit)
* Indirect Branch Prediction Barrier (IBPB)
* CPU indicates IBPB capability: YES (SPEC_CTRL feature bit)
* Single Thread Indirect Branch Predictors (STIBP)
* SPEC_CTRL MSR is available: YES
* CPU indicates STIBP capability: YES (Intel STIBP feature bit)
* Speculative Store Bypass Disable (SSBD)
يتكون كود الاختبار من عمليتين يتم تنفيذهما على نفس النواة المنطقية. يقوم المهاجم باستمرار بتسميم BTB بعنوان أداة spectre الموجودة في عملية الضحية. تقوم عملية الضحية بقياس معدل التوقع الخاطئ عن طريق التحقق مما إذا كان متغير الاختبار قد تم الوصول إليه بواسطة وظيفة أداة spectre. يعيد هذا عادةً المخرجات التالية:
esoj@oxigenio:~/CPU_exploits/prctlbleed$ ./attacker 0x55555554123 0x55555555345 0 &
esoj@oxigenio:~/CPU_exploits/prctlbleed$ ./victim-PRCTL 0x55555554123 0x55555555345 0
Rate: 941/1000
Rate: 1000/1000
Rate: 999/1000
Rate: 1000/1000
Rate: 1000/1000
Rate: 997/1000
Rate: 994/1000
Rate: 996/1000
Rate: 998/1000
Rate: 993/1000
Total misspredict rate: 9918/10000 (99.18 %)
ثم، يتم استخدام PRCTL لتخفيف الهجوم. يمكن تمكين التخفيف عن طريق إضافة prctl(PR_SET_SPECULATION_CTRL, PR_SPEC_INDIRECT_BRANCH, PR_SPEC_FORCE_DISABLE, 0, 0); في بداية البرنامج. من المتوقع أن يخفف هذا من هجوم spectre-BTI:
PRCTL GET value 0x9
Rate: 0/1000
Rate: 0/1000
Rate: 0/1000
Rate: 0/1000
Rate: 0/1000
Rate: 0/1000
Rate: 0/1000
Rate: 0/1000
Rate: 0/1000
Rate: 0/1000
Total misspredict rate: 0/10000 (0.00 %)
ومع ذلك، أظهرت بعض الاختبارات نتيجة مختلفة:
Rate: 50510/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Total misspredict rate: 50510/1000000 (5.05 %)
ويبدو أن تغيير 'nice' (الأولوية) يؤثر على معدل التوقع الخاطئ:
esoj@oxigenio:~/CPU_exploits/prctlbleed$ sudo nice -n -19 ./victim-PRCTL 0x55555554123 0x55555555345 0
Rate: 99994/100000
Rate: 7716/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Total misspredict rate: 107710/1000000 (10.77 %)
esoj@oxigenio:~/CPU_exploits/prctlbleed$ sudo nice -n 19 ./victim-PRCTL 0x55555554123 0x55555555345 0
Rate: 16715/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Total misspredict rate: 16715/1000000 (1.67 %)
يشير هذا إلى أن prctl يحمي العملية فقط بعد الجدولة التالية، كما هو مفهوم من تحليل الكود. سلوك غريب آخر لهذا الاختبار هو أنه بعد فرع غير صحيح، يجب تصحيح مسار التخمين ويجب كتابة القيمة الحقيقية على BTB. نظرًا لعدم وجود مهاجمين آخرين على الخيط الشقيق لإعادة تسميم BTB، فإن قيم التوقع الخاطئ العالية هذه غير متوقعة.
للتأكد من أن هذا ليس خطأ في القياس، أنشأنا POC بسيط. يقوم كود الضحية دائمًا بتنفيذ safe_function من خلال مؤشر دالة معرض لهجوم spectre-BTI. تطلب الضحية من النواة الحماية باستخدام استدعاء النظام prctl (داخل protect_me). تقوم الضحية أيضًا بتحميل سر من ملف نصي، مما يوضح أن استدعاءات النظام الأخرى أيضًا لا تتحقق من بت TIF أو تستدعي إعادة جدولة من شأنها أن تجبر IBPB.
//gcc -o victim victim.c -O0 -masm=intel -no-pie -fno-stack-protector
#include "common.h"
int main(int argc, char *argv[])
{