web-dev-qa-db-ja.com

純粋なCの安全なmemcpy

バッファオーバーフローは新しいものではありません。それでも、それらはまだ頻繁に、特にネイティブ(つまり、管理されていない)コードに表示されます...

根本的な原因の一部は、memcpy、strcpy、strncpyなどのC++ステープルを含む「安全でない」関数の使用です。これらの関数は制約のないバッファーを直接処理するため安全ではないと見なされ、注意深い境界チェックを行わないと、通常、ターゲットバッファーが直接オーバーフローします。

MicrosoftはSDLを介してこれらの「安全でない」関数の使用を禁止し、C++に代わる関数を提供しています。 strcpyのstrcpy_s、memcpyのmemcpy_sなど(環境によって異なります)。 Visual Studioの最新バージョンでは、これを自動的に行うこともできます...

しかし、「純粋な」C(つまり、C++ではない)はどうでしょうか?
そして特に、MS以外のプラットフォームについてはどうですか-LinuxやWindows上の非VSコンパイラを含めて...
これらに代わるより安全な代替機能はありますか?推奨される回避策(単により多くの境界チェックを行う以外に...)?
それとも、memcpyの使用を繰り返し続ける運命にあるのでしょうか?

12
AviD

そして特に、MS以外のプラットフォームについてはどうでしょう-LinuxやWindows上のVS以外のコンパイラも含みます.

AppleのコアOSの一部として、 CoreFoundation Lite と呼ばれるクロスプラットフォームのオープンソースプロジェクトがあり、バイトブロック、文字列、およびその他の基本的なデータ型を安全に操作するためのCタイプを提供しています。この説明に関連して、型チェック、境界チェック、メモリ管理を実装し、可変オブジェクトと不変オブジェクトを区別します。

推奨される回避策(単により多くの境界チェックを行う以外に...)?

ちなみに、Microsoftの_unsafe.h_にはGCCサポートが含まれています。これを行う方法は、GCCのpoisonプラグマを使用して、安全でない関数を使用するたびにエラーを発生させることです。それについては、自分が書いた本でも書きました(免責事項:私が書いたものです)。

それとも、memcpyの使用を繰り返し続ける運命にあるのでしょうか?

基本的に、解決策は「それをしないでください」です。いわゆる安全な実装により、自分のものではないメモリへの落書きを防ぐことができますが、それでも多くの「悪用ケース」を開いたままにしておきます。境界チェックは問題のほんの一部です。たとえば、あなたがmalloc(1024)で、バイト56へのポインタを作成し、それを16バイト長の特定の構造体への参照として扱うとしましょう。次に(「安全に」)元のポインタに58バイトをコピーすると、コピー操作は成功しますが、構造が壊れます。破損を検出できますか?多分ない:それはまだ有効な構造のようにlookかもしれません(特にマジックナンバーを最後に置くか、マジックナンバーがない場合) 。

「スタックスマッシング」と「ヒープ破損」の問題は、実際には「メモリを大きな型指定されていないバッグのバイトとして扱う」という問題の特別なケースです。この問題の他のいくつかの例は次のとおりです。

  • STR30-C。文字列リテラルを変更しないでください 変更可能なC文字列と不変のC文字列は "big bag o 'bytes"の観点からは同じに見えますが、実際に文字列リテラルを編集しようとすると、未定義の動作が発生します。ただし、コードは_char *_が文字配列を表すか文字列リテラルを表すかを判別できないため、正しい動作を保証するための苦痛はすべて、コードを呼び出すことによって処理する必要があります。
  • MEM01-C。 free() の直後のポインタに新しい値を格納します。もはやあなたのものではないメモリのブロックへの参照を持つことが可能であり、一般にそれが有効であるかどうかを見分けるのが難しいためです。
  • MEM05-C。大量のスタック割り当てを回避する これは、境界を正しく設定する必要があると主張し、残りの部分が適切に処理されると主張する場合に特に陽気です。この問題により、完全に内部的に一貫した正しいコードを使用して、スタックを破壊することができます。

