
CVE-2022-22629 Proof of Concept
本文是关于在 Safari 15.4 安全更新中修复的 WebGL 漏洞的概念验证。

如果你想详细了解 WebGL 的工作原理,可以使用以下官方资源,因为我们将在这里讨论漏洞的具体细节:
https://webglfundamentals.org/webgl/lessons/webgl-how-it-works.html
此概念验证并非关于如何利用原始体来利用此漏洞,我们只讨论漏洞分析和如何使 Web 内容崩溃 : )
WebGL 是一种 JavaScript API,用于在浏览器中渲染 2D 和 3D 图形。它使用 ANGLE 组件作为其主要 WebGL 后端,显著提高了与其他浏览器的兼容性,并在 WebKit 中实现了符合标准的 WebGL 2.0。WebGL 为开发者提供了许多扩展,以便进行优化渲染。因此,我们将研究我们所关注的扩展。

因此,该 API 提供了几种用于不同目的的扩展。我们将要讨论的漏洞存在于 EXT_ 中。
您可以从这里了解有关扩展的更多详细信息:
https://developer.mozilla.org/zh-CN/docs/Web/API/WebGL_API/Using_Extensions
总之,让我们转向这个漏洞吧 :)
WEBGL_multi_draw 扩展是 WebGL API 的一部分,允许通过单个函数调用渲染多个图元。这可以提高 WebGL 应用程序的性能,因为它减少了渲染器中的绑定开销,并通过统一数据加速 GPU 线程时间。
该扩展公开了
EXT_multi_draw_arrays功能中的Multi*绘制调用变体,以及ARB_shader_draw_parameters为 OpenGL 公开的顶点着色器内置变量gl_DrawID。这些函数的行为与标准函数
DrawArrays()和DrawElements()相同,只是它们在一次调用中处理多个顶点列表。它们的主要目的是允许一次函数调用渲染多个图元,例如三角形条带、三角形扇等。此外,此扩展为着色语言添加了一个额外的内置变量
gl_DrawID。该变量包含当前正在由Multi*变体处理的绘制的索引。
您可以在此处阅读有关该扩展的更多详细信息:
https://registry.khronos.org/webgl/extensions/WEBGL_multi_draw/
此扩展类似于 WebGL API 的 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 版本中,因为 Apple 的 iOS 中仅 Safari 15 支持此扩展。
补丁非常容易理解,它明确指出这是在渲染过程中发生的堆缓冲区溢出。此函数在 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:
查看实现,我们可以看到,我们可以控制 offset 和 drawcount,以在缓冲区大小之外进行绘制操作 : )
因此,我们的 POC 在这里……

如您所见,我们的网页崩溃了,因为 multiDrawArraysWEBGL() 中调用的 validateOffset() 没有检查我们的 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));
}
而应用补丁后,它将不会渲染任何内容,并通过 validateOffset() 函数调用返回 false:

PARS 防御团队
我们正在招聘新团队成员,请联系我们:info[at]parsdefense.com