web-dev-qa-db-ja.com

範囲外の配列にアクセスすることはどれほど危険ですか?

(Cで)境界外の配列にアクセスすることはどれほど危険ですか?時々、配列の外側から読んだり(プログラムの他の部分やそれ以上の部分で使用されているメモリにアクセスできるようになりました)、配列の外側のインデックスに値を設定しようとしています。プログラムがクラッシュすることもありますが、単に実行されるだけで、予期しない結果が生じることもあります。

今、私が知りたいのは、これは本当にどれほど危険なのか?それが私のプログラムにダメージを与えても、それほど悪くはありません。一方で、なんとかまったく関係のないメモリにアクセスできたために、プログラムの外で何かが壊れた場合、それは非常に悪いことです。私はたくさんの「何でも起こり得る」を読みました、 「セグメンテーションは最も悪い問題であるかもしれません」 、「あなたのハードディスクがピンク色になり、ユニコーンがあなたの窓の下で歌っているかもしれません」しかし、本当に危険なのは何ですか?

私の質問:

  1. 配列の外側から値を読み取ると、プログラム以外に何かを損傷する可能性がありますか?物事を見るだけでは何も変わらないことを想像しますか、それとも、たまたま到達したファイルの「最後に開いた時間」属性を変更しますか?
  2. 配列の外に値を設定すると、プログラム以外の何かに損傷を与える可能性がありますか?これから Stack Overflow question 私は、あらゆるメモリの場所にアクセスすることが可能であり、安全性の保証がないことを収集します。
  3. XCode内から小さなプログラムを実行するようになりました。それは自分のメモリの外に到達できない私のプログラムの周りにいくつかの追加の保護を提供しますか? XCodeに害を及ぼすことはありますか?
  4. 本質的にバグのあるコードを安全に実行する方法に関する推奨事項はありますか?

OSX 10.7、Xcode 4.6を使用しています。

218
ChrisD

ISO C標準(言語の公式定義)に関する限り、境界外の配列へのアクセスには、「undefined behavior」があります。これの文字通りの意味は次のとおりです。

移植性のない、またはエラーのあるプログラム構成、またはエラーのあるデータを使用した場合の動作。この国際規格は要件を課していない

非規範的なメモはこれを拡張します:

予測できない結果を伴う状況を完全に無視することから、環境に特有の文書化された方法での診断またはプログラム実行中の動作(診断メッセージの発行の有無にかかわらず)、翻訳または実行の終了(発行を伴う)診断メッセージの)。

それが理論です。現実は何ですか?

「最良の」ケースでは、現在実行中のプログラムが所有する(プログラムの誤動作を引き起こす可能性がある)、またはnot現在実行中のプログラムが所有している(これにより、セグメンテーションフォールトなどでプログラムがクラッシュする可能性があります)。または、プログラムが所有するメモリに書き込もうとするかもしれませんが、それは読み取り専用とマークされています。これにより、おそらくプログラムがクラッシュします。

これは、同時に実行されているプロセスを互いに保護しようとするオペレーティングシステムでプログラムが実行されていることを前提としています。コードが「ベアメタル」で実行されている場合、たとえばOSカーネルまたは組み込みシステムの一部である場合、そのような保護はありません。あなたの不正なコードは、その保護を提供するはずでした。その場合、損傷の可能性はかなり大きくなり、場合によっては、ハードウェア(または近くの物や人)の物理的損傷も含まれます。

保護されたOS環境でも、保護が常に100%とは限りません。たとえば、特権のないプログラムがルート(管理)アクセスを取得できるようにするオペレーティングシステムのバグがあります。通常のユーザー特権であっても、誤動作しているプログラムは過剰なリソース(CPU、メモリ、ディスク)を消費し、システム全体をダウンさせる可能性があります。多くのマルウェア(ウイルスなど)は、バッファオーバーランを悪用してシステムへの不正アクセスを取得します。

(歴史的な例: コアメモリ の古いシステムでは、タイトループ内の単一のメモリ位置に繰り返しアクセスすると、文字通りメモリのチャンクが溶けてしまうことがあると聞いたことがあります。 CRTディスプレイ、およびドライブキャビネットの高調波周波数でディスクドライブの読み取り/書き込みヘッドを移動し、テーブルを横切って床に落下させる)

そして、常に Skynet があります。

一番下の行はこれです:何か悪いを故意に行うプログラムを書くことができれば、少なくとも理論的にはバグのあるプログラムが同じことをする可能性があります偶然

