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

FIO22-C. プロセスを生成する前にファイルをクローズする

C 標準の FILE オブジェクトとそのビット表現 (POSIX プラットフォームにおけるファイル記述子や、その他のプラットフォームにおけるハンドル)は、有限の資源であり、慎重な管理が必要である。処理系が保証する同時にオープンできるファイル数は、<stdio.h> で定義される FOPEN_MAX マクロの制約を受ける。マクロの値は、8 以上になることが保証されている。このため、プログラムの可搬性を確保するには、同時に FOPEN_MAX 個を超えるファイルをオープンしないようにするか、またはリソースの枯渇によっt fopen() がエラーになる状況にあらかじめ備えておく必要がある。

必要なくなったファイルをクローズしないと、攻撃者によってシステムの資源を枯渇させられたり、場合によってはその内容を操作される可能性がある。この現象は一般に、ファイル記述子漏えい (file descriptor leakage) と呼ばれる。攻撃にはファイルポインタが使用される場合もある。また、必要以上に長時間ファイルをオープンし続けると、メモリ内のファイルバッファに書き込まれたデータがプログラムの異常終了時にファイルに強制的に書き出されない危険性が高まる。ファイル記述子漏えいを防ぎ、バッファされたデータが確実にファイルシステムに書き出されるようにするには、ファイルは使う必要がなくなった時点でクローズしなくてはならない。

FILE オブジェクトに対応付けられたファイルがクローズされた後で、FILE オブジェクトを指すポインタ値を使うと、プログラムの動作は未定義になる (C 言語規格の附属書 J 「未定義の動作」の 148 を参照)。プログラムで標準ストリーム (特に stdout であるが stderrstdin も含む) をクローズするときは、クローズしたストリームオブジェクトを後続の関数呼び出しで使用しないよう注意する必要がある。特に、ストリームオブジェクトを暗黙的に操作する関数 (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

     

Fortify SCA

5.0

 

CERT C Rule Pack を使ってこのルールの違反を検出できる

Klocwork

9.1

RH.LEAK

 

LDRA tool suite

8.5.4

49 D

実装済み
関連するガイドライン
CERT C Secure Coding Standard WIN03-C. Understand HANDLE inheritance 
CERT C++ Secure Coding Standard FIO42-CPP. Ensure files are properly closed when they are no longer needed
CERT Oracle Secure Coding Standard for Java FIO04-J. Release resources when they are no longer needed
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 145)

Top へ

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