JPCERT コーディネーションセンター

EXP34-C. null ポインタを参照しない

EXP34-C. null ポインタを参照しない

null ポインタ参照は未定義の動作である。

多くのプラットフォームでは、null ポインタ参照はプログラムの異常終了(abnormal program termination)を引き起こす。しかし、この挙動は言語規格で要請されているわけではない。null ポインタ参照を悪用してコードを実行させる攻撃手法の例については、"Clever Attack Exploits Fully-Patched Linux Kernel" [Goodin 2009] を参照。

違反コード

次のコード例は、一般的な ARM ベースの携帯電話で使用されている libpng ライブラリの脆弱なバージョンからとってきたものである[Jack 2007]。libpng は、PNG (Portable Network Graphics) 形式のラスターイメージファイルに対する読み書きなどの操作を提供するライブラリである。libpng ライブラリは malloc() に対するラッパーを実装し、エラー発生時や 0 バイト割り当てに対しては null ポインタを返すようにしている。

このコードは「ERR33-C. 標準ライブラリのエラーを検出し、対処する」にも違反している。

#include <png.h> /* libpng のヘッダファイル */
#include <string.h>

void func(png_structp png_ptr, int length, const void *user_data) {
  png_charp chunkdata;
  chunkdata = (png_charp)png_malloc(png_ptr, length + 1);
  /* ... */
  memcpy(chunkdata, user_data, length);
  /* ... */
}

引数 length の値が −1 だった場合、加算により 0 が得られ、png_malloc() は null ポインタを返し、これが chunkdata に代入される。chunkdata ポインタは memcpy() 呼出しでコピー先として使用され、ユーザが与えたデータがアドレス 0 で始まるメモリ領域に書き込まれることになる。ARM および XScale アーキテクチャでは、アドレス 0x0 は物理メモリにマップされ、例外ベクタテーブルとして使われる。したがって、アドレス 0x0 にアクセスしてもプログラムが異常終了するわけではない。

適合コード

次のコードでは、png_malloc() が返すポインタの値が null でないことを確認している。また、引数 length の型を符号無しの型 size_t とすることによって、負の値が渡されないようにしている。

#include <png.h> /* libpng のヘッダファイル */
#include <string.h>

void func(png_structp png_ptr, size_t length, const void *user_data) {
  png_charp chunkdata;
  if (length == SIZE_MAX) {
    /* エラー処理 */
  }
  chunkdata = (png_charp)png_malloc(png_ptr, length + 1);
  if (NULL == chunkdata) {
    /* エラー処理 */
  }
  /* ... */
  memcpy(chunkdata, user_data, length);
  /* ... */
}
違反コード

次の違反コードでは、input_strc_str で参照される動的に割り当てられたメモリ領域にコピーされる。malloc() 関数呼出しが失敗したときには null ポインタが返され c_str に代入されている。次に memcpy() の中で c_str が参照されると、未定義の動作を引き起こす。さらに、input_str の値が null ポインタだった場合には strlen() 呼出しからも null ポインタ参照が発生し未定義の動作を引き起こす。このコードは「ERR33-C. 標準ライブラリのエラーを検出し、対処する」にも違反している。

#include <string.h>
#include <stdlib.h>

void f(const char *input_str) {
  size_t size = strlen(input_str) + 1;
  char *c_str = (char *)malloc(size);
  memcpy(c_str, input_str, size);
  /* ... */
  free(c_str);
  c_str = NULL;
  /* ... */
}
適合コード

次の適合コードでは、input_strmalloc() が返すポインタが null でないことを確認している。

#include <string.h>
#include <stdlib.h>

void f(const char *input_str) {
  size_t size;
  char *c_str;

  if (NULL == input_str) {
    /* エラー処理 */
  }

  size = strlen(input_str) + 1;
  c_str = (char *)malloc(size);
  if (NULL == c_str) {
    /* エラー処理 */
  }
  memcpy(c_str, input_str, size);
  /* ... */
  free(c_str);
  c_str = NULL;
  /* ... */
}
違反コード

次のコード例は Linux カーネル 2.6.30 の drivers/net/tun.c からとってきたものである[Goodin 2009]。

static unsigned int tun_chr_poll(struct file *file, poll_table *wait)  {
  struct tun_file *tfile = file->private_data;
  struct tun_struct *tun = __tun_get(tfile);
  struct sock *sk = tun->sk;
  unsigned int mask = 0;

  if (!tun)
    return POLLERR;

  DBG(KERN_INFO "%s: tun_chr_poll\n", tun->dev->name);

  poll_wait(file, &tun->socket.wait, wait);

  if (!skb_queue_empty(&tun->readq))
    mask |= POLLIN | POLLRDNORM;

  if (sock_writeable(sk) ||
     (!test_and_set_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags) &&
     sock_writeable(sk)))
    mask |= POLLOUT | POLLWRNORM;

  if (tun->dev->reg_state != NETREG_REGISTERED)
    mask = POLLERR;

  tun_put(tun);
  return mask;
}

