
CVE-2017-13868 : Fuite d'informations de données non initialisées du tas du noyau dans XNU.
La fonction ctl_ctloutput de macOS High Sierra 10.13 ignore la valeur de retour d'un appel à
sooptcopyin, ce qui ouvre une fenêtre de course pour divulguer des données non initialisées du tas
noyau vers l'espace utilisateur. ctl_ctloutput-leak est un exploit de proof-of-concept qui tente de
déclencher cette fuite d'informations. L'exploitation nécessite des privilèges root.
Cet exploit a été confirmé comme fonctionnant sur macOS High Sierra 10.13.1 Beta 17B25c et iOS 10.1.1 14B100 (sous mach_portal).
Voici la partie pertinente de ctl_ctloutput sur macOS High Sierra 10.13 :
if (sopt->sopt_valsize && sopt->sopt_val) {
MALLOC(data, void *, sopt->sopt_valsize, M_TEMP, // (a) data is allocated
M_WAITOK); // without M_ZERO.
if (data == NULL)
return (ENOMEM);
/*
* 4108337 - copy user data in case the
* kernel control needs it
*/
error = sooptcopyin(sopt, data, // (b) sooptcopyin() is
sopt->sopt_valsize, sopt->sopt_valsize); // called to fill the
} // buffer; the return
len = sopt->sopt_valsize; // value is ignored.
socket_unlock(so, 0);
error = (*kctl->getopt)(kctl->kctlref, kcb->unit, // (c) The getsockopt()
kcb->userdata, sopt->sopt_name, // implementation is
data, &len); // called to process
if (data != NULL && len > sopt->sopt_valsize) // the buffer.
panic_plain("ctl_ctloutput: ctl %s returned "
"len (%lu) > sopt_valsize (%lu)\n",
kcb->kctl->name, len,
sopt->sopt_valsize);
socket_lock(so, 0);
if (error == 0) {
if (data != NULL)
error = sooptcopyout(sopt, data, len); // (d) If (c) succeeded,
else // then the data buffer
sopt->sopt_valsize = len; // is copied out to
} // userspace.
Ce code fait ce qui suit :
getsockopt, sans spécifier le
drapeau M_ZERO pour mettre à zéro les octets alloués.getsockopt depuis l'espace utilisateur à l'aide de sooptcopyin,
remplissant ainsi le tampon de données tout juste alloué. Cette copie est censée écrire
complètement par-dessus les données allouées, c'est pourquoi le drapeau M_ZERO n'était pas
nécessaire. Cependant, la valeur de retour de sooptcopyin n'est pas vérifiée, ce qui signifie
qu'il est possible que la copie ait échoué, laissant des données non initialisées dans le tampon.
La copie pourrait échouer si, par exemple, le programme a passé une adresse non mappée à
getsockopt.getsockopt pour cette socket de contrôle
noyau. Cette implémentation devrait traiter le tampon d'entrée, éventuellement le modifier et le
raccourcir, puis retourner un code de résultat. Cependant, l'implémentation est libre de supposer
que le tampon fourni a déjà été initialisé (puisque théoriquement il provient de l'espace
utilisateur), et par conséquent plusieurs implémentations ne modifient pas du tout le tampon. La
fonction NECP necp_ctl_getopt, par exemple, retourne simplement 0 sans
traiter le tampon de données.Ainsi, en spécifiant une adresse de données non mappée à getsockopt, nous pouvons provoquer
l'allocation d'un tampon du tas d'une taille contrôlée, empêcher le contenu de ce tampon d'être
initialisé, puis atteindre un appel à sooptcopyout qui tente d'écrire ce tampon vers l'adresse non
mappée. Tout ce que nous avons à faire pour que la copie réussisse est de remapper cette adresse entre
les appels à sooptcopyin et sooptcopyout. Si nous y parvenons, nous divulguerons des données non
initialisées du tas noyau vers l'espace utilisateur.
Il s'avère que cette course est assez facile à gagner. Lors de mes tests sur mon Macbook Pro de 2015,
le nombre moyen de tentatives pour gagner la course n'a jamais dépassé 600, et la médiane n'a jamais
dépassé 5. Sur iOS 10.1.1 sur un iPhone 7, la course était encore plus facile à gagner, nécessitant
généralement pas plus de 2 tentatives. (Ces tests ont été réalisés avec DEBUG désactivé, car les
printfs ralentissent considérablement l'exploit.)
Pour compiler, lancez make. Consultez le haut du Makefile pour les différentes options de compilation.
Exécutez l'exploit en spécifiant la taille de fuite cible sur la ligne de commande :
$ sudo ./ctl_ctloutput-leak 128
000000: ef be ad de ef be ad de 00 00 00 00 00 00 00 00
000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000020: 00 00 00 00 00 00 00 00 01 00 00 00 40 80 00 00
000030: de 28 45 00 04 00 00 00 a0 ff 4a 26 80 ff ff ff
000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000050: 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000070: 00 00 00 00 00 00 00 00 ef be ad de ef be ad de
J'ai signalé ce problème à Apple le 7 octobre 2017. Il a été identifié comme CVE-2017-13868. Apple a corrigé les problèmes dans macOS 10.13.2 et iOS 11.2.
Le code ctl_ctloutput-leak est placé dans le domaine public. Par courtoisie, je demande que si vous référencez ou utilisez une partie de ce code, vous me l'attribuiez.
getsockopt ne retourne pas d'erreur, ctl_ctloutput
appelle sooptcopyout pour copier le tampon de données vers l'espace utilisateur.