リストは続きます: CERT C Secure Coding Standard の残りの部分を参照してください。

11
user185

ここで明確にしましょう。

  1. insecure context の問題であり、特定の関数の「使用」の場合ではありません。失うものがほとんどなく、setuidまたはそのようなフラグがないユーザーとして実行しているプロセスでmemcpyを安全に使用しない場合、私ができる「最悪の」ことは、そのユーザーのシェルを取得してそこから移動することです。特権の昇格を達成するには、だまして何かを攻撃して、より高い特権を与える必要があります。
  2. C/C++は「危険なツール」ではありません。それらはツールです。危険なDIY器具について適切なメタファーを挿入します。

実際のところ、Rookがすでに述べたように、C/C++やその他のそのような他のマシンにコンパイルされた言語は世界で活躍しています。これらは、高速システム、オペレーティングシステム、システムサービスなどを構築するためのものです。必要に応じて、独自のメモリを管理することができます。あなたがコントロールしています。

何らかの形で自動メモリ管理を導入しない限り、割り当てられたメモリを超えてしまった場合、本当にうまくいく方法はありません。それでは、コンテナーを紹介しましょう。これで、これまでにあったすべてのメモリアクセスコールをチェックする必要があります。その特定の機能の範囲内ですか?いくつのオブジェクトがそれを指していますか? theyはどこにありますか?元の参照の範囲外にポインタを置くことはできますか?できる場合、それをどのように追跡できますか?すぐに、仮想マシンができたので、管理された言語ができました。

また、StackOverflowで説明したように、いずれにしても安全でない方法で_memcpy_s_を呼び出すことができます。 本当に基本的な問題は解決しませんが、間違いを少し難しくします。

それが違いです。 C/C++は、あなたが何かをするのに必要なすべての力を備えた豪華なアセンブリです。 Java/Pythonなどは、スピードとパワーという価格でお客様を保護します。

