この投稿は、Safari 15.4 のセキュリティアップデートで修正された WebGL バグの PoC に関するものです。

WebGL の仕組みを詳しく学びたい場合は、この公式リソースを利用できます。ここではバグ固有の詳細について説明するためです :
https://webglfundamentals.org/webgl/lessons/webgl-how-it-works.html
この PoC は、このバグを悪用するためのプリミティブを取得する方法に関するものではありません。ここでは、脆弱性の分析と webcontent をクラッシュさせる方法についてのみ説明します : )
WebGL は、ブラウザで 2D および 3D グラフィックスをレンダリングするために使用される JavaScript API です。プライマリ WebGL バックエンドとして ANGLE コンポーネントを使用し、他のブラウザとの互換性を大幅に向上させ、WebKit で適合する WebGL 2.0 を有効にします。WebGL は、開発者が最適化を伴うレンダリングを実行するための多くの拡張機能を提供します。そこで、ここでは対象となる拡張機能について見ていきます。

つまり、この API はさまざまな目的のためにいくつかの拡張機能を提供します。これから説明するバグは EXT_ にあります。
拡張機能の詳細については、こちらからお読みいただけます :
https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Using_Extensions
とにかく、バグの話に移りましょう :)
WEBGL_multi_draw 拡張機能は WebGL API の一部であり、1 回の関数呼び出しで複数のプリミティブをレンダリングできます。これにより、レンダラーでのバインディングコストが削減され、uniform データによる GPU スレッド時間が高速化されるため、WebGL アプリケーションのパフォーマンスが向上します。
この拡張機能は、OpenGL 向けに
ARB_shader_draw_parametersによって公開される頂点シェーダーの組み込み変数gl_DrawIDに加えて、EXT_multi_draw_arrays機能のMulti*描画呼び出しの派生形を公開します。これらの関数は、1 回の呼び出しで複数の頂点リストを処理することを除けば、標準の関数
DrawArrays()およびDrawElements()とまったく同じように動作します。それらの主な目的は、トライアングルストリップやトライアングルファンなどの複数のプリミティブを 1 回の関数呼び出しでレンダリングできるようにすることです。さらに、この拡張機能は、シェーディング言語にさらなる組み込み変数
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 の場合、この拡張機能は Safari 15 (ios) でのみサポートされているためです。
パッチは非常に理解しやすく、レンダリング中のヒープバッファオーバーフローであることを明確に示しています。この関数は、前述のすべての操作 [1] において、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: 3 つの頂点のグループごとに三角形を描画します。firstsList:
firstsOffset:
countsList:
countsOffset:
drawCount:
実装を見ると、オフセットと drawcount を制御して、バッファのサイズを超えて描画できることがわかります : )
そこで、PoC は こちら にあります ...

ご覧のとおり、multiDrawArraysWEBGL() 内で呼び出される validateOffset() は、firstsOffset が [0, size-drawcount] の範囲内にあるかどうかをチェックしないため、実際のバッファのサイズを超えて描画しており、Web ページはクラッシュしました。関数呼び出し m_context->graphicsContextGL()->multiDrawArraysANGLE() は、ANGLE として知られる WebKit のサードパーティコンポーネントに実装されており、レンダリング前のチェックがないため、メモリ領域に不正な値を書き込んでクラッシュします。
参考までに、multiDrawArraysANGLE() の擬似コードは次のように動作します:
for(i=0; i<drawcount; i++) {
if (*(counts+i)>0) DrawArrays(mode, *(firsts+i), *(counts+i));
}
そして、パッチが適用されると、validateOffset() 関数呼び出しを介して何もレンダリングせず false を返します:

js) ...
PARS Defense Team
新しいチームメンバーを募集しています。連絡先: info[at]parsdefense.com