
CVE-2022-22629 Prova de Conceito
Este post é sobre a PoC para o bug do WebGL que foi corrigido nas atualizações de segurança do Safari 15.4.

Se você quiser aprender em detalhes como o WebGL funciona, você pode usar este recurso oficial, já que vamos discutir detalhes específicos do bug aqui:
https://webglfundamentals.org/webgl/lessons/webgl-how-it-works.html
Esta PoC não é sobre como obter as primitivas para explorar esse bug, vamos apenas discutir sobre análise de vulnerabilidade e como derrubar o webcontent : )
WebGL é uma API JavaScript usada em navegadores para renderizar gráficos 2D e 3D. Ela usa o componente ANGLE como seu backend WebGL principal, melhorando significativamente a compatibilidade com outros navegadores e habilitando um WebGL 2.0 conforme no WebKit. WebGL oferece muitas extensões para um desenvolvedor realizar a renderização com otimizações. Então, vamos analisar a extensão com a qual estamos preocupados.

Então, esta API fornece várias extensões para diferentes finalidades. O bug que vamos discutir está em EXT_.
Você pode ler mais detalhes sobre as extensões aqui:
https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Using_Extensions
De qualquer forma, vamos em direção ao bug :)
A extensão WEBGL_multi_draw faz parte da API WebGL e permite renderizar mais de uma primitiva com uma única chamada de função. Isso pode melhorar o desempenho de uma aplicação WebGL, pois reduz os custos de binding no renderizador e acelera o tempo da thread da GPU com dados uniformes.
Esta extensão expõe as variantes de chamada de desenho
Multi*na funcionalidadeEXT_multi_draw_arrays, além da variável embutida do vertex shadergl_DrawIDexposta porARB_shader_draw_parameterspara OpenGL.Essas funções se comportam de forma idêntica às funções padrão
DrawArrays()eDrawElements(), exceto que elas lidam com múltiplas listas de vértices em uma única chamada. Seu principal objetivo é permitir que uma chamada de função renderize mais de uma primitiva, como triangle strip, triangle fan, etc.Além disso, esta extensão adiciona uma variável embutida adicional,
gl_DrawID, à linguagem de shading. Essa variável contém o índice do desenho que está sendo processado atualmente por uma varianteMulti*de um comando de desenho.
Você pode ler mais detalhes sobre a extensão aqui:
https://registry.khronos.org/webgl/extensions/WEBGL_multi_draw/
Esta extensão é semelhante às APIs WebGLRenderingContext.drawArrays() e WebGLRenderingContext.drawElements() do WebGL, que renderizam primitivas a partir de dados de array. A única diferença é que podemos renderizar usando esta extensão com múltiplos arrays.
Podemos realizar estas operações usando esta extensão: [1]
ext.multiDrawArraysWEBGL() renderiza múltiplas primitivas a partir de dados de array (idêntico a múltiplas chamadas a drawArrays).
ext.multiDrawElementsWEBGL() renderiza múltiplas primitivas a partir de dados de array de elementos (idêntico a múltiplas chamadas a drawElements).
ext.multiDrawArraysInstancedWEBGL() renderiza múltiplas primitivas a partir de dados de array (idêntico a múltiplas chamadas a drawArraysInstanced).
ext.multiDrawElementsInstancedWEBGL() renderiza múltiplas primitivas a partir de dados de array de elementos (idêntico a múltiplas chamadas a drawElementsInstanced).

https://github.com/WebKit/WebKit/commit/c087ca07c9125093ca62e8e44024b738bb7d46dc
A vulnerabilidade existe apenas do Safari 15.0 ao 15.3, porque esta extensão é suportada apenas no Safari 15 (iOS) no caso da Apple.
A correção é muito fácil de entender e deixa claro que se trata de um heap buffer overflow durante a renderização. Esta função é chamada aqui em WebGLMultiDraw.cpp em todas as operações que mencionamos acima [1]. Vamos entender o que está acontecendo antes de essa função ser chamada. Podemos analisar a implementação de uma das funções acima:
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);
}
Em JS, as especificações da função são definidas assim:
void ext.multiDrawArraysWEBGL(mode,
firstsList, firstsOffset,
countsList, countsOffset,
drawCount);
mode:
gl.POINTS: Desenha um único ponto.gl.TRIANGLES: Desenha um triângulo para um grupo de três vértices.firstsList:
firstsOffset:
countsList:
countsOffset:
drawCount:
Analisando a implementação, podemos ver que podemos controlar o offset e o drawcount para desenhar além do tamanho do buffer : )
Então, nossa PoC está aqui ...

Como você pode ver, nossa página web simplesmente crashou, pois o validateOffset() chamado em multiDrawArraysWEBGL() não verifica se o nosso firstsOffset está no intervalo [0, size-drawcount] e estamos desenhando além do tamanho do buffer real. A chamada de função m_context->graphicsContextGL()->multiDrawArraysANGLE() é implementada em componentes de terceiros para WebKit, conhecidos como ANGLE, e não possui nenhuma verificação antes da renderização, então crashamos ao escrever algum valor ruim na região de memória.
FYI, o pseudocódigo de multiDrawArraysANGLE() funciona assim:
for(i=0; i<drawcount; i++) {
if (*(counts+i)>0) DrawArrays(mode, *(firsts+i), *(counts+i));
}
E com a correção aplicada, ele não renderizará nada e retornará false através da chamada de função validateOffset():

js) ...
PARS Defense Team
Estamos contratando novos membros para a equipe, entre em contato: info[at]parsdefense.com