この文書は2016年以降更新されていません

C ベースのツールチェーンの強化に関するチートシート

OWASP 作成
ジャンプ先: 移動検索
Cheatsheets-header.jpg

最終改訂日 (yy/mm/dd):2015/07/18

はじめに

C ベースのツールチェーンの強化に関するチートシートでは、さまざまな開発環境での C、C++、および Objective C 言語使用時に、信頼性の高いセキュアなコードの配信に役立つプロジェクト設定の処理の概略を説明します。このトピックの詳細な処理については、ここを参照してください。このチートシートでは、実行可能ファイルを、強固な防衛の構えを整え、使用可能なプラットフォームセキュリティとの統合を強化する形で作成するために必要な手順について、順を追って説明しています。効果的にツールチェーンを構成すると、プロジェクトは、警告 / スタティック分析、セルフデバッグコードが強化されるなど、開発時に多くのメリットが得られるようになります。

ツールチェーンの強化を行うときに検討する領域は、構成、統合、スタティック分析、およびプラットフォームセキュリティの 4 つです。プロジェクト設定時、ほとんどすべての領域が無視されるか、ないがしろにされています。ないがしろにする傾向は幅広く見られ、ほぼすべてのプロジェクト (自動構成プロジェクトや Makefile ベース、Eclipse ベース、Xcode ベースを含む) に当てはまります。構成時および構築時にギャップに対処することが重要です。一部のプラットフォームでは、配布済みの実行可能ファイルに対して、後から強化を加えることが不可能に近いほど難しいからです。

この問題のさらに詳細な処理については、「C ベースのツールチェーンの強化」を参照してください。

実施可能な項目

C ベースのツールチェーンの強化に関するチートシートでは、次の実施可能な項目を提唱します。

  • デバッグ、リリース、およびテスト用の構成を用意する
  • アサートには有用な動作を提供する
  • ビルド構成を活用するようにコードを構成する
  • サードパーティのライブラリを適切に統合する
  • コンパイラに組み込まれているスタティック分析機能を使用する
  • プラットフォームのセキュリティ対策機能と統合する

このチートシートの残りの部分では、ここで箇条書きにした実施可能な項目について簡単に説明します。詳しい処理については、詳細記事を参照してください。

ビルド構成

サポートする必要のあるビルド構成は、3 つあります。第 1 はデバッグ、第 2 はリリース、第 3 はテストです。どれか 1 つだけでは不十分です。各ビルド構成は、エンジニアリングプロセスの異なる様相を表しています。開発中はデバッグビルドを使用します。継続的な統合やビルドサーバーにはテストビルドを使用し、リリースビルドを配信することになります。

1970 年代の K&R のコードと万能のフラグは、過去の時代の産物です。脅威など、現代の環境にまつわる課題に対処するべく、プロセスは進化し、成熟してきました。Autconfig や Automake などのツールは、ビルド構成の概念をサポートしていません。そのため、ユーザーは統合開発環境 (IDE) での作業を選択するか、必要なターゲットがサポートされるように makefile を記述する必要があります。さらに、Autconfig と Automake はたいてい、ユーザーが指定したフラグを無視します (各種のスクリプトとテンプレートを記述している作成者によります)。そのため、既存の自動ツールのファイルを改良するよりも、ゼロから makefile を記述したほうが簡単なこともあります。

デバッグビルド

デバッグビルドは開発中に使用します。このビルドはコード内の問題を発見するために役立てます。この段階でプログラムを開発し、そのプログラムが依存するサードパーティのライブラリとの統合をテストします。デバッグと診断を支援するために、プリプロセッサマクロ DEBUG_DEBUG (Windows プラットフォームの場合) を定義します。また、その他の "デバッグと診断" 向けのフラグをコンパイラとリンカーに指定することも必要です。選択したライブラリに応じた追加のプリプロセッサマクロについては、詳細記事を参照してください。

デバッグビルド時には、GCC に次のフラグを使用する必要があります。 -O0 (または -O1) および -g3 -ggdb。デバッグを簡単にする最適化はありません。最適化では、命令スケジューリングの改善のためにステートメントを再編成することや、不要なコードを削除することがよくあるためです。場合によっては、 -O1 を指定して、何らかの分析が実行されるようにする必要があります。 -g3 は、シンボル定数と #define を含む、最大サイズのデバッグ情報を使用可能にします

