(このページは Compiler Options Hardening Guide for C and C++ の参考訳です。)
このドキュメントは、C および C++のネイティブ(またはクロス)ツールチェーンを使用して、信頼性が高くセキュアなコードを提供するために貢献するコンパイラとリンカのオプションに関するガイドです。コンパイラオプションの強化の目的は、潜在的な攻撃や誤動作に対するセキュリティメカニズムを備えたアプリケーションバイナリ(実行可能ファイル)を生成することです。また、強化されたコンパイラオプションは、最新のオペレーティングシステム(OS) の既存のプラットフォーム セキュリティ機能とうまく統合するアプリケーションを生成するはずです。コンパイラコンパイラを効果的に設定することは、コンパイラーの警告、静的解析、デバッグインスツルメンテーションの強化など、開発中にもいくつかの利点をもたらします。
本ドキュメントは、以下の方々を対象としています:
- 組み込み機器、モノのインターネット機器、スマートフォン、パソコンなど、強化されたオプションで確実に動作するように、CまたはC++のコードを記述する人。
- Linuxディストリビューション、デバイスメーカー、ローカル環境用にCまたはC++をコンパイルする人など、実稼働環境で使用するためにCまたはC++のコードを構築する人。
このドキュメントでは、GNU Compiler Collection (GCC)とClang/LLVMの推奨オプションに焦点を当てます1。 将来、わたしたちは、Microsoft MSVCのような他のコンパイラもカバーするようにガイドを拡張することを目指します。
TL;DR: コンパイラのオプションは何を使うべきか?
GCCやclangのようなコンパイラでCやC++のコードをコンパイルする場合、コンパイル時に脆弱性を検出し、ランタイム保護メカニズムを有効にするために、これらのフラグをオンにします:
-O2 -Wall -Wformat=2 -Wconversion -Wtrampolines -Wimplicit-fallthrough \
-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 \
-D_GLIBCXX_ASSERTIONS \
-fstrict-flex-arrays=3 \
-fstack-clash-protection -fstack-protector-strong \
-Wl,-z,nodlopen -Wl,-z,noexecstack \
-Wl,-z,relro -Wl,-z,now \
-fPIE -pie -fPIC -shared
ほとんどのx86アーキテクチャ(amd64、i386、x32)でコンパイルする場合は、以下を追加します:
-fcf-protection=full
ARMでコンパイルする場合は、以下を追加します:
-mbranch-protection=standard
開発者はさらに-Werrorを使うべきですが、ソースコードを配布する際には省略することが望ましいです。-Werror は特定のツールチェーンベンダーとバージョンへの依存関係を作るからです。
各オプションの背景と詳細については、以下の議論を参照されたい。
開発者は、これらのオプションをデフォルトのオプションに設定するなどして、これらのオプションをすべて指定した状態でプログラムがコンパイルされ、自動テストに合格するようにしなければなりません。これらのオプションでプログラムがコンパイルできない場合は、バグとみなすことを開発者にお勧めします。本番用にプログラムをビルドする人は、プログラムが信頼されたデータだけを処理するのであれば、パフォーマンスを低下させるオプションのいくつかを省略することを選んでもかまいませんが、安全でないプログラムをデプロイしてすぐに間違ったことをするのは役に立たないということに注意してください。既存のプログラムは、これらのオプションの一部で動作するように、時間をかけて修正する必要があるかもしれません。
背景
なぜコンパイラオプションの強化をする必要があるのか?
悲しいことに、攻撃者は今日、私たちが毎日使っているソフトウェアを攻撃しています。多くのプログラミング言語のコンパイラには、コンパイル中に潜在的な脆弱性を検出したり、潜在的な攻撃に対する実行時の保護を挿入したりするオプションがあります。これらはどの言語でも重要ですが、CとC++では特に重要です。
CおよびC++プログラミング言語で書かれたアプリケーションは、メモリ セーフティ エラー、別名メモリエラーとして知られる一種のソフトウェア欠陥を引き起こす傾向があります。このクラスの欠陥には、バッファオーバーフロー、NULLポインタの再参照、use-after-freeエラーなどのバグが含まれます。CおよびC++の低レベルのメモリ管理には、ポインタ演算や直接メモリアクセスなどの操作のメモリ安全性を確保するための言語レベルの規定がないため、メモリエラーが発生する可能性があります。その代わりに、ソフトウェア開発者は、一般的な操作を実行する際に正しいコードを記述する必要がありますが、これは大規模では困難であることが証明されています。メモリエラーはメモリの脆弱性を引き起こす可能性があり、この脆弱性を悪用したランタイム攻撃によって、脅威者はコンピュータシステムに不正アクセスすることができます。Microsoftは、2006年から2018年にかけてのセキュリティ欠陥の70%がメモリ安全性の問題であることを発見しており2、Chromeチームも同様に、脆弱性の70%がメモリ安全性の問題であることを発見しています。3
ほとんどのプログラミング言語は、このような不具合をデフォルトで防止しています。いくつかの言語では、特別な状況下でこれらの保護を一時的に停止することができますが、それはプログラム全体ではなく、数行で使用するためのものです。CやC++のプログラムを他の言語で書き直そうという声もありますが、これには費用と時間がかかり、それなりのリスクもあり、今日では(特に一般的でないCPUでは)現実的でないこともあります。一般的な合意があったとしても、そのようなコードをすべて書き直すには何十年もかかるでしょう。したがって、欠陥が脆弱性になる可能性を減らすために、他の手段を講じることが重要です。コンパイラオプションを積極的に使用することで、脆弱性を検出したり、その実行時の影響に対抗したりすることができる場合があります。
ランタイム攻撃は、専用のプログラム実行ファイルを通じて悪意のあるプログラムの動作を実行する従来のマルウェアとは異なり、無害なプログラムに悪意のある動作をさせるという点で異なります。メモリの脆弱性を悪用するランタイム攻撃は、実行中のプログラムに悪意のあるコードを埋め込むなどして、脅威行為者がシステム上で存在感を示すための最初の攻撃ベクトルとして活用される可能性があります。
例えば、SEI CERT C4 やC++5などのセキュアコーディング標準や、プログラム解析は、アプリケーショ ンにメモリエラー(および他のソフトウェア欠陥)を積極的に持ち込まないことを目的としています。しかし、実際には、実稼働中のCおよびC++ソフトウェアにおけるメモリエラーを完全に根絶することは、ほぼ不可能であることが判明しています。
その結果、最新のオペレーティング システムは、潜在的なセキュリ ティ上の欠陥から保護するために、さまざまなランタイム メカニズムを導入しています。このような仕組みの主な目的は、潜在的に悪用可能なメモリ脆弱性を、攻撃者がそれを悪用してコード実行能力を獲得するのを防ぐ方法で軽減することです。軽減策を施しても、メモリエラーが発生すると、影響を受けるアプリケーションはクラッシュする可能性があります。ただし、代替手段がシステムの実行環境を侵害する場合には、そのような結果が望ましいと言えます。
OS が提供する保護メカニズムの恩恵を受けるためには、アプリケーションのバイナリをビルド時に準備し、軽減策と互換性を持たせる必要があります。通常、これはソフトウェアのビルド時に、コンパイラやリンカに対して特定のオプションフラグを有効にすることを意味します。
例えば、ありそうもない特定のエッジケースに対する潜在的なコンパイル上の問題や、特定のプログラム構造に追加される軽減策による性能上のオーバヘッドのために、追加的な構成や微調整が必要になる仕組みもあります。コンパイラのセキュリティ機能の中には、プログラムのデータフロー解析やヒューリスティックスに依存するものがあります。その結果、これらの機能によって実装される保護メカニズムは、必ずしも完全なカバレッジを提供するとは限りません。
このような問題は、オープンソースソフトウェア(OSS)コンパイラの古いバージョンに依存しているプロジェクトでは、さらに悪化します。一般に、Linuxディストリビューションに含まれる最新バージョンのコンパイラでは、セキュリティ軽減策がデフォルトで有効になっている可能性が高くなります。アップストリームのGCCプロジェクトで使用されているデフォルトでは、これらのセキュリティ緩和策の一部が有効になっていないことに注意してください。
ビルド時にコンパイラオプションの強化が見落とされたり軽視されたりすると、すでに配布されている実行ファイルに強化機能を追加することが不可能になる可能性があります。したがって、アプリケーションがどの軽減策をサポートすべきかを評価し、軽減策を有効にしないことでアプリケーションの防御態勢が弱くなる場合は、常に意識的に、十分な情報に基づいた決定を下すことが良い習慣となります。そのような運用が可能であることを確実にするために、現実的な範囲でできるだけ多くのオプションを用いてソフトウエアをテストするようにしてください。
組織によっては、強化ルールを選択することを要求しています。例えば、米国政府のNIST SP 800-218のプラクティスPW.6では、「コンパイル、インタプリタ、ビルドプロセスを構成して、実行ファイルのセキュリティを向上させる」ことを要求しています6。 カーネギーメロン大学(CMU)の「トップ10セキュアコーディングプラクティス」では、「コンパイラで利用可能な最高の警告レベルを使用してコードをコンパイルし、コードを修正することで警告を排除する」ことを推奨しています7。
コンパイラをコンパイルするときに何をすべきか?
C/C++コンパイラをコンパイルする場合、生成されるコンパイラのデフォルトオプションをセキュアオプションにしてください。たとえば、GCCをコンパイルする場合、–enable-default-pie (生成されたコンパイラ実行ファイルを使用するとき、デフォルトでフラグ-fPIE と -pie を有効にする)と–enable-default-ssp デフォルトで -fstack-protector-strongを有効にする)を使用してください。同様に、Linuxシステムでclangをコンパイルするときは、 CLANG_DEFAULT_PIE_ON_LINUXを設定してください(GCCをコンパイルするときの–enable-default-pieオプションと同じような効果があります)。
コンパイラオプションの強化では何ができないのか?
コンパイラオプションのハードニングは、銀の弾丸ではない。安全なソフトウェアを実現するために、セキュリティ機能や機能だけに頼るのでは不十分です。セキュリティは、システム全体の本質的な特性であり、すべての部分を適切に構築し、統合することに依存します。しかし、適切に使用すれば、セキュアなコンパイラオプションは、静的・動的解析、セキュアなコーディングの実践、ネガティブなテスト スイート、プロファイリング ツール、そして、最も重要なこととして、堅固な設計とアーキテクチャの一部としてのセキュリティ対策といった既存のプロセスを補完します。
推奨コンパイラオプション
このセクションでは、1)ソースコードに潜在的な欠陥があることを開発者に警告するコンパイル時チェックを有効にする(表1)、2)アプリケーションのメモリ脆弱性が悪用されたときに検出するように設計されたチェックなどのランタイム保護メカニズムを有効にする(表2)、コンパイラとリンカのオプションフラグに関する推奨事項を説明します。
表1と表2の推奨事項は、主にGCCとBinutilsツールチェーン、またはClang / LLVMツールチェーンのいずれかを使用するGNU/Linux環境におけるユーザー空間コードのコンパイルに適用可能であり、そのためこの文書に含まれている:
- Debian、Ubuntu、Red Hat、SUSE Linuxなど、主要なLinuxディストリビューションのビルド済みパッケージに広く導入され、デフォルトで有効になっている。
- GCCとClang / LLVMツールチェーンの両方でサポートされている。
- クロスプラットフォームで、(少なくとも)IntelとAMDの64ビットx86アーキテクチャとARMアーキテクチャの64ビット版(AArch64)でサポートされている。
歴史的な理由から、GCCコンパイラとBinutilsアップストリーム プロジェクトは、デフォルトで最適化やセキュリティ強化オプションを有効にしていません。GCCとBinutilsをソースからビルドするとき、デフォルト オプションの一部の部分を変更することができますが、GNU/Linuxディストリビューションで出荷されるツールチェーンで使われるデフォルトはさまざまです。ディストリビューションはまた、異なるデフォルトを持つ複数のバージョンのツールチェーンを出荷する可能性があります。その結果、開発者は、コンパイラとリンカのオプション フラグに注意を払い、最適化の必要性、警告とエラー検出のレベル、プロジェクトのセキュリティ強化に従って、それらを管理する必要があります。
あなたのシステムでGCCまたはClangが使用するデフォルト フラグを特定するには、cc -v <sourcefile.c> の出力を調べ、指定されたソース ファイルをビルドするためにコンパイラが使用する完全なコマンドラインを確認することができます。この情報には2つの主な目的があります:あなたのシステム上のコンパイラのセットアップを理解することと、ディストリビューションのメンテナが選択したオプションについての洞察を得ることです。さらに、オプションに関連する問題を診断したり、ソフトウェアのコンパイル中に発生した問題をトラブルシューティングしたりする際にも役立ちます。たとえば、あるオプションのフラグは、その出現順序に依存しています。 パラメータが複数回設定されている場合、通常、後に出現したものが優先されます。使用されるフラグの完全なリストを分析することで、順序に敏感なフラグ間の相互作用に起因する問題のトラブルシューティングが容易になります。
同様に、cc -O2 -dM -E – < /dev/null を実行すると、マクロで定義された定数の包括的なリストが出力されます。この出力は、特定のマクロ定義によって有効になっているコンパイラやライブラリの機能に関する問題のトラブルシューティングに役立ちます。
サードパーティベンダーからGCCを調達する場合、あなたのGCCのインスタンスは、あるデフォルトのフラグが有効あるいは無効になるようにあらかじめ設定されている可能性があることに注意することが重要です。これらのフラグはコンパイルされたコードのセキュリティに重大な影響を与えます。したがって、GCCがパッケージマネージャ、Linuxディストリビューション、あるいはそれ以外のものを通してソースされている場合、デフォルトのフラグを確認することが不可欠です。ツールチェーンのデフォルトに依存するのではなく、ビルドスクリプトやビルドシステムの設定で、必要なコンパイラフラグを明示的に有効にすることをお勧めします。Linuxディストリビューション用のパッケージを作成する場合、ディストリビューションのメンテナは、ビルドフラグを組み込むための独自の推奨方法をもっているかもしれません。そのような場合は、Debian8, Gentoo9, Fedora10, OpenSUSE11, or Ubuntu12などの対応するディストリビューションのドキュメントを参照してください。
一般的なコンパイラの設定は、システムヘッダからの警告を報告しません。GCCでは、-Wno-system-headers がデフォルトでオンになっており、clangも通常システムヘッダからの警告を抑制するからです。13. おそらく、サードパーティのインクルードファイルもシステムヘッダとしてマークして、警告のレベルを強く上げられるようにするといいでしょう。コマンドラインオプション--isystemで追加されたディレクトリは、GCC14 とClang15によってシステムヘッダーディレクトリとして扱われます。 Cmake 設定ファイルでは、include_directoriesのパラメータの前にSYSTEMを追加することで、これを行うことができます16。トレードオフがあります。システムヘッダやサードパーティライブラリの警告を消せば、アプリケーションに影響を与える脆弱性が隠されてしまうかもしれません。一方、警告を消さないようにすると、開発者が通常コントロールできない問題に労力を集中させることになり、CIジョブで--Werrorを使用する際の進捗を妨げたり、古いバージョンのサードパーティコードでのビルドをサポートするのが難しくなったりすることがよくあります。
表1のオプションで有効化されるコンパイル時チェックは、コンパイラが 生成するバイナリコードに影響を与えないため、パフォーマンスやその他の実行時 特性の面でトレードオフを生じることはありません。むしろ、ソースコードに潜在的な欠陥が見つかったことを知らせる警告(または -Werror オプションが有効な場合はエラー)を発行するだけです。
このような追加の警告が有効になっている場合、開発者はコンパイラによってフラグが立てられた根本的な問題を理解し、それに対処するために時間をかける必要があります。
表2のオプションは2種類に分類できます:
1) メモリエラーを検出することを目的としたランタイムチェックで、生成されるバイナリを拡張するようにコンパイラに指示するオプション、2) 生成されるバイナリがOSで強制される保護機構と互換性があることを保証するために、生成されるバイナリやコードのプロパティを調整するようにコンパイラに指示するオプション。
コンパイラが生成するバイナリに影響するため、表 2 に示したオプションのいずれかを有効にした場合の影響を検証するには、テストが不可欠です。以下に説明するコンパイラオプションの中には、ソフトウェアのパフォーマンスに影響するものもあります。しかし、このパフォーマンスへの影響は通常、コンテキストに固有であり、ほとんどの場合、影響は最小限か、メリットがオーバーヘッドを上回ります。どのような場合にパフォーマンスに重大な影響が出るかについては、このドキュメントの後半で、これらのオプションの詳細な説明をご覧ください。
パフォーマンスが重要な要素であるソフトウェアを扱う場合、開発者は、そのソフトウェアの特定のユースケースを考慮に入れて、より安全なオプションを有効にすることと、観察されたパフォーマンステストデータとのトレードオフを慎重に評価する必要があります。本番環境に変更を実装する前に、徹底的なベンチマークとテストを実施することが不可欠です。これによって、コンパイラのオプションがソフトウエアのパフォーマンスとセキュリティの両面にどのような影響を与えるかを知ることができます。高速に動作するが、敵に対して脆弱なシステムは、ユーザーにとって受け入れがたいものである可能性が高いことに留意してください。ベンチマークは、平均時間、ワーストケース時間、実行中のメモリ使用量など、関連するパフォーマンス特性を考慮する必要があります。さらに、特に組込みシステムでは、生成されるバイナリのサイズへの影響が懸念されることがあります。
Table 1: 厳密なコンパイル時チェックを有効にする推奨コンパイラオプション。
コンパイラフラグ | 以降でサポート | 説明 |
---|---|---|
-Wall -Wextra |
GCC 2.95.3 Clang 4.0 |
欠陥に関連することが多い構成要素に対する警告を有効にする |
-Wformat=2 | GCC 2.95.3 Clang 4.0 |
追加のフォーマット機能警告を有効にする |
-Wconversion -Wsign-conversion |
GCC 2.95.3 Clang 4.0 |
暗黙的な変換警告を有効にする |
-Wtrampolines | GCC 4.3 | 実行可能なスタックを必要とするトランポリンに関する警告を有効にする |
-Wimplicit-fallthrough | GCC 7 Clang 4.0 |
スイッチケースの異常終了を警告 |
-Werror -Werror=<warning-flag> |
GCC 2.95.3 Clang 2.6 |
コンパイラの警告をエラーにする (ソース配布ではなく開発で使用) |
Table 2: ランタイム保護メカニズムを有効にする推奨コンパイラオプション
コンパイラフラグ | 以降でサポート | 説明 |
---|---|---|
-D_FORTIFY_SOURCE=3 (requires -O1 or higher, may require prepending -U_FORTIFY_SOURCE) |
GCC 12.0 Clang 9.0.017 |
安全でない libc の使用やバッファオーバーフローをコンパイル時および実行時にチェックすることで、ソースを堅牢化する。堅牢化レベルによっては、パフォーマンスに影響を与える可能性があります |
-D_GLIBCXX_ASSERTIONS -D_LIBCPP_ASSERT |
libstdc++ 6.0 libc++ 3.3.0 |
C++標準ライブラリ呼び出しの前提条件チェック。パフォーマンスに影響を与える可能性がある |
-fstrict-flex-arrays=3 | GCC 13 Clang 16.0.0 |
構造体の末尾の配列が[]として宣言されている場合は、柔軟な配列 []とみなす |
-fstack-clash-protection | GCC 8 Clang 11.0.0 |
可変サイズスタック割り当ての有効性をランタイムにチェックできるようにします。パフォーマンスに影響を与える可能性があります |
-fstack-protector-strong | GCC 4.9.0 Clang 5.0.0 |
スタックベースのバッファオーバーフローのランタイムチェックを有効にします。パフォーマンスに影響を与える可能性があります |
-fcf-protection=full | GCC 8 Clang 7.0.0 |
リターン指向プログラミング(ROP)およびジャンプ指向プログラミング(JOP)攻撃に対抗するため、多くのx86アーキテクチャで制御フロー保護を有効にします |
-mbranch-protection=standard | GCC 9 Clang 8 |
AArch64のROP(Return Oriented Programming)攻撃やJOP(Jump Oriented Programming)攻撃に対抗するため、分岐保護を有効にします |
-Wl,-z,nodlopen | Binutils 2.10 | 共有オブジェクトへの dlopen(3)呼び出しを制限 |
-Wl,-z,noexecstack | Binutils 2.14 | スタックメモリを実行不可能とすることで、データ実行防止を有効にします |
-Wl,-z,relro -Wl,-z,now |
Binutils 2.15 | ロード時に解決された再配置テーブルエントリを読み取り専用としてマークします-Wl,-z,nowは起動時のパフォーマンスに影響を与える可能性があります。 |
>-fPIE -pie | Binutils 2.16 Clang 5.0.0 |
位置に依存しない実行ファイルとしてビルドします。32ビットアーキテクチャでのパフォーマンスに影響を与える可能性があります |
-fPIC -shared | < Binutils 2.6 Clang 5.0.017 |
位置に依存しないコードとしてビルドします。32ビットアーキテクチャでのパフォーマンスに影響を与える可能性があります |
欠陥に関連することが多い構成要素に対する警告を有効にする
コンパイラフラグ | 以降でサポート | 説明 |
---|---|---|
-Wall -Wextra |
GCC 2.95.3 Clang 4.0 |
欠陥に関連することが多い構成要素に対する警告を有効にする |
概要
警告とは、コンパイル時の診断メッセージのことで、本質的な誤りはないものの、リスクのあるプログラミング構成や、プログラミングエラーが発生した可能性を示すものです。
-Wall および -Wextra コンパイラーフラグは、あらかじめ定義されたコンパイル時警告のセットを有効にします。
–Wallセットに含まれる警告は、一般的に避けるのが簡単か、問題のあるコードを修正することで簡単に回避することができます。
-Wextraの一連の警告は、状況に応じたものであるか、回避するのが難しく、場合によっては必要な問題のある構造を示しています。
注意:-Wallオプションはその名前にかかわらず、可能なすべての警告診断を有効にするのではなく、あらかじめ定義されたサブセットを有効にします。-Wallと-Wextraコンパイラで有効になる特定の警告の完全なリストについては、それぞれGCC18とClang19のドキュメントを参照してください。
追加のフォーマット機能警告を有効にする
コンパイラフラグ | 以降でサポート | 説明 |
---|---|---|
-Wformat=2 | GCC 2.95.3 Clang 4.0 |
追加のフォーマット機能警告を有効にする |
概要
printf関数とscanf の呼び出しをチェックし、渡された引数が指定された書式文字列に適した型を持っていること、および書式文字列で指定された変換が意味をなしていることを確認します。
オプションの-Wformat=2形式は、以下のような追加チェックも可能になります:
- 書式文字列が文字列リテラルでなく、チェックできない場合の警告。ただし、format関数が書式引数を-Wformat-nonliteral)として受け取る場合を除きます。
- セキュリティ上の問題を引き起こす可能性のあるフォーマット関数の使用に関する警告(-Wformat-security)。
- 年号が2桁になるstrftimeフォーマットに関する警告(-Wformat-y2k)。
暗黙的な変換警告を有効にする
コンパイラフラグ | 以降でサポート | 説明 |
---|---|---|
-Wconversion -Wsign-conversion |
GCC 2.95.3 Clang 4.0 |
暗黙的な変換警告を有効にする |
概要
次のような値を変更する可能性のある暗黙の変換をチェックします:
- 実数と整数のデータ型間の変換
- 符号付きデータ型と符号なしデータ型の変換
- サイズの異なるデータ型間の変換
- C++におけるユーザー定義変換のオーバーロード解消の混乱
- C++で型変換演算子を使用しない変換:voidへの変換、同じ型への変換、基底クラスへの変換、参照への変換。
データの値が変更されるようなデータ型間の変換は、情報が省略されたり、予期せぬ値を生成するような方法で変換されたりする可能性があります。
結果の値がメモリ アクセスやセキュリティの決定を制御するコンテキストで使用される場合、危険な動作が発生する可能性があります。たとえば、整数の符号や切り捨てエラーによりバッファ オーバーフローが発生する可能性があります。
C++では、符号付き整数と符号なし整数の変換に関する警告は、-Wsign-conversionが明示的に有効になっていない限り、デフォルトで無効となっています。
実行可能なスタックを必要とするトランポリンに関する警告を有効にする
コンパイラフラグ | 以降でサポート | 説明 |
---|---|---|
-Wtrampolines | GCC 4.3 | 実行可能なスタックを必要とするトランポリンに関する警告を有効にする |
概要
コンパイラが、スタックの仮想メモリー保護(実行不可能なスタック)を妨害する可能性のあるネストされた関数へのポインタに対してトランポリンを生成するかどうかをチェックします。
トランポリンとは、ネストされた関数のアドレスが取得されたときに、実行時にスタック上に作成される小さなデータやコードのことで、ネストされた関数を間接的に呼び出すために使用されます。
64ビットx86を含むほとんどのターゲットアーキテクチャでは、トランポリンはコードで構成されているため、プログラムが正しく動作するためにはスタックを実行可能にする必要があります。これは、コードインジェクション攻撃(セクション2.10参照)を防ぐために、全ての主要なオペレーテ ィングシステムで使われている、実行不可能なスタックの軽減策を妨げます。
switch文の暗黙のフォールスルーに関する警告
コンパイラフラグ | 以降でサポート | 説明 |
---|---|---|
-Wimplicit-fallthrough | GCC 7 Clang 4.0 |
switch文の暗黙のフォールスルーに関する警告 |
概要
switch文の中で暗黙のフォールスルーが発生した場合に警告を出します。
CおよびC++のswitchは、(break、return、gotoなどが前にない限り)caseブロックが次のcaseブロックにフォールスルーすることを許します。これは、C言語では設計上の欠陥と広くみなされています。というのも、よくある間違いは、意図していないのにフォールスルーが発生してしまうことだからです20。
この警告フラグは、フォールスルーが発生すると、それが意図的であると特別にマークされていない限り 警告を発します。Linuxカーネルプロジェクトではこのフラグが使われており、多くのバグの発見と修正に役立てられました21。
この警告フラグはパフォーマンスには影響しません。しかし、フォールスルーが意図的な場合もあります。このフラグは、フォールスルーが意図的である(まれな)ケースについて、警告を抑制するようにソースコードに注釈を付けることを開発者に求めます。もちろん、この注釈は意図的な場合にのみ使用してください。C++17(またはそれ以降)のコードでは、標準に従って [[fallthrough]]属性を使用する必要があります(後に;を付けることを忘れないでください)。
C17標準22は、意図的なフォールスルーをマークするメカニズムを提供していません。さまざまなツールは、属性やコメントなど、さまざまな形でフォールスルーをマークするメカニズムをサポートしています23。Linuxカーネルバージョン5.10以降で使用されている、意図的なフォールスルーをマークするための移植可能な方法は、関連するツール(コンパイラなど)のメカニズムに合わせて、意図的なフォールスルーをマークするためのfallthroughというキーワードのようなマクロを定義することです
#if __has_attribute(__fallthrough__)
# define fallthrough __attribute__((__fallthrough__))
#else
# define fallthrough do {} while (0) /* fallthrough */
#endif
コンパイラの警告をエラーにする
コンパイラフラグ | 以降でサポート | 説明 |
---|---|---|
-Werror -Werror=<warning-flag> |
GCC 2.95.3 Clang 2.6 |
コンパイラの警告をエラーにする |
概要
コンパイラが、すべてまたは特定の警告診断をエラーとして扱うようにします。
開発者は-Werrorを使うべきだが、ソースコードを配布するときには省略することが勧められています。-Werrorを使うと、特定のツールチェーンベンダーとバージョン24に依存することになるからです。このようなツールチェーンの依存関係、つまり、プロジェクトがどのコンパイラバージョンで動作するのかを、プロジェクトのドキュメントに明確に記述するか、コンテナレシピなどでビルド環境を完全に把握する必要があります。
ブランケット-Werrorゼロ警告ポリシーを実装するために使用できますが、そのようなポリシーはCIレベルで実施することもできます。CIベースの警告ゼロまたは制限付き警告ポリシーは、上で説明した理由と、コンパイラの警告以外にも拡張できることから、しばしば推奨さ れます。たとえば、静的解析ツールからの警告を含めたり、FIXMEやTODOコメントが見つかったときに警告を生成したりすることができます。
選択的形式: -Werror=<warning-flag>は、一律にゼロ警告ポリシーを導入することなく、警告をエラーとして制御するために使用できます。これは、ある種の望ましくない構成や欠陥が生成されたビルドに混入しないようにするために役立ちます。
例えば、開発者は、OSの防御メカニズムへの干渉(例:-Werror=trampolines)、未定義の動作(例:-Werror=return-type)、ソフトウェアの弱体化エラーに関連する構造(例:-Werror=conversion)を示す警告を促進することを決定することができます。
安全でない libc の使用とバッファオーバーフローに対するソースの堅牢化
コンパイラフラグ | 以降でサポート | 説明 |
---|---|---|
-D_FORTIFY_SOURCE=1 | GCC 4.0 Clang 5.0.0 |
安全でない libc の使用やバッファオーバーフローをコンパイル時および実行時にチェックし、ソースを堅牢化する。 |
-D_FORTIFY_SOURCE=2 (requires -O1 or higher) |
GCC 4.0 Clang 5.0.017 |
-D_FORTIFY_SOURCE=1でカバーされるチェックに加えて、C標準に準拠していても安全でないコードをトラップする。 |
-D_FORTIFY_SOURCE=3 (requires -O1 or higher) |
GCC 12.0 Clang 9.0.017 |
-D_FORTIFY_SOURCE=2と同じチェックが行われるが、まれにパフォーマンスに影響を与える可能性のある呼び出しが大幅に強化される。 |
概要
_FORTIFY_SOURCEマクロは、GNU Cライブラリ(glibc)の一連の拡張を有効にするもので、多くの関数のエントリーポイントでチェックを行い、安全でない動作に遭遇した場合に直ちに実行を中止することができます。このチェックの主な特徴は、これらの関数呼び出しに渡されるオブジェクトを検証し、呼び出しがバッファオーバーフローを引き起こさないようにすることです。これは、コンパイラがコンパイル時に保護オブジェクトのサイズを計算できることに依存しています。これらの関数の完全なリストは、GNU Cライブラリのマニュアル24に記載されています25:
memcpy, mempcpy, memmove, memset, strcpy, stpcpy, strncpy, strcat, strncat, sprintf, vsprintf, snprintf, vsnprintf, gets
_FORTIFY_SOURCEメカニズムには3つの動作モードがあります:
- -D_FORTIFY_SOURCE=1: 保守的で、コンパイル時および実行時のチェックが可能で、プログラムの(定義された)動作を変更することはありません。オーバーフローのチェックは、コンパイラがプロテクトオブジェクトのコンパイル時定数サイズを推定できる場合に有効になります。
- -D_FORTIFY_SOURCE=2:C標準に準拠していても安全でない可能性のある動作を検出します。特定のプログラミング構文を禁止することで、プログラムの 動作に影響を与える可能性があります。このようなチェックの例として、%n書式指定子を読み取り専用の書式文字列に制限することが挙げられます。
- -D_FORTIFY_SOURCE=3: -D_FORTIFY_SOURCE=2 でカバーされるチェックと同じですが、コンパイラがコンパイル時定数だけでなく、式として保護オブジェクトのサイズを推定できる場合でもチェックが有効になります。
_FORTIFY_SOURCEチェックの恩恵を受けるには、以下の条件を満たす必要があります:
- アプリケーションは-O1 以上の最適化を使用して構築する必要があります。 少なくとも -O2 を推奨します。
- コンパイラは、コンパイル時にデスティネーションバッファのサイズを推定できなければなりません。これは、アプリケーションやライブラリによって、GCC や Clang25 がサポートする関数属性拡張を使用することで容易になります26。
- アプリケーションコードは、前述の関数のglibcバージョンを使用しなければなりません(標準ヘッダ、例えば<stdio.h> や <string.h>に含まれています)。
_FORTIFY_SOURCE によって追加されたチェックが実行時に安全でない動作を検出した場合、エラー メッセージが表示され、アプリケーションが終了します。
FORTIFY_SOURCE のデフォルト モードは、特定のコンパイラに対して事前定義されている場合があります。例えば、Ubuntu 22.04に同梱されているGCCはデフォルトでFORTIFY_SOURCE=2を使用しています。デフォルトと異なるFORTIFY_SOURCEモードがコマンドラインで設定されると、コンパイラーはFORTIFY_SOURCEマクロの再定義について警告します。これを避けるには、希望する値を設定する前に -U_FORTIFY_SOURCE で定義済みモードを解除することができます。
パフォーマンスへの影響
_FORTIFY_SOURCE=1 および _FORTIFY_SOURCE=2 はどちらも、実行時パフォーマン スに与える影響はごくわずか(~0.1%)と予想されます。
どのような場合に使用してはいけないのか?
_FORTIFY_SOURCEglibcに依存するすべてのアプリケーションに推奨され、広くデプロイさ れるべきです。主要なLinuxディストリビューションのほとんどのパッケージは、少なくとも_FORTIFY_SOURCE=2を有効にしており、中には__FORTIFY_SOURCE=3を有効にしているものもあります。_FORTIFY_SOURCEが既存のアプリケーションを壊す可能性がある状況がいくつかあります:
- 強化されたglibc関数コールがアプリケーションのパフォーマンスプロ ファイルにホットスポットとして表示される場合、_FORTIFY_SOURCEがパフォーマンスに悪影 響を与える可能性があります。これは一般的なものでも、広く見られる速度低下26 でもありませんが、このオプションによる速度低下が観察された場合、覚えておく価値があります。
- GNU拡張の柔軟な配列メンバをstructsで使用するアプリケーション27 は、オブジェクトが実際よりも小さいとコンパイラを混乱させ、結果として偽のアボートを引き起こす可能性があります。これを安全に解決するには、これらの用途をC99フレキシブル配列に移植することですが、それが不可能な場合(例えば、C99フレキシブル配列をサポートしていないコンパイラを サポートする必要がある場合など)、_FORTIFY_SOURCE保護をダウングレードするか無効にする 必要があるかもしれません。
その他の考慮事項
- malloc_usable_size28関数が報告する追加サイズを使用するために malloc_usable_size関数を誤って使用したアプリケーションは、実行時にアボートする可能性があります。malloc_usable_sizeによって報告される追加サイズは一般的に逆参照が安全ではなく、診断用途にのみ28使用されるため、これはアプリケーションのバグです。多くのLinuxシステムでは、ELFバイナリをreadelf -Ws <path>を実行し、malloc_usable_size@GLIBC29を検索することで、これらの不正な使用を検出することができます。malloc_usable_sizeを避けることができない場合、reallocを呼び出してブロックを使用可能なサイズにリサイズし、_FORTIFY_SOURCE=3の恩恵を受けることができます。
C++標準ライブラリ呼び出しの前提条件チェック
コンパイラフラグ | 以降でサポート | 説明 |
---|---|---|
-D_GLIBCXX_ASSERTIONS | libstdc++ 6.0 | (libcstdc++ を使用する C++ のみ) libstdc++ 呼び出しの前提条件チェック |
概要
GCCのC++標準ライブラリ実装(libstdc++)は、C++文字列やコンテナの境界チェック、スマートポインタを再参照するときのヌルポインタチェックなど、C++標準ライブラリ呼び出しに対する実行時前提条件チェックを提供します。
これらの前提条件チェックは、libstdc++を呼び出すC++コードをコンパイルするときに、-D_GLIBCXX_ASSERTIONSマクロを定義することによって有効にすることができます30。
パフォーマンスへの影響
C++標準ライブラリのほとんどの呼び出しには前提条件があります。いくつかの前提条件は一定時間でチェックできますが、他の前提条件はよりコストがかかります。-D_GLIBCXX_ASSERTIONSで有効になるチェックは軽量31、 つまり一定時間でのチェックを意図していますが、正確な動作は標準ライブラリのバージョンによって異なる場合があります。libstdc++ のバージョンによっては、-D_GLIBCXX_ASSERTIONS マクロが性能に少なからぬ影響を与えることがあります。最大6%の速度低下が報告されています32。
どのような場合に使用してはいけないのか?
-D_GLIBCXX_ASSERTIONSは、信頼できないデータを扱う可能性のあるC++アプリケーションや、テスト中のC++アプリケーションに推奨されます。
このオプションは、完全に信頼されたデータのみを扱う本番アプリケーションのセキュリティには不要です。
コンパイラフラグ | 以降でサポート | 説明 |
---|---|---|
-fstrict-flex-arrays=3 | GCC 13 Clang 16.0.0 |
構造体の末尾にある配列は、[]として宣言された場合のみ、柔軟な配列と見なします |
-fstrict-flex-arrays=2 | GCC 13 Clang 15.0.0 |
末尾の配列は、[]または[0]として宣言された場合のみ、柔軟な配列とみなします |
-fstrict-flex-arrays=1 | GCC 13 Clang 15.0.0 |
末尾の配列は、[]、 [0]または[1]として宣言された場合のみ、柔軟な配列とみなします |
-fstrict-flex-arrays=0 | GCC 13 Clang 15.0.0 |
末尾の配列(構造体の末尾)をフレキシブル配列とみなします(デフォルト) |
概要
コンパイラが末尾の配列と判断したものを変更します。レベルが高くなると、コンパイラーは末尾配列のサイズをより厳密に尊重するようになります33 (これは境界チェックに影響します)34。
デフォルトでは、GCCとClangは、_FORTIFY_SOURCE35で使用される__builtin_object_size()計算の目的のために、宣言されたサイズに関係なく、すべての末尾の配列(最後のメンバまたは構造体として配置される配列)を柔軟なサイズの配列として扱います。これは、常に無効にする必要のない様々な境界チェックを無効にします。例えば、与えられたデフォルトの設定では次のようになります。:
struct trailing_array {
int a;
int b;
int c[4];
};
struct trailing_array *trailing;
__builtin_object_size(trailing->c, 1)の値は -1 (“unknown size”)であり、境界チェックを行ないません。このデフォルト動作の根拠は、末尾の配列を(宣言されたサイズに関係なく)可変サイズとして扱うことができる「構造体ハック」イディオムを許容するためです34。
-fstrict-flex-arraysオプションは、コンパイラが末尾の配列メンバのサイズをより厳密に考慮するようにします。これにより、_FORTIFY_SOURCE や -fsanitize=bounds36などのインスツルメンテーションによって追加される境界チェックで、末尾の配列のサイズを正しく判断できるようになります。
そのトレードオフとして、任意のサイズの末尾配列の「構造体ハック」に依存するコードは、結果として壊れる可能性があります37。そのようなコードは、特定の境界を持たないことを明示するように修正する必要があるかもしれません。
C99 の柔軟な配列記法[]は、配列の境界が具体的に記述されていない場合の標準的な記法です。しかし、コードベースによっては、GCCのゼロ長配列拡張[0]を使用したり、柔軟な配列メンバを示すために1サイズの配列[1]を使用するコードベースもあります。オプション値1 と 2 このような場合に[0] と [1] を使用するプログラムが、ソースコードを修正することなく、ある程度の境界チェックを行えるようにするために作成されました38。
このガイドでは、非標準の[0] や 誤解を招きやすい[1]ではなく、標準のC99フレキシブル配列記法[]を使用し、-fstrict-flex-arrays=3を使用してこのような場合の境界チェックを改善することをお勧めします。この場合、フレキシブル配列に[0]を使用しているコードは、代わりに []を使用するように修正する必要があります。フレキシブル配列に [1]を使用しているコードは、 []を使用するように修正し、さらにoff-by-oneエラーを排除するために広範囲に修正する必要があります。 [1]を使用することは、誤解を招く39だけでなく、エラーが発生しやすくなります。フレキシブル配列を示すために[1]を使用している既存のコードには、現在 off-by-one エラー40が発生している可能性があるので注意してください。
一度配置すれば、構造体の末尾で宣言された固定サイズの配列で境界チェックを行うことができます。さらに、ソースコードは、フレキシブル配列が使用されているケースを標準的な方法で明確に示します。必要な変更が行われれば)通常、このオプションによる性能の大きなトレードオフはありません。
可変サイズのスタック割り当ての有効性をランタイムにチェックできるようにする
コンパイラフラグ | 以降でサポート | 説明 |
---|---|---|
-fstack-clash-protection | GCC 8 Clang 11.0.0 |
可変サイズのスタック割り当ての有効性をランタイムにチェックできるようにする |
-param stack-clash-protection-guard-size=<gap size> | GCC 8 Clang 11.0.0 |
インストルメンテーションされたコードのプローブ粒度を決定するために使用されるスタックガード ギャップサイズを設定します |
概要
スタッククラッシュ保護は、オペレーティングシステムのスタックガードギャップを迂回しようとする攻撃を軽減します。スタックガードギャップは、隣接するメモリ領域を破壊するためにスタックをオーバーフローさせるシーケンシャル スタックオーバーフローからプロセスを保護するLinuxカーネルのセキュリティ機能です。
スタックガードギャップがバイパスされるのを防ぐため、スタック上に新しく割り当てを行うたびに、スタックガードギャップが存在する場合、新しく割り当てられたメモリをプローブする必要があります。スタッククラッシュ プロテクションは、1 回の割り当てがスタックガード ギャップサイズより大きくならないことを保証し、コンパイラは大きな割り当てを一連の小さなサブ割り当てに変換します。さらに、プローブが介在しない限り、一連のサブ割り当てがスタックガード ギャップのサイズを超えないようにします。
プローブ命令には、暗黙的なものと明示的なものがあります。暗黙的プローブは、x86やx86_64のコール命令がリターンアドレスをスタックにプッシュするときなど、アプリケーションのコードの一部として自然に発生します。暗黙的なプローブには、追加の性能コストは発生しません。一方、明示的プローブは、コンパイラが追加で発行するプローブ命令で構成されます。
パフォーマンスへの影響
関数が一度にスタック空間メモリの最大スタックガードギャップサイズを割り当てるアプリケーションでは、スタッククラッシュプロテクションによる性能への悪影響はありません。
しかし、スタックガードギャップサイズを超える大規模な割り当てを行うアプリケーショ ンでは、スタック衝突保護が性能低下を引き起こす可能性があります。パフォーマンスへの影響は、大規模な割り当てのサイズと必要な明示的なプローブの数によって変化します。パフォーマンスの低下は、vm.heap-stack-gap sysctl parameter)パラメーターによって制御される Linux のスタックガードギャップサイズを大きくし、対応する -param stack-clash-protection-guard-size を指定してアプリケーションをコンパイルすることで軽減できます。この値を大きくすると、明示的なプローブの数は減りますが、カーネルのガードギャップよりも大きな値を設定すると、スタッククラッシュスタイルの攻撃に対してコードが脆弱になります。
vm.heap-stack-gapはギャップをページサイズの倍数で表すのに対し、stack-clash-protection-guard-sizeは2のべき乗のバイト数で表すことに注意してください。したがって、x86でvm.heap-stack-gap=256(256 * 4KiB = 1MiBギャップ)の場合、対応するstack-clash-protection-guard-sizeは20(2^20 = 1MiBギャップ)となります。
スタックベースのバッファオーバーフローのランタイムチェックを有効にする
コンパイラフラグ | 以降でサポート | 説明 |
---|---|---|
-fstack-protector-strong | GCC 4.9.0 Clang 5.0.0 |
強力なヒューリスティックを使用したスタックベースのバッファオーバーフローのランタイムチェックを有効にする |
-fstack-protector-all | GCC Clang |
すべての関数でスタックベースのバッファオーバーフローのランタイムチェックを有効にする |
-fstack-protector –param=ssp-buffer-size=<n> |
GCC Clang |
n バイト以上の文字配列を持つ関数で、スタックベースのバッファオーバーフローのランタイムチェックを有効にする |
概要
スタックプロテクターは、実行時にプログラムスタックに割り当てられたバッファのオーバーフロー(俗に「スタックスマッシュ」と呼ばれる)を検出するために、コンパイラが生成するコードを実装します。
検出は、関数プロローグのスタックフレームへのカナリア値の挿入に基づいて行われます。カナリアは関数のエピローグで参照値と照合されます。もし両者が異なる場合、ランタイムは__stack_chk_fail()を呼び出し、問題のあるアプリケーションを終了します。
これにより、スタックに格納されたリターンアドレスを破壊して任意のコードを実行させる可能性のある、コントロールフローハイジャック攻撃の可能性が軽減します。
64ビットArm(Aarch64)プロセッサ用にコンパイルする場合、GCCの古いバージョンは、可変長配列やalloca()を使って割り当てられたバッファのような、動的にサイズ変更されるローカル変数のオーバーフローを検出しなかったり、防御しなかったりする可能性があります41Aarch64 用の GCC ベースのツールチェーンのユーザは、すべてのローカル変数をセーブされたレジスタの下に置き、それらの間にスタックプロテクターカナリアを置く代替のスタックフレームレイアウトをサポートする、GCC 7 以降の最新バージョンを確実に使用する必要があります42。
64ビットArm(Aarch64)プロセッサ用にコンパイルする場合、GCCのバージョンによっては43、可変長配列やalloca()を使って割り当てられたバッファのような、動的にサイズ変更されるローカル変数のオーバーフローを検出したり防御したりできないことがあります41。Aarch64 用の GCC ベースのツールチェーンのユーザーは、それに依存する前に、すべてのローカル変数をセーブされたレジスタの下に置き、それらの間にスタックプロテクターカナリアを配置する代替のスタックフレームレイアウトをサポートしているかどうかを判断する必要があります42.
パフォーマンスへの影響
スタックプロテクターは、コンパイル時に実行時チェックを行う関数を決定するために使用される3つの異なるヒューリスティックをサポートしています:
- -fstack-protector-strong44: 以下のような関数を使用します。
- 代入の右辺または関数の引数の一部として、ローカル変数のアドレスを取ります。
- 型や長さに関係なく、ローカル配列を確保します。
- 配列の型や長さに関係なく、配列を含むローカル構造体またはユニオンを確保します。
- 明示的なローカルレジスタ変数があります。
- -fstack-protector:alloca() を呼び出す関数、または n バイト以上のサイズの文字配列を割り当てる関数をインスツルメンテーションする。インスツルメンテーションのしきい値は、 –param=ssp-buffer-size=n オプションで調整できます(デフォルトは 8 バイト)。
- -fstack-protector-all: すべての機能を計測する。
パフォーマンスのオーバーヘッドは、インストルメント化された関数の数と、インストルメント化された関数が実行時にアクティブになる頻度に依存します。 -fstack-protector-strongを有効にすると、関数のカバレッジと性能のバランスが最もよくなるため、これを推奨します。古いバージョンのコンパイラを使用しているプロジェクトでは、-fstack-protector-all または -fstack-protectorのように、より厳しいしきい値を設定した–param=ssp-buffer-size=4 を使用することができます。
どのような場合に使用してはいけないのか?
-fstack-protector-strong は、従来のスタック動作をするすべてのアプリケーションに推奨されます。スタックのレイアウトを前提とした手書きのアセンブラ最適化を使用するアプリケーションは、スタックプロテクター機能と互換性がない可能性があります。
制御フローの整合性チェックの実装
コンパイラフラグ | 以降でサポート | 説明 |
---|---|---|
-fcf-protection=full | GCC 8 Clang 7.0.0 |
リターン指向プログラミング(ROP)およびジャンプ指向プログラミング(JOP)攻撃に対抗するため、多くのx86アーキテクチャで制御フロー保護を有効にします |
-mbranch-protection=standard | GCC 9 Clang 8 |
AArch64のROP(Return Oriented Programming)攻撃やJOP(Jump Oriented Programming)攻撃に対抗するため、分岐保護を有効にします |
概要
リターン指向プログラミング(ROP)は、(バッファオーバーフローのような)最初の破壊を利用して、別の命令シーケンスを実行する間接ジャンプを行います。これは既存のコードが悪用されることが多いため、しばしば「コード再利用攻撃」と呼ばれます。対策としては、ジャンプアドレスとリターンアドレスが正しいことを確認することです。これは完全な解決策でありませんが、攻撃を行いにくくします。
パフォーマンスへの影響
パフォーマンスへの影響はありますが、ハードウェアアシスタンスにより、通常は軽度です。-fcf-protection=fullフラグはインテルのControl-Flow Enforcement Technology (CET)45を有効にし、シャドウスタック(SHSTK)と間接ブランチトラッキング(IBT)を導入します。-mbranch-protection=standardフラグは、AArch64の同様の保護を呼び出します。clangでは、-mbranch-protection=standardは -mbranch-protection=bti+pac-retと同等で、AArch64のブランチターゲット識別(BTI)とキーAを使ったポインタ認証(pac-ret)46を呼び出します。
共有オブジェクトへのdlopen呼び出しを制限する
コンパイラフラグ | 以降でサポート | 説明 |
---|---|---|
-Wl,-z,nodlopen | Binutils 2.10 | 共有オブジェクトへの dlopen(3)呼び出しを制限する |
概要
共有オブジェクトを構築する際にリンカに渡されるnodlopen オプションは、結果として生成されるオブジェクトを dlopen(3) 呼び出しでは利用できないものとしてマークします。これは、共有オブジェクトをロードし操作する攻撃者の能力を低下させるのに 役立ちます。新しいオブジェクトをロードしたり、既に存在する共有オブジェクトをプロセス内で複製したりすることは、実行時悪用における攻撃チェーンの一部を構成する可能性があります。
nodlopenの制限は、オブジェクトの.dynamicセクションタグに DF_1_NOOPENフラグを設定することに基づいています。制限された呼び出しの実施は、dlopen(3)が呼び出されたときに libc の内部で行われるので、攻撃者は、1) ディスク上のオブジェクトファイルを変更する能力を持っている場合、オブジェクトに埋め込まれたタグを操作する、あるいは、2) dlopen(3)をバイパスして、攻撃者が制御するコード (例えば、シェルコードの一部やリターン指向プログラミングのガジェット) を通して共有オブジェクトをロードする、ことによってチェックをバイパスすることが可能です。しかしながら、リンク時に設定される dlopen(3) の制限は、攻撃者が任意のコード実行能力を得る前に制限するのに有用です。
パフォーマンスへの影響
なし。共有オブジェクトをdlopen(3)に制限されたものとしてマークしても、実行時のパフォーマンスには影響しません。
どのような場合に使用してはいけないのか?
アプリケーションによっては、従来のダイナミックリンクに頼らずに、dlopen(3) を使って直接ライブラリのロードを管理することが望ましい場合があります。そのような状況には以下のようなものがあります:
- ロードするアプリケーションプラグインの選択
- 特定のCPUに最適化されたライブラリのバージョンを選択します。例えば、環境ごとに異なる数学演算の実装を提供する数学ライブラリなどで活用されます。
- 異なるベンダーによるAPIの実装を選択する。
- アプリケーションの起動時間を短縮するために、共有ライブラリのロードを遅延させます。(セクション2.11の遅延バインディングも参照)。
nodlopenは共有オブジェクトを操作するためにdlopen(3) に依存しているアプリケーションを妨害するので、そのような機能に依存しているアプリケーションでは使用できません。
データ実行防止機能を有効にする
コンパイラフラグ | 以降でサポート | 説明 |
---|---|---|
-Wl,-z,noexecstack | Binutils 2.14 | スタックメモリを実行不可能とすることで、データ実行防止機能を有効にします |
概要
すべての主要な最新のプロセッサ アーキテクチャには、スタックやヒープなどの特定のメモリ領域を実行不可能としてマークする機能を OS に提供するメモリ管理プリミティブが組み込まれています。たとえば、AMD の「非実行」(NX) ビットや Intel の「実行無効」(XD)ビットなどです。 このメカニズムにより、実行時攻撃中にスタックまたはヒープが悪意のあるコードの挿入に使用されるのを防ぎます。
-Wl,-z,noexecstack オプションは、対応するプログラムセグメントを非実行可能としてマークするようリンカーに指示するもので、これにより、プログラム実行ファイルがメモリーにロードされるときに、OSがメモリーアクセス権を正しく設定できるようになります。
しかし、ネストされた関数のアドレスを取得する(ISO標準CのGNU C拡張)など、言語レベルのプログラミング構成には、リンカがスタックセグメントを実行不可能として正しくマークするのを妨げるような、特別なコンパイラ処理が必要なものもあります47。
その結果、 -Wl,-z,noexecstack オプションは、言語構成要素がスタック仮想メモリー保護に干渉するかどうかを示す適切な警告フラグ(利用可能な場合は-Wtrampolines)と組み合わせた場合に、最も効果的に機能します。
パフォーマンスへの影響
なし。スタックやヒープを実行不可能とマークしても、実行時のパフォーマンスには影響しません。
どのような場合に使用してはいけないのか?
マネージドバイトコードやインタプリタ言語ランタイムのジャストインタイム(JIT)コンパイルを活用するアプリケーションは、JITコードを実行するために、ヒープの一部など特定の書き込み可能なメモリ領域を実行可能なままにする必要がある場合があります。
このようなアプリケーションには、潜在的に悪意のあるJITコードからアプリケーションのメモリ整合性を保護するサンドボックス技術が必要です。
このようなアプリケーションでは、悪意のあるコードインジェクションに対する保護に加えて、投機的実行サイドチャネル47に対する特別な軽減策も必要となる場合があります48。
ロード時に解決された再配置テーブルエントリを読み取り専用としてマークする
コンパイラフラグ | 以降でサポート | 説明 |
---|---|---|
-Wl,-z,relro -Wl,-z,now |
Binutils 2.15 | ロード時に解決された再配置テーブルエントリーを読み取り専用としてマークする |
“読み取り専用再配置” (RELRO)は、動的リンカ/ローダ(ld.so)によって解決された後、再配置テーブルのエントリを読み取り専用としてマークします。再配置とは、ld.soによって実行される処理で、未解決のシンボリック参照を、対応するメモリ内オブジェクトの適切なアドレスに接続することです。
再配置を読み取り専用にすることで、グローバルオフセットテーブル(GOT)エントリを破壊してプログラムの実行を乗っ取ったり、意図しないデータアクセスを引き起こしたりするランタイム攻撃を軽減することができます。このような攻撃を総称して、GOT オーバーライト攻撃または GOT ハイジャックと呼びます。
RELROは、部分RELROまたは完全RELROの2つのモードのいずれかでインスタンス化できます。完全な RELRO は、GOT の上書き攻撃を効果的に軽減するために必要であり、部分的な RELRO では十分 ではありません。
部分RELRO (-Wl,-z,relro) は、ランタイムローダによる初期化後、特定のELFセクションを読み取り専用にマークします。これには、.init_array, .fini_array, .dynamic, および.gotの非PLT部分が含まれます。しかし、部分RELROでは、GOTの補助手続きリンケージ部分 (.got.plt)は、遅延バインディングを容易にするために、書き込み可能なままになっています。
完全RELRO (-Wl,-z,relro -Wl,-z,now)は、遅延バインディングを無効にします。これにより、ld.soはアプリケーション起動時にGOT全体を解決し、GOTのPLT部分も読み取り専用としてマークできるようになります。
パフォーマンスへの影響
遅延バインディングは、アプリケーションのライフタイムを通じてシンボル解決処理を分散させることで、アプリケーションの起動時間を短縮することを主な目的としているため、完全なRELROを有効にすると、多数の動的依存関係を持つアプリケーションの起動時間が長くなる可能性があります。パフォーマンスへの影響は、動的にリンクされる関数の数に比例します
どのような場合に使用してはいけないのか?
起動時間に対するパフォーマンスの影響に敏感なアプリケーションは、完全な RELRO による起動時間の増加がユーザーエクスペリエンスに影響を与えるかどうかを考慮する必要があります。別の方法として、開発者は大規模なライブラリの依存関係をアプリケーションの実行ファイルに静的にリンクすることを検討することができます。
静的リンクは、動的シンボル解決の必要性を完全に回避しますが、共有 ライブラリをアップグレードするのに比べて、依存関係にパッチを適用する のが難しくなる可能性があります。例えば、主要な Linux ディストリビューションでは、共有アプリケー ションの依存関係を静的にリンクすることを一般的に禁止しています。
ポジションに依存しないコードとしてビルドする
コンパイラフラグ | 以降でサポート | 説明 |
---|---|---|
-fPIE -pie | Binutils 2.16 Clang 5.0.0 |
ポジションに依存しない実行ファイルとしてビルドする |
< Binutils 2.6 Clang 5.0.017 |
ポジションに依存しないコードとしてビルドする |
概要
位置非依存コード(PIC)と実行可能コード(PIE)は、プロセスメモリにロードされた正確なアドレスに関係なく正しく実行されるマシンコードオブジェクトです。
GNU/Linuxでは、アドレス空間レイアウトランダム化(ASLR)のメリットを得るために、プログラム実行ファイルをPIEとしてビルドする必要があります。ASLRは、最近のGNU/Linuxディストリビューションにおけるreturn-to-libcやreturn-orientedプログラミングなど、コード再利用の悪用を軽減する主要な手段です。code-reuseエクスプロイトでは、プログラムスタックに格納されたリターンアドレスなどの脆弱なコードポインタを破壊し、プログラムメモリ内の既存の実行可能コードを参照させます。ASLRは、オブジェクトがメモリにロードされるたびに、共有ライブラリとプログラム実行可能コードの位置をランダムに変更し、エクスプロイトに有効なメモリアドレスを予測しにくくします。
パフォーマンスへの影響
64ビット アーキテクチャでは無視できます。
32ビットx86では、PICは中程度の性能低下(5~10%)を示します12。これは、32ビットx86のmov命令を使ったデータアクセスが絶対アドレスしかサポートしていないためです。コードの位置依存性をなくすために、メモリ参照は、プログラムデータへの正しいアドレスがロード時に入力されるグローバルオフセットテーブル(GOT)からメモリアドレスをルックアップするように変換されます。その結果、32ビットx86上の非PICコードと比較して、データ参照は追加のメモリロードを要します。しかし、性能低下の主な原因は、GOTへのルックアップアドレスをレジスタ49で利用可能な状態に保つために生じるレジスタプレッシャーの増大です。
x86_64アーキテクチャは、命令ポインタ(すなわち現在実行中の命令のアドレス)からの相対オフセットを使用してメモリをアドレス指定するmov命令をサポートしています。これはRIPアドレッシングと呼ばれます。PIC on x86_64は、GOTへのアクセスにRIPアドレッシングを使用することで、32ビットx86のPICに関連するレジスタのプレッシャーから解放され、性能への影響が小さくなります。共有ライブラリは、デフォルトでPIC on x86_64で作成されます50。
どのような場合に使用してはいけないのか?
リソースに制約のある組込みシステムでは、コンパイル時に実行可能ファイルをプリリンクすることで、メモリを節約することができます。プリリンクは、通常ダイナミックリンカが行う再配置の決定を、事前に実行します。その結果、ダイナミックリンカが実行する再配置の回数が減り、 アプリケーションの起動時間とメモリ消費量が削減されます。PIEはプリリンクを妨げませんが、プリリンクされたバイナリでASLRを有効にすると、コンパイル時の決定が上書きされるため、プリリンクによって得られる実行時のメモリ節約効果は無効になります。プリリンクによるメモリ削減がシステムにとって重要である場合、PIEは、リスクが高い実行可能ファイルのサブセット(信頼できない外部入力を処理するアプリケーションなど)に対して有効にすることができます。
非推奨コンパイラオプション
このセクションでは、生成されたバイナリにセキュリティ上の問題を引き起こす可能性のある、推奨されないコンパイラとリンカのオプション フラグについて説明します。
Table 3: 推奨されないコンパイラーとリンカーのオプションのリスト
コンパイラフラグ | 以降でサポート | 説明 |
---|---|---|
-Wl,-rpath,path_to_so | Binutils 2.11 | 実行ファイルやライブラリにランタイム検索パスをハードコードする |
実行ファイルやライブラリにランタイム検索パスをハードコードする
コンパイラフラグ | 以降でサポート | 説明 |
---|---|---|
-Wl,-rpath,path_to_so | Binutils 2.11 | 実行ファイルやライブラリにランタイム検索パスをハードコードする |
概要
The -rpathオプションは、生成されるELFバイナリのDT_RPATH またはDT_RUNPATHヘッダー値に、指定された共有オブジェクトファイルへのパスを記録します。記録されたrpathは、実行可能ファイルまたはライブラリーが必要とする指定されたライブラリー依存関係を見つけるためにダイナミックリンカーが使用するシステムのデフォルト検索パスを上書きまたは補足することができます。
オリジナルの(そしてデフォルトの)DT_RPATHエントリによって提供されるrpathは、LD_LIBRARY_PATH のような環境のオーバーライドよりも優先され、あるオブジェクトのDT_RPATHは、別のオブジェクトの依存関係を解決するために使用することができます。これらは、DT_RUNPATHよりも優先順位が低く、オブジェクト自身の直接の依存関係の検索パスにのみ影響するLD_LIBRARY_PATH値の導入により、設計上のエラーが修正されました51。
リリースバイナリで rpath を設定することは(DT_RPATH または DT_RUNPATHのどちらを使用しているかに関係なく)、安全でないプログラミング方法であり、特定の条件下ではセキュリティの脆弱性につながる可能性があります。例えば、攻撃者は、rpath が指しているディレクトリに自分自身の共有ファイルを提供し、それによってオペレーティングシステムから提供されるライブラリを上書きすることができる可能性があります。これは、攻撃者が作業ディレクトリーを制御し、悪意のあるファイルを配置することができるディレクトリーを指定できる環境で、相対 rpath(つまり foo.so ではなく /usr/lib/foo.so)を設定した結果として発生する可能性があります。
rpathのキーワード$ORIGIN は、(ダイナミックローダーによって) オブジェクトが見つかるディレクトリのパスに展開さ れます。rpathが設定されたオブジェクトの場所をコントロールできる攻撃者は(例えばハードリンクを介して)、$ORIGINを操作して、コントロールできるディレクトリを指すようにすることができます。
setuid/setgidプログラム内でrpathを設定すると、設定されたrpathを介してロードされた信頼できないライブラリが特権プログラムの一部として実行される状況で、特権の昇格につながる可能性があります。setuid/setgidバイナリは、検索パスに対する環境のオーバーライド(LD_PRELOAD, LD_LIBRARY_PATHなど)を無視しますが、このようなバイナリ内のrpathは、依存関係の検索パスを操作する同等の機能を攻撃者に提供する可能性があります。
Sanitizers
Sanitizersは、CやC++で書かれたアプリケーションのメモリ安全性の問題やその他の不具合を検出し、ピンポイントで指摘するために設計されたコンパイラーベースのツールスイートです。Valgrindのようなフレームワーク上に構築された動的解析ツールと同様の機能を提供します。しかし、Valgrindとは異なり、サニタイザーはコンパイル時のインスツルメンテーションを活用して、メモリアクセスをインターセプトし、監視します。これにより、サニタイザーはダイナミックアナライザーと比較して、より効率的で正確なものになります。平均して、サニタイザーはインスツルメンテーションされたバイナリに2倍から4倍の速度低下をもたらしますが、動的インスツルメンテーションは20倍から50倍52の速度低下をもたらします。トレードオフとして、サニタイザーはコンパイル時に有効にしなければなりませんが、Valgrind は変更されていないバイナリで使用できます。表4は、GCCとClangがサポートするSanitizersオプションの一覧です。
Sanitizersは、動的解析に比べて効率的ではあるが、Releaseビルドで使用するには、パフォーマンスペナルティとメモリオーバーヘッドの点でまだ法外にコストがかかりますが、Debugビルドや、場合によってはTestビルドでのメモリ診断には優れています。例えば、ファズテスト(あるいは、「ファジング」)は、メモリ関連のバグを引き起こす条件を特定するために設計された、 一般的なセキュリティ保証活動です。ファジングは、主に、アプリケーションのクラッシュにつながるメモリエラーを特定するために役立ちます。しかし、もしファズテストが、Sanitizers機能を備えたバイナリで実施されるなら、アプリケーションをクラッシュさせないバグを特定することも可能です。もう一つの利点は、Sanitizersによって生成される診断情報が強化されることです。
すべてのテスト手法と同様に、サニタイザーはバグの不在を絶対的に証明することはできません。しかし、適切かつ定期的に使用することで、特定が困難な潜在的なメモリ、並行動作、未定義動作に関連するバグを特定するのに役立ちます。
Table 4: GCCとClangのSanitizersオプション
コンパイラフラグ | 以降でサポート | 説明 |
---|---|---|
-fsanitize=address | GCC 4.8 Clang 3.1 |
AddressSanitizerが実行時にメモリエラーを検出できるようにします |
-fsanitize=thread | GCC 4.8 Clang 3.2 |
ThreadSanitizerが実行時にデータ競合のバグを検出できるようにします |
-fsanitize=leak | GCC 4.8 Clang 3.1 |
LeakSanitizerを有効にして、実行時にメモリリークを検出できるようにします |
-fsanitize=undefined | GCC 4.9 Clang 3.3 |
UndefinedBehaviorSanitizer を有効にして、実行時に未定義の動作を検出します |
AddressSanitizer
コンパイラフラグ | 以降でサポート | 説明 | ||||
---|---|---|---|---|---|---|
-fsanitize=address | GCC 4.8 Clang 3.1 |
AddressSanitizerが実行時にメモリエラーを検出できるようにします | -fsanitize=thread | GCC 4.8 Clang 3.2 |
ThreadSanitizerが実行時にデータ競合のバグを検出できるようにします |
AddressSanitizer(ASan)はメモリエラー検出器であり、メモリの欠陥を特定することができます:
- スタック、ヒープ、グローバル変数のバッファオーバーフロー
- Use-after-free条件(ダングリングポインタの再参照)
- Use-after-return条件(関数からのリターン後、ローカル用に確保されたスタックメモリの使用)
- Use-after-Scope条件(変数のレキシカルスコープ外のスタックアドレスの使用)
- 初期化順序のバグ
- メモリリーク(セクション 0 の LeakSanitizer も参照のこと)
ASanを有効にするには、コンパイラーフラグ(Cの場合はCFLAGS、C++の場合はCXXFLAGS)およびリンカーフラグ(LDFLAGS)に-fsanitize=addressを追加します。
- -O1(インライン化を無効にし、スタックトレースを改善します。)
- -g (生成されるエラーメッセージにソースファイル名と行番号を表示します)
- -fno-omit-frame-pointer(スタックトレースをさらに改善します)
- -fno-optimize-sibling-calls (テールコールの最適化を無効にします)
- -fno-common(グローバルの追跡を改善するために共通シンボルを無効にします)
環境変数ASAN_OPTIONSを使用すると、ASanのランタイム動作に影響を与えることができます。ランタイムオプションを使用すると、追加のメモリーエラーチェックを有効にしたり、ASanのパフォーマンスを微調整したりできます。サポートされているオプションの最新リストは、プロジェクトのGitHub Wik53のAddressSanitizerFlagsの記事で入手できます。ASAN_OPTIONS=help=1に設定すると、インスツルメンテッド プログラムの起動時に利用可能なオプションが表示されます。これは、使用するコンパイラに統合された特定のバージョンのASanでサポートされているオプションを判断するのに特に役立ちます。デフォルトの動作と比較して、より積極的な診断を有効にする便利なプリセットを以下に示します:
ASAN_OPTIONS=strict_string_checks=1:detect_stack_use_after_return=1:
check_initialization_order=1:strict_init_order=1 ./instrumented-executable
ASanがメモリエラーに遭遇すると、(デフォルトで)アプリケーションを終了し、検出されたエラーの性質と場所を説明するエラーメッセージとスタックトレースを表示します。ASanによって報告されるさまざまなエラーの種類と対応する根本原因の体系的な説明は、プロジェクトのGitHub Wiki54にあるAddressSanitizerの記事に記載されています。
ASanはThreadSanitizerやLeakSanitizerと同時に使うことはできません。GCCとClangのASan実装は相互に互換性がないため、GCCで生成されたASan実装のコードとClangで生成されたASan実装のコードを混在させることはできません。
ThreadSanitizer
コンパイラフラグ | 以降でサポート | 説明 |
---|---|---|
-fsanitize=thread | GCC 4.8 Clang 3.2 |
ThreadSanitizerが実行時にデータ競合のバグを検出できるようにします |
ThreadSanitizer(TSan)は、C/C++用のデータ競合検出器です。データ競合は、同じプロセスの2つ(またはそれ以上)のスレッドが、同期を取らずに同じメモリ位置に同時にアクセスすることで発生します。アクセスの少なくとも1つが書き込みである場合、アプリケーションは矛盾した内部状態に陥る危険性があります。もし2つ以上のスレッドが同時にメモリ位置に書き込もうとすると、データ競合によってメモリ破壊が発生する可能性があります。データ競合はデバッグが難しいことで有名です。なぜなら、アクセスの順序は一般的に非決定的であり、問題となるスレッドのイベントの正確なタイミングに依存するからです。
TSanを有効にするには、コンパイラーフラグ(Cの場合はCFLAGS、 C++の場合はCXXFLAGS)およびリンカーフラグ(LDFLAGS)に -fsanitize=threadを追加します。TSanと以下のコンパイラーフラグとの組み合わせを検討してください:
- -O2 (or higher for reasonable performance)
- -g (to display source file names and line numbers in the produced warning messages)
TSanの実行時の動作は、環境変数TSAN_OPTIONSを使って変更できます。サポートされているオプションの最新リストは、プロジェクトのGitHub Wiki55のThreadSanitizerFlagsの記事で入手できます。TSAN_OPTIONS=help=1に設定すると、インスツルメンテッドプログラムの起動時に利用可能なオプションが表示されます。
TSanが潜在的なデータ競合に遭遇すると、(デフォルトでは)データ競合につながったプログラム状態の説明を含む警告メッセージを表示して、競合を報告します。レポート形式の詳細な説明は、プロジェクトのGitHub Wiki56にあるThreadSanitizerReportFormatの記事を参照してください。
TSanはAddressSanitizer(ASan)やLeakSanitizer(LSan)と同時に使うことはできません。GCCとClangのTSan実装は相互に互換性がないため、GCCで生成されたTSan実装のコードと、Clangで生成されたTSan実装のコードを混在させることはできません。TSanは一般に、正しく動作させるために、すべてのコードを-fsanitize=threadでコンパイルすることを要求します。
LeakSanitizer
コンパイラフラグ | 以降でサポート | 説明 |
---|---|---|
-fsanitize=leak | GCC 4.8 Clang 3.1 |
LeakSanitizerを有効にして、実行時にメモリリークを検出できるようにします |
LeakSanitizer(LSan)は、ASanに組み込まれたメモリリーク検出のスタンドアロンバージョンです。LSanは、ASanのような速度低下を伴うことなく、メモリリークの解析を可能にします。ASanとは異なり、LSanはコンパイル時のインスツルメンテーションを必要とせず、ランタイムライブラリのみで構成されています。-fsanitize=leakオプションは、malloc()や他のアロケータ関数をオーバーライドするLSanライブラリに対して、アプリケーション実行ファイルをリンクするようにリンカに指示します。
LSanの実行時の動作は、環境変数LSAN_OPTIONS を使用して変更できます。LSAN_OPTIONS=help=1に設定すると、プログラム起動時に利用可能なオプションが表示されます。
LSanは、AddressSanitizer(ASan)またはThreadSanitizer(TSan)と同時に使用することはできません。ビルド中に ASan または TSan が有効になっている場合、 -fsanitize=leakオプションはリンカによって無視されます。
UndefinedBehaviorSanitizer
コンパイラフラグ | 以降でサポート | 説明 |
---|---|---|
-fsanitize=undefined (requires -O1 or higher) |
GCC 4.9 Clang 3.3 |
UndefinedBehaviorSanitizer を有効にして、実行時に未定義の動作を検出します |
UndefinedBehaviorSanitizer (UBSan)は、ISO C標準に明確に定義されていない動作を引き起こす、移植性のない、あるいは誤ったプログラム構成要素を検出するものです。UBSanは、さまざまなクラスの未定義動作のチェックを個別に有効/無効にするための多数のサブオプションを提供します。サポートされているサブオプションの最新情報については、それぞれGCC57 と Clang58のドキュメントを参照してください。
UBSanを有効にするには、コンパイラーフラグ(Cの場合はCFLAGS 、C++の場合はCXXFLAGS)およびリンカーフラグ(LDFLAGS)に-fsanitize=undefinedを追加し、必要なサブオプションを追加します。TSanと以下のコンパイラーフラグを組み合わせてください:
- -O1 (妥当なパフォーマンスを得るには必須かそれ以上)
- -g (生成される警告メッセージにソースファイル名と行番号を表示します)
UBSanのランタイムの動作は、環境変数 UBSAN_OPTIONSを使って変更することができます。UBSAN_OPTIONS=help=1 に設定すると、使用可能なオプションがインスツルメンテッド プログラムのスタートアップ時に表示されます。
デバッグ情報を別ファイルに保持する
アプリケーションのデバッグ情報は、アプリケーションの実行ファイルとは別のデバッグ情報ファイルに置くことができます。これにより、実行ファイルからデバッグ情報を取り除いた状態で出荷することができ、リリースの実行ファイルの問題を診断する際に、デバッガがデバッグ情報をデバッグ情報ファイルから取得することができます。GNUデバッガ(GDB)とLLVMデバッガ(LLDB)では、ストリップされたバイナリのデバッグ情報を別のデバッグ情報ファイルから読み込むことができます。
開発者がデバッグ情報を実行ファイルから切り離したいと思う理由はいくつかあります:
- デバッグ情報は非常に大きくなる可能性があり、場合によっては実行コードそのものよりも大きくなることもあります!デバッグ情報を別にすれば、不要な場所では省略することができます。このため、ほとんどのLinuxディストリビューションでは、アプリケーション パッケージのデバッグ情報を個別のデバッグ情報ファイルで配布しています。
- ソースコードが利用できない場合、アプリケーションの実装に関する機密事項が不注意に明らかになることを避けることができます。シンボル情報が利用可能であれば、アプリケーションの実行ファイルのバイナリ解析やリバースエンジニアリングが容易になります。しかし、逆コンパイラのようなツールは、デバッグ情報なしでも動作しますから、システムのセキュリ ティは、そのような情報の省略に依存してはなりません。
以下の一連のコマンドは、デバッグ情報ファイルを生成し、メイン実行ファイルからデバッグ情報を取り除き、デバッグリンクセクションを追加します。
objcopy --only-keep-debug executable_file executable_file.debug
objcopy --strip-unneeded executable_file
objcopy --add-gnu-debuglink=executable_file.debug executable_file
ELFバイナリ形式のデバッグ情報
ELFバイナリでは、デバッグ情報とシンボル情報は、個別のデバッグ情報ファ イルが作成されない限り、個別のELFセクションに格納されます。表5に、通常デバッグ情報、シンボル情報、その他の補助情報を含むELFセクションを示します。
Elf Section | 説明 |
---|---|
.debug | デバッガ用のシンボリックデバッグ情報(通常はDWARF形式59) |
.comment | GCCのバージョン情報 |
.dynstr | .dynsymによる動的シンボル名検索に必要な文字列 |
.dynsym | ランタイム再配置に使用される動的シンボルルックアップテーブル |
.note | ABIタグ60やビルドID61などの補助メタデータ |
.strtab | .symtab内の名前を表す文字列 |
.symtab | デバッガによるシンボル名検索に使用されるグローバルシンボルテーブル |
特定のセクションがELFバイナリに存在するかしないかは、どのような種類の情報が利用可能であるかを示します。デバッガ、逆アセンブラ、およびGhidra62 やIDA Pro63のようなバイナリコード解析ツールは、利用可能なシンボル情報を使用して、自動的にデコンパイルされたマシンコードに注釈を付けることができるため、シンボル情報の利用可能性は、バイナリ解析を容易にします。同様に、デバッグ情報があれば、デバッガでのアプリケーションの動的解析が容易になります。バイナリから不要なデバッグ情報やシンボル情報を削除しても、リバースエンジニアリングに対して無害になるわけではあり ませんが、悪用を成功させるために必要なコストと手作業は大幅に増加します。
デバッグ情報ファイルの作成
デバッグ情報ファイルは、アプリケーションの元の実行ファイルと同じセクションレイアウトを持つ普通の実行ファイルですが、実行ファイルのデータは含まれていません。デバッグ情報ファイルは、必要なデバッグ情報を含むアプリケーション実行可能ファイルをコンパイルし、objcopyユーティリティで実行可能ファイルを処理して、取り除かれた実行可能ファイル(デバッグ情報なし)とデバッグ情報ファイル(実行可能データなし)を生成することで作成されます。GNU binutils objcopy64とLLVM llvm-objcopy65の両方が、デバッグ情報の削除とデバッグ情報ファイルの作成に同じオプションをサポートしています。以下のシェルのスニペットは、デバッグ情報付きの実行ファイルからデバッグ情報ファイルを作成するためのobjcopyの呼び出しを示しています。
objcopy --only-keep-debug executable_file executable_file.debug
デバッグリンクのファイル名には特に指定はありませんが、一般的な慣例として、実行ファイルに対してデバッグ情報を「executable.debug」のように命名します。デバッグ情報ファイルは実行ファイルと同じ名前でもかまいませんが、”.debug “のような拡張子を使う方が、デバッグ情報ファイルを実行ファイルと同じディレクトリに置くことができるので好ましいです。
デバッグ情報ファイルは、デバッグ情報とシンボル情報をそのままに、オリジナルのバイナリと同じ方法でバイナリを解析することを可能にします。デバッグ情報ファイルは慎重に扱われるべきであり、敵が入手する可能性のあるコンピューティング環境では公開されるべきではありません。
デバッグ情報とシンボル情報を取り除く
デバッグ情報ファイルが作成されると、Binutilsが提供するobjcopy または strip66ユーティリティ、あるいはLLVMが提供するllvm-objcopy または llvm-strip67同等のユーティリティを使用して、元のバイナリからデバッグ情報とシンボル情報を取り除くことができます。以下のシェルのスニペットは、それぞれ objcopy と stripを使用して、実行ファイルからデバッグ情報と不要なシンボル情報を削除する方法を示しています。アプリケーションバイナリにコード署名を適用する場合は、バイナリが署名される前にデバッグ情報とシンボル情報を取り除く必要があります。
strip --strip-unneeded executable_file
objcopy --strip-unneeded executable_file
objcopyの–strip-unneeded オプションは、再配置の処理に不要なすべてのシンボル情報 (ELF .symtab と .strtabセクション) をバイナリから削除します。さらに、バイナリからシンボリックデバッグ情報(ELFの .debugセクションとプレフィックスが.debugのすべてのセクション)を削除します。
再配置に使用されるシンボル情報を削除することは、動的にリンクされた シンボル(ELFの .dynsym セクションと .dynstrクション)の解決や、実行時 のASLR(Address Space Layout Randomization)に支障をきたす可能性があるた め、推奨されません。その結果、デバッガやバイナリ解析では、動的にリンクされた関数の呼び出しを正しいシンボル情報に解決できることが期待されます。静的リンクは、動的にリンクされたシンボルが結果として生成されるバイナリに表示されたままになるのを避けるために、該当する場合は代替手段として考慮することができる。
追加セクションの除去
–strip-unneeded は、標準的なELFセクションのみを不要なものとして破棄することに注意してください。ELFバイナリは、objcopy や stripにとって未知のセクションをいくつでも追加できるため、そのような未認識のセクションが削除しても安全かどうかを判断できません。これには、例えばGCCによって追加された .commentセクションが含まれます。 以下のシェルのスニペットは、–strip-unneededによって識別された不要なセクションに加えて、.commentのような非標準のセクションを削除する方法を示しています。アプリケーションにカスタムでアプリケーション固有のELFセクションがあり、通常の運用ではランタイムに必要とされない診断情報やメタデータが含まれている可能性がある場合、開発者はリリースバイナリからそのような追加セクションを削除したいと思うかもしれません。
objcopy --strip-unneeded --remove-section=.comment executable_file
strip --strip-unneeded --remove-section=.comment executable_file
バイナリにデバッグリンクを追加する
デバッガが正しいデバッグ情報を識別できるようにするには、実行ファイルを対応するデバッグ情報ファイルと関連付ける必要があります。これには2つの方法があります:
- 実行ファイル内に、対応するデバッグ情報ファイル名を指定する“debug link”を含める。
- 実行ファイルに“build ID”(一意のビット列)を含め、そこからデバッグ情報ファイル名を特定する。
ほとんどの場合、デバッグ リンクの方が、開発者がデバッグ情報ファイルに名前を付けることができ、デバッグ中にシンボル情報がファイルから取得される前に、デバッグ情報ファイルの内容のチェックサムを検証することができるため、推奨されます。
デバッグ リンク
デバッグリンクとは、実行ファイル内の特別なセクション (..gnu_debuglinkのことで、対応するデバッグ情報ファイルの名前と、デバッグ情報ファイルの全内容に対して計算された 32 ビットの巡回冗長検査 (CRC) を含んでいます。.gnu_debuglink. という名前のセクションがあれば、どのような形式の実行ファイルでもデバッグリンク情報を含めることができます。以下のシェルのスニペットは、objcopy (またはllvm-objcopy) を使って実行ファイルにデバッグリンクを追加する方法を示しています。
objcopy --add-gnu-debuglink=executable_file.debug executable_file
デバッグ情報ファイルがある場所にビルドされ、後で別の場所にインス トールされる場合は、–add-gnu-debuglinkオプションを、ビルドされたデバッ グ情報ファイルへのパスとともに使用する必要があります。デバッグ情報ファイルは、デバッガが読み込むデバッグ情報ファイルが実行ファイルのものと一致するかどうかを検証するためのCRC計算に必要なので、指定したパスに存在する必要があります。
.gnu_debuglinkにはデバッグ情報のフルパス名は含まれず、先頭のディレク トリ成分が取り除かれたファイル名のみが含まれることに注意してください。GDBは、指定されたファイル名のデバッグ情報ファイルを、実行ファイルが置かれているディレクトリーから始まる一連の検索ディレクトリーで探します。検索パスの完全なリストについては、GDBドキュメント68を参照してください。
Build ID
ビルドIDは、ELF.noteセクションの .note.gnu.build-id に格納されている一意のビット列で、バイナリファイルに対して(統計的に)一意です。デバッガは、同じビルドIDがデバッグ情報ファイルにも存在する場合、ビルドIDを使って対応するデバッグ情報ファイルを識別することができます。
ビルドID方式を使用する場合、デバッグ情報ファイルの名前はビルドIDから計算されます。GDBはグローバルデバッグディレクトリ(通常は/usr/lib/debug)から .build-id/xx/yyyy.debugファイルを検索します。ここでxx はビルドIDの最初の2文字の16進数、yyyyはビルドIDビット列の残りの16進数です(実際のビルドID文字列は32文字以上の16進数です)。
ビルドIDは、実行ファイルやデバッグ情報ファイルのチェックサムとしては機能しないことに注意してください。ビルド ID 機能の詳細については、GDB64 および GNU linker61のドキュメントを参照してください。
貢献者
OpenSSF Developer BEST Practices Working groupは、本ガイドの共同作業を開始するために、Ericsson社から寛大にも最初のコンテンツを提供していただいたことに感謝します。
- Thomas Nyman, Ericsson
- Robert Byrne, Ericsson
- Jussi Auvinen, Ericsson
- Christopher “CRob” Robinson, Intel
- David A. Wheeler, Linux Foundation
- Gabriel Dos Reis, Microsoft
- Georg Kunz, Ericsson
- Jack Kelly, ControlPlane
- Randall T. Vasquez, Linux Foundation
- Robert C. Seacord, Woven by Toyota
- Siddharth Sharma, Red Hat
- Siddhesh Poyarekar, Red Hat
ライセンス
Copyright 2023, OpenSSF contributors, licensed under CC BY 4.0
付録 検討されたコンパイラ オプションのリスト
セキュリティに関連するコンパイラオプションは、本ガイドで推奨して いる以外にも数多く存在します。これらのオプションのいくつかを本ガイドに含めることも検討されましたが、 様々な理由により、最終的には推奨オプションから除外されました。以下の表は、検討されたオプションとその除外の根拠を示したものです。これらは推奨リストには含まれていませんが、あなたの目的には役立つかもしれません。
コンパイラフラグ | 以降でサポート | Rationale |
---|---|---|
-Wl,-z,nodump | Binutils 2.10 | Solaris互換性69のための単一機能。 |
-Wl,-z,noexecheap | Binutils 2.15 (Hardened Gentoo / PaX only ) | アップストリームツールチェーンにはない、Gentoo / PaX 固有の Binutils 拡張70 |
-D_LIBCPP_ASSERT | libc++ 3.3.0 | _LIBCPP_ENABLE_HARDENED_MODE71に取って代わられ、非推奨。 |
-D_LIBCPP_ENABLE_ASSERTIONS | libc++ 3.3.0 | _LIBCPP_ENABLE_HARDENED_MODE71に取って代わられ、非推奨。 |
References
- Intel C/C++コンパイラ、Arm Compiler for Embedded、Xcodeに含まれるApple Clangコンパイラ、IBM Open XL C/C++コンパイラ、Red Hat Developer Toolset、Siemens Sourcery Toolchain、AdaCore GNAT Pro Enterpriseなど。 ↩
- Cimpanu, Catalin, Microsoft: 70 percent of all security bugs are memory safety issues, ZDNet, 2019-02-11 ↩
- Cimpanu, Catalin, Chrome: 70% of all security bugs are memory safety issues, ZDNet, 2020-05-22 ↩
- Carnegie Mellon University (CMU), SEI CERT C Coding Standard Rules for Developing Safe, Reliable, and Secure Systems, 2016 edition, June 2016. ↩
- Carnegie Mellon University (CMU), SEI CERT C++ Coding Standard Rules for Developing Safe, Reliable, and Secure Systems, 2016 edition, March 2017. ↩
- US NIST, Secure Software Development Framework (SSDF) Version 1.1: Recommendations for Mitigating the Risk of Software Vulnerabilities, NIST SP 800-218, February 2018. ↩
- Carnegie Mellon University (CMU), Top 10 Secure Coding Practices, SEI CERT Coding Standards Wiki, 2018-05-02. ↩
- Software in the Public Interest, Hardening in Debian, Debian Wiki, 2022-01-07. ↩
- Gentoo Foundation, Hardening in Gentoo, Gentoo Wiki, 2023-03-08. ↩
- Red Hat, Using RPM build flags in Fedora, Fedora Package Sources (redhat-rpm-config), 2023-08-04. ↩
- SUSE, openSUSE Security Features, openSUSE Wiki, 2022-12-8. ↩
- Ubuntu, Ubuntu Security Features, Ubuntu Wiki, 2023-08-07. ↩ ↩2
- LLVM team, Controlling Diagnostics in System Headers, Clang Compiler User’s Manual, 2017-03-08. ↩
- GCC team, Options for Directory Search, GCC Manual, 2023-07-27. ↩
- LLVM team, Clang command line argument reference¶: -isystem<directory>, Clang documentation, 2017-09-05. ↩
- Kitware, include_directories¶, Cmake Documentation, 2023-10-23. ↩
- GNU libc (glibc)の -D_FORTIFY_SOURCE={1,2,3}の実装は、GCC内の実装の詳細に大きく依存しています。Clangは独自のスタイルの強化された関数呼び出し(もともとはAndroidのbionic libcのために導入されました)を実装していますが、Clang / LLVM 14.0.6では、いくつかのglibc関数への _FORTIFY_SOURCE を使った非強化呼び出しが誤って生成されます。Clangで強化されたコードでもコンパイルは可能ですが、glibcの強化された関数のバリエーションが必ずしも恩恵を受けるとは限りません。詳しくは Toward _FORTIFY_SOURCE parity between Clang and GCC. Red Hat Developer, Red Hat Developer, 2020-02-11 and Poyarekar, Siddhesh, D91677 Avoid simplification of library functions when callee has an implementation, LLVM Phabricator, 2020-11-17. ↩ ↩2 ↩3 ↩4 ↩5
- GCC team, Using the GNU Compiler Collection (GCC): Warning Options., GCC Manual, 2023-07-27. ↩
- LLVM Team, Clang documentation: Diagnostics flags in Clang, Clang documentation, 2023-03-17. ↩
- Polacek, Marek, “-Wimplicit-fallthrough in GCC 7”, Red Hat Developer, 2017-03-10 ↩
- Corbet, Jonathan. “An end to implicit fall-throughs in the kernel”, LWN, 2019-08-01. ↩
- ISO/IEC, Programming languages — C (“C17”), ISO/IEC 9899:2018, 2017. Note: The official ISO/IEC specification is paywalled and therefore not publicly available. The final specification draft is publicly available. ↩
- Shafik, Yaghmour, “GCC 7, -Wimplicit-fallthrough warnings, and portable way to clear them?”, StackOverflow, 2015-01-15. ↩
- Johnston, Philip. -Werror is Not Your Friend. Embedded Artistry Blog, 2017-05-22. ↩
- GNU C Library team, Source Fortification in the GNU C Library, GNU C Library (glibc) manual, 2023-02-01. ↩
- Poyarekar, Siddhesh, How to improve application security using _FORTIFY_SOURCE=3, Red Hat Developer, 2023-02-06. ↩ ↩2
- GCC team, Arrays of Length Zero, GCC Manual (experimental 20221114 documentation), 2022-11-14. ↩
- Linux Man Pages team, malloc_usable_size(3), Linux manual page, 2023-03-30. ↩ ↩2
- kpcyrd, Task Todo List Prepare packages for -D_FORTIFY_SOURCE=3, Arch Linux Task Todo List, 2023-09-05. ↩
- GCC team, Using Macros in the GNU C++ Library, The GNU C++ Library Manual, 2023-07-27. ↩
- Wakely, Jonathan, Enable lightweight checks with _GLIBCXX_ASSERTIONS, GCC Mailing List, 2015-09-07 ↩
- Metzger-Kraus, Christof. Don’t use GLIBCXX_ASSERTIONS in production, Object Oriented Particle Accelerator Library (OPAL) Issue Tracker, 2021-01-16. ↩
- GCC team, Using the GNU Compiler Collection (GCC): Warning Options: -Wstrict-flex-arrays, GCC Manual, 2023-07-27. ↩
- Guelton, Serge, The benefits and limitations of flexible array members, Red Hat Developer, 2022-09-29. ↩ ↩2
- Cook, Kees, GCC Bug 101836 – __builtin_object_size(P->M, 1) where M is an array and the last member of a struct fails, GCC Bugzilla, 2021-08-09. ↩
- GCC team, Program Instrumentation Options: -Wsanitize=bounds, GCC Manual, 2023-07-27. ↩
- Corbet, Jonathan, “GCC features to help harden the kernel”, LWN, 2023-09-05. ↩
- Zhao, Qing, “[GCC13][Patch][V2][1/2]Add a new option -fstrict-flex-array[=n] and attribute strict_flex_array(n) and use it in PR101836”, GCC Mailing List, 2022-08-01. ↩
- Edge, Jake, Safer flexible arrays for the kernel, LWN, 2022-09-22. ↩
- Cook, Kees, and Gustavo A.R. Silva, “Progress on Bounds Checking in C and the Linux Kernel”, Linux Security Summit North America 2023, 2023-05-12. ↩
- Hebb, Tom, GCC’s -fstack-protector fails to guard dynamic stack allocations on ARM64, GitHub metaredteam/external-disclosures Advisories, 2023-09-12. ↩ ↩2
- Arm, GCC Stack Protector Vulnerability AArch64, Arm Security Center, 2023-09-12. ↩ ↩2
- Common Vulnerability Enumeration Database, CVE-2023-4039, 2023-09-13. ↩
- Shen, Han, New stack protector option for gcc, Google Docs, 2011-11-30. ↩
- Intel, “A Technical Look at Intel’s Control-flow Enforcement Technology”, 2020-06-13. ↩
- ARM Developer, Arm Compiler armclang Reference Guide Version 6.12 -mbranch-protection. ↩
- GCC team, Support for Nested Functions., GCC Internals, 2023-07-27. ↩
- Intel, Managed Runtime Speculative Execution Side Channel Mitigations, Intel Developer Zone, 2018-03-01. ↩
- Bendersky, Eli, Position Independent Code (PIC) in shared libraries, Eli Bendersky’s website, 2011-11-03. ↩
- Bendersky, Eli. Position Independent Code (PIC) in shared libraries on x64, Eli Bendersky’s website, 2011-11-11. ↩
- Kerrisk, Michael, Building and Using Shared Libraries on Linux, Shared Libraries: The Dynamic Linker, man7.org, February 2023. ↩
- Kratochvil, Jan, Memory error checking in C and C++: Comparing Sanitizers and Valgrind, Red hat Developers, 2021-05-05. ↩
- LLVM Sanitizers team, AddressSanitizerFlags, GitHub google/sanitizers Wiki, 2019-05-15. ↩
- LLVM Sanitizers team, AddressSanitizer, GitHub google/sanitizers Wiki, 2019-05-15. ↩
- LLVM Sanitizers team, ThreadSanitizerFlags, GitHub google/sanitizers Wiki, 2015-08-31. ↩
- LLVM Sanitizers team, ThreadSanitizerReportFormat, GitHub google/sanitizers Wiki, 2015-08-31. ↩
- GCC team, Program Instrumentation Options, GCC Manual, 2023-07-27. ↩
- LLVM team, UndefinedBehaviorSanitizer, Clang documentation, 2023-03-17. ↩
- DWARF Debugging Information Format Committee, DWARF Version 5 Debugging Format Standard, DWARF Debugging Standard Website, 2017-02-13. ↩
- Linux Foundation, Linux Standard Base Core Specification, Generic Part, Chapter 10.8. ABI note tag., Linux Foundation Referenced Specifications, 2015-05-27. ↩
- Binutils team, LD Options, Documentation for binutils, 2023-07-30. ↩ ↩2
- US NSA, Ghidra homepage ↩
- Hex-Rays, IDA Pro homepage ↩
- Binutils team, objcopy, Documentation for binutils, 2023-07-30. ↩ ↩2
- LLVM team, llvm-objcopy, LLVM Command Guide, 2023-03-17. ↩
- Binutils team, strip, Documentation for binutils, 2023-07-30. ↩
- LLVM team, llvm-strip, LLVM Command Guide, 2023-03-17. ↩
- GDB team, Debugging Information in Separate Files, Debugging with GDB, 2023-08-16. ↩
- -Wl,-z,nodump オプションは、オブジェクトの .dynamic セクションタグに DF_1_NODUMP フラグを設定します。Solaris では、これはオブジェクトに対する dldump(3) の呼び出しを制限されます。しかし、他のオペレーティングシステムではDF_1_NODUMPフラグは無視されます。BinutilsはSolarisとの互換性のために-Wl,-z,nodumpを実装していますが、lldではサポートしないことにしました(D52096 lld: add -z nodump support)。 ↩
- -Wl,-z,noexecheapオプションは、PaXから移植されたBinutilsのHardened Gentoo拡張です。PaXはLinuxカーネルとBinutilsへのパッチで、PT_PAX_FLAGS プログラムヘッダをELFオブジェクトに追加し、PaXカーネルが適用できるメモリ保護情報を格納します。PT_PAX_FLAGSに格納された保護情報は、PaXカーネルがないシステムで動作するソフトウェアには役に立ちません。2.15以降の様々なバージョンのBinutils用のGentooパッチ(63_all_binutils-<version>-pt-pax-flags-<date>.patch) for various versions of Binutils since 2.15 can be found at https://dev.gentoo.org/~vapier/dist/. ↩にあります。
- 71. LLVMのlibc++は、「安全な」動作モードについて何度も設計を繰り返してきました。libc++リリース17.0.0から、「安全」モードは廃止され、より限定されたチェックセット(実運用で使用するのに十分な性能を持つ、セキュリティクリティカルなチェック)を提供する新しい硬化動作モードが採用されています。詳細は以下を参照してください: LLVM team, Libc++ 17.0.0 Release Notes, Libc++ documentation, 2023-07-27; LLVM Team, Hardened Mode, Libc++ documentation, 2023-07-27 and Varlamov, Konstatin, Deprecate _LIBCPP_ENABLE_ASSERTIONS, LLVM Phabricator, 2022-07-11. ↩ ↩2
翻訳協力:吉田行男
- OpenSSF、CおよびC++のコンパイラ・オプション強化ガイドを発表 - 2023-12-12
- CおよびC++のコンパイラ・オプション強化ガイド - 2023-12-12