MEM31-C. 動的に割り当てられたメモリは一度だけ解放する
C 標準 [ISO/IEC 9899:2011] 附属書 J 「未定義の動作」の 179 に記載の通り、以下の場合、プログラムの動作は未定義である。
free
またはrealloc
関数へのポインタ引数がメモリ管理関数によって以前に返されたポインタと一致しないか、もしくは、領域がfree
またはrealloc
によって解放された。
メモリを何度も解放すると、解放済みメモリにアクセスするのと同じような結果になる。(「MEM30-C. 解放済みメモリにアクセスしない」を参照)最初に、解放されたメモリを指すポインタの値を読み取ると、ポインタ値は不定であり、さらにトラップ表現の場合があるため、未定義の動作となる。後者の場合、それによってハードウェアトラップを引き起こす可能性がある。解放されたポインタの値を読み取ってもトラップが起こらない場合は、プログラムにセキュリティ上の脆弱性が生じてしまうような方法で、ヒープを管理する下位のデータ構造が破壊される可能性がある。この種の問題は、二重解放の脆弱性と呼ばれる。実際に、二重解放の脆弱性を悪用して任意のコードを実行することができる。一例として、 VU#623332 では MIT Kerberos 5 の関数 krb5_recvauth() の二重解放の脆弱性についてとり上げている。
二重解放の脆弱性を排除するには、動的メモリが一度だけ解放されることを保証する必要がある。プログラマは特に、ループや条件文の中でメモリを解放する場合に注意すべきである。誤ってコーディングすると、これらの構文の中で二重解放の脆弱性が引き起こされる可能性がある。realloc()
関数の誤用が二重解放の脆弱性を引き起こすというのも、よくあるコーディングエラーである(「MEM04-C. サイズ 0 のメモリ割り当てを行わない」を参照)。
違反コード (malloc()
)
このコード例では、x
が参照するメモリが二度解放される可能性がある。まず error_condition
が真の場合にメモリが解放され、コードの最後で再度メモリが解放される。
int f(size_t n) { int error_condition = 0; int *x = (int*)malloc(n * sizeof(int)); if (x == NULL) return -1; /* x を使って、エラー時の error_condition を設定する。 */ if (error_condition == 1) { /* エラー条件の処理 */ free(x); } /* ... */ free(x); return error_condition; }
適合コード (malloc()
)
この適合コードでは、x
によって参照される解放は一度だけ解放される。これは、error_condition
の設定時の free()
の呼び出しをなくすことで実現される。
int f(size_t n) { int error_condition = 0; if (n > SIZE_MAX / sizeof(int)) { errno = EOVERFLOW; return -1; } int *x = (int*)malloc(n * sizeof(int)); if (x == NULL) { /* 割り当ての失敗を呼び出し側に報告する。 */ return -1; } /* x を使って、エラー時の error_condition を設定する。 */ if (error_condition != 0) { /* エラー条件の処理と進行 */ } free(x); return error_condition; }
この解決法では数値オーバーフローもチェックしている(「INT32-C. 符号付き整数演算がオーバーフローを引き起こさないことを保証する」を参照)。
違反コード (realloc()
)
以下のコード例では、p
によって参照されているメモリは 2 回解放される場合がある。
/* p は動的に割り当てられたメモリへのポインタである */ p2 = realloc(p, size); if (p2 == NULL) { free(p); /* (size == 0) の場合、p は不定である可能性がある */ return; }
C 標準 [ISO/IEC 9899:2011] セクション 7.22.3 には次のように記載されている。
要求された領域の大きさが 0 であるとき、その動作は処理系定義とする。
さらに、セクション 7.22.3.5 には次のように記載されている。
新しいオブジェクトのメモリを割り当てできない場合、古いオブジェクトは解放されず、その値は変更されない。
realloc()
を size
が 0 で呼び出し、その後、NULL ポインタが返されると、古い値は未変更である。ただし、以下を含む、一般的だが適合していない、ポインタを解放する処理系もある。
- Glibc (GNU/Linux)
- AIX
- HP-UX
- Solaris
- OSF/1
つまり、元のポインタで free
を呼び出すと、二重解放の脆弱性が生じる可能性がある。ただし、元のポインタで free
を呼び出さないと、メモリリークを引き起こす可能性がある。
適合コード (realloc()
)
この適合コードでは、ゼロバイトの割り当てを避け、p
が正確に一度だけ解放されるように保証している。
/* p は動的に割り当てられたメモリへのポインタである */ if (size) { p2 = realloc(p, size); if (p2 == NULL) { free(p); return; } } else { free(p); return; }
例外
MEM31-EX1: 一部のライブラリ処理系では、解放済みのメモリの解放を受け入れて無視する。プロジェクトで使用されているすべてのライブラリがこのように動作すると検証されている場合は、このルールを無視して構わない。
リスク評価
メモリを何度も解放すると、攻撃者が脆弱性のあるプロセスの権限で任意のコードを実行する可能性がある。
ルール |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
MEM31-C |
高 |
中 |
中 |
P12 |
L1 |
自動検出(最新の情報はこちら)
ツール |
バージョン |
チェッカー |
説明 |
---|---|---|---|
Compass/ROSE |
|||
Coverity |
6.5 |
RESOURCE_LEAK USE_AFTER_FREE |
リソースを所有しているが、対象外となった変数からのリソースリークを検出する。 解放済みメモリが再度解放される事例を検出できる。Coverity Prevent は、このルールの違反をすべて検出できるわけではないため、さらなる検証が必要である。 |
Fortify SCA |
5.0 |
Double Free |
|
V. 9.1 |
MLK |
||
LDRA tool suite |
V. 8.5.4 |
484 S |
実装済み |
Splint |
V. 3.1.1 |
関連するガイドライン
CERT C Secure Coding Standard | MEM04-C. サイズ 0 のメモリ割り当てを行わない |
CERT C++ Secure Coding Standard | MEM31-CPP. Free dynamically allocated memory exactly once |
ISO/IEC TR 24772:2013 | Dangling Reference to Heap [XYK] Memory Leak [XYL] |
ISO/IEC TS 17961 (ドラフト) | Freeing memory multiple times [dblfree] |
MITRE CWE | CWE-415, Double free |
参考資料
[ISO/IEC 9899:2011] | Section 7.22.3, "Memory Management Functions" |
[MIT 2005] | |
[OWASP Double Free] | "Double Free" |
[Viega 2005] | "Doubly Freeing Memory" |
[VU#623332] |
翻訳元
これは以下のページを翻訳したものです。
MEM31-C. Free dynamically allocated memory exactly once (revision 121)