
Доказательство концепции для CVE-2022-22629, переполнение буфера в куче в расширении multi-draw WebGL в Safari, с анализом уязвимости и шагами воспроизведения сбоя.
Этот пост о PoC для бага WebGL, который был исправлен в обновлениях безопасности Safari 15.4.

Если вы хотите подробно узнать, как работает WebGL, вы можете воспользоваться этим официальным ресурсом, так как здесь мы будем обсуждать детали, специфичные для бага:
https://webglfundamentals.org/webgl/lessons/webgl-how-it-works.html
Этот PoC не о том, как получить примитивы для эксплуатации этого бага, мы просто обсудим анализ уязвимости и как вызвать сбой веб-контента : )
WebGL — это JavaScript API, используемый в браузерах для рендеринга 2D и 3D графики. Он использует компонент ANGLE в качестве основного бэкенда WebGL, что значительно улучшает совместимость с другими браузерами и обеспечивает соответствие WebGL 2.0 в WebKit. WebGL предлагает множество расширений для разработчика, чтобы выполнять рендеринг с оптимизациями. Итак, мы рассмотрим расширение, которое нас интересует.

Вы можете прочитать более подробно о расширениях здесь:
https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Using_Extensions
В любом случае, давайте перейдем к багу :)
Расширение WEBGL_multi_draw является частью WebGL API и позволяет отрисовывать более одного примитива за один вызов функции. Это может улучшить производительность приложения WebGL, так как снижает затраты на привязку в рендерере и ускоряет время работы потока GPU с однородными данными.
Это расширение предоставляет варианты вызова отрисовки
Multi*в функциональностиEXT_multi_draw_arraysв дополнение к встроенной переменной вершинного шейдераgl_DrawID, предоставляемойARB_shader_draw_parametersдля OpenGL.Эти функции ведут себя идентично стандартным функциям
DrawArrays()иDrawElements(), за исключением того, что они обрабатывают несколько списков вершин за один вызов. Их основная цель — позволить одному вызову функции отрисовывать более одного примитива, такого как треугольная полоса, треугольный веер и т.д.Кроме того, это расширение добавляет дополнительную встроенную переменную
gl_DrawIDв язык шейдеров. Эта переменная содержит индекс отрисовки, который в данный момент обрабатывается вариантомMulti*команды рисования.
Вы можете прочитать более подробно о расширении здесь:
https://registry.khronos.org/webgl/extensions/WEBGL_multi_draw/
Это расширение похоже на API WebGL WebGLRenderingContext.drawArrays() и WebGLRenderingContext.drawElements() и т.д., которые отрисовывают примитивы из данных массивов. Единственное отличие в том, что с помощью этого расширения можно отрисовывать, используя несколько массивов.
Мы можем выполнять эти операции с помощью данного расширения: [1]
ext.multiDrawArraysWEBGL() отрисовывает несколько примитивов из данных массивов (идентично множественным вызовам drawArrays).
ext.multiDrawElementsWEBGL() отрисовывает несколько примитивов из данных массивов элементов (идентично множественным вызовам drawElements).
ext.multiDrawArraysInstancedWEBGL() отрисовывает несколько примитивов из данных массивов (идентично множественным вызовам drawArraysInstanced).
ext.multiDrawElementsInstancedWEBGL() отрисовывает несколько примитивов из данных массивов элементов (идентично множественным вызовам drawElementsInstanced).

https://github.com/WebKit/WebKit/commit/c087ca07c9125093ca62e8e44024b738bb7d46dc
Уязвимость существует только в Safari 15.0–15.3, потому что это расширение поддерживается только в Safari 15 (iOS) в случае Apple.
Патч очень легко понять, и в нем четко указано, что это переполнение буфера в куче при рендеринге. Эта функция вызывается здесь в WebGLMultiDraw.cpp во всех операциях, которые мы упомянули выше [1]. Давайте разберемся, что происходит до вызова этой функции. Мы можем посмотреть на реализацию одной из вышеуказанных функций:
void WebGLMultiDraw::multiDrawArraysWEBGL(GCGLenum mode, Int32List firstsList, GCGLuint firstsOffset, Int32List countsList, GCGLuint countsOffset, GCGLsizei drawcount)
{
if (!m_context || m_context->isContextLost())
return;
if (!validateDrawcount("multiDrawArraysWEBGL", drawcount)
|| !validateOffset("multiDrawArraysWEBGL", "firstsOffset out of bounds", firstsList.length(), firstsOffset, drawcount)
|| !validateOffset("multiDrawArraysWEBGL", "countsOffset out of bounds", countsList.length(), countsOffset, drawcount)) {
return;
}
m_context->graphicsContextGL()->multiDrawArraysANGLE(mode, makeSpanWithOffset(firstsList, firstsOffset), makeSpanWithOffset(countsList, countsOffset), drawcount);
}
В JS спецификации функций определены так:
void ext.multiDrawArraysWEBGL(mode,
firstsList, firstsOffset,
countsList, countsOffset,
drawCount);
mode:
gl.POINTS: Отрисовывает одну точку.gl.TRIANGLES: Отрисовывает треугольник для группы из трех вершин.firstsList:
firstsOffset:
countsList:
countsOffset:
drawCount:
Взглянув на реализацию, мы видим, что можем управлять смещением и drawcount, чтобы отрисовывать за пределами размера буфера : )
Итак, наш PoC находится здесь ...

Как видите, наша веб-страница просто вылетела, потому что validateOffset(), вызываемый в multiDrawArraysWEBGL(), не проверяет, находится ли наш firstsOffset в диапазоне [0, size-drawcount], и мы рисуем за пределами фактического буфера. Вызов функции m_context->graphicsContextGL()->multiDrawArraysANGLE() реализован в сторонних компонентах WebKit, известных как ANGLE, и в нем нет никаких проверок перед рендерингом, поэтому мы вызываем сбой, записывая некорректное значение в область памяти.
К сведению, псевдокод multiDrawArraysANGLE() работает так:
for(i=0; i<drawcount; i++) {
if (*(counts+i)>0) DrawArrays(mode, *(firsts+i), *(counts+i));
}
А с примененным патчем он ничего не отрисует и вернет false через вызов функции validateOffset():

js) ...
PARS Defense Team
Мы нанимаем новых членов команды, свяжитесь с нами: info[at]parsdefense.com