web-dev-qa-db-ja.com

C / C + +プログラムで可能な/潜在的なスタックオーバーフローの問題を検出する方法は?

アプリのスタックスペースの量を確認する標準的な方法はありますか?また、実行中のスタック使用量の最高水準点は何ですか?

また、実際のオーバーフローの恐ろしいケースではどうなりますか?

クラッシュしますか、例外またはシグナルをトリガーしますか?標準はありますか、それともすべてのシステムとコンパイラで異なりますか?

私は特にWindows、Linux、Macintoshを探しています。

41
KPexEA

Windowsでスタックオーバーフローexceptionが生成されます。

次のWindowsコードはこれを示しています。

#include <stdio.h>
#include <windows.h>

void StackOverFlow()
{
  CONTEXT context;

  // we are interested control registers
  context.ContextFlags = CONTEXT_CONTROL;

  // get the details
  GetThreadContext(GetCurrentThread(), &context);

  // print the stack pointer
  printf("Esp: %X\n", context.Esp);

  // this will eventually overflow the stack
  StackOverFlow();
}

DWORD ExceptionFilter(EXCEPTION_POINTERS *pointers, DWORD dwException)
{
  return EXCEPTION_EXECUTE_HANDLER;
}

void main()
{
  CONTEXT context;

  // we are interested control registers
  context.ContextFlags = CONTEXT_CONTROL;

  // get the details
  GetThreadContext(GetCurrentThread(), &context);

  // print the stack pointer
  printf("Esp: %X\n", context.Esp);

  __try
  {
    // cause a stack overflow
    StackOverFlow();
  }
  __except(ExceptionFilter(GetExceptionInformation(), GetExceptionCode()))
  {
    printf("\n****** ExceptionFilter fired ******\n");
  }
}

このexeを実行すると、次の出力が生成されます。

Esp: 12FC4C
Esp: 12F96C
Esp: 12F68C
.....
Esp: 33D8C
Esp: 33AAC
Esp: 337CC

****** ExceptionFilter fired ******
15
jussij

Linuxでは、コードがスタックを超えて書き込もうとすると、セグメンテーション違反が発生します。

スタックのサイズは、プロセス間で継承されるプロパティです。 _ulimit -s_(shkshzsh内)または_limit stacksize_(tcshzsh)などのコマンドを使用してシェルで読み取りまたは変更できる場合。

プログラムから、スタックのサイズを使用して読み取ることができます

_#include <sys/resource.h>
#include <stdio.h>

struct rlimit l;
getrlimit(RLIMIT_STACK, &l);
printf("stack_size = %d\n", l.rlim_cur);
_

利用可能なスタックのサイズを取得する標準的な方法がわかりません。

スタックはargcで始まり、その後にargvの内容と環境のコピー、続いて変数が続きます。ただし、カーネルはスタックの開始位置をランダム化でき、argcの上にいくつかのダミー値が存在する可能性があるため、_l.rlim_cur_バイトが_&argc_の下にあると想定するのは誤りです。

スタックの正確な場所を取得する1つの方法は、ファイル_/proc/1234/maps_(_1234_はプログラムのプロセスID)を確認することです。これらの境界がわかったら、最新のローカル変数のアドレスを調べることにより、スタックの使用量を計算できます。

12
adl

gccは、「安全でない」関数呼び出しの戻りアドレスと通常の変数の間に追加のメモリブロックを配置します(この例では、関数はvoid test(){char a [10]; b [20]}です:

call stack:
-----------
return address
dummy
char b[10]
char a[20]

関数がポインター 'a'に36バイトを書き込むと、オーバーフローにより戻りアドレスが「破損」します(セキュリティー違反の可能性があります)。ただし、ポインタと戻りアドレスの間の「ダミー」の値も変更されるため、プログラムは警告付きでクラッシュします(-fno-stack-protectorでこれを無効にできます)。

10
Kknd

Linuxでは、Gnu libsigsegvlibrary には、検出できる(場合によっては役立つ)関数stackoverflow_install_handlerが含まれていますスタックオーバーフローから回復します。

7
Norman Ramsey

Windowsでは、(特定のスレッドの)スタックは、作成前にこのスレッドに指定されたスタックサイズに達するまでオンデマンドで拡張されます。

オンデマンドの拡張は、ガードページを使用して強制されます。最初に利用できるスタックのフラグメントのみがあり、その後にガードページが続きます。ガードページは、ヒットすると例外をトリガーします-この例外は特別であり、システムによって処理されます。あなた-処理によって利用可能なスタック領域が増加し(制限に達しているかどうかもチェックされます!)、読み取り操作が再試行されます。

制限に達すると、スタックオーバーフロー例外が発生する成長はありません。現在のスタックベースと制限は、スレッド環境ブロックの_NT_TIB(スレッド情報ブロック)という構造体に格納されます。あなたが便利なデバッガを持っているなら、これはあなたが見るものです:

0:000> dt ntdll!_teb @$teb nttib.
   +0x000 NtTib  : 
      +0x000 ExceptionList : 0x0012e030 _EXCEPTION_REGISTRATION_RECORD
      +0x004 StackBase : 0x00130000 
      +0x008 StackLimit : 0x0011e000 
      +0x00c SubSystemTib : (null) 
      +0x010 FiberData : 0x00001e00 
      +0x010 Version : 0x1e00
      +0x014 ArbitraryUserPointer : (null) 
      +0x018 Self   : 0x7ffdf000 _NT_TIB

StackLimit属性はオンデマンドで更新されます。このメモリブロックの属性を確認すると、次のようなものが表示されます。

0:000> !address 0x0011e000 
    00030000 : 0011e000 - 00012000
                    Type     00020000 MEM_PRIVATE
                    Protect  00000004 PAGE_READWRITE
                    State    00001000 MEM_COMMIT
                    Usage    RegionUsageStack
                    Pid.Tid  abc.560

その隣のページをチェックすると、guard属性が明らかになります。

0:000> !address 0x0011e000-1000
    00030000 : 0011d000 - 00001000
                    Type     00020000 MEM_PRIVATE
                    Protect  00000104 PAGE_READWRITE | PAGE_GUARD
                    State    00001000 MEM_COMMIT
                    Usage    RegionUsageStack
                    Pid.Tid  abc.560

それが役に立てば幸い。

6
deemok

スタックオーバーフローはおそらく処理するのが最も厄介なタイプの例外です-例外ハンドラーは最小限のスタックを処理する必要があるためです(通常、この目的のために予約されているのは1つのページだけです)。

このタイプの例外を処理する難しさの興味深い説明については、次のブログ投稿を参照してください。 1 および 2 Chris Brummeによる、.NETの観点からの問題、特にホスティングに焦点を当てたものCLR。

4
Rob Walker

Linuxを使用している場合は、alternate-signal-stackを使用することをお勧めします。

  1. この場合、すべての信号は代替スタックで処理されます。
  2. スタックオーバーフローが発生した場合、システムはSEGV信号を生成します。これは代替スタックで処理できます。
  3. これを使用しないと...信号を処理できなくなり、プログラムは処理やエラー報告なしにクラッシュする可能性があります。
1

Visual Studioでeditbinを使用してスタックサイズを変更することができます。情報は msdn.Microsoft.com/en-us/library/35yc2tc3.aspx にあります。

1
Richard

一部のコンパイラは、スタックの残りの空き容量を返すstackavail()関数をサポートしています。多くのスタックスペースを必要とするプログラムで関数を呼び出す前にこの関数を使用して、それらを呼び出しても安全かどうかを判断できます。

0
dmityugov