Mon équipe a récemment découvert
L'équipe surveille en permanence les projets open source pour détecter de nouvelles vulnérabilités et packages malveillants et les partage avec la communauté au sens large pour aider à améliorer leur posture de sécurité globale.
Cette série de blogs en deux parties fournit des détails sur le fonctionnement interne du format de fichier vulnérable Xpm et approfondit l'exploitation de ces vulnérabilités.
Xorg X11, souvent appelé système X Window, est un protocole de serveur graphique open source qui permet la création et la gestion d'interfaces utilisateur graphiques dans les systèmes d'exploitation de type Unix. Il fournit un cadre pour exécuter des applications graphiques, gérer Windows et gérer les entrées des utilisateurs dans un environnement en réseau.
Le package libx11 offre les bibliothèques partagées essentielles dont les applications clientes ont besoin pour restituer et présenter les données sur votre bureau.
libXpm fournit des fonctions pour lire, écrire et afficher des images au format X Pixmap (XPM).
XPM vise principalement à générer des pixmaps d'icônes avec prise en charge des pixels transparents. Il est basé sur la syntaxe XBM et peut être soit un fichier texte brut au format XPM2, soit utiliser une syntaxe de langage de programmation C, ce qui le rend approprié pour être inclus dans un fichier programme C.
Format d'image XPM – versions
Prédécesseur – XBM
Avant la création de XPM (X PixMap) en 1989, il existait le format XBM (X BitMap).
Un format d'image binaire en texte brut, utilisé pour stocker les bitmaps d'icônes et de curseurs utilisés dans l'interface graphique X.
Les fichiers XBM sont structurés comme des fichiers source C. C’est leur principale distinction par rapport à la plupart des formats d’image actuels. De cette façon, ils peuvent être intégrés directement dans les applications. Cependant, pour cette raison et du fait qu'aucune compression ne peut être utilisée (mappage de 1 caractère à 1 octet), ils sont également beaucoup plus volumineux que leurs données brutes de pixels.
Les données X BitMap (XBM) comprennent une séquence de tableaux de caractères statiques non signés qui stockent les informations brutes de pixels monochromes.
Par exemple, le code C suivant est le fichier XBM pour hello
dans l'image :
#define hello_width 35 #define hello_height 25 static char hello_bits[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x88, 0x00, 0x00, 0x10, 0x00, 0x88, 0x00, 0x00, 0x10, 0x00, 0x88, 0x00, 0x00, 0xD0, 0xE1, 0x88, 0x78, 0x00, 0x30, 0x13, 0x89, 0xC4, 0x00, 0x10, 0x12, 0x89, 0x84, 0x00, 0x10, 0xF2, 0x89, 0x84, 0x00, 0x10, 0x12, 0x88, 0x84, 0x00, 0x10, 0x12, 0x88, 0x44, 0x00, 0x10, 0xE2, 0x89, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
Au lieu des en-têtes de format de fichier image habituels, les fichiers XBM ont des instructions #define
. La première paire de valeurs définit les dimensions en pixels du bitmap, indiquant sa hauteur et sa largeur.
Les données d'image X BitMap (XBM) sont structurées comme une séquence continue de valeurs de pixels, stockées dans un tableau statique. Étant donné que chaque pixel est symbolisé par un seul bit (0 indiquant le blanc et 1 représentant le noir), chaque octet du tableau englobe les données de huit pixels individuels. Notamment, le bit le moins significatif du premier octet sert d'ancre pour le pixel supérieur gauche dans le bitmap.
Découvrez la version XPM
Sorti pour la première fois en 1989, il partage de nombreuses similitudes avec le format XBM décrit ci-dessus. Alors que le XBM était monochrome, le XPM1 introduisait des couleurs dans les images.
Il utilise des macros et des variables supplémentaires pour les couleurs et remplace les octets par des caractères pour les données de pixels.
Par exemple, voici la même image hello
et blanc au format XPM1 :
/* XPM */ static char *_hello[] = { /* columns rows colors chars-per-pixel */ "35 25 2 1 ", " c white", "bc black", /* pixels */ " ", " ", " ", " ", " ", " ", " ", " bbb ", " bbb ", " bbb ", " b bbb bbb bb bbbb ", " bb bb bbbbb bb ", " bbbbbbbb ", " bb bbbbb bbbb ", " bbbbbbb ", " bbbbbbb ", " bb bbbb bb bbb ", " ", " ", " ", " ", " ", " ", " ", " " };
XFACE_ncolors
désigne ici le nombre de couleurs dans l'image et XFACE_chars_per_pixel
désigne le nombre de caractères par pixel.
Chaque a
sera remplacé par la couleur blanche « #ffffff », et chaque b
sera remplacé par la couleur noire « #000000 ».
Un an plus tard, en 1990, la deuxième version du format devait améliorer les choses. Cela a simplifié les choses et supprimé tout le code C du format d'image.
La structure simplifiée :
! XPM2 <Header> <Colors> <Pixels>
La ligne d'en-tête indique les dimensions de l'image similaires aux instructions #define de XPM1.
La section couleurs définit les valeurs des caractères.
Un nouveau concept de type a été introduit :
c
– est pour le pixel de couleur
m
– est pour monochrome
g
– glace pour les niveaux de gris
s
– est pour symbolique
La fonctionnalité symbolique est utilisée pour attribuer des couleurs par contexte et pour leur créer des noms de variables pour une lecture plus facile.
Exemple d'image XPM2, avec le même hello
que celui présenté dans les exemples précédents :
! XPM2 35 25 2 1 ac #000000 bc #ffffff aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaabaaaaaaaaaaaaaabaaabaaaaaaaaaaa aaaabaaaaaaaaaaaaaabaaabaaaaaaaaaaa aaaabaaaaaaaaaaaaaabaaabaaaaaaaaaaa aaaababbbaaaabbbaaabaaabaaabbbbaaaa aaaabbaabbaabaaabaabaaabaabaaabbaaa aaaabaaaabaabaaabaabaaabaabaaaabaaa aaaabaaaabaabbbbbaabaaabaabaaaabaaa aaaabaaaabaabaaaaaabaaabaabaaaabaaa aaaabaaaabaabaaaaaabaaabaabaaabaaaa aaaabaaaabaaabbbbaabaaabaaabbbaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Outre les codes de couleur hexadécimaux, les couleurs peuvent également être spécifiées en utilisant n'importe quel nom de couleur X11 (par exemple red
), None
indiquant la transparence.
Il s'agit de la version actuelle du format XPM.
La version, publiée en 1991, a ramené le code C, mais au lieu d'utiliser un style de code C pur, les valeurs à l'intérieur sont essentiellement les mêmes que dans le format XPM2.
Exemple:
/* XPM */ static char *_hello[] = { /* columns rows colors chars-per-pixel */ "35 25 2 1 ", " c white", "bc black", /* pixels */ " ", " ", " ", " ", " ", " ", " ", " bbb ", " bbb ", " bbb ", " b bbb bbb bb bbbb ", " bb bb bbbbb bb ", " bbbbbbbb ", " bb bbbbb bbbb ", " bbbbbbb ", " bbbbbbb ", " bb bbbb bb bbb ", " ", " ", " ", " ", " ", " ", " ", " " };
Comme indiqué, le format XPM peut également représenter des images plus sophistiquées, comme le logo JFrog :
Vulnérabilité DoS – CVE-2023-43786
La vulnérabilité CVE-2023-43786 est essentiellement une boucle sans fin résultant d'un calcul incorrect de la condition d'arrêt de récursion.
Validation corrigée :
https://gitlab.freedesktop.org/xorg/lib/libx11/-/commit/204c3393c4c90a29ed6bef64e43849536e863a86
XPutImage
est une fonction de libX11 qui vous permet de placer des images sur un X Drawable, généralement une X Window. Avec cette fonction, on peut transférer les informations de pixels d'une structure XImage vers un dessin désigné, tel qu'une fenêtre ou une pixmap, et les positionner selon les besoins.
xpmCreatePixmapFromImage
La fonction xpmCreatePixmapFromImage
libXpm appelle cette fonction XPutImage
:
void xpmCreatePixmapFromImage( Display *display, Drawable d, XImage *ximage, Pixmap *pixmap_return) { GC gc; XGCValues values; *pixmap_return = XCreatePixmap(display, d, ximage->width, ximage->height, ximage->depth); /* set fg and bg in case we have an XYBitmap */ values.foreground = 1; values.background = 0; gc = XCreateGC(display, *pixmap_return, GCForeground | GCBackground, &values); XPutImage(display, *pixmap_return, gc, ximage, 0, 0, 0, 0, ximage->width, ximage->height); XFreeGC(display, gc); }
Dans cette fonction, ximage
correspond aux données de pixels de l'image source à afficher et est copiée dans l'objet X Drawable
(dans ce cas pixmap_return
).
XPutImage
Voici la fonction XPutImage
libX11 :
int XPutImage ( register Display *dpy, Drawable d, GC gc, register XImage *image, int req_xoffset, int req_yoffset, int x, int y, unsigned int req_width, unsigned int req_height){ ..... PutSubImage(dpy, d, gc, &img, 0, 0, x, y, (unsigned int) width, (unsigned int) height, dest_bits_per_pixel, dest_scanline_pad); UnlockDisplay(dpy); SyncHandle(); Xfree(img.data); return 0; } }
LockDisplay(dpy); FlushGC(dpy, gc); PutSubImage(dpy, d, gc, image, req_xoffset, req_yoffset, x, y, (unsigned int) width, (unsigned int) height, dest_bits_per_pixel, dest_scanline_pad);......... }
Il appelle la fonction PutSubImage
:
static void PutSubImage ( register Display *dpy, Drawable d, GC gc, register XImage *image, int req_xoffset, int req_yoffset, int x, int y, unsigned int req_width, unsigned int req_height, int dest_bits_per_pixel, int dest_scanline_pad) { int left_pad, BytesPerRow, Available; if ((req_width == 0) || (req_height == 0)) return; Available = ((65536 < dpy->max_request_size) ? (65536 << 2) : (dpy->max_request_size << 2)) - SIZEOF(xPutImageReq); if ((image->bits_per_pixel == 1) || (image->format != ZPixmap)) { [1] left_pad = (image->xoffset + req_xoffset) & (dpy->bitmap_unit - 1); BytesPerRow = (ROUNDUP((long)req_width + left_pad, dpy->bitmap_pad) >> 3) * image->depth; } else { [2] left_pad = 0; BytesPerRow = ROUNDUP((long)req_width * dest_bits_per_pixel, [3] dest_scanline_pad) >> 3; } if ((BytesPerRow * req_height) <= Available) { [4] PutImageRequest(dpy, d, gc, image, req_xoffset, req_yoffset, x, y, req_width, req_height, dest_bits_per_pixel, dest_scanline_pad); } else if (req_height > 1) { int SubImageHeight = Available / BytesPerRow; if (SubImageHeight == 0) SubImageHeight = 1; PutSubImage(dpy, d, gc, image, req_xoffset, req_yoffset, x, y, req_width, (unsigned int) SubImageHeight, dest_bits_per_pixel, dest_scanline_pad); PutSubImage(dpy, d, gc, image, req_xoffset, req_yoffset + SubImageHeight, x, y + SubImageHeight, req_width, req_height - SubImageHeight, dest_bits_per_pixel, dest_scanline_pad); } else { [5] int SubImageWidth = (((Available << 3) / dest_scanline_pad) [6] * dest_scanline_pad) - left_pad; PutSubImage(dpy, d, gc, image, req_xoffset, req_yoffset, x, y, (unsigned int) SubImageWidth, 1, dest_bits_per_pixel, dest_scanline_pad); PutSubImage(dpy, d, gc, image, req_xoffset + SubImageWidth, req_yoffset, x + SubImageWidth, y, req_width - SubImageWidth, 1, dest_bits_per_pixel, dest_scanline_pad); } }
Prenons l'exemple d'image suivant :
Available [the requested size] = (65,536 * 4) - 28 = 262,116 bits_per_pixel = 32 width = 90,000 pixels height = 1 pixel
Puisque l'image bits_per_pixel
est de 32, l'instruction conditionnelle en [1] ne passera pas, nous amenant à entrer le bloc de code alternatif défini dans [2].
Il calcule ensuite le BytesPerRow
sur [3] puis le divise par 8. Dans notre exemple : BytesPerRow = 90000 * 32 / 8 = 360,0
Dans l'exemple, la vérification sur [4] ne réussirait pas, car 360000 n'est pas inférieur à la taille demandée 262116, et n'est pas capable d'insérer une seule ligne de la largeur demandée dans une seule requête - cela lance le else
sur [5 ].
Cela détermine le nombre de pixels pouvant être inclus dans une seule requête. Il lance ensuite un appel récursif à la fonction PutSubImage
pour transmettre uniquement ce sous-ensemble, suivi d'un appel récursif ultérieur pour gérer la partie restante de la ligne. Si nécessaire, cette partie restante peut également être divisée par des appels récursifs supplémentaires.
Cependant, le calcul de [6] ne prend pas en compte les bits par pixel, et l'appel récursif effectue des requêtes envoyant 2 096 928 pixels au lieu de 2 096 928 bits, ce qui est plus grand que ce que peut contenir une seule requête.
Cela conduit à une boucle sans fin de tentatives de division de la ligne de pixels , ce qui entraîne systématiquement un nombre trop grand pour correspondre et réessaye le processus pour réessayer avec les mêmes valeurs. Cette récursion persiste jusqu'à ce que la pile d'appels soit épuisée.
Le correctif de bug a modifié le calcul sur [6] et a pris en compte le bits_per_pixel
. Dans l'exemple, cela entraînerait un appel récursif demandant d'envoyer seulement 65 529 pixels, ce qui donnerait un BytesPerRow de 262 116 qui s'adapte parfaitement à l'espace disponible, permettant ainsi à la récursion de progresser et de se terminer en seulement 2 appels.
Exemple d'image de preuve de concept pour déclencher le bug : https://github.com/jfrog/jfrog-CVE-2023-43786-libX11_DoS/blob/main/cve-2023-43786.xpm
Un exemple d'application qui appelle la fonction vulnérable de la bibliothèque libXpm est l'utilitaire CLI sxpm
, qui est utilisé pour afficher les images Xpm à l'écran.
Il appelle la fonction vulnérable xpmCreatePixmapFromImage
Xpm, qui appelle ensuite les fonctions vulnérables libX11 XPutImage
puis PutSubImage
.
Également publié ici.