あなたは今日の過程で繰り返し言ってきました(私はSOバージョンもフォローしています)が、他のC/C++で安全に開発する方法についてはまだ答えがないと言っていますLinuxのようなシステムです。まあ、まずVS2005/2008では、私は習慣的に_CRT_SECURE_NO_DEPRECATE_を設定していますが、とにかく、次のことを実行できます。

  1. StackOverflowで尋ねます 。あなたはメモを取り、学び、読み、そして再度読みます。
  2. あなたのコンパイラは(ほとんど)あなたの友達です。それを聞きなさい。警告を使用します。それらをエラーに変えます。つまり、_cl /W4_および_gcc -Wall -Werror -pedantic -std=c99_です。はい、エラーメッセージに関してはOTTです。しかし、それぞれを説明できず、なぜ無視しているのかを正当化できない場合、自分のコードが理解できません。
  3. メモリ割り当てを確認してください。 valgrindのデフォルトの呼び出しでは、割り当てと割り当て解除がチェックされ、メモリが失われていないことが示されます。メモリリークがある場合は、コードについて十分に考えていません。これは、1つずれたエラー、無効な境界チェックなどがあることを示す良い兆候です。
  4. 再度valgrindを使用してください。私はこれを拾ったばかりですが、見てください valgrindに実験的なオーバー/アンダーランチェッカーがあります 。たとえば、スタックの最後でクラッシュするか(ほとんどのローカル変数が割り当てられていると考えられる場合)、またはbrk() 'dヒープの外側でクラッシュするかどうかがわかります。
  5. splint a.k.a. secure lintを使用し、その出力をメモします。繰り返しになりますが、出力について説明できない場合は、コードを理解できません。
  6. C Secure Coding Standardを内部でダイジェストします。ここで特に関連:

    • STR31-C。文字列のストレージに文字データとヌルターミネータのための十分なスペースがあることを保証します。

      データを保持するのに十分な大きさでないバッファにデータをコピーすると、バッファオーバーフローが発生します。 NULLで終了するバイト文字列(NTBS)に限定されませんが、NTBSデータの操作時にバッファオーバーフローが発生することがよくあります。このようなエラーを防ぐには、切り捨てによってコピーを制限するか、できれば、宛先がコピーされる文字データとヌル終了文字を保持するのに十分なサイズであることを確認してください。

    • STR35-C。無制限のソースから固定長配列にデータをコピーしないでください

      無制限のコピーを実行する関数は、多くの場合、適切なサイズになるように外部入力に依存しています。このような仮定は誤りであることが判明し、バッファオーバーフローが発生する可能性があります。このため、無制限のコピーを実行する可能性のある関数を使用するときには注意が必要です。

      SplintはCERTと同様のガイダンスを提供します。 memcpyは、STR35-Cの最初の例では準拠ソリューションと見なされることに注意してください

  7. C++を使用している場合は、_std::string_および_boost::shared_ptr_(および関連するもの、適切なものを使用)を使用します。 Cとの相互作用を除いて、C++では_malloc'ing_およびmemcpying文字列になる引数は絶対にありません。それでもstring.c_str()を使用して、操作をC++に任せてください。
  8. 可能な場合は動的にリンクします。サードパーティのコードをアプリに静的にリンクしていて、セキュリティ上の問題があることが判明した場合は、それも原因であり、画像も再配布する必要があります。共有オブジェクトは一般的に便利なだけでなく、デフォルトでセキュリティアップデートも取得できます。私はあなたができない場合があることを知っています、それが私が「可能な場合」と言う理由です。
  9. これを開発サイクルに組み込みます。
  10. 何かお見逃しなく。
  11. 関連する可能性のある関連するセキュリティコミュニティの更新に遅れないようにしてください。
  12. セキュリティコミュニティに親切に。 sec脆弱性を認識し、修正するための手順を実行します。実行する価値がある場合、すべてのコードにバグがあります(またはバグがありました)。そのような単純な。

結局のところ、問題は「安全な」関数で解決されるとは思いません。問題は、いくつかの適切なツール、重要なバージョン(最新のベータ版とリリース候補)からの貧弱なコードを拒否する適切な開発プロセス、そして最終的には良い実践/現在の問題の認識を使用することで解決されると思います。

最後に、_memcpy_s_はC99標準の一部ではありません。これはCライブラリの拡張であり(コアの一部ではないため)、使用しているプラ​​ットフォーム上での動作は保証されていません。 memcpyです。クロスプラットフォームでコンパイルする必要のあるソフトウェアプロジェクトの場合、それはおそらく、使用する関数を決定する要因になります。

11
Ninefingers