sk は、tun が null ポインタかどうかをチェックする前に tun->sk に初期化されている。null ポインタ参照を行った場合の動作は未定義であること、そして、tun->sk がアクセスされた後に置かれているエラーチェックのコード if (!tun) では、(tun->sk がアクセスできるのだから) tun は null ではないと言えることから、コンパイラ(この場合 GCC)は最適化の一環として、エラーチェックのコード if (!tun) を省略してしまう。結果としてこの違反コード例は、null ポインタ参照が許される(null ポインタ参照を行ってもプログラムが異常終了しない)環境において、null ポインタ参照を悪用する攻撃に対して脆弱となる。例えば Linux や Mac OS X で MAP_FIXED フラグを指定して mmap(2) を使うとか、あるいは POSIX の shmat() 関数を SHM_RND フラグを指定して使うなどする場合があてはまる。

適合コード

次の適合コードでは、sktun->sk による初期化を、null ポインタチェックの後に行うことで、null ポインタ参照を防止している。

static unsigned int tun_chr_poll(struct file *file, poll_table *wait)  {
  struct tun_file *tfile = file->private_data;
  struct tun_struct *tun = __tun_get(tfile);
  struct sock *sk;
  unsigned int mask = 0;

  if (!tun)
    return POLLERR;

  sk = tun->sk;

  /* 残りのコードは同じなので省略... */

}
リスク評価

null ポインタ参照は未定義の動作であり、多くの場合プログラムは異常終了する。ただし、null ポインタ参照を悪用して任意のコードを実行することが可能な場合がある[Jack 2007][van Sprundel 2006]。ここに示す深刻度は任意のコード実行が可能な場合のものである。null ポインタ参照による任意のコード実行が不可能なプラットフォームにおいては、深刻度は低である。

ルール

深刻度

可能性

修正コスト

優先度

レベル

EXP34-C

P18

L1

自動検出(最新の情報はこちら
ツール バージョン チェッカー 説明
CodeSonar 4.0

LANG.MEM.NPD

LANG.STRUCT.NTAD

LANG.STRUCT.UPD

Null Pointer Dereference

Null Test After Dereference

Unchecked Parameter Dereference

Compass/ROSE    

このルールの違反を検出できる。ROSE は、malloc(), calloc(), realloc() が返すポインタが使われる前にその値が NULL でないかどうかを最初にチェックしている (チェックしていない場合は free() している) ことを確認する。ただし、メモリ割り当てが変数ではない lvalue に代入されている場合は調べていない(たとえば struct のメンバや参照を返す C++ の関数呼び出しなど)。

Coverity

 

7.5.0

CHECKED_RETURN

NULL_RETURNS

REVERSE_INULL

FORWARD_NULL

ポインタが NULL かどうかチェックした後で参照されている場合を検出する

null ポインタを返す可能性がある関数でその返り値がチェックされていないものを検出する

ポインタを参照したあとでそのポインタが NULL かどうかチェックしているコードを検出する

NULL が明示的に参照されるか、またはポインタが NULL かどうかをチェックしていてもその結果によらずそのポインタを参照している場合を検出可能。Coverity Prevent は、このルールの違反をすべて検出できるわけではないため、さらなる検証が必要である

Fortify SCA

5.0

   

Klocwork

9.1

NPD.* *RNPD.*

 

LDRA tool suite

8.5.4

45 D

実装済み
PRQA QA-C v8.2

2810,2811,2812,2813,2814,2820,2821,2822,2823,2824 

実装済み

Splint

3.1.1    
関連するガイドライン
CERT C Secure Coding Standard void MEM32-C. Detect and handle memory allocation errors 
CERT C++ Secure Coding Standard EXP34-CPP. Ensure a null pointer is not dereferenced
CERT Oracle Secure Coding Standard for Java EXP01-J. Never dereference null pointers
ISO/IEC TR 24772:2013 Pointer Casting and Pointer Type Changes [HFC]
Null Pointer Dereference [XYH]
ISO/IEC TS 17961 Dereferencing an out-of-domain pointer [nullref]
MITRE CWE CWE-476, NULL Pointer Dereference
参考資料
[Goodin 2009]  
[Jack 2007]  
[Liu 2009]  
[van Sprundel 2006]  
[Viega 2005] Section 5.2.18, "Null-Pointer Dereference"
翻訳元

これは以下のページを翻訳したものです。

EXP34-C. Do not dereference null pointers (revision 144)

Top へ

Topへ
最新情報(RSSメーリングリストTwitter