
PoC et rapport technique pour CVE-2023-0045 : contourne les mitigations Spectre-BTI de prctl/seccomp de Linux en utilisant l'empoisonnement BTB et Flush+Reload pour fuiter les secrets des processus.
José Oliveira (esoj)
Rodrigo Branco (BSDaemon)
Lors des tests du taux de succès des attaques Spectre-BTI, nous avons détecté un schéma étrange lors de l'utilisation de l'API du noyau comme mitigation[^1]. Nos tests ont révélé que le noyau Linux n'atténue pas correctement l'attaque, laissant le processus exposé pendant une courte période après l'appel système.
Des investigations plus approfondies ont montré que le noyau n'émet pas d'IBPB immédiatement lors de l'appel système. La fonction ib_prctl_set[^2] met à jour les indicateurs d'informations de thread (TIF) pour la tâche et met à jour le MSR SPEC_CTRL dans la fonction __speculation_ctrl_update [^3], mais l'IBPB n'est émis que lors de la prochaine planification, lorsque les bits TIF sont vérifiés. Cela laisse la victime vulnérable aux valeurs déjà injectées dans le BTB avant l'appel système prctl. Le comportement n'est corrigé qu'après une replanification de la tâche. De plus, l'entrée du noyau (due à l'appel système lui-même) n'émet pas d'IBPB dans les scénarios par défaut (c'est-à-dire lorsque le noyau se protège via retpoline ou eIBRS).
L'exécution d'un prctl pour atténuer les attaques Spectre-BTI en utilisant :
prctl(PR_SET_SPECULATION_CTRL, PR_SPEC_INDIRECT_BRANCH, PR_SPEC_FORCE_DISABLE, 0, 0);
mène à la fonction ib_prctl_set[^2] sur le noyau 5.15. Lorsque l'option SPEC_DISABLE est utilisée, le bit TIF pour task_set_spec_ib_disable est défini, et task_update_spec_tif est appelée :
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 appelle set_tsk_thread_flag(tsk, TIF_SPEC_FORCE_UPDATE); et si la tâche cible est la tâche courante, elle appelle 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, après le wrapper speculation_ctrl_update, exécute __speculation_ctrl_update avec tifp = ~tifp ; ici la mise à jour du wrmsr pour définir STIBP est exécutée mais aucun IBPB n'est émis :
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);
}
L'appel système seccomp utilise également ib_prctl_set[^2] comme mitigation, à l'intérieur de arch_seccomp_spec_mitigate[^4], donc le même résultat est attendu avec seccomp.
Bien que par analyse de code nous soyons certains que la fenêtre d'exploitation existe, il n'était pas clair si elle était suffisamment grande pour que la victime charge des secrets et que l'attaquant les fuie (car on s'attend à ce que les secrets ne soient pas dans l'espace d'adressage de la victime jusqu'à ce que l'appel prctl soit effectué). Les tests ont été exécutés sur une machine physique avec support pour les mitigations matérielles, avec Ubuntu 22.04.1 LTS installé :
Le noyau est Linux 5.15.0-56-generic #62-Ubuntu SMP Tue Nov 22 19:54:14 UTC 2022 x86_64
Le CPU est Intel(R) Core(TM) i7-4790 CPU @ 3.60GHz
* Support matériel (microcode CPU) pour les techniques de mitigation
* Spéculation indirecte de branchement restreinte (IBRS)
* Le MSR SPEC_CTRL est disponible : Oui
* Le CPU indique la capacité IBRS : Oui (bit de fonctionnalité SPEC_CTRL)
* Barrière de prédiction de branchement indirect (IBPB)
* Le CPU indique la capacité IBPB : Oui (bit de fonctionnalité SPEC_CTRL)
* Prédicteurs de branchement indirect monothread (STIBP)
* Le MSR SPEC_CTRL est disponible : Oui
* Le CPU indique la capacité STIBP : Oui (bit de fonctionnalité Intel STIBP)
* Désactivation spéculative du contournement de magasin (SSBD)
Le code de test se compose de deux processus s'exécutant sur le même cœur logique. L'attaquant empoisonne constamment le BTB avec l'adresse d'un gadget spectre présent dans un processus victime. Le processus victime mesure le taux de mauvaises prédictions en vérifiant si une variable de test a été accédée par la fonction gadget spectre. Cela renvoie généralement la sortie suivante :
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 %)
Ensuite, le PRCTL est utilisé pour atténuer l'attaque. La mitigation peut être activée en ajoutant prctl(PR_SET_SPECULATION_CTRL, PR_SPEC_INDIRECT_BRANCH, PR_SPEC_FORCE_DISABLE, 0, 0); au début du programme. Cela devrait atténuer l'attaque 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 %)
Cependant, certains tests ont montré un résultat différent :
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 %)
Et modifier la valeur 'nice' (priorité) semble affecter le taux de mauvaises prédictions :
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 %)