MEM10-C. ポインタ検証関数を定義して使用する
ポインタを引数として取る関数は多い。関数が無効なポインタを参照したり(「EXP34-C. NULL ポインタを参照しない」を参照)、オブジェクトを指していないポインタを読み書きした場合、動作は未定義である。一般に、無効なポインタが参照されるとプログラムは異常終了するが、無効なポインタが参照されても異常終了せずメモリが変更されることがあり得る [Jack 2007]。ポインタが有効かどうかを判断するのは難しく、この種のプログラムはデバッグし難くなるだろう。
無効なポインタを排除する方法の1つは、ポインタ引数を受け取り、有効性の何らかの基準に照らし合わせて、ポインタが有効であるか無効であるかを示す関数を定義することである。たとえば、以下に示す関数はNULL 以外のポインタはすべて有効であるとする。
int valid(void *ptr) {
  return (ptr != NULL);
}
プラットフォームによっては、プラットフォーム固有のポインタ検証ツールが存在する。
以下のコードは、AIX、Linux、QNX、IRIX、Solaris などの多くのプラットフォーム上で、ローダによってプログラムテキストの後の最初のアドレスとして定義されている _etext アドレスを使用している。これは POSIX に適合しておらず、Windows 上でも使用できない。
#include <stdio.h>
#include <stdlib.h>
int valid(void *p) {
  extern char _etext;
  return (p != NULL) && ((char*) p > &_etext);
}
int global;
int main(void) {
  int local;
  printf("pointer to local var valid? %d\n", valid(&local));
  printf("pointer to static var valid? %d\n", valid(&global));
  printf("pointer to function valid? %d\n", valid((void *)main));
  int *p = (int *) malloc(sizeof(int));
  printf("pointer to heap valid? %d\n", valid(p));
  printf("pointer to end of allocated heap valid? %d\n", valid(++p));
  free(--p);
  printf("pointer to freed heap valid? %d\n", valid(p));
  printf("null pointer valid? %d\n", valid(NULL));
  return 0;
}
Linux プラットフォームでは、このプログラムの出力は次のようになる。
pointer to local var valid? 1 pointer to static var valid? 1 pointer to function valid? 0 pointer to heap valid? 1 pointer to end of allocated heap valid? 1 pointer to freed heap valid? 1 null pointer valid? 0
valid() 関数はポインタが有効であることは保証しない(NULL ポインタと無効な関数へのポインタを識別するだけである)。ただし、見過ごされがちな問題の多くを検出することができる。
違反コード
以下のコード例では、incr() 関数は、引数によって参照された値をインクリメントする。また、引数が NULL ポインタではないことも確認する。しかし、それでもポインタが無効になるかもしれず、その場合は関数がメモリを壊したり、異常終了する可能性がある。
void incr(int *intptr) {
  if (intptr == NULL) {
    /* エラー処理 */
  }
  *intptr++;
}
適合コード
valid() 関数を使えば incr() 関数は改善できる。無効なポインタの参照や、有効なオブジェクトの範囲外のメモリへの書き込みを行う可能性の低い実装になるだろう。
void incr(int *intptr) {
  if (!valid(intptr)) {
    /* エラー処理 */
  }
  *intptr++;
}
valid() 関数は、処理系依存にして、可能な場合は追加のプラットフォーム依存検査を実行できる。valid() 関数の最小限の処理としては、違反コード例と同じく NULL ポインタ検査を実行するにすぎないが、それ以外のポインタ検証が可能なプラットフォームでは、valid() 関数のなかでその検査を行うとよいだろう。
リスク評価
ポインタ検証関数を使用することで、一部の無効なポインタを検出し、そのようなポインタに対して操作が実行されるのを防ぐことができる。
| 
         ルール  | 
      
         深刻度  | 
      
         可能性  | 
      
         修正コスト  | 
      
         優先度  | 
      
         レベル  | 
    
|---|---|---|---|---|---|
| 
         MEM10-C  | 
      
         高  | 
      
         低  | 
      
         高  | 
      
         P3  | 
      
         L3  | 
    
関連するガイドライン
| CERT C++ Secure Coding Standard | MEM10-CPP. Define and use a pointer validation function | 
| MITRE CWE | CWE-20, Insufficient input validation | 
参考資料
| [Jack 2007] | 
| [van Sprundel 2006] | 
翻訳元
これは以下のページを翻訳したものです。
MEM10-C. Define and use a pointer validation function (revision 42)
