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_str
が c_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_str
と malloc()
が返すポインタが 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
フラグを指定して使うなどする場合があてはまる。
適合コード
次の適合コードでは、sk
の tun->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 は、 |
||
Coverity
|
7.5.0 |
CHECKED_RETURN NULL_RETURNS REVERSE_INULL FORWARD_NULL |
ポインタが null ポインタを返す可能性がある関数でその返り値がチェックされていないものを検出する ポインタを参照したあとでそのポインタが
|
5.0 |
|||
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 |
実装済み |
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)