アサートは、セルフデバッグプログラムの記述に役立ちます。最初の障害点の前に、素早く簡単にプログラムから警告を通知できます。アサートは非常に強力であるため、コードには次のアサートをすべて完全に組み込むべきです。(1) 関数またはメソッドに関連するプログラムの状態をすべて検証してアサートする、(2) すべての関数パラメーターを検証してアサートする、(3) 値を返す関数またはメソッドの戻り値をすべて検証してアサートする。項目 (3) により、障害を伝達できない void 関数には特に注意するべきです。

検証用の if ステートメントがある場所には、必ずアサートを含めます。アサートのある場所には、 if ステートメントを必ず含めます。これらは、連携しています。Posix では、 NDEBUG が定義されていない場合、 assert "失敗した特定の呼び出しに関する情報を stderr に書き出して、abort を呼び出す" としています。開発時の abort の呼び出しは、無駄な動作にしかなりません。そのため、独自のアサート SIGTRAP を指定する必要があります。Unix と Linux の SIGTRAP ベースのアサートの例については、詳細記事を参照してください。

その他のデバッグと診断の方法、たとえばブレークポイントや printf とは異なり、アサートは永久にとどまり、静かな守護者となります。一見無関係なコードパスで誤って何かを変更した場合でも、アサートによってデバッガーのスイッチが入ります。適用期間が永続することで、デバッグコード (および、そのコードの追加の診断と計測) は、何もないリリースコードよりも価値の高いものになります。追加のデバッグや診断 (完全なアサートを含む) を持たないコードがチェックインされたときには、チェックインを拒否する必要があります。

リリースビルド

リリースビルドは、デバッグ構成とは正反対になります。リリース構成では、運用環境での使用に合わせてプログラムをビルドします。プログラムは正しく、安全、かつ効率的に実行されることが期待されます。デバッグと診断の期間は終了し、プログラムでは NDEBUG を定義して補足的な情報と動作が削除されるようにします。

リリース構成では、 -O2 / -O3 / -Os-g1 / -g2 も使用する必要があります。最適化により、スタックトレースは多少理解しにくいものになりますが、そうした最適化はほとんど行われません。フラグ -gN により、デバッグ情報が事後解析に使用できるようになります。リリースビルドのデバッグ情報を生成した場合、その情報は配信前に削除して、シンボル情報をタグ付きのビルドとともにバージョンコントロールシステムにチェックインする必要があります。

NDEBUG は、アサートを void に定義することで、プログラムからアサートも削除します。運用環境では、 abort によるクラッシュは容認できないためです。アサートに依存してクラッシュレポートを作成してはいけません。そのようなレポートには機密情報が含まれていることがあり、外部のシステム、たとえばWindows エラー報告などのレポートに示される可能性があるためです。クラッシュダンプが必要な場合は、機密情報の漏洩がないことを確認しながら、制御された方法によって自分で生成する必要があります。

リリースビルドでは、ログ出力も削減する必要があります。前述のガイダンスに従っていれば、コードは適切に装備されていて、最初の障害点を素早く簡単に判断できます。障害が発生したことと関連パラメーターのみを記録します。すべての NSLog 呼び出し、および同様の呼び出しを削除します。これは、機密情報がシステムロガーに記録されることがあるためです。ログのデータがバックアップや同期によって外部流出するという、さらに悪い事態も考えられます。既定の構成にロギングレベル 10 (最大詳細) が含まれていると、おそらく安定性に欠けるようになり、現場で問題を追跡することになります。これは、通常、プログラムやライブラリが運用環境で使用できる状態にないことを意味します。

テストビルド

テストビルドとリリースビルドは密接に関係しています。このビルド構成は、可能な限り運用環境に近づける必要があります。そのため、 -O2 / -O3 / -Os -g1 / -g2 を使用する必要があります。テストビルドに対して、ポジティブテストとネガティブテストの組み合わせを実行します。

また、public インターフェイスだけでなく、プログラムによって提供される関数やメソッドのすべてを使用してみる必要があります。そのため、すべてを public にする必要があります。たとえば、すべてのメンバー関数 (C++ クラス)、すべてのセレクター (Objective C)、すべてのメソッド (Java)、および、すべてのインターフェイス (ライブラリまたは共有オブジェクト) を、テストに使用できるようにする必要があります。したがって、次のようにする必要があります。

  • -Dprotected=public -Dprivate=public CFLAGS および CXXFLAGS に追加する
  • __attribute__ ((visibility ("hidden")))__attribute__ ((visibility ("default"))) に変更する

オブジェクト指向純粋主義者の多くは、private インターフェイスのテストに反対しますが、これはオブジェクト指向らしさの問題ではありません。これ (参照) は、信頼性の高いセキュアなソフトウェアの構築を目的としています。

