
CVE-2018-4185: divulgation de pointeur du noyau iOS 11.2-11.2.6 introduite par l'atténuation de Meltdown d'Apple.
iOS 11.2 a introduit une fuite d'informations du noyau qui pouvait être utilisée pour déterminer le décalage kASLR. Le problème était le résultat d'une nouvelle fonctionnalité, __ARM_KERNEL_PROTECT__, qui a involontairement fait apparaître l'adresse de la fonction du noyau Lel0_synchronous_vector_64_long dans le registre x18 lors de l'obtention des valeurs des registres d'un thread à l'aide de thread_get_state. Le problème a été découvert lorsque des pointeurs du noyau ont commencé à apparaître dans les journaux de crash des applications iOS.
Dans iOS 11.2, Apple a introduit une fonctionnalité sur arm64 appelée __ARM_KERNEL_PROTECT__. Selon un commentaire dans osfmk/arm64/proc_reg.h :
__ARM_KERNEL_PROTECT__ est une fonctionnalité destinée à se protéger contre d'éventuelles vulnérabilités architecturales ou microarchitecturales qui pourraient permettre aux cœurs de lire/accéder aux mappages réservés à EL1 alors qu'ils sont en mode EL0. Ceci est réalisé en supprimant autant de mappages que possible lorsque le cœur passe du mode EL1 au mode EL0, et en restaurant ces mappages lorsque le cœur passe du mode EL1 au mode EL0.
C'est-à-dire que lors de la transition d'EL1 (mode noyau) à EL0 (mode utilisateur), autant de mappages du noyau que possible seront supprimés. Cela devrait limiter la surface d'attaque possible contre les mappages mémoire du noyau lors de l'exploitation de vulnérabilités microarchitecturales comme Spectre ou Meltdown.
Si vous examinez la différence entre les versions XNU 4570.20.62 et 4570.31.3, vous trouverez un certain nombre de nouvelles références au registre x18 dans le fichier [osfmk/arm64/locore.s][XNU 4570.31.3 locore.s] en relation avec __ARM_KERNEL_PROTECT__. En particulier, vous verrez que le vecteur d'exception Lel0_synchronous_vector_64, qui est le vecteur d'exception invoqué lors d'un appel système (instruction svc #0), ressemble maintenant à ceci :
.text
.align 7
Lel0_synchronous_vector_64:
MAP_KERNEL
BRANCH_TO_KVA_VECTOR Lel0_synchronous_vector_64_long, 8
La macro BRANCH_TO_KVA_VECTOR est définie comme suit :
.macro BRANCH_TO_KVA_VECTOR
#if __ARM_KERNEL_PROTECT__
/*
* Find the kernelcache table for the exception vectors by accessing
* the per-CPU data.
*/
mrs x18, TPIDR_EL1
ldr x18, [x18, ACT_CPUDATAP]
ldr x18, [x18, CPU_EXC_VECTORS]
/*
* Get the handler for this exception and jump to it.
*/
ldr x18, [x18, #($1 << 3)]
br x18
#else
b $0
#endif /* __ARM_KERNEL_PROTECT__ */
.endmacro
Cette macro effectue un branchement indirect vers la véritable implantation du vecteur d'exception,
Lel0_synchronous_vector_64_long, en chargeant un pointeur vers cette fonction dans le registre x18.
Notez cependant que cet écrasement de x18 se produit avant que les registres de l'espace utilisateur ne soient sauvegardés par la
fonction fleh_dispatch64, qui est appelée par Lel0_synchronous_vector_64_long. Cela signifie que
lorsque les registres utilisateur sont sauvegardés, x18 sera en fait un pointeur vers
Lel0_synchronous_vector_64_long plutôt que la valeur originale de l'espace utilisateur.
Même si x18 est effacé au retour d'exception, stocker un pointeur du noyau dans l'état des registres utilisateur
est problématique car thread_get_state peut être utilisée pour copier l'état sauvegardé des registres utilisateur
vers l'espace utilisateur, y compris la valeur du registre x18. Tout ce qu'un thread doit faire pour
obtenir l'adresse de la fonction Lel0_synchronous_vector_64_long est d'appeler thread_get_state sur
lui-même et de regarder la valeur rapportée de x18. Cela rend trivial la détermination du décalage kASLR
en soustrayant la valeur de x18 ainsi obtenue de l'adresse statique de
Lel0_synchronous_vector_64_long.
Comme mentionné ci-dessus, l'exploitation est triviale : il suffit d'appeler la fonction thread_get_state, de regarder
la valeur du registre x18, et de lui soustraire l'adresse statique de la fonction du noyau
Lel0_synchronous_vector_64_long.
J'ai découvert ce problème le 26 février 2018, après avoir remarqué un pointeur du noyau dans le registre x18 d'un
journal de crash d'application iOS. Une vérification rapide a montré que la même valeur apparaissait dans le registre x18
de chaque journal de crash sur l'appareil, ce qui suggérait une fuite d'informations sérieuse.
J'ai ensuite essayé de déterminer exactement ce qui se passait avec le registre x18 par expérimentation. J'ai
placé un point d'arrêt dans une application iOS vide et utilisé lldb pour lire la valeur du registre x18, confirmant
que la fuite n'était pas limitée aux applications en crash. Ensuite, j'ai essayé de lire la valeur de x18
en utilisant l'assembleur en ligne et j'ai constaté que la valeur obtenue ne correspondait pas à la valeur affichée par le
débogueur lors de l'utilisation d'une commande comme reg read x18. Cela suggérait que la fuite se trouvait peut-être vraiment
dans thread_get_state, et que le registre x18 ne contenait pas réellement un pointeur du noyau pendant que le CPU
s'exécutait dans l'espace utilisateur. Une preuve de concept rapide qui lisait la valeur de x18 en utilisant
thread_get_state a confirmé que cette fonction était bien la source de la fuite.
J'ai signalé le problème à Apple le 26 février 2018, le jour même où je l'ai découvert.
Par Brandon Azad