MEM00-C. メモリの割り当てと解放は、同じ翻訳単位内の同一抽象レベルで行う
動的メモリ管理は、セキュリティ上の脆弱性につながるプログラミング上の欠陥を生みやすい。メモリ領域の動的な割り当て、使用、解放をどのように行うかは、プログラマが決定する責任を負う。メモリ管理が適切に行われないと、ヒープバッファオーバーフロー、ダングリングポインタ、二重解放といったセキュリティ上の問題につながる可能性がある [Seacord 2013]。プログラマの観点からは、メモリ管理には、メモリの割り当て、読み書き、解放といった作業がある。
メモリの割り当てと解放を異なる翻訳単位、異なる抽象レベルで行うと、メモリ領域が解放されたか否か、またいつ解放されたかを特定することが難しくなり、メモリリーク、二重解放、解放済みメモリへのアクセス、解放済みまたは未割り当てメモリへの書き込みといったプログラミング上の欠陥につながることがある。
このような状況を避けるためには、メモリの割り当てと解放は同一抽象レベルで、理想的には同じ翻訳単位の中で行うべきである。C 標準 [ISO/IEC 9899:2011] のセクション 7.23.3 に記載されている以下のメモリ割り当ておよび解放関数を使う場合が、これに該当する。
void *malloc(size_t size); void *calloc(size_t nmemb, size_t size); void *realloc(void *ptr, size_t size); void *aligned_alloc(size_t alignment, size_t size); void free(void *ptr);
このレコメンデーションに従わなかったために、現実に脆弱性が発生している。例えば、MIT Kerberos 5 の脆弱性 [MIT 2004] は、異なる翻訳単位でメモリを解放していたことが原因で作り込まれたものである。MIT Kerberos 5 には、ASN.1 デコーダによって割り当てられたメモリへのポインタが null でなければメモリを解放するというエラー処理ロジックが組み込まれていた。一方、ASN.1 デコーダ自体にも、検知可能なエラーが発生した場合にメモリを解放する処理が組み込まれていた。そのため、ASN.1 デコーダからエラーを受け取ったライブラリ関数が同じメモリをもういちど解放することとなり、二重解放の脆弱性につながった。
違反コード
以下のコード例は、異なる抽象レベルでメモリの割り当てと解放を行うことで作り込まれる二重解放の脆弱性を示している。このコードでは、配列 list
のメモリが process_list()
関数で割り当てられている。次にこの配列は list
のサイズに関するエラー検査を実行する verify_size()
関数に渡される。list
のサイズが最小サイズよりも小さい場合、割り当てられたメモリは解放され、関数は呼び出し元に戻る。その後、呼び出し元の関数は同じメモリを再度解放するため、二重解放の脆弱性となり、攻撃に使われる可能性がある。
enum { MIN_SIZE_ALLOWED = 32 }; int verify_size(char *list, size_t size) { if (size < MIN_SIZE_ALLOWED) { /* エラー状態の処理 */ free(list); return -1; } return 0; } void process_list(size_t number) { char *list = (char *)malloc(number); if (list == NULL) { /* 割り当てエラーの処理 */ } if (verify_size(list, number) == -1) { free(list); return; } /* リストの処理を続行 */ free(list); }
verify_size()
関数内で行われるメモリの解放は、process_list()
関数内のサブルーチンの中で行われるメモリ割り当てとは異なる抽象レベルで行われているため、このレコメンデーションに違反している。さらに、エラー処理コード内でもメモリ解放が行われているが、エラー処理のコードは正常系のコードほどの十分なテストが行われないことが多い。
適合コード
上記の問題を修正するために、verify_size()
内のエラー処理コードで list
を解放しないように変更している。この変更により list
は、process_list()
関数の中の同じ抽象レベルで一度だけ解放される。
enum { MIN_SIZE_ALLOWED = 32 }; int verify_size(const char *list, size_t size) { if (size < MIN_SIZE_ALLOWED) { /* エラー条件の処理 */ return -1; } return 0; } void process_list(size_t number) { char *list = (char *)malloc(number); if (list == NULL) { /* 割り当てエラーの処理 */ } if (verify_size(list, number) == -1) { free(list); return; } /* リストの処理を続行 */ free(list); }
リスク評価
メモリを正しく管理しないと、メモリを複数回解放したり、解放済みメモリへの書き込みを行ったりする可能性が生じる。これらのコーディングエラーが原因となり、攻撃者によって脆弱なプロセスの権限で任意のコードを実行される恐れがある。また、メモリ管理関連のコーディングエラーは、資源の枯渇やサービス運用妨害(DoS)攻撃につながることもある。
レコメンデーション |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
MEM00-C |
高 |
中 |
中 |
P12 |
L1 |
自動検出(最新の情報はこちら)
ツール |
バージョン |
チェッカー |
説明 |
---|---|---|---|
Compass/ROSE |
|
|
|
Coverity | 6.5 | RESOURCE_LEAK | 実装済み |
Fortify SCA |
5.0 |
|
CERT C Rule Packを使ってこのルールの違反を検出できる |
LDRA tool suite |
V. 8.5.4 |
50 D |
一部実装済み |
関連するガイドライン
CERT C++ Secure Coding Standard | MEM11-CPP. Allocate and free memory in the same module, at the same level of abstraction |
ISO/IEC TR 24772:2013 | Memory Leak [XYL] |
MITRE CWE | CWE-416, Use after free CWE-415, Double free |
参考資料
[MIT 2004] | |
[Plakosh 2005] | |
[Seacord 2013] | Chapter 4, "Dynamic Memory Management" |
翻訳元
これは以下のページを翻訳したものです。
MEM00-C. Allocate and free memory in the same module, at the same level of abstraction (revision 116)