バッファオーバーフロー保護とは、ソフトウェア開発中に使用されるさまざまな手法の総称で、スタックに割り当てられた変数のバッファオーバーフローを検出し、プログラムの誤動作や深刻なセキュリティ脆弱性の発生を防ぐことで、実行可能プログラムのセキュリティを強化するものです。スタックバッファオーバーフローは、プログラムが、本来のデータ構造(通常は固定長のバッファ)の範囲外にある、プログラムのコールスタック上のメモリ アドレスに書き込んだ場合に発生します。スタックバッファオーバーフローバグは、プログラムがスタック上のバッファに、そのバッファに実際に割り当てられているデータ量よりも多くのデータを書き込んだ場合に発生します。これはほぼ必ずスタック上の隣接データの破損につながり、プログラムのクラッシュ、誤動作、またはセキュリティ問題を引き起こす可能性があります。
一般的に、バッファオーバーフロー保護では、スタックに割り当てられたデータの構成を変更し、スタックバッファオーバーフローによって破壊された際に、メモリ内でその前のバッファがオーバーフローしたことを示すカナリア値を含めるようにします。カナリア値を検証することで、影響を受けたプログラムの実行を終了させ、プログラムが誤動作したり、攻撃者がプログラムを制御したりするのを防ぐことができます。その他のバッファオーバーフロー保護技術には、割り当てられた各メモリブロックへのアクセスをチェックして、実際に割り当てられた領域を超えないようにする境界チェックや、データを格納するために割り当てられたメモリに実行可能なコードが含まれないようにするタグ付けなどがあります。
スタック上に割り当てられたバッファがオーバーフローすると、ヒープ上のバッファがオーバーフローするよりもプログラムの実行に影響を与える可能性が高くなります。これは、スタックにはすべてのアクティブな関数呼び出しの戻りアドレスが含まれているためです。ただし、ヒープ上のオーバーフローに対しても、同様の実装固有の保護機能が存在します。
バッファオーバーフロー保護の実装は複数存在し、GNU Compiler Collection、LLVM、Microsoft Visual Studio、その他のコンパイラにも同様の実装が見られる。
スタックバッファオーバーフローは、プログラムが、本来のデータ構造(通常は固定長バッファ)の範囲外にあるプログラムのコールスタック上のメモリ アドレスに書き込んだときに発生します。スタックバッファオーバーフローバグは、プログラムがスタック上のバッファに、そのバッファに実際に割り当てられているデータ量よりも多くのデータを書き込んだときに発生します。これはほぼ常にスタック上の隣接データの破損につながり、オーバーフローが誤って発生した場合は、プログラムがクラッシュしたり、正しく動作しなくなったりすることがよくあります。スタックバッファオーバーフローは、バッファオーバーフロー(またはバッファオーバーラン)として知られる、より一般的なプログラミングの不具合の一種です。スタック上のバッファをオーバーフローさせると、ヒープ上のバッファをオーバーフローさせるよりもプログラムの実行が中断される可能性が高くなります。これは、スタックにはすべてのアクティブな関数呼び出しの戻りアドレスが含まれているためです。[ 1 ]
スタックバッファオーバーフローは、スタックスマッシングと呼ばれる攻撃の一環として意図的に引き起こされることがあります。影響を受けるプログラムが特別な権限で実行されている場合、または信頼できないネットワークホスト(たとえば、公開ウェブサーバー)からデータを受け入れている場合、このバグは潜在的なセキュリティ脆弱性となり、攻撃者が実行中のプログラムに実行可能コードを注入してプロセスを制御できるようになります。これは、攻撃者がコンピュータへの不正アクセスを取得するための最も古く、かつ信頼性の高い方法の1つです。[ 2 ]
通常、バッファオーバーフロー保護は、関数呼び出しのスタックフレーム内のデータの構成を変更し、「カナリア」値を含めるようにします。このカナリア値は、破棄されると、メモリ内でその前のバッファがオーバーフローしたことを示します。これにより、攻撃のクラス全体を防ぐという利点が得られます。一部の研究者によると、[ 3 ]これらの技術のパフォーマンスへの影響は無視できるほど小さいとのことです。
スタック破壊対策は、特定の種類の攻撃には対応できません。例えば、ヒープ内のバッファオーバーフローには対応できません。構造体内のデータのレイアウトを変更する合理的な方法はありません。構造体はモジュール間で同じであることが想定されており、特に共有ライブラリではその傾向が顕著です。バッファの後の構造体内のデータは、カナリアを使って保護することは不可能です。そのため、プログラマーは変数の構成方法や構造体の使用方法に細心の注意を払う必要があります。
カナリア、カナリアワード、またはスタッククッキーは、バッファオーバーフローを監視するためにスタック上のバッファと制御データの間に配置される既知の値です。バッファオーバーフローが発生すると、通常最初に破損するのはカナリアデータです。そのため、カナリアデータの検証に失敗するとオーバーフローが発生したことが警告され、例えば破損したデータを無効にするなどの処理を行うことができます。カナリア値は、 センチネル値と混同しないでください。
この用語は、炭鉱でカナリアが有毒ガスを感知する生物学的警報システムとして利用されていた歴史的な慣習に由来する。カナリアは鉱夫よりも早く有毒ガスの影響を受けるため、この警告システムとして機能していた。カナリアはスタッククッキーとも呼ばれ、値が破損した際に「割れたクッキー」を連想させることからこの名がついた。
使用されているカナリアには、ターミネーター、ランダム、ランダムXORの3種類があります。StackGuardの最新バージョンはこれら3種類すべてをサポートしていますが、ProPoliceはターミネーターとランダムのカナリアのみをサポートしています。
ターミネータカナリーは、バッファオーバーフロー攻撃のほとんどが文字列終端文字で終わる特定の文字列操作に基づいているという観察を利用しています。この観察に対する対策として、カナリーはヌル終端文字、CR、LF、FFで構成されます。その結果、攻撃者はカナリーを変更しないように、戻りアドレスを書き込む前にヌル文字を書き込む必要があります。これにより、strcpy()ヌル文字をコピーして戻るメソッドなどを使用した攻撃は防止されますが、望ましくない結果としてカナリーが既知になります。この保護があっても、攻撃者は既知の値でカナリーを上書きし、不一致の値で制御情報を上書きして、特定のプロセッサの呼び出しからの戻り命令の直前に実行されるカナリーチェックコードを通過させる可能性があります。
ランダムカナリアは、攻撃者がその値を知ることを防ぐために、通常はエントロピー収集デーモンによってランダムに生成されます。通常、カナリアを読み取って悪用することは論理的に不可能または不自然です。カナリアは、知る必要のある者(この場合はバッファオーバーフロー保護コード)のみが知る安全な値です。
通常、ランダムなカナリアはプログラムの初期化時に生成され、グローバル変数に格納されます。この変数は通常、マップされていないページで埋められているため、RAMから読み取るバグを悪用するあらゆる手法で読み取ろうとすると、セグメンテーション違反が発生し、プログラムが終了します。ただし、攻撃者がカナリアの場所を知っている場合、またはプログラムにスタックから読み取らせることができれば、カナリアを読み取ることは可能です。
ランダムXORカナリアとは、制御データの全部または一部を用いてXOR演算によってランダムにスクランブルされたカナリアのことです。このようにして、カナリアまたは制御データが一度でも改ざんされると、カナリアの値は誤った値になります。
ランダムXORカナリアは、ランダムカナリアと同様の脆弱性を抱えていますが、カナリアを取得する「スタックからの読み取り」方法がやや複雑です。攻撃者は、保護を偽装するために必要な元のカナリアを再生成するために、カナリア、アルゴリズム、および制御データを取得する必要があります。
さらに、ランダムXORカナリアは、構造体内のバッファをポインタにオーバーフローさせてポインタを制御データの一部を指すように変更する特定の種類の攻撃から保護することができます。XORエンコーディングのため、制御データまたは戻り値が変更されると、カナリアは誤った値を示します。ポインタのおかげで、制御データまたは戻り値は、カナリアをオーバーフローさせることなく変更できます。
これらのカナリアは、制御データがポインタの改ざんによって変更されるのを防ぐものの、他のデータやポインタ自体は保護しません。特に関数ポインタは問題となります。関数ポインタはオーバーフローによってシェルコードが呼び出される可能性があるためです。
境界チェックは、割り当てられたメモリブロックごとに実行時境界情報を追加し、実行時にすべてのポインタをそれらと照合するコンパイラベースの手法です。C および C++ の場合、境界チェックはポインタ計算時[ 4 ]または逆参照時[ 5 ] [ 6 ] [ 7 ]に実行できます。
このアプローチの実装では、割り当てられたメモリブロックごとに記述する中央リポジトリ[ 4 ] [ 5 ] [ 6 ]、またはポインタと、ポインタが指す領域を記述する追加データの両方を含むファットポインタ[ 7 ]のいずれかを使用します。
タグ付け[ 8 ]は、コンパイラベースまたはハードウェアベース(タグ付きアーキテクチャが必要)の手法で、メモリ内のデータの型をタグ付けするもので、主に型チェックに使用されます。メモリの特定領域を実行不可としてマークすることで、データを格納するために割り当てられたメモリに実行可能なコードが含まれることを効果的に防止します。また、メモリの特定領域を割り当て不可としてマークすることで、バッファオーバーフローを防ぐこともできます。
歴史的に、タグ付けは高水準プログラミング言語の実装に使用されてきました。[ 9 ]オペレーティングシステムの適切なサポートがあれば、タグ付けはバッファオーバーフローの検出にも使用できます。[ 10 ] 例として、Intel、AMD、ARMプロセッサでサポートされているNXビットハードウェア機能があります。
スタック破壊保護は、 1997 年にStackGuardによって初めて実装され、1998 年のUSENIX Security Symposiumで発表されました。[ 11 ] StackGuard は、 GCC 2.7の Intel x86 バックエンドへのパッチのセットとして導入されました。StackGuard は 1998 年から 2003 年までImmunix Linux ディストリビューション向けに維持され、ターミネータ、ランダム、ランダム XOR カナリアの実装で拡張されました。StackGuard は、GCC 2003 Summit Proceedings [ 12 ]で GCC 3.x への組み込みが提案されましたが、これは実現しませんでした。
IBMは2001年から2005年にかけて、スタック破壊防止のためのGCCパッチであるProPoliceを開発しました。[ 13 ]これはスタックフレーム内のローカルポインタと関数引数の後にバッファを配置することでStackGuardのアイデアを改良したものです。これによりポインタの破損を防ぎ、任意のメモリ位置へのアクセスを防止しました。
Red Hat のエンジニアは ProPolice に問題があることを認識し、2005 年にスタック破壊保護を再実装して GCC 4.1 に組み込みました。[ 14 ] [ 15 ]この作業では-fstack-protector、脆弱な関数の一部のみを保護するフラグと、-fstack-protector-all必要かどうかに関わらずすべての関数を保護するフラグが導入されました。[ 16 ]
2012年、Googleのエンジニアは-fstack-protector-strongセキュリティとパフォーマンスのバランスをより良くするためにフラグを実装しました。[ 17 ]このフラグは、よりも多くの種類の脆弱な関数を保護します-fstack-protectorが、すべての関数を保護するわけではなく、よりも優れたパフォーマンスを提供します-fstack-protector-all。GCCバージョン4.9以降で利用可能です。[ 18 ]
Fedora Core 5 以降、すべてのFedoraパッケージは でコンパイルされており、 Fedora 20 以降はでコンパイルされています。 [ 19 ] [ 20 ] Ubuntuのほとんどのパッケージは6.10 以降でコンパイルされています。[ 21 ] Arch Linux のすべてのパッケージは2011 以降でコンパイルされています。 [ 22 ] 2014 年 5 月 4 日以降にビルドされたすべての Arch Linux パッケージは を使用しています。[ 23 ]スタック保護はDebianの一部のパッケージでのみ使用され、[ 24 ] FreeBSDベース システム 8.0 以降でのみ使用されます。[ 25 ]スタック保護はOpenBSD、[ 26 ] Hardened Gentoo [ 27 ]およびDragonFly BSDなど、特定のオペレーティングシステムで標準となっています。-fstack-protector-fstack-protector-strong-fstack-protector-fstack-protector-fstack-protector-strong
StackGuard と ProPolice は、関数ポインタにオーバーフローする自動割り当て構造体のオーバーフローを防ぐことはできません。ProPolice は少なくとも割り当て順序を並べ替えて、そのような構造体が関数ポインタより前に割り当てられるようにします。ポインタ保護のための別のメカニズムが PointGuard [ 28 ]で提案されており、 Microsoft Windowsで利用可能です。[ 29 ]
Microsoft のコンパイラ スイートは、バージョン 2003 以降、/GSコマンドライン スイッチを介してバッファ オーバーフロー保護を実装しており、バージョン 2005 以降はデフォルトで有効になっています。[ 30 ] /GS-を使用すると保護が無効になります。
スタック破壊防止はコンパイラフラグで有効にできます-qstackprotect。[ 31 ]
-fstack-protectorClang はGCC [ 32 ]と同じオプションと、同様にパフォーマンスへの影響が少ない強力な「セーフスタック」( -fsanitize=safe-stack ) システムをサポートしています。 [ 33 ] Clang には、AddressSanitizer ( ) [ 6 ] 、 UBSan ( ) [ 34 ] 、および非公式の SafeCode (LLVM 3.0 用に最後に更新) [ 35 ]という 3 つのバッファオーバーフロー検出器もあります。-fsanitize=address-fsanitize=bounds
これらのシステムは、パフォーマンスの低下、メモリのオーバーヘッド、検出されるバグの種類に関して、それぞれ異なるトレードオフを持っています。スタック保護は、OpenBSDを含む特定のオペレーティングシステムで標準となっています。[ 36 ]
IntelのCおよびC++コンパイラは、GCCやMicrosoft Visual Studioが提供するオプションと同様のオプションでスタック破壊保護をサポートしています。[ 37 ]
Fail-Safe C [ 7 ]は、ファットポインタとオブジェクト指向メモリアクセスに基づいて境界チェックを実行するオープンソースのメモリセーフな ANSI C コンパイラです。[ 38 ]
マイク・フランツェンによって考案されたStackGhostは、レジスタウィンドウのスピル・フィルルーチンに簡単な変更を加えることで、バッファオーバーフローの悪用をはるかに困難にするものです。これは、 Sun Microsystems SPARCアーキテクチャの独自のハードウェア機能である、遅延実行、オンスタック、インフレームのレジスタウィンドウ・スピル・フィルを利用して、リターンポインタの変更(エクスプロイトが実行パスを乗っ取る一般的な方法)を透過的に検出し、実行ファイルやソースコードを変更することなく、すべてのアプリケーションを自動的に保護します。パフォーマンスへの影響はごくわずかで、1%未満です。発生したgdbの問題は、2年後にマーク・ケッテニスによって解決され、この機能が有効になりました。この出来事の後、StackGhostのコードはOpenBSDオペレーティングシステムのSPARC版に統合(および最適化)されました。
{{cite web}}: CS1 maint: bot: 元の URL の状態が不明です (リンク)4.9に搭載されました。
{{cite web}}: CS1メンテナンス: アーカイブサービスは非推奨になりました (リンク)ProPolice
スタック保護拡張機能が付属しており
、これはデフォルトで有効になっています。
の強化版GCCは、明示的に無効にしない限り、デフォルトでスタックプロテクターを有効にします。
clang はデフォルトでスタック保護が有効になっており、
他のシステムでは
-fstack-protector-strongオプションに相当します。