MEM05-C. スタック上で大きなサイズの割り当てを行わない
スタック上に過度に大きなサイズの割り当てを行うことは避けなければならない。割り当てるサイズを攻撃者が制御できるような状況では、特に注意する必要がある。
違反コード
C 標準では可変長配列(VLA, Variable-Length Array)を使用することができる。配列の長さが信頼できない入力源から取得されている場合、攻撃者がスタック上で過大な割り当てを実行する可能性がある。
以下の違反コード例では、入力源のファイルから読み取ったデータを一時的にバッファに格納する。 バッファはサイズ bufsize の可変長配列としてスタック上に割り当てられる。悪意あるユーザが bufsize を制御できる場合、このコードを悪用してサービス運用妨害(DoS)攻撃を引き起こすことが可能である。
int copy_file(FILE *src, FILE *dst, size_t bufsize) {
  char buf[bufsize];
  while (fgets(buf, bufsize, src)) {
    if (fputs(buf, dst) == EOF) {
      /* エラー処理 */
    }
  }
  return 0;
}
BSD 拡張の alloca() 関数は可変長配列と同様の動作を行う。alloca() の使用は推奨されない[Loosemore 2007]。
適合コード
以下の適合コードは、可変長配列を malloc() の呼び出しに置き換えている。malloc() が失敗する場合でも、その返り値を検査していればプログラムの異常終了を防ぐことができる。
int copy_file(FILE *src, FILE *dst, size_t bufsize) {
  if (bufsize == 0) {
    /* エラー処理 */
  }
  char *buf = (char *)malloc(bufsize);
  if (!buf) {
    return -1;
  }
  while (fgets(buf, bufsize, src)) {
    if (fputs(buf, dst) == EOF) {
      /* エラー処理 */
    }
  }
  /* ... */
  free(buf);
  return 0;
}
違反コード
再帰的な処理もスタック上の過大な割り当てにつながることがある。 再帰的な呼び出しを行う関数では、過度の再帰呼び出しによりスタックを使い果たさないように注意する必要がある。
以下のコード例では、再帰呼び出しを使ってフィボナッチ関数を実装している。
unsigned long fib1(unsigned int n) {
  if (n == 0) {
    return 0;
  }
  else if (n == 1 || n == 2) {
    return 1;
  }
  else {
    return fib1(n-1) + fib1(n-2);
  }
}
必要なスタック領域の量は、パラメータ n に対して指数関数的に増大する。n の値が大きいと、プログラムの異常終了を引き起こす。
適合コード
以下の適合コードでは、再帰呼び出しを使わずにフィボナッチ関数を実装している。
unsigned long fib2(unsigned int n) {
  if (n == 0) {
    return 0;
  }
  else if (n == 1 || n == 2) {
    return 1;
  }
  unsigned long prev = 1;
  unsigned long cur = 1;
  unsigned int i;
  for (i = 3; i <= n; i++) {
    unsigned long tmp = cur;
    cur = cur + prev;
    prev = tmp;
  }
  return cur;
}
再帰的な呼び出しを行わないため、必要なスタック領域の量はパラメータ n に依存せず、スタックオーバーフローのリスクは大幅に軽減される。
リスク評価
関数内部でスタック上に割り当てられたオブジェクトは関数終了時に自動的に解放される。そのため、プログラムスタックは便利な一時記憶域としてよく使われる。一般にオペレーティングシステムは、必要に応じてスタック領域を拡大する。しかし、(アーキテクチャにもよるが)メモリ不足や他の割り当て済みアドレス領域との衝突が原因で、スタック領域を拡大できないことがある。スタックが使い果たされると、オペレーティングシステムはプログラムを異常終了させることがある。スタック上に割り当てられるメモリの量を攻撃者が制御できる場合、この挙動を使ってサービス運用妨害(DoS)攻撃が引き起こされる可能性がある。
| 
         レコメンデーション  | 
      
         深刻度  | 
      
         可能性  | 
      
         修正コスト  | 
      
         優先度  | 
      
         レベル  | 
    
|---|---|---|---|---|---|
| 
         MEM05-C  | 
      
         低  | 
      
         高  | 
      
         中  | 
      
         P6  | 
      
         L2  | 
    
自動検出(最新の情報はこちら)
| 
         ツール  | 
      
         バージョン  | 
      
         チェッカー  | 
      
         説明  | 
    
|---|---|---|---|
| 
         Coverity  | 
      6.5 | 
         STACK_USE  | 
      
         一度に過大なスタック割り当てが行われる場合を検出できるが、再帰処理による過度なスタックの利用は検出しない。  | 
    
| PRQA QA-C | 8.1 | 
         1520  | 
      部分的に実装済み | 
関連するガイドライン
| CERT C++ Secure Coding Standard | MEM05-CPP. Avoid large stack allocations | 
| ISO/IEC TR 24772:2013 | Recursion [GDL] | 
| MISRA-C | Rule 16.2 | 
参考資料
| [Loosemore 2007] | Section 3.2.5, "Automatic Storage with Variable Size" | 
| [Seacord 2013] | Chapter 4, "Dynamic Memory Management" | 
| [van Sprundel 2006] | "Stack Overflow" | 
翻訳元
これは以下のページを翻訳したものです。
MEM05-C. Avoid large stack allocations (revision 85)
