LLMファジングの限界と新たなアプローチ

コンパイラのバグを見つけることは、ソフトウェア開発において非常に重要な課題です。記事の筆者は、Gleamの変更履歴やイシュートラッカーを定期的にチェックする中で、コード生成やJavaScript/Erlang間の出力差異に関連する問題に直面していました。そこで、まずはLLMを使ったファジングを試みたそうです。

LLMに過去のGleamのイシューを学習させ、より多くのエッジケースを「深く考える」ように指示した結果、ビット配列の組み合わせやネストされた匿名関数、useパターンといったアイデアが出てきました。しかし、このアプローチは期待通りの成果にはつながりませんでした。

20ドルのトークンを使った結果、たった1つの問題しか発見できませんでした。(出典)

記事では、LLMファジングが高価で、非決定論的であり、まるでスロットマシーンのレバーを引くようなものだと指摘されています。私の個人プロダクトでもLLMを積極的に活用していますが、確かに特定の構造や規則に沿ったコード生成で「思い通り」の結果を得るのは難しいと感じることがあります。

特に、特定の制約を満たす複雑なプログラムを生成させる場合、プロンプトエンジニアリングのコストが無視できません。個人的には、LLMで「思考」させるというよりは、定型的なパターンやバリエーションを網羅的に生成する用途の方が向いているのではないかと感じています。

構造化ファジングとは何か

ソフトウェア開発におけるバグの探索を自動化する手法の一つに「ファジング」があります。これは、プログラムにランダムな入力を与えることで、開発者が想定していなかったエッジケースや脆弱性を発見しようとするものです。

ファジングには、完全にランダムなバイト列を与えるものから、文法を意識したAST(抽象構文木)を生成するものまで様々なレベルがあります。完全にランダムなバイト列を与えるファジングは、画像処理やファイル、ネットワークプロトコルなどを扱うプログラムで有効です。

例えば、Firefoxの画像ファイルにビットを反転させることでブラウザがクラッシュする脆弱性(CVE-2007-6715)は、このようなファジングによって発見されました(出典)。GoogleのOSS-Fuzzプロジェクトも、多くの重要なオープンソースプロジェクトに対して継続的にファジングを行っています(出典)。

しかし、コンパイラのように明確な文法を持つプログラムの場合、単なるランダムなバイト列では有効な入力となりづらいのが現状です。そこで登場するのが「構造化ファジング」です。これは、ランダムなバイト列ではなく、ソースコードやASTのような構造を持ったコードストリームを生成する手法です。

ファジングの種類 入力形式 主な適用対象
ランダムファジング ランダムなバイト列 画像、ファイル、ネットワークプロトコルなど
構造化ファジング ソースコード、ASTなど コンパイラ、パーサー、言語処理系など