Mein Team hat es kürzlich entdeckt
Das Team überwacht ständig Open-Source-Projekte, um neue Schwachstellen und Schadpakete zu finden, und teilt sie mit der breiteren Community, um zur Verbesserung ihrer allgemeinen Sicherheitslage beizutragen.
Diese zweiteilige Blogserie bietet Details zum Innenleben des anfälligen Xpm-Dateiformats und geht tief in die Ausnutzung dieser Schwachstellen ein.
Xorg X11, oft auch als X Window System bezeichnet, ist ein grafisches Open-Source -Serverprotokoll, das die Erstellung und Verwaltung grafischer Benutzeroberflächen in Unix-ähnlichen Betriebssystemen ermöglicht. Es bietet ein Framework für die Ausführung grafischer Anwendungen, die Verwaltung von Windows und die Verarbeitung von Benutzereingaben in einer Netzwerkumgebung.
Das libx11-Paket bietet die wesentlichen gemeinsam genutzten Bibliotheken, die Clientanwendungen zum Rendern und Präsentieren von Daten auf Ihrem Desktop benötigen.
libXpm bietet Funktionen zum Lesen, Schreiben und Anzeigen von Bildern im X Pixmap (XPM)-Format.
XPM zielt in erster Linie darauf ab, Icon-Pixmaps mit Unterstützung für transparente Pixel zu generieren. Es basiert auf der XBM-Syntax und kann entweder eine reine Textdatei im XPM2-Format sein oder eine C-Programmiersprachensyntax verwenden, wodurch es für die Einbindung in eine C-Programmdatei geeignet ist.
XPM-Bildformat – Versionen
Vorgänger – XBM
Bevor XPM (X PixMap) im Jahr 1989 auf den Markt kam, gab es das XBM-Format (X BitMap).
Ein binäres Nur-Text-Bildformat, das zum Speichern von Symbol- und Cursor-Bitmaps verwendet wird, die in der X-GUI verwendet wurden.
XBM-Dateien sind als C-Quelldateien strukturiert. Dies ist ihr Hauptunterschied zu den meisten heutigen Bildformaten. Dadurch können sie direkt in Anwendungen eingebunden werden. Aus diesem Grund und aufgrund der Tatsache, dass keine Komprimierung angewendet werden kann (1-Zeichen-zu-1-Byte-Zuordnung), sind sie jedoch auch weitaus größer als ihre Rohpixeldaten.
X-BitMap-Daten (XBM) bestehen aus einer Folge statischer, vorzeichenloser Zeichenarrays, in denen die rohen monochromen Pixelinformationen gespeichert sind.
Der folgende C-Code ist beispielsweise die XBM-Datei für hello
im Bild:
#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 };
Anstelle der üblichen Header im Bilddateiformat verfügen XBM-Dateien über #define
Anweisungen. Das erste Wertepaar definiert die Pixelabmessungen der Bitmap und gibt deren Höhe und Breite an.
X-BitMap-Bilddaten (XBM) sind als kontinuierliche Folge von Pixelwerten strukturiert, die in einem statischen Array gespeichert werden. Da jedes Pixel durch ein einzelnes Bit symbolisiert wird (wobei 0 für Weiß und 1 für Schwarz steht), umfasst jedes Byte im Array die Daten für acht einzelne Pixel. Insbesondere dient das niedrigstwertige Bit des ersten Bytes als Anker für das obere linke Pixel innerhalb der Bitmap.
Lernen Sie die XPM-Version kennen
Es wurde erstmals 1989 veröffentlicht und weist viele Ähnlichkeiten mit dem oben beschriebenen XBM-Format auf. Während das XBM monochrom war, brachte das XPM1 Farben in die Bilder ein.
Es verwendet zusätzliche Makros und Variablen für Farben und ersetzt Bytes durch Zeichen für Pixeldaten.
Hier ist zum Beispiel das gleiche hello
im XPM1-Format:
/* 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
bezeichnet hier die Anzahl der Farben im Bild und XFACE_chars_per_pixel
bezeichnet die Anzahl der Zeichen pro Pixel.
Jedes a
wird durch die weiße Farbe „#ffffff“ und jedes b
durch die schwarze Farbe „#000000“ ersetzt.
Ein Jahr später, 1990, sollte die zweite Version des Formats Abhilfe schaffen. Es hat die Dinge vereinfacht und den gesamten C-Code aus dem Bildformat entfernt.
Die vereinfachte Struktur:
! XPM2 <Header> <Colors> <Pixels>
Die Kopfzeile bezeichnet die Bildabmessungen ähnlich den #define-Anweisungen von XPM1.
Der Abschnitt „Farben“ definiert die Zeichenwerte.
Ein neues Typenkonzept wurde eingeführt:
c
– steht für Farbpixel
m
– steht für Monochrom
g
– Eis für Graustufen
s
– steht für symbolisch
Die symbolische Funktion wird verwendet, um Farben nach Kontext zuzuweisen und ihnen zur leichteren Lesbarkeit Variablennamen zu erstellen.
Beispiel eines XPM2-Images mit demselben hello
wie in den vorherigen Beispielen:
! 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
Abgesehen von hexadezimalen Farbcodes können die Farben auch mit jedem der X11-Farbnamen (z. B. red
) angegeben werden, wobei None
Transparenz angibt.
Dies ist die aktuelle Version des XPM-Formats.
Die 1991 veröffentlichte Version brachte den C-Code zurück, aber anstatt einen reinen C-Codestil zu verwenden, sind die darin enthaltenen Werte im Wesentlichen dieselben wie im XPM2-Format.
Beispiel:
/* 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 ", " ", " ", " ", " ", " ", " ", " ", " " };
Wie bereits erwähnt, kann das XPM-Format auch anspruchsvollere Bilder darstellen, beispielsweise das JFrog-Logo:
DoS-Schwachstelle – CVE-2023-43786
Bei der Sicherheitslücke CVE-2023-43786 handelt es sich im Wesentlichen um eine Endlosschleife, die aus einer falschen Berechnung der Rekursionsstoppbedingung resultiert.
Festes Commit:
https://gitlab.freedesktop.org/xorg/lib/libx11/-/commit/204c3393c4c90a29ed6bef64e43849536e863a86
XPutImage
ist eine Funktion in libX11, mit der Sie Bilder auf einem X-Drawable, normalerweise einem X-Fenster, platzieren können. Mit dieser Funktion kann man Pixelinformationen von einer XImage-Struktur auf ein bestimmtes Zeichenelement, wie ein Fenster oder eine Pixmap, übertragen und es nach Bedarf positionieren.
xpmCreatePixmapFromImage
Die xpmCreatePixmapFromImage
libXpm-Funktion ruft diese XPutImage
Funktion auf:
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); }
In dieser Funktion sind ximage
die anzuzeigenden Quellbildpixeldaten und werden in das X Drawable
-Objekt (in diesem Fall pixmap_return
) kopiert.
XPutImage
Hier ist die XPutImage
libX11-Funktion:
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);......... }
Es ruft die PutSubImage
Funktion auf:
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); } }
Nehmen wir das folgende Beispielbild:
Available [the requested size] = (65,536 * 4) - 28 = 262,116 bits_per_pixel = 32 width = 90,000 pixels height = 1 pixel
Da das Bild bits_per_pixel
32 beträgt, wird die bedingte Anweisung bei [1] nicht bestanden, was dazu führt, dass wir den in [2] definierten alternativen Codeblock eingeben.
Anschließend berechnet es BytesPerRow
auf [3] und dividiert es dann durch 8. In unserem Beispiel: BytesPerRow = 90000 * 32 / 8 = 360,0
Im Beispiel würde die Prüfung auf [4] nicht erfolgreich sein, da 360000 nicht kleiner als die angeforderte Größe 262116 ist und nicht in der Lage ist, eine einzelne Zeile der angeforderten Breite in eine einzelne Anfrage zu passen – dies initiiert die else
auf [5 ].
Dies bestimmt die Anzahl der Pixel, die in einer einzelnen Anfrage enthalten sein können. Anschließend wird ein rekursiver Aufruf der PutSubImage
Funktion initiiert, um nur diese Teilmenge zu übergeben, gefolgt von einem anschließenden rekursiven Aufruf, um den verbleibenden Teil der Zeile zu verwalten. Bei Bedarf kann dieser verbleibende Teil auch durch weitere rekursive Aufrufe weiter aufgeteilt werden.
Die Berechnung in [6] berücksichtigt jedoch nicht die Bits pro Pixel, und der rekursive Aufruf führt dazu, dass Anfragen 2096928 Pixel anstelle von 2096928 Bits senden – was mehr ist, als in eine einzelne Anfrage passen.
Dies führt zu einer Endlosschleife, in der versucht wird, die Pixelreihe zu teilen , was immer wieder dazu führt, dass die Zahl zu groß ist, um hineinzupassen, und der Vorgang erneut versucht wird, es mit den gleichen Werten zu versuchen. Diese Rekursion bleibt bestehen, bis der Aufrufstapel erschöpft ist.
Der Bugfix hat die Berechnung auf [6] geändert und die bits_per_pixel
berücksichtigt. Im Beispiel würde dies dazu führen, dass ein rekursiver Aufruf nur 65529 Pixel senden soll, was zu einer BytesPerRow von 262116 führt, die perfekt in den verfügbaren Platz passt, sodass die Rekursion in nur zwei Aufrufen voranschreiten und abgeschlossen werden kann.
Beispielhaftes Proof-of-Concept-Bild zum Auslösen des Fehlers: https://github.com/jfrog/jfrog-CVE-2023-43786-libX11_DoS/blob/main/cve-2023-43786.xpm
Ein Beispiel für eine App, die die anfällige libXpm-Bibliotheksfunktion aufruft, ist das CLI-Dienstprogramm sxpm
, das zur Anzeige von Xpm-Bildern auf dem Bildschirm verwendet wird.
Es ruft die anfällige Xpm-Funktion xpmCreatePixmapFromImage
auf, die dann die anfälligen libX11-Funktionen XPutImage
und dann PutSubImage
aufruft.
Auch hier veröffentlicht.