
PoC и разбор для CVE-2023-0045: обход средств защиты Linux prctl/seccomp Spectre-BTI с помощью отравления BTB и Flush+Reload для утечки секретов процесса.
Хосе Оливейра (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, такие высокие значения неверных предсказаний неожиданны.