
CVE-2020-25578 et CVE-2020-25579: Des bugs de fuite d'informations FreeBSD que j'ai trouvés en 2020.
ufs_create et trouver 0 bugsstruct dirent alloués sur la pilemsdosfs_readdir est incomplet. Ils ont corrigé une instance du bug, mais pas une deuxième.mqueuefs, autofs, smbfs et tmpfs qui me permettent de faire fuiter un pointeur complet de 8 octets. Écrire un PoC pour confirmer.Comme mentionné ci-dessus, le bug original que j'ai trouvé était dans msdosfs_readdir pendant que j'analysais le correctif pour le commit lié ci-dessus.
Le flux de base pour appeler readdir sur FreeBSD est le suivant :
#include <dirent.h>
int main(void) {
struct dirent *dp;
DIR *dirp;
dirp = opendir("./somedir");
dp = readdir(dirp);
}
Selon le système de fichiers dans lequel somedir réside, l'une des nombreuses fonctions *_readdir du noyau FreeBSD peut être appelée.
Le correctif ci-dessus ajoute une fonction appelée dirent_terminate qui est destinée à être appelée avant qu'un objet struct dirent soit renvoyé à l'espace utilisateur (souvent fait en utilisant la fonction uiomove). Cette fonction va mettre à zéro les octets de bourrage ainsi que tous les octets restants dans le champ d_name de la structure. La définition de struct dirent se trouve ici.
En regardant le correctif, à la ligne 1562, vous pouvez voir dirent_terminate appelé avec la variable dirbuf comme argument. Ensuite, uiomove est appelé pour copier le contenu de dirbuf vers l'espace utilisateur. Cependant, remarquez que ces lignes de code se trouvent dans le bloc de cette instruction if. Le commentaire au-dessus de cette instruction if explique que cette branche n'est prise que si readdir est appelé sur la racine du système de fichiers MSDOS, donc nous pouvons simplement ignorer cette instruction if en appelant readdir dans n'importe quel sous-répertoire au-delà de la racine du système de fichiers.
Plus loin, nous voyons un autre appel à uiomove à la ligne 1691. Cependant, en lisant attentivement le code, vous verrez que dirent_terminate n'est pas appelé dans cette instance, ce qui signifie que les octets de bourrage resteront non initialisés. Malheureusement, le champ d_name a été mis à zéro au début de cette fonction (ici), donc nous ne pouvons pas obtenir une fuite plus importante.
D'abord, je n'avais pas de clé USB, donc j'ai dû trouver un moyen de monter un système de fichiers MSDOS. Ce qui suit fonctionne :
$ dd if=/dev/zero of=test.img bs=512 count=256000
$ sudo mdconfig -a -t vnode -f test.img
$ sudo newfs_msdos -s 131072000 /dev/md1 # My mdconfig returned md1
$ mkdir ./temp
$ sudo mount -t msdosfs /dev/md1 ./temp
$ mkdir ./temp/test_dir
Le PoC se trouve dans original_poc.c. Il suffit de compiler avec clang et de l'exécuter depuis le même répertoire que les commandes ci-dessus, et vous verrez les octets fuités être affichés.
J'ai commencé à chercher des variantes de ceci. Je pense avoir simplement grepé uiomove\(&.*, qui a renvoyé environ 15-20 résultats, et j'ai tous vérifiés manuellement. Malheureusement, aucune des variantes n'existe dans FreeBSD par défaut (les systèmes de fichiers doivent être activés / compilés manuellement dans le noyau). Les fonctions comportant les variantes sont les suivantes :
mqfs_readdirtmpfs_dir_getdotdenttmpfs_dir_getdotdotdentsmbfs_readvdirautofs_readdir_oneLe bug est exactement le même dans toutes ces fonctions, donc je vais juste couvrir mqfs_readdir.
struct dirent entry est allouée sur la piledirent_terminate est appelé pour mettre à zéro les champs de bourrage + d_name de la structurevfs_read_dirent est appelé. Cette fonction appellera uiomove pour copier la structure vers l'espace utilisateurTout a l'air bon jusqu'à présent, n'est-ce pas ? Pas nécessairement. Nous devons nous assurer que tous les champs de la structure sont initialisés. Si vous regardez attentivement le code, vous verrez que le champ d_off est laissé non initialisé. Le type de ce champ est off_t, qui est essentiellement un int64_t. Lorsque la structure est copiée vers l'espace utilisateur, nous obtenons des données non initialisées dans ce champ.
Ce même PoC fonctionnera pour toutes les variantes, il suffit de l'exécuter sur un système de fichiers différent. Pour mqueuefs, faites ce qui suit (nécessite mqueuefs activé / compilé dans le noyau d'abord) :
$ mkdir ./temp
$ sudo mount -t mqueuefs null ./temp
Le PoC lui-même se trouve dans variants_poc.c. Il suffit de compiler avec clang et de l'exécuter depuis le même répertoire que les commandes ci-dessus. Vous verrez des pointeurs noyau être affichés (probablement un pointeur de pile et un pointeur de section de code / tas, je n'ai pas vérifié).