FIO22-C. プロセスを生成する前にファイルをクローズする
C 標準の FILE
オブジェクトとそのビット表現 (POSIX プラットフォームにおけるファイル記述子や別のプラットフォームにおけるハンドルなど)は有限の資源であり、慎重な管理が必要である。処理系(implementation)が保証する同時にオープンできるファイル数は、<stdio.h>
で定義される FOPEN_MAX
マクロの制約を受ける。マクロの値は、8 以上であることが保証されている。このため、プログラムの可搬性を確保するには、同時に FOPEN_MAX
個を超えるファイルをオープンしないようにするか、またはリソースの枯渇によって fopen()
がエラーになる状況にあらかじめ備えておく必要がある。
必要なくなったファイルをクローズしないと、攻撃者によってシステムの資源を枯渇させられたり、場合によってはその内容を操作される可能性がある。この現象は一般に、ファイル記述子漏えい (file descriptor leakage) と呼ばれる。攻撃にはファイルポインタが使用される場合もある。また、必要以上に長時間ファイルをオープンし続けると、メモリ内のファイルバッファに書き込まれたデータがプログラムの異常終了時にファイルに強制的に書き出されない危険性が高まる。ファイル記述子漏えいを防ぎ、バッファされたデータが確実にファイルシステムに書き出されるようにするには、ファイルは使う必要がなくなった時点でクローズしなくてはならない。
FILE
オブジェクトに対応付けられたファイルがクローズされた後で、FILE
オブジェクトを指すポインタ値を使うと、プログラムの動作は未定義になる(「未定義の動作148」を参照)。プログラムで標準ストリーム(特に stdout
であるが stderr
と stdin
も含む)をクローズするときは、クローズしたストリームオブジェクトを後続の関数呼び出しで使用しないよう注意する必要がある。特に、ストリームオブジェクトを暗黙的に操作する関数 (printf()
、perror()
、getc()
など) では、いっそうの注意を要する。
違反コード
下記の違反コード例は、OpenBSD の chpass
プログラムに作り込まれた脆弱性を元にしている[NAI 1998]。プログラムは機密データを含むファイルをオープンする。get_validated_editor()
関数は環境変数 EDITOR
に登録されている値を取得し、「FIO02-C. 汚染された情報源から取得したパス名は正規化する」に従って無害化している。この関数はエディタを起動するコマンド文字列を呼出し元に返し、その文字列は system()
関数に渡される。system()
関数が子プロセスを生成するように実装されている場合、子プロセスは親プロセスがオープンしているファイル記述子を継承する。POSIX システムではそのように実装されているわけだが、その結果、子プロセス(この例では、EDITOR
環境変数に指定されているプログラム)は、機密情報を含む file_name
の内容にアクセスすることができる。
#include <stdio.h>
#include <stdlib.h>
extern const char *get_validated_editor(void);
void func(const char *file_name) {
FILE *f;
const char *editor;
f = fopen(file_name, "r");
if (f == NULL) {
/* エラー処理 */
}
editor = get_validated_editor();
if (editor == NULL) {
/* エラー処理 */
}
if (system(editor) == -1) {
/* エラー処理 */
}
}
get_validated_editor()
が返すコマンドが常に単純なパス (たとえば /usr/bin/vim
) であり、POSIX システム上で実行されるのであれば、「ENV33-C. system() を呼び出さない」に従い、system()
ではなく execve()
を呼び出すことでセキュリティを強化することができる。
一般にUNIX ベースのシステムでは、子プロセスは fork()
および exec()
を使って生成される。さらに close-on-exec フラグが指定されていないファイル記述子を親プロセスから継承する。Microsoft Windows では、ファイルハンドルの継承はファイル単位、および生成されるプロセス単位で決定される。詳細は「WIN03-C. Understand HANDLE inheritance」を参照。
適合コード
下記の適合コードでは、エディタを起動する前に file_name
をクローズしている。
#include <stdio.h>
#include <stdlib.h>
extern const char *get_validated_editor(void);
void func(const char *file_name) {
FILE *f;
const char *editor;
f = fopen(file_name, "r");
if (f == NULL) {
/* エラー処理 */
}
fclose(f);
f = NULL;
editor = get_validated_editor();
if (editor == NULL) {
/* エラー処理 */
}
/* Sanitize environment before calling system() */
if (system(editor) == -1) {
/* エラー処理 */
}
}
適合コード (POSIX)
system()
関数や exec()
関数を呼び出す前に、オープンしているすべてのファイル記述子をクローズすることは、時として現実的ではない。POSIX システムにおいては、FD_CLOEXEC
フラグもしくは O_CLOEXEC
フラグを使い、ファイルディスクリプタに対して close-on-exec フラグを設定するという別の方法も考えられる。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
extern const char *get_validated_editor(void);
void func(const char *file_name) {
int flags;
char *editor;
int fd = open(file_name, O_RDONLY);
if (fd == -1) {
/* エラー処理 */
}
flags = fcntl(fd, F_GETFD);
if (flags == -1) {
/* エラー処理 */
}
if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) {
/* エラー処理 */
}
editor = get_validated_editor();
if (editor == NULL) {
/* getenv() のエラーを処理する */
}
if (system(editor) == -1) {
/* エラー処理 */
}
}
適合コード (Linux)
ある種のシステム (たとえば Linux カーネル 2.6.23 以降など) では O_CLOEXEC
フラグが用意されている。このフラグを指定することで、open()
関数で直接 close-on-exec の機能を利用することができる。このフラグは IEEE Std 1003.1 で規定されている[IEEE Std 1003.1:2013]。マルチスレッドプログラムでは、可能な限りこのフラグを利用するべきである。なぜなら、FD_CLOEXEC
を使うときに生じる open()
を呼び出してから fcntl()
を呼び出すまでのすき間の時間に、別のスレッドが子プロセスを生成して close-on-exec フラグが設定される前のファイルディスクリプタを操作する可能性を排除できるからである。
#include <stdio.h>
#include <stdlib.h>
extern const char *get_validated_editor(void);
void func(const char *file_name) {
char *editor;
int fd = open(file_name, O_RDONLY | O_CLOEXEC);
if (fd == -1) {
/* エラー処理 */
}
editor = get_validated_editor();
if (editor == NULL) {
/* エラー処理 */
}
if (system(editor) == -1) {
/* エラー処理 */
}
}
リスク評価
ファイルを適切にクローズしないと、システムの資源が想定外のアクセスを受けたり、枯渇させられる可能性がある。
ルール |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
FIO22-C |
中 |
低 |
中 |
P4 |
L3 |
自動検出(最新の情報はこちら)
ツール |
バージョン |
チェッカー |
説明 |
---|---|---|---|
Compass/ROSE |
|
|
|
Klocwork |
2022.3
|
RH.LEAK |
|
LDRA tool suite |
9.7.1
|
49 D |
Partially implemented |
Parasoft C/C++test |
2022.1 |
CERT_C-FIO22-a |
Ensure resources are freed |
関連するガイドライン
SEI CERT C Coding Standard | WIN03-C. Understand HANDLE inheritance |
SEI CERT C++ Coding Standard | FIO51-CPP. Close files when they are no longer needed |
JavaコーディングスタンダードCERT/Oracle版 | 不要になったリソースは解放する |
MITRE CWE | CWE-403, UNIX file descriptor leak CWE-404, Improper resource shutdown or release CWE-770, Allocation of resources without limits or throttling |
参考資料
[Dowd 2006] | Chapter 10, "UNIX Processes" ("File Descriptor Leaks," pp. 582–587) |
[IEEE Std 1003.1:2013] | XSH, System Interfaces, open |
[MSDN] | Inheritance (Windows) |
[NAI 1998] |
|
翻訳元
これは以下のページを翻訳したものです。
FIO22-C. Close files before spawning processes (revision 154)