
Технический анализ и доказательство концепции, демонстрирующие обход пользовательских средств защиты от Spectre-BTI ядра Linux через prctl и seccomp, с объяснением на уровне кода и результатами тестирования.
Жозе Оливейра (esoj)
Родриго Бранку (BSDaemon)
Тестируя уровень успешности атак Spectre-BTI, мы обнаружили странную закономерность при использовании API ядра в качестве смягчения[^1]. Наши тесты показали, что ядро Linux неправильно смягчает атаку, оставляя процесс уязвимым на короткий промежуток времени после системного вызова.
Дальнейшее исследование показало, что ядро не выдает IBPB немедленно во время системного вызова. Функция ib_prctl_set[^2] обновляет флаги информации о потоке (TIF) для задачи и обновляет 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] в ядре 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_current после оболочки speculation_ctrl_update выполняет __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 адресом спекулятивного гаджета, присутствующего в процессе жертвы. Жертва измеряет частоту неправильных предсказаний, проверяя, была ли тестовая переменная доступна функцией спекулятивного гаджета. Обычно это возвращает следующий вывод:
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, такие высокие значения неправильных предсказаний неожиданны.