包括的な答えは、C(またはC++)の使用をやめることです。それは古風で全く危険なツールです。安全性が統合された言語に切り替える(Java、C#、Scheme、OCaml、Python ...選択肢は大きい)。

バッファオーバーフローはバグであり、「プログラマは自分が何をしているのか十分に認識していない」と呼ばれる一般的なバグの一種です。境界チェック(またはmemcpy_s()の使用)を備えた言語は、そのようなバグを削除しません。結果が少し悲惨になるだけです(たとえば、例外がスローされ、通常は即座にスレッドが終了することを意味します)。それは安全ベルトのようなものです。安全ベルトは自動車事故を防止するのではなく、あなたの車が忘却に陥る間あなたを生かそうとするだけです。したがって、常にシートベルトを着用する必要があるのと同じ理由で、バインドされたチェック(このようなチェックを透過的に組み込む特定の言語)を使用する必要があります。

Cの場合:memcpy_s()とそのilkは、実際にはISO/IEC TR 24731(2005年の標準)で定義されているため、ある時点でCおよびC++コンパイラーに浸透するはずです。しかし、Microsoftだけが現在それらを推進しているようです。 「文字列」関数(strcpy()およびstrcat())には、標準(C99)関数(strncpy()およびstrncat())といくつかのBSDオペレーティングシステム(主にOpenBSD)は、やや使いやすいAPI(strlcpy()およびstrlcat())を持つバリアントを使用しています。ただし、これらの関数はどれもバグを修正しません。これは、プログラマーがデータを小さすぎるバッファーに入れようとしていることです。彼らは単にプログラマにaware彼のバッファのサイズが不適切であるかもしれないという事実を知らせているだけです。

4
Thomas Pornin

Linuxカーネルはstrcpy()strcat()のような、いわゆる安全でない関数を何千呼び出しますが、バッファこのコードベースのオーバーフローは非常にまれです。実際、オーバーヘッドを減らすため、「安全でない関数」は静的テキストのコピーに適しています。 「安全な関数呼び出し」を安全でない方法で使用できます、strncpy(dest,source,strlen(source));。機能の選択は、システムの安全性に何の意味もありません。

この問題は、最新のメモリ保護システムで解決されます。これらは暗黙的であり、コードを書き直す必要はありません。カナリア、ASLR、NXゾーンを備えた最新のソフトウェアでは、バッファオーバーフローだけではあまり役に立ちません。 Dangling pointers where where used in the 2010 pwn2own for Windows 7 and IE8 and 最新のフラッシュ0 -dayは、バッファオーバーフローをまったく使用しませんでした

3
rook

しかし、「純粋な」C(つまり、C++ではない)はどうでしょうか?

Memcpy_sなどの関数はmemcpyの「安全な」置換として実際に適格であるとは思わないが、どちらの方法でも純粋な「C」内で同等に適用可能であり、それらについて「C++」であるものはあまりありません。また、それらはプラットフォーム固有ではありません(プラットフォームCライブラリの一部であることに依存できない場合でも、それらのポータブルバージョンを作成することはそれほど難しいことではありません)。

安全性に関する限り、memcpyとcoはdefinedであり、メモリ上で任意の落書きが可能です。それが目的であり、サイズパラメータを追加してもそれは変わりません。

同時に、「C」のような言語が唯一の実用的なオプションであるシステムはまだたくさんあるので、「まだ 'C'/C++を使用しない」と言っても妥当ではないと思います。

本当の疑問は、memcpyよりもセーフティネットが多く、CおよびC++から使用できるバッファ管理の安全な代替策があるかどうかです。ええ、確かに。 'C' APIまたはC++クラスを定義して、境界チェック済みバッファーと配列の割り当て、管理、アクセスを行うことができます。これは、Java VM他はアクセスしています。

Objective-Cのような、マネージコードではない言語もありますが、memcpyなどが利用可能であるにもかかわらず、マネージコードの安全面の多くを備えています。これは、Cocoaフレームワークがより安全な代替策の使用を奨励し、少なくとも許可する傾向があるためです。

2
frankodwyer

Cで文字列を処理するためのもう1つの「安全な」APIは Daniel J Bernsteinの文字列ライブラリ です。これを私に指摘してくれた AndréPang の功績です。

1
user185

人々は間違いを犯すのをやめません。いわゆる「安全な実装」やThomas Porninが言及した言語のリストでさえ、いくつかの脆弱性が存在する場所があります。現在の回答では、C/C++の詳細については掘り下げたくありません。これは、すでに述べられている内容の補遺としてはあまり意味がありません。ここでは、別の解決策はバグ自体の防止ではなく、悪用による影響の軽減であることを指摘したいと思います。これは、セキュリティのためにできることのほんの一部です。最良の例として、Googleのブラウザを見てくださいChrome-彼らはサンドボックスを実装しています。別の可能性は、コンパイラのセキュリティオプションとOSレベルの悪用の緩和策です。これらは、非常に最初の文であり、バグの導入はすべてのソフトウェア開発の避けられない部分であるという事実の承認です。

0
anonymous