実際には、veryMacOS Xシステムで実行されているバグのあるプログラムがクラッシュよりも深刻なことをすることはほとんどありません。しかし、バグのあるコードが本当に悪いことをするのを防ぐ完全にすることはできません。

120
Keith Thompson

一般に、現在のオペレーティングシステム(とにかく人気のあるオペレーティングシステム)は、仮想メモリマネージャーを使用して、保護されたメモリ領域ですべてのアプリケーションを実行します。プロセスに割り当てられた/割り当てられた領域の外にあるREAL空間に存在する場所を単に読み書きするのは(言うまでもなく)非常に簡単ではないことがわかります。

直接の答え:

1)読み取りによって別のプロセスが直接損傷を受けることはほとんどありませんが、プログラム/プロセスの暗号化、復号化、または検証に使用されるKEY値を読み取った場合、間接的にプロセスに損傷を与える可能性があります。読み込んでいるデータに基づいて決定を下す場合、範囲外の読み込みはコードに多少の悪影響/予期しない影響を与える可能性があります

2)メモリアドレスによってアクセス可能なローションに書き込むことで何かを実際に損傷することができる唯一の方法は、書き込み先のメモリアドレスが実際にハードウェアレジスタ(実際にはデータストレージではなく、一部を制御するための場所である場合ハードウェアの)RAMの場所ではありません。すべての事実において、再書き込み可能でない(またはその性質のもの)一度だけプログラム可能な場所を書いていない限り、通常は何かに通常損傷を与えることはありません。

3)通常、デバッガー内から実行すると、デバッグモードでコードが実行されます。デバッグモードで実行すると、実践的でない、またはまったく違法と思われる何かを行ったときに、コードがより速く停止する傾向があります(常にではありません)。

4)マクロを使用したり、既に配列インデックスの境界チェックを組み込んだデータ構造を使用したりするなど。

ADDITIONAL上記の情報は、メモリ保護ウィンドウを備えたオペレーティングシステムを使用しているシステムにのみ有効であることを付け加えます。組み込みシステム、またはメモリ保護ウィンドウ(または仮想アドレス指定ウィンドウ)を持たないオペレーティングシステム(リアルタイムまたはその他)を使用するシステム用のコードを記述する場合、メモリの読み取りと書き込みにはさらに注意する必要があります。また、これらの場合、セキュリティの問題を回避するために、SAFEおよびSECUREコーディング手法を常に採用する必要があります。

25
trumpetlicks

境界をチェックしないと、セキュリティホールなどのsideい副作用が発生する可能性があります。いものの1つは 任意のコード実行 です。古典的な例:固定サイズの配列があり、strcpy()を使用してユーザー指定の文字列をそこに置くと、ユーザーはバッファーをオーバーフローさせ、CPUのコードアドレスを含む他のメモリ位置を上書きする文字列を与えることができます関数が終了すると戻るはずです。

つまり、ユーザーがプログラムに基本的にexec("/bin/sh")を呼び出す文字列を送信して、シェルに変換し、すべてのデータを収集し、マシンをボットネットノードに変換するなど、システム上で必要なものをすべて実行できます。

これを行う方法の詳細については、「 楽しみと利益のためにスタックを壊す 」を参照してください。

9
che

あなたが書く:

私は多くの「何でも起こり得る」、「セグメンテーションは最も悪い問題であるかもしれない」、「ハードディスクがピンク色になり、ユニコーンがあなたの窓の下で歌うかもしれない」を読んだ、これはすべてニースですが、本当に危険なのは何ですか?

銃を装填してください。特定の照準や射撃をせずに窓の外に向けます。危険は何ですか?

問題は、あなたが知らないということです。コードがプログラムをクラッシュさせる何かを上書きする場合、それはそれを定義された状態に停止するので問題ありません。ただし、クラッシュしない場合、問題が発生し始めます。プログラムの制御下にあるリソースはどれですか?プログラムの制御下にあるリソースはどれですか?このようなオーバーフローによって引き起こされた少なくとも1つの大きな問題を知っています。問題は、実稼働データベースの関連のない変換テーブルを台無しにする、一見無意味な統計関数にありました。結果は、いくつかのveryその後のクリーンアップでした。実際、この問題でハードディスクがフォーマットされていれば、はるかに安価で簡単に処理できたはずです...言い換えると、ピンクのユニコーンが最も問題にならないかもしれません。

オペレーティングシステムがあなたを保護するという考えは楽観的です。可能であれば、範囲外の書き込みを避けるようにしてください。

8
Udo Klein

