わずか65バイトのモデルファイルが、人気の高い Llama.cpp 推論エンジンを突然停止させることがあります。この極小のペイロードは、パーサー内部でゼロ除算を引き起こし、SIGFPE クラッシュを発生させます。
なぜこのクラッシュが重要なのか
何が問題だったのか
パーサーはモデルのメタデータを C++ 構造体に読み込み、各テンソルの次元が「非負(non-negative)」であることを確認します。ゼロはこの条件を満たすため、チェックを通過してしまいます。その直後のコード行では、値が正であることを前提とした計算において、その次元を分母(除数)として使用しています。次元がゼロの場合、この除算によって浮動小数点例外(SIGFPE)が発生し、プログラムが終了します。
このバリデーションは、必要とされる不変条件(invariant)の半分しかカバーしていませんでした。負の値はブロックしていましたが、ゼロを無視しており、その後の算術演算においてゼロも同様に危険だったのです。
開発者がすべきこと
- モデルファイルをバイナリとして扱う。 モデルのロードは、生のバイトデータをメモリ構造にパースする作業であり、チェックされていないデータがプロセスをクラッシュさせたり、さらに深刻な事態を引き起こしたりする典型的な境界領域です。
- 不完全なチェックを避ける。 必要な不変条件の一部しかカバーしていない条件は、誤った安心感を与えます。今回の場合、「非負(non-negative)」では不十分であり、コードには「正(positive)」であることが求められていました。
- 各チェックの後に前提条件を疑う。 境界テストが行われた際、その後のコードがより厳格なプロパティに依存していないかを確認してください。
- ファジングツールを活用する。 著者は、AddressSanitizer とともに libFuzzer を実行することでこのバグを発見しました。これは入力を変異させ、ゼロ除算のような不正な操作を検知するツールです。
修正の適用方法
修正では、次元がゼロの場合にオーバーフロー計算をスキップするガードを追加しています。この小さな条件分岐により、一部のモデルバリアントでオプション機能として使用される正当なゼロサイズのテンソルへのサポートを維持しつつ、クラッシュへの経路を排除しています。
教訓: たった一つの65バイトのファイルが、広く普及している推論エンジンを停止させることがあります。徹底したバリデーションと体系的なファジングが、モデルローダーの安全性を保ちます。
出典: https://dev.to/harrisonsec/your-model-file-is-untrusted-input-1eap
