Mein Team hat es kürzlich entdeckt , die weit verbreitete Grafikbibliothek – (mit einem hohen NVD-Schweregrad ). Diese Schwachstellen verursachen einen Denial-of-Service und die Ausführung von Code aus der Ferne. Die neuesten Versionen von X11 enthalten Korrekturen für diese Schwachstellen. zwei Sicherheitslücken in X.Org libX11 CVE-2023-43786 und CVE-2023-43787 CVSS 7.8 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. Was ist libX11? Xorg X11, oft auch als X Window System bezeichnet, ist ein grafisches -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. Open-Source Das libx11-Paket bietet die wesentlichen gemeinsam genutzten Bibliotheken, die Clientanwendungen zum Rendern und Präsentieren von Daten auf Ihrem Desktop benötigen. Was ist libXpm? libXpm bietet Funktionen zum Lesen, Schreiben und Anzeigen von Bildern im (XPM)-Format. X Pixmap 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 im Bild: hello #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 Anweisungen. Das erste Wertepaar definiert die Pixelabmessungen der Bitmap und gibt deren Höhe und Breite an. #define 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. XPM1 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 im XPM1-Format: hello /* 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 ", " ", " ", " ", " ", " ", " ", " ", " " }; bezeichnet hier die Anzahl der Farben im Bild und bezeichnet die Anzahl der Zeichen pro Pixel. XFACE_ncolors XFACE_chars_per_pixel Jedes wird durch die weiße Farbe „#ffffff“ und jedes durch die schwarze Farbe „#000000“ ersetzt. a b XPM2 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: – steht für Farbpixel c – steht für Monochrom m – Eis für Graustufen g – steht für symbolisch s Die symbolische Funktion wird verwendet, um Farben nach Kontext zuzuweisen und ihnen zur leichteren Lesbarkeit Variablennamen zu erstellen. Beispiel eines XPM2-Images mit demselben wie in den vorherigen Beispielen: hello ! 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. ) angegeben werden, wobei Transparenz angibt. red None XPM3 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 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. XPutImage xpmCreatePixmapFromImage Die libXpm-Funktion ruft diese Funktion auf: xpmCreatePixmapFromImage 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); } In dieser Funktion sind die anzuzeigenden Quellbildpixeldaten und werden in das -Objekt (in diesem Fall ) kopiert. ximage X Drawable pixmap_return XPutImage Hier ist die libX11-Funktion: XPutImage 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 Funktion auf: 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); } } Details zur technischen Schwachstelle 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 32 beträgt, wird die bedingte Anweisung bei [1] nicht bestanden, was dazu führt, dass wir den in [2] definierten alternativen Codeblock eingeben. bits_per_pixel Anschließend berechnet es auf [3] und dividiert es dann durch 8. In unserem Beispiel: BytesPerRow = 90000 * 32 / 8 = 360,0 BytesPerRow 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 auf [5 ]. else Dies bestimmt die Anzahl der Pixel, die in einer einzelnen Anfrage enthalten sein können. Anschließend wird ein rekursiver Aufruf der 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. PutSubImage 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 , 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. einer Endlosschleife, in der versucht wird, die Pixelreihe zu teilen Der hat die Berechnung auf [6] geändert und die 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. Bugfix bits_per_pixel 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 Wie der Fehler ausgelöst werden kann Ein Beispiel für eine App, die die anfällige libXpm-Bibliotheksfunktion aufruft, ist das CLI-Dienstprogramm , das zur Anzeige von Xpm-Bildern auf dem Bildschirm verwendet wird. sxpm Es ruft die anfällige Xpm-Funktion auf, die dann die anfälligen libX11-Funktionen und dann aufruft. xpmCreatePixmapFromImage XPutImage PutSubImage Auch hier veröffentlicht.