メモリ安全性
この項目「メモリ安全性」は翻訳されたばかりのものです。不自然あるいは曖昧な表現などが含まれる可能性があり、このままでは読みづらいかもしれません。(原文:英語版 "Memory safety" 2019年9月28日 (土) 16:25(UTC)) 修正、加筆に協力し、現在の表現をより自然な表現にして下さる方を求めています。ノートページや履歴も参照してください。(2019年10月) |
メモリ安全性 (メモリあんぜんせい、英語: Memory safety) は、バッファオーバーフローやダングリングポインタなどの、RAMアクセス時に発生するバグやセキュリティホールなどから保護されている状態のことである[1]。例えば、Javaは実行時エラー検出で配列の境界とポインタの参照外しを確認するので、メモリ安全であると言われている[1]。対照的に、CとC++は境界チェックを行わないメモリアドレスを直接参照するポインタを使用した任意のポインタ演算が可能なので[2]、メモリ安全ではない[3]。
歴史
[編集]メモリエラーは資源管理及びタイムシェアリングシステムでFork爆弾などの問題を回避するために検討されたことが始まりである[4]。Fingerプロトコルでのバッファオーバーフローを悪用したモリスワームが登場するまでは、これは殆ど理論上の存在であった[5]。その後、コンピュータセキュリティの分野は急速に発展し、Return-to-libc攻撃などの多くの新たなサイバー攻撃手法が次々と誕生し、これに対する防御手法として実行保護[6]やアドレス空間配置のランダム化などの技術が開発された。アドレス空間配置のランダム化は殆どのバッファオーバーフロー攻撃を防ぎ、攻撃者がアドレスを取得する際に、ヒープスプレーかその他のアプリケーションに依存する方法を利用することを必要とさせるものであるが、これが採用されるまでには長い時間を必要とした[5]。但し、これらの技術が利用されるのは、ライブラリとスタックの場所をランダムにするときに限定されることが一般的である。
手法
[編集]DieHard[7]及びこれの再設計であるDieHarder[8]とArm DDTは、自身のランダム仮想メモリページにオブジェクトを割り当てる特別なヒープアロケータであり、無効な読み取りと書き込みを停止し、これを引き起こす命令の詳細までデバッグすることができる。この保護はハードウェアのメモリ保護に依存しているので、オーバーヘッドはそれほど大きくないことが通常であるが、プログラムで割り当てを多用した場合には大きくなる可能性がある[9]。アドレス空間配置のランダム化はメモリエラーに対する確率的保護のみを提供するが、多くの場合、既存のソフトウェアにバイナリを再リンクすることで容易に実装することができる。
ValgrindのMemcheckツールは、命令セットシミュレータを使用してメモリチェック仮想マシンでコンパイル済みのプログラムを実行し、実行時メモリエラーのサブセットの検出を保証する。但し、一般的にプログラムの実行速度が40倍遅くなる[10]。更に、カスタムメモリアロケータを明示的に通知する必要がある[11][12]。
ソースコードにアクセスすることによって、ポインタに対する正当な値 (メタデータ) を収集及び追跡し、各ポインタアクセスがメタデータに対して有効なものであるのかを検証するBoehm garbage collectorなどのライブラリが存在する[13]。一般的に、メモリ安全性はガベージコレクションのトレースと、全てのメモリアクセスで実行時検証を行うことによって保証することができる。この手法にはオーバーヘッドがあるが、Valgrindの手法よりは少ない。ガベージコレクションを採用している全ての言語がこの手法を採用している[1]。CとC++では、実行時にメモリ安全性の検証を行うためにコードのコンパイル時変換を実行するCheckPointer[14]やAddressSanitizerなどの多くのツールが存在し、これらを使用した場合に実行速度が2倍遅くなる[15]。
他の手法としては、静的コード解析と自動定理証明を使用して、プログラムにメモリエラーがないことを確認する方法がある。例えば、Rustはメモリ安全性を確保するためにボローチェッカーを実装している[16]。CoverityなどのツールはCで静的コード解析を提供する[17]。C++のスマートポインタは、この手法の限定的な形式である。
境界チェック
[編集]境界チェックは操作が境界(範囲)を超えているか否かを検査する機能である。配列アクセス時のインデックスチェック (index checking) が有名である。検査は実行時におこなわれる場合が多い(コンパイル時の完全な境界チェックは実用段階に至っていない)。実行時境界チェックによる例外を用いることで、バッファオーバーフローをはじめとした多くのメモリアクセスに対して安全性が得られる。
メモリエラーの種類
[編集]様々な種類のメモリエラーが発生する可能性がある[18][19]:
- アクセスエラー: ポインタの無効な読み取りと書き込み
- バッファオーバーフロー - 範囲外の書き込みは、隣接するオブジェクトの中身や内部データ (ヒープの管理領域など)、リターンアドレスを破壊する可能性がある。
- バッファオーバーリード - 範囲外の読み取りは、機密データを明らかにしたり、攻撃者がアドレス空間配置のランダム化を回避するのに役立つ。
- 競合状態 - 共有メモリへ同時に読み取りと書き込みを行う。
- 無効ページフォールト - 仮想アドレス空間で未定義のポインタへアクセスする。ヌルポインタの参照外しは、殆どの環境では例外又はプログラムの終了を引き起こすが、メモリ保護のない場合や、ヌルポインタの使用時に大きなオフセットや負のオフセットが含まれていた場合、カーネル又はシステムが破壊される可能性がある。
- 解放後使用 - 削除されたオブジェクトのアドレスを格納しているダングリングポインタの参照外し。
- 未初期化変数: 定義されていない変数を使用した場合、望ましくない値が代入されたり、一部の言語では破損した値が代入される場合がある
- メモリリーク: メモリ使用量が追跡されていない又は誤って追跡されている場合
- スタックの枯渇 - 一般的に、深すぎる再帰呼出しによってプログラムがスタック領域を使い果たした場合に発生する。通常、ガードページはプログラムを停止し、メモリが破壊されることを防ぐが、スタックフレームが大きな関数ではページをバイパスする場合がある。
- ヒープの枯渇 - プログラムが利用可能なメモリよりも多くのメモリを確保しようとする。一部の言語では、確保後にこの状態を手動で確認する必要がある。
- 二重解放 - freeを繰り返し呼び出すと、同じアドレスにある新しいオブジェクトが早期解放される場合がある。正確なアドレスが再利用されていない場合、他の破壊が発生する可能性があり、特にFree listを使用するアロケータでは可能性が高い。
- 無効な解放 - 無効なアドレスをfreeに渡すと、ヒープが破壊される可能性がある。
- 不一致な解放 - 複数のアロケータが使用されている場合、異なるアロケータの解放用の関数でメモリを解放しようとする[20]。
- 不要なエイリアシング - 無関係な目的のためにメモリロケーションが2回割り当て及び変更される場合。
脚注
[編集]- ^ a b c Dhurjati, Dinakar; Kowshik, Sumant; Adve, Vikram; Lattner, Chris (2003-01-01). “Memory Safety Without Runtime Checks or Garbage Collection” (英語). Proceedings of the 2003 ACM SIGPLAN Conference on Language, Compiler, and Tool for Embedded Systems (ACM): 69–80. doi:10.1145/780732.780743 2019年10月26日閲覧。.
- ^ “How C Makes It Hard To Check Array Bounds”. Dr. Dobb's. 2019年8月7日時点のオリジナルよりアーカイブ。2017年3月13日閲覧。
- ^ Akritidis, Periklis (2011-06). Practical memory safety for C. University of Cambridge, Computer Laboratory. ISSN 1476-2986. UCAM-CL-TR-798 2019年10月26日閲覧。.
- ^ Anderson, James P. Computer Security Planning Study. 2. Electronic Systems Center. ESD-TR-73-51 2019年10月26日閲覧。.
- ^ a b van der Veen, Victor; dutt-Sharma, Nitish; Cavallaro, Lorenzo; Bos, Herbert. “Memory Errors: The Past, the Present, and the Future”. Lecture Notes in Computer Science 7462 (RAID 2012): 86–106. doi:10.1007/978-3-642-33338-5_5 2019年10月26日閲覧。.
- ^ “Defeating Solar Designer's Non-executable Stack Patch”. insecure.org. 2019年10月26日閲覧。
- ^ Berger, Emery D; Zorn, Benjamin G (2006-01-01). “DieHard: Probabilistic Memory Safety for Unsafe Languages” (英語). Proceedings of the 27th ACM SIGPLAN Conference on Programming Language Design and Implementation (ACM): 158–168. doi:10.1145/1133981.1134000 2019年10月26日閲覧。.
- ^ Novark, Gene; Berger, Emery D (2010-01-01). “DieHarder: Securing the Heap”. Proceedings of the 17th ACM Conference on Computer and Communications Security (ACM): 573–584. doi:10.1145/1866307.1866371 2019年10月26日閲覧。.
- ^ “Memory Debugging in Allinea DDT”. 2015年2月3日時点のオリジナルよりアーカイブ。2019年10月26日閲覧。
- ^ “Using Valgrind's Memcheck Tool to Find Memory Errors and Leaks”. computing.llnl.gov. 2018年11月7日時点のオリジナルよりアーカイブ。2017年3月13日閲覧。
- ^ “Memcheck: a memory error detector” (英語). Valgrind User Manual. valgrind.org. 2019年10月26日閲覧。
- ^ “Why custom allocators/pools are hard”. Proper Fixation. 2019年10月26日閲覧。
- ^ “Using the Garbage Collector as Leak Detector” (英語). www.hboehm.info. 2019年10月26日閲覧。
- ^ “Semantic Designs: CheckPointer compared to other safety checking tools”. www.semanticdesigns.com. Semantic Designs, Inc.. 2019年10月26日閲覧。
- ^ “AddressSanitizerPerformanceNumbers”. GitHub. 2019年10月26日閲覧。
- ^ “Validating References with Lifetimes” (英語). The Rust Programming Language. doc.rust-lang.org. 2019年10月26日閲覧。
- ^ Bessey, Al; Engler, Dawson; Block, Ken; Chelf, Ben; Chou, Andy; Fulton, Bryan; Hallem, Seth; Henri-Gros, Charles et al. (2010-02-01). “A few billion lines of code later”. Communications of the ACM 53 (2): 66–75. doi:10.1145/1646353.1646374 2019年10月26日閲覧。.
- ^ “How to Avoid, Find (and Fix) Memory Errors in your C/C++ Code”. Cprogramming.com. 2019年10月26日閲覧。
- ^ “CWE-633: Weaknesses that Affect Memory” (英語). Community Weakness Enumeration. MITRE. 2019年10月26日閲覧。
- ^ “CWE-762: Mismatched Memory Management Routines” (英語). Community Weakness Enumeration. MITRE. 2019年10月26日閲覧。