C関数に渡すレジスタパラメータが少なすぎることの結果

本記事はMicrosoft開発チームのレイモンド・チェンによる、C言語で関数呼び出し時に期待されるパラメータ数より少ない引数を渡した場合の危険性を詳細に解説した技術記事です。C言語の柔軟性により、不足したパラメータで関数を呼び出すことは技術的には可能ですが、呼び出し規約(call...

本記事はMicrosoft開発チームのレイモンド・チェンによる、C言語で関数呼び出し時に期待されるパラメータ数より少ない引数を渡した場合の危険性を詳細に解説した技術記事です。C言語の柔軟性により、不足したパラメータで関数を呼び出すことは技術的には可能ですが、呼び出し規約(calling convention)によって深刻な問題が発生します。

特にレジスタベースの呼び出し規約を採用するアーキテクチャでは、渡されなかったパラメータに対応するレジスタが初期化されないため、予期しない値が関数内で参照され、メモリ破損やセキュリティ脆弱性につながる危険があります。Itaniumアーキテクチャはx86-64よりもレジスタ依存度が高く、メモリベースのスタック渡しより初期化されない値がより予測不可能になるため、問題の深刻度が極めて高いとされています。

従来のC標準では関数ポインタの型チェックが厳密ではなく、不完全な関数シグネチャ宣言が許容されていたことがこの問題を助長していました。C23以降の新しい標準では、より厳格な型チェックが導入され、こうしたバグの根本原因を事前に検出できるようになりました。

本記事はハードウェアアーキテクチャの仕様とC言語の仕様が交差する複雑な領域における落とし穴を理解する上で重要です。

HNの反応

コミュニティは記事の技術的価値を認識しつつ、C言語の呼び出し規約の詳細な仕様や、C23での型チェック強化による影響、そして呼び出し規約の検出を実務で活用した事例などを議論している。

注目コメント

「私はこれを利用して、実行時にコードが使用している呼び出し規約を検出するという興味深い経験をしました。OSのバージョンによって2つの異なるオプションがあり、1つはjnienv*を最初のパラメータとして渡し、もう1つは渡しません。そのため、0で呼び出すことで、最初の引数がNULLかどうかに基づいて、どちらの規約が使用されているかを判定できました。これはこのような動作を示す定義されたABIを持つ特定のアーキテクチャにのみ使用される手法です。」— @charleslmunger

元記事を読むHN討議を見る