Rootまたは他の特権ユーザーとしてプログラムを実行しなくても、システムに損害を与えることはありません。したがって、一般的にこれは良い考えかもしれません。

ランダムなメモリ位置にデータを書き込むことにより、各プロセスが独自のメモリ空間で実行されるため、コンピュータで実行されている他のプログラムを直接「破損」することはありません。

プロセスに割り当てられていないメモリにアクセスしようとすると、オペレーティングシステムはセグメンテーションフォールトでプログラムの実行を停止します。

そのため、直接(rootとして実行し、/ dev/memなどのファイルに直接アクセスすることなく)、プログラムがオペレーティングシステムで実行されている他のプログラムに干渉する危険はありません。

それにも関わらず、おそらくこれは危険という観点から聞いたことでしょう-偶然にランダムなデータをランダムなメモリの場所に書き込むことで、破損する可能性のあるものはすべて破損する可能性があります。

たとえば、プログラムは、プログラムのどこかに保存されているファイル名で指定された特定のファイルを削除する場合があります。誤ってファイル名が保存されている場所を上書きしてしまった場合は、代わりに非常に異なるファイルを削除する可能性があります。

7
mikyra

コードをテストするときに Valgrindmemcheck ツールを使用してみてください。スタックフレーム内の個々の配列境界違反をキャッチしませんが、キャッチする必要があります。他の多くの種類のメモリの問題。単一の機能の範囲外の微妙でより広い問題を引き起こすものを含む。

マニュアルから:

Memcheckはメモリエラー検出器です。 CおよびC++プログラムで一般的な次の問題を検出できます。

  • すべきではないメモリへのアクセス、例えばヒープブロックのオーバーランとアンダーラン、スタックの最上部のオーバーラン、解放されたメモリへのアクセス。
  • 未定義の値、つまり初期化されていない値、または他の未定義の値から派生した値を使用します。
  • ヒープブロックの二重解放などのヒープメモリの不正な解放、またはmalloc/new/new []とfree/delete/delete []の使用の不一致
  • Memcpyおよび関連する関数でのsrcおよびdstポインターの重複。
  • メモリリーク。

ETA:しかし、Kazの答えが示すように、それは万能薬ではなく、特にexcitingアクセスパターン。

4
Aesin

システムレベルのプログラミングや組み込みシステムのプログラミングを行う場合、ランダムなメモリ位置に書き込むと非常に悪いことが起こる可能性があります。古いシステムと多くのマイクロコントローラーはメモリマップされたIOを使用するため、特に非同期で行われる場合、周辺レジスタにマップするメモリ位置への書き込みは大混乱を招く可能性があります。

例は、フラッシュメモリのプログラミングです。メモリチップのプログラミングモードは、チップのアドレス範囲内の特定の場所に特定の値のシーケンスを書き込むことで有効になります。それが進行中に別のプロセスがチップの他の場所に書き込むと、プログラミングサイクルが失敗します。

場合によっては、ハードウェアがアドレスをラップします(アドレスの最上位ビット/バイトは無視されます)ので、物理アドレス空間の終わりを超えてアドレスに書き込むと、実際にはデータが途中で書き込まれます。

そして最後に、MC68000などの古いCPUは、ハードウェアリセットによってのみ再び稼働できるようになるまでロックできます。数十年間それらに取り組んできませんでしたが、例外を処理しようとしているときにバスエラー(存在しないメモリ)が発生したときに、ハードウェアリセットがアサートされるまで停止するだけだと思います。

私の最大の推奨事項は、製品の露骨なプラグインですが、個人的な関心はなく、どのような形でも提携していません。 Lintは、こうした種類のエラーを検出するだけでなく、悪い習慣についてconstantly harpingすることで、より優れたC/C++プログラマーをあなたから追い出します。

また、誰かからコピーを入手できる場合は、MISRA Cコーディング標準を読むことをお勧めします。私は最近のものを見たことがありませんが、昔は彼らがカバーすることをすべき/すべきでない理由を説明してくれました。

Dunnoはあなたについてですが、2回目または3回目には、アプリケーションからコアダンプまたはハングアップを取得します。それを作成した会社についての私の意見は半減します。 4回目または5回目と、パッケージが何であれ棚器になり、パッケージ/ディスクの中央に木製の杭を打ち込んで、それが戻ってくるのを忘れないようにしています。

3
Dan Haynes

