Mi equipo descubrió recientemente
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.
Xorg X11, a menudo denominado X Window System, es un protocolo de servidor gráfico de código abierto 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.
El paquete libx11 ofrece las bibliotecas compartidas esenciales que las aplicaciones cliente necesitan para procesar y presentar datos en su escritorio.
libXpm proporciona funciones para leer, escribir y mostrar imágenes en el formato X Pixmap (XPM).
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 hello
en la imagen:
#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 #define
. El primer par de valores define las dimensiones en píxeles del mapa de bits, indicando su alto y ancho.
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.
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 hello
y negro en formato 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
aquí indica la cantidad de colores en la imagen y XFACE_chars_per_pixel
indica la cantidad de caracteres por píxel.
Cada a
será reemplazada por el color blanco “#ffffff” y cada b
será reemplazada por el color negro “#000000”.
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:
c
– es para píxeles de color
m
– es para monocromo
g
– hielo para escala de grises
s
– es para simbólico
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 hello
que se muestra en los ejemplos anteriores:
! 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, red
), donde None
indica transparencia.
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
XPutImage
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.
xpmCrearPixmapFromImage
La función xpmCreatePixmapFromImage
libXpm llama a esta función 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, ximage
son los datos de píxeles de la imagen de origen que se mostrarán y se copian en el objeto X Drawable
(en este caso pixmap_return
).
XPutImagen
Aquí está la función 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);......... }
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); } }
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 bits_per_pixel
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].
Luego calcula BytesPerRow
en [3] y luego lo divide por 8. En nuestro ejemplo: BytesPerRow = 90000 * 32 / 8 = 360,0
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 else
en [5 ].
Esto determina la cantidad de píxeles que se pueden incluir en una sola solicitud. Luego inicia una llamada recursiva a la función PutSubImage
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.
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 un bucle interminable de intentos de dividir la línea de píxeles , 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.
La corrección del error cambió el cálculo en [6] y tuvo en cuenta bits_per_pixel
. 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.
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
Un ejemplo de una aplicación que llama a la función de biblioteca vulnerable libXpm es la utilidad CLI sxpm
, que se utiliza para mostrar imágenes Xpm en la pantalla.
Llama a la función vulnerable xpmCreatePixmapFromImage
XPM, que luego llama a las funciones vulnerables libX11 XPutImage
y luego PutSubImage
.
También publicado aquí.