回答:
ソフトウェア、ハードウェア、またはその両方、またはなしのいずれかです。
オーバーフローには2つの種類があります。スタックを成長させるとき(関数を入力するとき)のオーバーフローと、スタック上の配列にアクセスするときのオーバーフローです。スタックの成長時のオーバーフローは、関数のエントリで境界チェックを行い、十分なスペースがあることを確認することで検出できます(エラーがない場合はエラーを通知するか、スタックを成長させます)。スタック上の配列にアクセスする際のオーバーフローは、配列の境界を検証しない低レベル言語の問題にすぎません。解決策は、配列の境界を確認することです。
これらのソフトウェアアプローチには、完全に確実に動作するという利点があります。スタックオーバーフローが確実に検出されます。彼らの欠点は、コードサイズと実行時間が増加することです。ハードウェアは、オーバーフローが発生しない限り、ほとんどのオーバーフローを無料で検出する方法を提供することで役立ちます。MMU¹を備えたアーキテクチャでは、ランタイム環境は次のページがマッピングされないまま、ページ境界にスタックをマップするように調整できます。
+---------------+---------------+---------------+---------------+
| stack | unmapped | other stuff |
| ----> direction of growth | | |
+---------------+---------------+---------------+---------------+
^ ^ ^ ^ ^ page boundaries
そのように、ソフトウェアがページ境界を超えてデータにアクセスしようとすると(スタックポインターが境界を超えて移動したか、配列アクセスが境界外で境界を超えたため)、マップされていない領域にアクセスすることでエラーが発生します。これは、オーバーフローが十分に小さい場合にのみ発生します。オーバーフローの量が大きすぎると、プログラムはアドレス空間のギャップの反対側にある他のものにアクセスすることになります。
ハードウェアアプローチの欠点は、大量のオーバーフローが検出されない可能性があるため、完全に信頼できないことと、アドレス可能なスペース内に残る配列オーバーフローを検出しないことです。
配列のオーバーフローを検出するための別のソフトウェア技術はカナリアです。スタックの最上部またはフレーム間に特別な値を設定し、関数の戻り時にカナリア値が変更されていないことを確認します。これは、オーバーフローがカナリアを完全に回避するか、カナリア値がチェックされるまでにカナリア値が復元されているために検出されない可能性があるため、不完全な手法でもあります。それにもかかわらず、セキュリティの脆弱性を悪用するのを難しくすることは有用です。
スタックオーバーフローを回避する最も安全で安価な方法は、静的分析により、プログラムが実行を開始する前に必要なスタックの量を計算することです。ただし、これは常に実用的とは限りません。プログラムが必要とするスタックの量は一般に決定できず、プログラムが操作するデータに依存します。
¹ 同じ原則は、MPUのみに適用することも、既存の物理マッピングの端にスタックがある単一のスレッドがある場合はメモリ保護なしに適用することもできます。