
Technische Analyse und Proof-of-Concept, der eine Umgehung der Spectre-BTI-User-Space-Mitigationen des Linux-Kernels über prctl und seccomp demonstriert, mit Code-Ebene-Erklärung und Testergebnissen.
José Oliveira (esoj)
Rodrigo Branco (BSDaemon)
Beim Testen der Erfolgsrate von Spectre-BTI-Angriffen haben wir ein seltsames Muster bei der Verwendung der Kernel-API als Mitigation[^1] festgestellt. Unsere Tests zeigten, dass der Linux-Kernel den Angriff nicht korrekt mitigiert und den Prozess für einen kurzen Zeitraum nach dem Syscall ungeschützt lässt.
Weitere Untersuchungen ergaben, dass der Kernel während des Syscalls kein IBPB unmittelbar auslöst. Die Funktion ib_prctl_set[^2] aktualisiert die Thread Information Flags (TIFs) für den Task und aktualisiert das SPEC_CTRL-MSR in der Funktion __speculation_ctrl_update [^3], aber das IBPB wird erst beim nächsten Scheduler-Durchlauf ausgelöst, wenn die TIF-Bits geprüft werden. Dadurch bleibt das Opfer anfällig für Werte, die bereits vor dem prctl-Syscall in den BTB injiziert wurden. Dieses Verhalten wird erst nach einer Neuplanung des Tasks korrigiert. Darüber hinaus wird beim Kernel-Eintritt (aufgrund des Syscalls selbst) in den Standard-Szenarien (d.h. wenn der Kernel sich selbst per Retpoline oder eIBRS schützt) kein IBPB ausgelöst.
Das Ausführen eines prctl zur Mitigation von Spectre-BTI-Angriffen mit:
prctl(PR_SET_SPECULATION_CTRL, PR_SPEC_INDIRECT_BRANCH, PR_SPEC_FORCE_DISABLE, 0, 0);
führt in Kernel 5.15 zur Funktion ib_prctl_set[^2]. Wenn die Option SPEC_DISABLE verwendet wird, wird das TIF-Bit für task_set_spec_ib_disable gesetzt und task_update_spec_tif aufgerufen:
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 ruft set_tsk_thread_flag(tsk, TIF_SPEC_FORCE_UPDATE); auf, und wenn der Ziel-Task der aktuelle ist, ruft es speculation_ctrl_update_current(); auf:
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();
}
Die speculation_ctrl_update_current-Funktion führt nach dem speculation_ctrl_update-Wrapper __speculation_ctrl_update mit tifp = ~tifp aus. Hier wird das wrmsr zum Setzen von STIBP ausgeführt, aber kein IBPB ausgelöst:
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);
}
Der seccomp-Syscall verwendet ebenfalls ib_prctl_set[^2] als Mitigation, innerhalb von arch_seccomp_spec_mitigate[^4], sodass dasselbe Ergebnis bei seccomp zu erwarten ist.
Obwohl wir durch die Code-Analyse sicher sind, dass das Fenster für eine Ausnutzung existiert, war unklar, ob es groß genug ist, damit das Opfer Geheimnisse lädt und der Angreifer sie abgreift (da erwartet wird, dass Geheimnisse nicht im Adressraum des Opfers sind, bis der prctl-Aufruf ausgeführt wird). Die Tests wurden auf einer Bare-Metal-Maschine mit Unterstützung für Hardware-Mitigationen und einer installierten Ubuntu 22.04.1 LTS ausgeführt:
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)
Der Testcode besteht aus zwei Prozessen, die auf demselben logischen Kern ausgeführt werden. Der Angreifer vergiftet ständig den BTB mit der Adresse einer Spectre-Gadget, die in einem Opferprozess vorhanden ist. Der Opferprozess misst die Fehlvorhersagerate, indem er prüft, ob eine Testvariable von der Spectre-Gadget-Funktion aufgerufen wurde. Dies liefert normalerweise die folgende Ausgabe:
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 %)
Dann wird PRCTL verwendet, um den Angriff zu mitigieren. Die Mitigation kann aktiviert werden, indem prctl(PR_SET_SPECULATION_CTRL, PR_SPEC_INDIRECT_BRANCH, PR_SPEC_FORCE_DISABLE, 0, 0); am Anfang des Programms hinzugefügt wird. Es wird erwartet, dass dies den Spectre-BTI-Angriff mitigiert:
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 %)
Einige der Tests zeigten jedoch ein anderes Ergebnis:
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 %)
Und die Änderung der Priorität ('nice') scheint die Fehlvorhersagerate zu beeinflussen:
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 %)