Objective-CのNSArraysには、特定のメモリブロックが割り当てられます。配列の境界を超えると、配列に割り当てられていないメモリにアクセスすることになります。これの意味は:

  1. このメモリには任意の値を設定できます。データ型に基づいてデータが有効かどうかを知る方法はありません。
  2. このメモリには、秘密鍵やその他のユーザー資格情報などの機密情報が含まれる場合があります。
  3. メモリアドレスが無効であるか、保護されている可能性があります。
  4. メモリは、別のプログラムまたはスレッドによってアクセスされているため、値が変化する可能性があります。
  5. 他には、メモリマップドポートなどのメモリアドレス空間を使用します。
  6. 不明なメモリアドレスにデータを書き込むと、プログラムがクラッシュし、OSのメモリスペースが上書きされ、一般的にSunが破裂する可能性があります。

プログラムの観点から、コードがいつ配列の境界を超えているかを常に知りたいです。これにより、不明な値が返され、アプリケーションがクラッシュしたり、無効なデータが提供されたりする可能性があります。

3
Richard Brown

私は、意図的にCコードから配列の終わりを超えてアクセスするコードを生成するDSPチップ用のコンパイラを使用しています。

これは、反復の終わりが次の反復のためにデータをプリフェッチするようにループが構造化されているためです。そのため、最後の反復の最後にプリフェッチされたデータムは実際には使用されません。

そのようなCコードを記述すると、未定義の動作が呼び出されますが、それは最大限の移植性に関係する標準ドキュメントの形式にすぎません。

多くの場合そうではありませんが、境界外にアクセスするプログラムは巧妙に最適化されていません。それは単にバグです。コードはガベージ値をフェッチし、前述のコンパイラーの最適化されたループとは異なり、コードはその後の計算でuses値を使用するため、その値が破損します。

そのようなバグをキャッチする価値があるので、その理由だけでも動作を未定義にする価値があります。実行時に「main.cの42行目の配列オーバーラン」のような診断メッセージを生成できます。

仮想メモリを備えたシステムでは、後続のアドレスが仮想メモリのマップされていない領域にあるように、アレイが割り当てられることがあります。アクセスすると、プログラムが爆撃されます。

余談ですが、Cでは、配列の末尾を過ぎたポインターを作成することが許可されていることに注意してください。そして、このポインターは、配列の内部へのポインターよりも大きく比較する必要があります。これは、Cの実装ではメモリの最後に配列を配置できないことを意味します。1プラスのアドレスはラップして、配列内の他のアドレスより小さく見えます。

それにもかかわらず、初期化されていない値または範囲外の値へのアクセスは、最大限の移植性がない場合でも、有効な最適化手法である場合があります。これは、たとえば、Valgrindツールが初期化されていないデータへのアクセスを報告するのではなく、その値が後でプログラムの結果に影響を与える何らかの方法で使用される場合にのみ報告する理由です。 「xxx:nnnの条件分岐は初期化されていない値に依存します」などの診断メッセージが表示され、その発生元を追跡するのが難しい場合があります。そのようなアクセスがすべて即座にトラップされた場合、正しく最適化されたコードだけでなく、コンパイラーによって最適化されたコードから多くの誤検知が発生します。

そういえば、私はLinuxに移植してValgrindで実行したときにこれらのエラーを発生させていたベンダーのコーデックを使用していました。しかし、ベンダーは、使用されている値のbitsが実際には初期化されていないメモリからのものであり、それらのビットはロジックによって慎重に回避されていると確信しました。値の良いビットのみが使用され、Valgrind個々のビットまで追跡する機能がありません。初期化されていない素材は、エンコードされたデータのビットストリームの終わりを過ぎてWordを読み込んだものですが、コードはストリーム内のビット数を認識しており、実際よりも多くのビットを使用しません。ビットストリームアレイの末尾を超えてアクセスしても、DSPアーキテクチャに害を及ぼさないため(アレイの後に仮想メモリはなく、メモリマップドポートはなく、アドレスはラップしません)、これは有効な最適化手法です。

「未定義の動作」は実際にはあまり意味がありません。ISOCによれば、C標準で定義されていないヘッダーを単に含めるか、プログラム自体またはC標準で定義されていない関数を呼び出すことが未定義の例です動作。未定義の動作とは、「地球上の誰にも定義されていない」という意味ではなく、「ISO C標準に定義されていない」という意味です。しかし、もちろん、未定義の動作は、実際には絶対に誰にも定義されていない場合があります。

2
Kaz

あなた自身のプログラムに加えて、あなたは何も壊さないと思います。最悪の場合、あなたはカーネルがあなたのプロセスに割り当てなかったページに対応するメモリアドレスから読み書きしようとし、適切な例外を生成します。殺されます(つまり、あなたのプロセス)。

1
jbgs