Моя команда недавно обнаружила
Команда постоянно отслеживает проекты с открытым исходным кодом, чтобы находить новые уязвимости и вредоносные пакеты, и делится ими с более широким сообществом, чтобы помочь улучшить общий уровень безопасности.
В этой серии блогов, состоящей из двух частей, подробно рассказывается о внутренней работе уязвимого формата файлов Xpm и подробно рассматриваются способы использования этих уязвимостей.
Xorg X11, часто называемый X Window System, представляет собой протокол графического сервера с открытым исходным кодом , который позволяет создавать и управлять графическими пользовательскими интерфейсами в Unix-подобных операционных системах. Он обеспечивает основу для запуска графических приложений, управления Windows и обработки пользовательского ввода в сетевой среде.
Пакет libx11 предлагает основные общие библиотеки, необходимые клиентским приложениям для рендеринга и представления данных на рабочем столе.
libXpm предоставляет функции для чтения, записи и отображения изображений в формате X Pixmap (XPM).
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 — черному), каждый байт массива содержит данные для восьми отдельных пикселей. Примечательно, что младший бит первого байта служит привязкой для верхнего левого пикселя растрового изображения.
Встречайте версию XPM
Впервые выпущенный в 1989 году, он имеет много общего с форматом XBM, описанным выше. В то время как XBM был монохромным, XPM1 придавал изображениям цвета.
Он использует дополнительные макросы и переменные для цветов и заменяет байты символами для данных пикселей.
Например, вот та же hello
-белая картинка в формате 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
здесь обозначает количество цветов в изображении, а XFACE_chars_per_pixel
обозначает количество символов на пиксель.
Каждый a
будет заменен белым цветом «#ffffff», а каждый b
будет заменен черным цветом «#000000».
Год спустя, в 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
указывает на прозрачность.
Это текущая версия формата 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
XPutImage
— это функция в libX11, которая позволяет размещать изображения в X Drawable, обычно в X Window. С помощью этой функции можно перенести информацию о пикселях из структуры XImage в назначенный объект рисования, например окно или растровое изображение, и расположить его по мере необходимости.
xpmCreatePixmapFromImage
Функция xpmCreatePixmapFromImage
libXpm вызывает эту функцию 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
Вот функция 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);......... }
Он вызывает функцию 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
Поскольку bits_per_pixel
изображения равен 32, условный оператор в [1] не пройдет, что приведет нас к входу в альтернативный блок кода, определенный в [2].
Затем он вычисляет BytesPerRow
для [3] и затем делит его на 8. В нашем примере: BytesPerRow = 90000 * 32/8 = 360,0.
В примере проверка на [4] не пройдет, так как 360000 не меньше запрошенного размера 262116 и не может уместить одну строку запрошенной ширины в один запрос – это инициирует else
на [5 ].
Это определяет количество пикселей, которые можно включить в один запрос. Затем он инициирует рекурсивный вызов функции PutSubImage
для передачи только этого подмножества, за которым следует последующий рекурсивный вызов для управления оставшейся частью строки. При необходимости оставшуюся часть можно также разделить с помощью дополнительных рекурсивных вызовов.
Однако расчет в [6] не учитывает количество битов на пиксель, и рекурсивный вызов отправляет запросы, отправляющие 2096928 пикселей вместо 2096928 бит, что больше, чем может быть уложено в один запрос.
Это приводит к бесконечному циклу попыток разделить строку пикселей , что постоянно приводит к тому, что число становится слишком большим для размещения, и повторяется попытка повторить процесс с теми же значениями. Эта рекурсия сохраняется до тех пор, пока стек вызовов не будет исчерпан.
Исправление ошибки изменило расчет в [6] и приняло во внимание значение bits_per_pixel
. В данном примере это приведет к рекурсивному вызову с запросом на отправку всего 65529 пикселей, в результате чего BytesPerRow будет равен 262116, что идеально вписывается в доступное пространство, что позволит рекурсии продолжить движение вперед и завершиться всего за два вызова.
Пример изображения для проверки концепции, вызывающего ошибку: https://github.com/jfrog/jfrog-CVE-2023-43786-libX11_DoS/blob/main/cve-2023-43786.xpm
Примером приложения, которое вызывает уязвимую функцию библиотеки libXpm, является утилита CLI sxpm
, которая используется для отображения изображений Xpm на экране.
Он вызывает уязвимую функцию xpmCreatePixmapFromImage
Xpm, которая затем вызывает уязвимые функции libX11 XPutImage
а затем PutSubImage
.
Также опубликовано здесь.