Mi equipo descubrió recientemente , la biblioteca de gráficos muy popular: (con una alta gravedad NVD ). Estas vulnerabilidades provocan una denegación de servicio y la ejecución remota de código. Las últimas versiones de X11 contienen correcciones para estas vulnerabilidades. dos vulnerabilidades de seguridad en X.Org libX11 CVE-2023-43786 y CVE-2023-43787 CVSS 7.8 El equipo monitorea constantemente proyectos de código abierto para encontrar nuevas vulnerabilidades y paquetes maliciosos y los comparte con la comunidad en general para ayudar a mejorar su postura general de seguridad. Esta serie de blogs de dos partes proporciona detalles del funcionamiento interno del vulnerable formato de archivo Xpm y profundiza en la explotación de estas vulnerabilidades. ¿Qué es libX11? Xorg X11, a menudo denominado X Window System, es un protocolo de servidor gráfico que permite la creación y gestión de interfaces gráficas de usuario en sistemas operativos tipo Unix. Proporciona un marco para ejecutar aplicaciones gráficas, administrar Windows y manejar la entrada del usuario en un entorno de red. de código abierto El paquete libx11 ofrece las bibliotecas compartidas esenciales que las aplicaciones cliente necesitan para procesar y presentar datos en su escritorio. ¿Qué es libXpm? libXpm proporciona funciones para leer, escribir y mostrar imágenes en el formato (XPM). X Pixmap XPM tiene como objetivo principal generar mapas de píxeles de iconos compatibles con píxeles transparentes. Se basa en la sintaxis XBM y puede ser un archivo de texto plano en formato XPM2 o utilizar una sintaxis del lenguaje de programación C, lo que lo hace adecuado para su inclusión en un archivo de programa C. Formato de imagen XPM – versiones Predecesor - XBM Antes de que existiera XPM (X PixMap) en 1989, existía el formato XBM (X BitMap). Un formato de imagen binaria de texto plano, utilizado para almacenar mapas de bits de iconos y cursores que se utilizaron en la GUI de X. Los archivos XBM están estructurados como archivos fuente C. Ésta es su principal distinción con respecto a la mayoría de los formatos de imagen actuales. De esa forma, se pueden incorporar directamente a las aplicaciones. Sin embargo, debido a esto y al hecho de que no se puede emplear compresión (asignación de 1 carácter a 1 byte), también son mucho más grandes que sus datos de píxeles sin procesar. Los datos de X BitMap (XBM) comprenden una secuencia de matrices de caracteres estáticas sin firmar que almacenan la información de píxeles monocromática sin procesar. Por ejemplo, el siguiente código C es el archivo XBM de en la imagen: 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 }; En lugar de los encabezados de formato de archivo de imagen habituales, los archivos XBM tienen declaraciones . El primer par de valores define las dimensiones en píxeles del mapa de bits, indicando su alto y ancho. #define Los datos de imagen X BitMap (XBM) están estructurados como una secuencia continua de valores de píxeles, que se almacenan dentro de una matriz estática. Dado que cada píxel está simbolizado por un solo bit (0 indica blanco y 1 representa negro), cada byte de la matriz abarca los datos de ocho píxeles individuales. En particular, el bit menos significativo del primer byte sirve como ancla para el píxel superior izquierdo dentro del mapa de bits. XPM1 Conoce la versión XPM Lanzado por primera vez en 1989, comparte muchas similitudes con el formato XBM descrito anteriormente. Mientras que el XBM era monocromático, el XPM1 introdujo colores en las imágenes. Utiliza macros y variables adicionales para los colores y reemplaza bytes con caracteres para datos de píxeles. Por ejemplo, aquí está la misma imagen en y negro en formato XPM1: 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 ", " ", " ", " ", " ", " ", " ", " ", " " }; aquí indica la cantidad de colores en la imagen y indica la cantidad de caracteres por píxel. XFACE_ncolors XFACE_chars_per_pixel Cada será reemplazada por el color blanco “#ffffff” y cada será reemplazada por el color negro “#000000”. a b XPM2 Un año después, en 1990, se creó la segunda versión del formato para mejorar las cosas. Simplificó las cosas y eliminó todo el código C del formato de imagen. La estructura simplificada: ! XPM2 <Header> <Colors> <Pixels> La línea del encabezado indica las dimensiones de la imagen similares a las declaraciones #define de XPM1. La sección de colores define los valores de los caracteres. Se introdujo un nuevo concepto de tipo: – es para píxeles de color c – es para monocromo m – hielo para escala de grises g – es para simbólico s La función simbólica se utiliza para asignar colores por contexto y crear nombres de variables para facilitar la lectura. Ejemplo de una imagen XPM2, con el mismo que se muestra en los ejemplos anteriores: 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 Además de los códigos de color hexadecimales, los colores también se pueden especificar utilizando cualquiera de los nombres de color X11 (por ejemplo, ), donde indica transparencia. red None XPM3 Esta es la versión actual del formato XPM. La versión, lanzada en 1991, recuperó el código C, pero en lugar de utilizar un estilo de código C puro, los valores internos son esencialmente los mismos que en el formato XPM2. Ejemplo: /* 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 ", " ", " ", " ", " ", " ", " ", " ", " " }; Como se mencionó, el formato XPM también puede representar imágenes más sofisticadas, como el logotipo de JFrog: Vulnerabilidad DoS – CVE-2023-43786 La vulnerabilidad CVE-2023-43786 es esencialmente un bucle sin fin resultante de un cálculo incorrecto de la condición de parada de recursión. Compromiso fijo: https://gitlab.freedesktop.org/xorg/lib/libx11/-/commit/204c3393c4c90a29ed6bef64e43849536e863a86 es una función en libX11 que le permite colocar imágenes en un X Drawable, generalmente una X Window. Con esta función, se puede transferir información de píxeles desde una estructura XImage a un elemento de diseño designado, como una ventana o un mapa de píxeles, y colocarlo según sea necesario. XPutImage xpmCrearPixmapFromImage La función libXpm llama a esta función : 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); } En esta función, son los datos de píxeles de la imagen de origen que se mostrarán y se copian en el objeto (en este caso ). ximage X Drawable pixmap_return XPutImagen Aquí está la función libX11: 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);......... } Llama a la función : 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); } } Detalles técnicos de la vulnerabilidad Tomemos la siguiente imagen de ejemplo: Available [the requested size] = (65,536 * 4) - 28 = 262,116 bits_per_pixel = 32 width = 90,000 pixels height = 1 pixel Dado que la imagen es 32, la declaración condicional en [1] no pasará, lo que nos llevará a ingresar el bloque de código alternativo definido en [2]. bits_per_pixel Luego calcula en [3] y luego lo divide por 8. En nuestro ejemplo: BytesPerRow = 90000 * 32 / 8 = 360,0 BytesPerRow En el ejemplo, la verificación en [4] no pasaría, ya que 360000 no es menor que el tamaño solicitado 262116 y no puede caber una sola línea del ancho solicitado en una sola solicitud; esto inicia el en [5 ]. else Esto determina la cantidad de píxeles que se pueden incluir en una sola solicitud. Luego inicia una llamada recursiva a la función para pasar solo ese subconjunto, seguida de una llamada recursiva posterior para administrar la parte restante de la línea. Si es necesario, esta parte restante también se puede dividir mediante llamadas recursivas adicionales. PutSubImage Sin embargo, el cálculo en [6] no tiene en cuenta los bits por píxel, y la llamada recursiva realiza solicitudes que envían 2096928 píxeles en lugar de 2096928 bits, que es más grande de lo que puede caber en una sola solicitud. Esto conduce a , lo que da como resultado constantemente un número demasiado grande para caber y se intenta nuevamente el proceso para volver a intentarlo con los mismos valores. Esta recursividad persiste hasta que se agota la pila de llamadas. un bucle interminable de intentos de dividir la línea de píxeles La cambió el cálculo en [6] y tuvo en cuenta . En el ejemplo, resultaría en una llamada recursiva solicitando enviar solo 65529 píxeles, lo que resultaría en un BytesPerRow de 262116 que encaja perfectamente dentro del espacio disponible, permitiendo así que la recursividad avance y finalice en solo 2 llamadas. corrección del error bits_per_pixel Ejemplo de imagen de prueba de concepto para desencadenar el error: https://github.com/jfrog/jfrog-CVE-2023-43786-libX11_DoS/blob/main/cve-2023-43786.xpm Cómo se puede activar el error Un ejemplo de una aplicación que llama a la función de biblioteca vulnerable libXpm es la utilidad CLI , que se utiliza para mostrar imágenes Xpm en la pantalla. sxpm Llama a la función vulnerable XPM, que luego llama a las funciones vulnerables libX11 y luego . xpmCreatePixmapFromImage XPutImage PutSubImage También publicado aquí.