Моя команда недавно обнаружила , широко популярные графические библиотеки — (с высокой степенью опасности NVD ). Эти уязвимости приводят к отказу в обслуживании и удаленному выполнению кода. Последние версии X11 содержат исправления этих уязвимостей. две уязвимости безопасности в X.Org libX11 CVE-2023-43786 и CVE-2023-43787 CVSS 7.8 Команда постоянно отслеживает проекты с открытым исходным кодом, чтобы находить новые уязвимости и вредоносные пакеты, и делится ими с более широким сообществом, чтобы помочь улучшить общий уровень безопасности. В этой серии блогов, состоящей из двух частей, подробно рассказывается о внутренней работе уязвимого формата файлов Xpm и подробно рассматриваются способы использования этих уязвимостей. Что такое libX11? Xorg X11, часто называемый X Window System, представляет собой протокол графического сервера , который позволяет создавать и управлять графическими пользовательскими интерфейсами в Unix-подобных операционных системах. Он обеспечивает основу для запуска графических приложений, управления Windows и обработки пользовательского ввода в сетевой среде. с открытым исходным кодом Пакет libx11 предлагает основные общие библиотеки, необходимые клиентским приложениям для рендеринга и представления данных на рабочем столе. Что такое libXpm? libXpm предоставляет функции для чтения, записи и отображения изображений в формате (XPM). X Pixmap XPM в первую очередь нацелен на создание растровых изображений значков с поддержкой прозрачных пикселей. Он основан на синтаксисе XBM и может представлять собой либо простой текстовый файл в формате XPM2, либо использовать синтаксис языка программирования C, что делает его пригодным для включения в файл программы C. Формат изображения XPM – версии Предшественник — XBM До появления XPM (X PixMap) в 1989 году существовал формат XBM (X BitMap). Формат двоичного изображения в виде простого текста, используемый для хранения растровых изображений значков и курсоров, которые использовались в графическом интерфейсе X. Файлы XBM имеют структуру исходных файлов C. В этом их главное отличие от большинства современных форматов изображений. Таким образом, их можно напрямую включать в приложения. Однако из-за этого, а также из-за невозможности использования сжатия (преобразование 1 символа в 1 байт), они также намного больше, чем их необработанные пиксельные данные. Данные X BitMap (XBM) содержат последовательность статических массивов беззнаковых символов, в которых хранится необработанная информация о монохромных пикселях. Например, следующий код C представляет собой файл XBM для на изображении: 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 }; Вместо обычных заголовков формата файла изображения в файлах XBM есть операторы . Первая пара значений определяет размеры растрового изображения в пикселях, указывая его высоту и ширину. #define Данные изображения X BitMap (XBM) структурированы как непрерывная последовательность значений пикселей, которые хранятся в статическом массиве. Учитывая, что каждый пиксель обозначается одним битом (0 соответствует белому цвету, а 1 — черному), каждый байт массива содержит данные для восьми отдельных пикселей. Примечательно, что младший бит первого байта служит привязкой для верхнего левого пикселя растрового изображения. ХРМ1 Встречайте версию XPM Впервые выпущенный в 1989 году, он имеет много общего с форматом XBM, описанным выше. В то время как XBM был монохромным, XPM1 придавал изображениям цвета. Он использует дополнительные макросы и переменные для цветов и заменяет байты символами для данных пикселей. Например, вот та же -белая картинка в формате 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 ", " ", " ", " ", " ", " ", " ", " ", " " }; здесь обозначает количество цветов в изображении, а обозначает количество символов на пиксель. XFACE_ncolors XFACE_chars_per_pixel Каждый будет заменен белым цветом «#ffffff», а каждый будет заменен черным цветом «#000000». a b ХРМ2 Год спустя, в 1990 году, была выпущена вторая версия формата, призванная улучшить ситуацию. Это упростило ситуацию и удалило весь код C из формата изображения. Упрощенная структура: ! XPM2 <Header> <Colors> <Pixels> Строка заголовка обозначает размеры изображения, аналогичные операторам #define XPM1. Раздел цветов определяет значения символов. Была введена новая концепция типа: – для цветного пикселя c – для монохромного m – лед для оттенков серого g – символическое s Символьная функция используется для назначения цветов в зависимости от контекста и создания для них имен переменных для облегчения чтения. Пример изображения XPM2 с тем же , что и в предыдущих примерах: 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 Помимо шестнадцатеричных кодов цветов, цвета также могут быть указаны с использованием любого из названий цветов X11 (например, ), где указывает на прозрачность. red None ХРМ3 Это текущая версия формата XPM. Версия, выпущенная в 1991 году, вернула код C, но вместо использования чистого стиля кода C значения внутри по существу такие же, как и в формате XPM2. Пример: /* 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 ", " ", " ", " ", " ", " ", " ", " ", " " }; Как уже говорилось, формат XPM также может представлять более сложные изображения, такие как логотип JFrog: DoS-уязвимость — CVE-2023-43786. Уязвимость CVE-2023-43786 по сути представляет собой бесконечный цикл, возникающий из-за неправильного расчета условия остановки рекурсии. Исправлена фиксация: https://gitlab.freedesktop.org/xorg/lib/libx11/-/commit/204c3393c4c90a29ed6bef64e43849536e863a86 — это функция в libX11, которая позволяет размещать изображения в X Drawable, обычно в X Window. С помощью этой функции можно перенести информацию о пикселях из структуры XImage в назначенный объект рисования, например окно или растровое изображение, и расположить его по мере необходимости. XPutImage xpmCreatePixmapFromImage Функция libXpm вызывает эту функцию : 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); } В этой функции — это отображаемые пиксельные данные исходного изображения, которые копируются в объект (в данном случае ). ximage X Drawable pixmap_return XPutImage Вот функция 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);......... } Он вызывает функцию : 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); } } Подробности технической уязвимости Давайте возьмем следующий пример изображения: Available [the requested size] = (65,536 * 4) - 28 = 262,116 bits_per_pixel = 32 width = 90,000 pixels height = 1 pixel Поскольку изображения равен 32, условный оператор в [1] не пройдет, что приведет нас к входу в альтернативный блок кода, определенный в [2]. bits_per_pixel Затем он вычисляет для [3] и затем делит его на 8. В нашем примере: BytesPerRow = 90000 * 32/8 = 360,0. BytesPerRow В примере проверка на [4] не пройдет, так как 360000 не меньше запрошенного размера 262116 и не может уместить одну строку запрошенной ширины в один запрос – это инициирует на [5 ]. else Это определяет количество пикселей, которые можно включить в один запрос. Затем он инициирует рекурсивный вызов функции для передачи только этого подмножества, за которым следует последующий рекурсивный вызов для управления оставшейся частью строки. При необходимости оставшуюся часть можно также разделить с помощью дополнительных рекурсивных вызовов. PutSubImage Однако расчет в [6] не учитывает количество битов на пиксель, и рекурсивный вызов отправляет запросы, отправляющие 2096928 пикселей вместо 2096928 бит, что больше, чем может быть уложено в один запрос. Это приводит к , что постоянно приводит к тому, что число становится слишком большим для размещения, и повторяется попытка повторить процесс с теми же значениями. Эта рекурсия сохраняется до тех пор, пока стек вызовов не будет исчерпан. бесконечному циклу попыток разделить строку пикселей изменило расчет в [6] и приняло во внимание значение . В данном примере это приведет к рекурсивному вызову с запросом на отправку всего 65529 пикселей, в результате чего BytesPerRow будет равен 262116, что идеально вписывается в доступное пространство, что позволит рекурсии продолжить движение вперед и завершиться всего за два вызова. Исправление ошибки bits_per_pixel Пример изображения для проверки концепции, вызывающего ошибку: https://github.com/jfrog/jfrog-CVE-2023-43786-libX11_DoS/blob/main/cve-2023-43786.xpm Как может возникнуть ошибка Примером приложения, которое вызывает уязвимую функцию библиотеки libXpm, является утилита CLI , которая используется для отображения изображений Xpm на экране. sxpm Он вызывает уязвимую функцию Xpm, которая затем вызывает уязвимые функции libX11 а затем . xpmCreatePixmapFromImage XPutImage PutSubImage Также опубликовано здесь.