ネガティブテストにも集中して取り組む必要があります。機能テストと回帰テスト以外のポジティブなセルフテストは、あまり役に立ちません。これについては主要業務または専門分野であるため、安全な環境で操作するときの適切なビジネスロジックを持っているはずです。悪意のある環境や有害な環境のほうが重要性が高く、そうした環境で攻撃を受けたら現場でライブラリやプログラムがどのように失敗するかを知りたいところです。

ライブラリの統合

プログラムでは、ライブラリを適切に統合して活用する必要があります。適切な統合には、受け入れテスト、ビルドシステムに応じた構成、使用する必要のあるライブラリの特定、およびライブラリの適切な使用が含まれます。適切に統合されたライブラリはコードの支えになりますが、質の悪いライブラリはプログラムの質を損ねます。求められる機能を備えている安定したライブラリはなかなか見つかりにくく、ライブラリの統合も難しいため、依存関係を最小限に抑えるようにして、可能な場合はサードパーティのライブラリの使用を避けることも必要になります。

ライブラリの受け入れテストは、実質的には存在しません。テストはコードをレビューするだけになることも、ネガティブセルフテストなどの追加手段を含めることもあります。ライブラリが不完全な場合や、標準を満たしていない場合は、ライブラリを修正するか、拒否する必要があります。受け入れテストの欠如の例として、Adobe で欠陥 Sablotron ライブラリを含めたことが挙げられます。これは、CVE-2012-1525 として文書化されました。もう 1 つの例として、 libupnp の欠陥による脆弱性がある、1000 万台~ 1 億台の埋め込みデバイスがあります。他人に責任転嫁してしまうこともよくありますが、結局のところライブラリを選ぶのは自分であり、その責任は自分にあります。

また、ライブラリを確実にビルドプロセスに統合する必要もあります。たとえば、OpenSSL ライブラリは、SSLv2、SSLv3、および圧縮を無効にして構成する必要があります。これらには欠陥があるためです。つまり、 config は、 -no-comp -no-sslv2 および -no-sslv3 を指定して実行する必要があるということです。さらにもう 1 つの例を挙げると、開発中にライブラリから追加の診断情報が提供されるようにするために、デバッグ構成では STLPort を使用して、 _STLP_DEBUG=1_STLP_USE_DEBUG_LIB=1_STLP_DEBUG_ALLOC=1_STLP_DEBUG_UNINITIALIZED=1 を定義する必要もあります。

デバッグビルドにも、追加のライブラリを使用してコード内の問題箇所を見つける機会があります。たとえば、開発中は Debug Malloc Library (Dmalloc) などのメモリチェッカーを使用することになります。Dmalloc を使用していない場合は、同等のチェッカー (GCC 4.8 の -fsanitize=memory など) を用意してください。これは、明らかに万能なものがない 1 つの領域です。

ライブラリを適切に使用することは元来難しいものですが、ドキュメントがない場合はなおさらです。ライブラリの強化に関するドキュメントがあればレビューします。適切に API を使用するために必ずライブラリのドキュメントをチェックするようにしてください。必要に応じて、コードをレビューしたり、デバッガーでライブラリコードをステップ実行して、バグやドキュメントに記載されていない機能がないことを確認します。

スタティック分析

コンパイラライターは、ソースコードからオブジェクトコードを生成するという大切な仕事をします。このプロセスでは、コードの分析に役立つ大量の追加情報が作成されます。コンパイラは分析を使用して、コードの問題点を見つけられるよう警告をプログラマーに提示しますが、忘れてはならないのは、コンパイラへの指示はプログラマーが出さなくてはならないことです。指示どおり、ステートメントにフラグが付けられたら、時間をかけて根本的な問題は何かを理解する必要があります。たとえばコンパイラは、符号付き整数と符号なし整数の比較時、 C / C++ のプロモーションの後、 -1 > 1 となったら警告を出します。また、一部の警告を切り捨てて、必要なものと不要なものを切り分ける必要があります。たとえば、インターフェイスプログラミングは一般的な C++ のパラダイムであるため、おそらく -Wno-unused-parameter は C++ コードで役に立つはずです。

クリーンなコンパイルは、セキュリティゲートの役目を果たすと考える必要があります。警告をオンにすると手に負えないと感じた場合、詳細部分の細かい点に見落としがある可能性があります。また、個々のコンパイラとプラットフォームにはそれぞれ独自の特性 (および C / C++ 標準の解釈) があるため、複数のコンパイラ / プラットフォームをサポートするようにする必要があります。Linux および Windows プラットフォーム上の Clang、GCC、ICC、および Visual Studio で、コアモジュールがクリーンコンパイルされるようになると、コードにあった多数の安定性の障害は取り除かれています。

