GGUFファイルフォーマットは、GGMLライブラリのモデル重みの保存と読み込みに使用されるバイナリファイルフォーマットです。 ライブラリのドキュメントには、以下のような形式が記述されています:
"GGUFは、GGMLによる推論のためのモデルや、GGMLに基づく実行形式を保存するためのファイルフォーマットです。 GGUFは、モデルの読み込みと保存を高速化し、読みやすくするために設計されたバイナリフォーマットです。 モデルは伝統的にPyTorchや他のフレームワークを使用して開発され、GGMLで使用するためにGGUFに変換されます。"
GGUFフォーマットは、学習済みの機械学習モデルを配布するために最近普及しており、低レベルのコンテキストからモデルを利用する際に、Llama-2で最も一般的に使用されるフォーマットの1つとなっています。 llama.cpp、pythonのllmモジュール、Huggingfaceのようなggufファイルをロードするときのctransformersライブラリなど、このローダーにデータを提供するために使用できるベクターが複数あります。
GGMLライブラリは入力ファイルに対して不十分な検証を行うため、解析中に潜在的に悪用可能なメモリ破壊の脆弱性を含んでいます。 攻撃者はこれらの脆弱性を利用し、細工したgguf
ファイルを提供することで、被害者のコンピュータ上でコードを実行する可能性があります。
このブログでは、悪用がかなり簡単なヒープ・オーバーフローをいくつか見ていきます。 ファイルの境界チェックはほとんど行われないため、境界のないユーザー入力やラップされた値でアロケーションが実行されるケースは他にも数多くあります。 また、メモリ割り当てを含め、コードベース全体でチェックされている戻り値がほとんどないことも注目に値します。 すべ てのヒープオーバーフローは、gguf_init_from_file()
エントリポイントを経由して到達可能です。
タイムライン
- 2024年1月23日ベンダーに連絡、バグ報告
- 2024年1月25日CVEの要請
- 2024年1月28日GGMLのGithubで修正をレビューしました。
- 2024年1月29日パッチを master ブランチにマージ
CVE-2024-25664 ヒープオーバーフロー #1: KV カウントのチェック漏れ
保存されたモデルをロードする場合、ライブラリへのエントリーポイントは、通常、gguf_init_from_file()
関数(以下に注釈付きで示されています)を介して行われます。 この関数は、マジック値" GGUF" をチェックする前に、ファイルから
gguf ヘッダーを読み込むことから始めます。この後、ファイル内のキーと値のペアが読み込まれ、解析されます。
https://github.com/ggerganov/ggml/blob/faab2af1463aa556899b72 1289efcbf50c557f55/src/ggml.c#L19215
ラップアロケーションの内容はgguf_kv
構造体の配列で、ファイルから読み込んだデータのキーと値のペアを格納するために使用されます。 構造の定義は以下の通り:
https://github.com/ggerganov/ggml/blob/faab2af1463aa556899b72 1289efcbf50c557f55/src/ggml.c#L19133
これにより、隣接するメモリーを、valueフィールドを使って私たちがコントロールするデータか、keyフィールドの場合は私たちがコントロールするデータへのポインターで上書きすることができます。 これらはヒープを悪用するためのかなり強力なプリミティブです。 構文解析の性質上、ヒープの状態をかなり任意に操作することも可能です。 これによって搾取も容易になるはずです。
以下のPoCコードでは、0xe0の
アロケーションが発生し、n_kv
値は0x555555555555aで
、チャンクへの書き込みループ・カウンタとして使用されます。 各書き込みは0x30
サイズ(sizeof(struct gguf_kv)
)で、その結果、0x500のkvを
含むヒープメモリが破壊されます。 ループを終了させるには、単純にファイルを早めに終了させ、EOFを読み込んでエラー状態にします。 その後にヒープを使用すると、オーバーフローによるチェックサムが失敗し、abort()
エラーメッセージが表示されます。
CVE-2024-25665 ヒープオーバーフロー #2: 文字列型の読み込み
ファイルから文字列型データを読み込むために繰り返し使用される関数gguf_fread_str()
にも、ヒープオーバーフローの脆弱性が存在する可能性があります。
以下のコードでわかるように、この関数は長さエンコードされた文字列を検証なしでファイルから直接読み込みます。 まず、gguf_fread_el()
を使って文字列の長さを読み込みます。 次に、任意の入力サイズ+1の割り当てが行われます。 アロケータが
このサイズを受け取ると、アロケータが使用する可能な最小のクア ンタのサイズのチャンクを返します。 これが終わると、fread()
関数を使って、大きなアンラップ・サイズを使ってコピーが実行されます。
https://github.com/ggerganov/ggml/blob/faab2af1463aa556899b72 1289efcbf50c557f55/src/ggml.c#L19177
CVE-2024-25666 ヒープオーバーフロー #3: テンソルカウントの未チェック
ファイル中のgguf_tensor_infos
を解析する際に、#1 と非常によく似たヒープオーバーフローが発生します。 ctx->header.n_tensors
の値がチェックされず、構造体のサイズにもう一度乗算されるため、ラップが発生し、アロケーションが小さくなります。 この後、ループが各要素を順番にコピーし、ヒープがオーバーフローします。 以下のコードはこの脆弱性を示しています。
https://github.com/ggerganov/ggml/blob/faab2af1463aa556899b72 1289efcbf50c557f55/src/ggml.c#L19345
CVE-2024-25667 ヒープオーバーフロー #4: ユーザーが指定した配列要素
ファイルからkv
値をアンパックするとき、アンパックできる型の1つは配列型(GGUF_TYPE_ARRAY
)です。 配列をパースするとき、コードは配列の型と、その型の要素数を読み取ります。 そして、GGUF_TYPE_SIZE
配列の型サイズと要素数を掛け合わせて、データを格納するためのアロケーションサ イズを計算します。 繰り返しになりますが、配列の要素数はユーザーが任意に指定できるため、この計算が折り返される可能性があり、その結果、小さなメモリ割り当てと大きなコピーループが発生します。 配列データはコンパクトであるため、ヒープ内容のオーバーフローは非常に抑制されます。
https://github.com/ggerganov/ggml/blob/faab2af1463aa556899b72 1289efcbf50c557f55/src/ggml.c#L19297
CVE-2024-25668 ヒープオーバフロー #5: kv 文字列型配列のアンパッキング
また、文字列型の配列を扱う際に、配列kvのアンパック時にヒープオーバーフローの問題が発生します。 文字列を解析する場合、再び、要素カウントは、チェックされていないファイルから直接読み込まれます。 この値はgguf_str
構造体のサイズと乗算され、結果としてラップされ、nに対して小さなアロケーションになります。 続いて、チャンクに入力するために、nの値までループが実行されます。 この結果、文字列構造体の内容が境界外に書き込まれます。
https://github.com/ggerganov/ggml/blob/faab2af1463aa556899b72 1289efcbf50c557f55/src/ggml.c#L19318
束縛されない配列インデックス
GGUFファイル内の配列のパース中に、前述のように、型の必要サイズは、GGUF_TYPE_SIZE[]
配列(以下に示す)を介 して割り当てのために決定されます。
https://github.com/ggerganov/ggml/blob/faab2af1463aa556899b72 1289efcbf50c557f55/src/ggml.c#L19076
配列にインデックスを付けることで、乗算によって割り当てサイズを計算するために適切なサイズが返されます。
この配列へのアクセスに使用されるインデックスはファイルから直接読み取られ、サニタイズされていません。したがって、攻撃者はこの配列の境界外のインデックスを指定し、整数ラップを引き起こすサイズを返す可能性があります。 これはアロケーションとコピーの両方で使われます。
次のコードはこのパスを示しています:
https://github.com/ggerganov/ggml/blob/faab2af1463aa556899b72 1289efcbf50c557f55/src/ggml.c#L19300
まとめ
これらの脆弱性は、攻撃者が機械学習モデルを利用してマルウェアを配布し、開発者を危険にさらすための新たな手段を提供することになります。 この新しい、そして急速に成長している研究分野のセキュリティ態勢は、セキュリティレビューに対するより厳密なアプローチから大きな恩恵を受けます。 その中で、DatabricksはGGML.aiのチ ームと緊密に連携し、これらの問題に迅速に対処しました。 コミット6b14d73以降、この投稿で取り上げた 6 件の脆弱性すべてを修正したパッチが利用可能になっています。