スタック保護によるセキュリティ/脆弱性対策
スタックオーバーフローは、プログラムが固定長であるコールスタック上のアドレスに意図したデータ構造以外のものをストアすることで発生し、セキュリティホールになりえます。スタックオーバーフローにより関数の戻りアドレスが書き換えられ、制御を乗っ取られる可能性があるからです。スタック保護のためカナリアと呼ぶ特別な値を使用する方法があり(炭鉱のカナリアに由来)、多くのアーキテクチャに対するIAR Embedded Workbenchの最新版で利用できます。
IAR Embedded Workbenchはまず関数がスタック保護を必要とするか判定します。いずれかのローカル変数が配列または配列をメンバとする構造体なら、関数はスタック保護が必要です。もしくは、いずれかのローカル変数のアドレスが関数外に伝播する場合も、関数のスタック保護が必要です。コールスタックの範囲を超えたアドレスにアクセスする可能性があり、セキュリティ上の問題/脆弱性となりうるためです。
関数がスタック保護を必要とする場合、その関数に割り当てるスタックの中で、ローカル変数は配列型の変数が最上位のアドレスになるよう並び替えられます。並び替えた変数の後ろにカナリアを配置します。カナリアは関数の入口で初期化されます。初期化する値はグローバル変数__stack_chk_guardを用います。関数の出口でカナリアの値が保持されているかをチェックします。保持されていなければ関数__stack_chk_failを呼び出して、スタックオーバーフローを通知します。
必要と考えられる関数のスタック保護を有効にするため、コンパイラオプション--Project>Options>C/C++ Compiler>Code>Stack protection--を使います。
必要に応じてProject>Options>C/C++ Compiler>Extra Options ページを使用し --stack_protection コマンドラインで、スタック保護を有効にすることも可能です。
スタック保護のためにアプリケーション側に必要な記述
ソースコード上で必要なオブジェクトを定義します。
- extern uint32_t __stack_chk_guard
カナリアの値として使用するグローバル変数__stack_chk_guardは使用前に初期化が必要です。初期値をランダムに生成すると、よりプログラムの安全性が高まります。
-
__interwork __nounwind __noreturn void __stack_chk_fail(void)
関数__stack_chk_failの目的は、問題の通知とアプリケーションの終了です。この関数の戻りアドレスはスタックが壊れた関数を指します。
arm\src\lib\runtimeフォルダにあるstack_protection.cというファイルが__stack_chk_guardと__stack_chk_failのテンプレートとして使われます。