GCC でプログラムをコンパイルするときには、プログラムのエラーを見つけるために、次に示すフラグを使用する必要があります。C ソースファイルを使用するプログラムの場合は、 CFLAGS 、C++ ソースファイルを使用するプログラムの場合は、 CXXFLAGS にオプションを追加する必要があります。Objective C の開発者は、 CFLAGS: -Wall -Wextra -Wconversion (または -Wsign-conversion)、-Wcast-align、-Wformat=2 -Wformat-security、-fno-common、-Wmissing-prototypes、-Wmissing-declarations、-Wstrict-prototypes、-Wstrict-overflow、および -Wtrampolines に、独自の警告を追加する必要があります。C++ には GCC の下での追加のチャンスがあり、そのフラグには -Woverloaded-virtual、-Wreorder、-Wsign-promo、-Wnon-virtual-dtor と、場合によっては -Weffc++が含まれます。最後に、Objective C では、 -Wstrict-selector-match および -Wundeclared-selector を含める必要があります。

Microsoft のプラットフォームでは、 /W4/Wall、および /analyze を含める必要があります。もし、 /Wall を使用しない場合、Microsoft では /W4 の使用と、C4191、C4242、C4263、C4264、C4265、C4266、C4302、C4826、C4905、C4906、および C4928 の有効化を推奨しています。最後に、 /analyze は Enterprise コード分析であり、Windows Server 2008 の Windows SDK および .NET Framework 3.5 SDK と一緒に無料で入手できます (Visual Studio Enterprise エディションは不要)。

GCC と Windows のオプションおよびフラグの詳細については、『GCC Options to Request or Suppress Warnings』、『“Off By Default” Compiler Warnings in Visual C++』、および『Protecting Your Code with Visual C++ Defenses』を参照してください。

プラットフォームセキュリティ

プラットフォームセキュリティとの統合は、防衛の構えを整えるための必須事項です。セキュリティに影響のあるバグが発見されたときには、プラットフォームセキュリティが安全の基盤となるので、常に配慮しておく必要があります。たとえば、パーサーが機能すれば、no-execute のスタックとヒープが原因でゼロデイ攻撃が起こらず、クラッシュするだけで済みます。統合を実施していないと、ユーザーや顧客はたいてい、悪意のあるコードに対して脆弱なままになります。一部のフラグについて詳しくない場合でも、それらのフラグを省略したときの影響についてはよく知っているのではないでしょうか。たとえば、Android の Gingerbreak は ELF ヘッダーの Global Offset Table (GOT) を上書きしていましたが、 -z,relro の欠陥による脆弱性がある、1000 万台~ 1 億台の埋め込みデバイスがあります。

Linux ホストでプラットフォームセキュリティを統合するときには、次のフラグを使用する必要があります。 -fPIE (コンパイラ) および -pie (リンカー)、-fstack-protector-all (または -fstack-protector)、 -z,noexecstack-z,now-z,relro。可能な場合は、 _FORTIFY_SOURCES=2 (または _FORTIFY_SOURCES=1 、Android 4.2 の場合)、 -fsanitize=address および -fsanitize=thread も使用します (最後の 2 つはデバッグ構成に使用します)。 -z,nodlopen および -z,nodump は、攻撃者が共有オブジェクトを読み込んで操作できる機会を減らせる場合があります。Gentoo などの no-exec ヒープを備えたシステムでは、 -z,noexecheap も使用する必要があります。

Windows プログラムには、 /dynamicbase/NXCOMPAT/GS、および /SafeSEH を含めて、アドレス空間配置のランダム化 (ASLR)、データ実行防止 (DEP)、スタッククッキーの使用、および例外ハンドラーの上書きの防止を確実にします。

GCC および Windows のオプションとフラグの詳細については、『GCC Options Summary』および『Protecting Your Code with Visual C++ Defenses』を参照してください。

Authors and Editors

  • Jeffrey Walton - jeffrey, owasp.org
  • Jim Manico - jim, owasp.org
  • Kevin Wall - kevin, owasp.org

Other Cheatsheets

Developer Cheat Sheets (Builder)

Assessment Cheat Sheets (Breaker)

Mobile Cheat Sheets

OpSec Cheat Sheets (Defender)

Draft Cheat Sheets