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

FIO07-J. 外部プロセスに IO バッファをブロックさせない

外部プロセスを起動したいときは、java.lang.Runtime クラスの exec() メソッドや、ProcessBuilder.start() メソッドを使うことができる。起動されたプロセスは java.lang.Process オブジェクトとして表現される。POSIX システムにおけるプロセスと同様、このプロセスは入力ストリーム、出力ストリーム、エラーストリームを持っている。Java プログラムは Process オブジェクトを通じて外部プログラムと通信することができる。外部プログラムの入力ストリームは OutputStream オブジェクトとして表現され、Process.getOutputStream() メソッドを通じてアクセスできる。出力ストリームとエラーストリームは InputStream オブジェクトとして表現され、それぞれ Process.getInputStream()Process.getErrorStream() メソッドを使ってアクセスできる。

これらの外部プロセスは入力を必要としたり、出力ストリームやエラーストリームへの出力を行うかもしれない。外部プロセスを適切に扱わないと、予期せぬ例外、サービス運用妨害、その他のセキュリティ問題が発生する可能性がある。

空の入力ストリームから読み込みを行うと、プロセスは入力が供給されるまで待ち状態になる。入力ストリームから読み込みを行うようなプロセスを起動するときには必ず入力を供給すること。

外部プロセスからの出力は、専用に確保された出力ストリームやエラーストリームのバッファを使い果たしてしまう可能性がある。そのような状況が発生すると、バッファへの出力を再開できるまで、外部プロセスは待ち状態にはいってしまう。のみならず、その外部プロセスを起動した Java プログラムまでも待ち状態にはいってしまう。多くのプラットフォームでは出力ストリームのバッファサイズを制限している。出力ストリームにデータを出力するような外部プロセスを起動するときは、出力ストリームから適切にデータの読み出しを行うこと。エラーストリームにデータを出力するのであれば、エラーストリームからも適切にデータの読み出しを行うこと。

違反コード (exitValue())

以下の違反コード例では、notemaker という外部コマンドを使ってメモ帳アプリを起動する。notemaker は入力ストリームからは何も読み込まないが、出力ストリームおよびエラーストリームの両方にデータを出力する。

以下のコードでは exec() メソッドを使って notemaker を起動している。exec() メソッドは Process オブジェクトを返す。exitValue() メソッドはプロセスが終了したときにその終了値を返す。しかし、まだ生きているプロセスに対して呼び出されると IllegalThreadStateException 例外を投げる。このコードでは notemaker プロセスの終了を待っておらず、exitValue() の呼び出しによって IllegalThreadStateException 例外が発生する。

public class Exec {
  public static void main(String args[]) throws IOException {
    Runtime rt = Runtime.getRuntime();
    Process proc = rt.exec("notemaker");
    int exitVal = proc.exitValue();
  }
}
違反コード (waitFor())

以下の違反コードでは、waitFor() メソッドを呼び出したスレッドは、notemaker プロセスが終了するまで待ち状態になる。これによって、前述のコード例のような IllegalThreadStateException 例外は発生しない。しかし、プロセスはいつ終了するか分からない。また、プロセスの終了を待っている間に呼び出し元スレッドは出力ストリームやエラーストリームへの出力の読み取りを行っていないので、notemaker プロセスの出力がバッファを使い果たす可能性がある。出力ストリームやエラーストリームどちらかのバッファが一杯になると、notemaker プロセスの処理も元の Java プログラムの処理も待ち状態になってしまう。

public class Exec {
  public static void main(String args[])
                          throws IOException, InterruptedException {
    Runtime rt = Runtime.getRuntime();
    Process proc = rt.exec("notemaker");
    int exitVal = proc.waitFor();
  }
}
違反コード (プロセスの出力ストリーム)

以下の違反コード例では、プロセスの出力ストリームを空にしており、出力バッファが一杯になって処理が止まることはない。しかし、エラーストリームを空にすることを忘れている。エラーストリームのバッファが一杯になって処理が止まるかもしれない。

public class Exec {
  public static void main(String args[])
                     throws IOException, InterruptedException {
    Runtime rt = Runtime.getRuntime();
    Process proc = rt.exec("notemaker");
    InputStream is = proc.getInputStream();
    int c;
    while ((c = is.read()) != -1) {
      System.out.print((char) c);
    }
    int exitVal = proc.waitFor();
  }
}
適合コード (redirectErrorStream())

以下の適合コードでは、プロセスのエラーストリームを出力ストリームにリダイレクトしている。そのため、前述の違反コードのような心配はなく、出力ストリームからの読み出しを行うだけでよい。

public class Exec {
  public static void main(String args[])
                          throws IOException, InterruptedException {
    ProcessBuilder pb = new ProcessBuilder("notemaker");
    pb = pb.redirectErrorStream(true);
    Process proc = pb.start();
    InputStream is = proc.getInputStream();
    int c;
    while ((c = is.read()) != -1) {
      System.out.print((char) c);
    }
    int exitVal = proc.waitFor();
  }
}
適合コード (プロセスの出力ストリームとエラーストリーム)

以下の適合コードでは2つのスレッドを起動し、それぞれプロセスの出力ストリームとエラーストリームからの読み取りを行っている。そのため、これらのバッファが一杯になってプロセスが止まることはない。

出力ストリームとエラーストリームを別々に扱うのであれば、それぞれ独立に読み取りを行わなければならない。そうしなければ、プログラムが永遠に止まったままになってしまうかもしれない。

class StreamGobbler extends Thread {
  InputStream is;
  PrintStream os;

  StreamGobbler(InputStream is, PrintStream os) {
    this.is = is;
    this.os = os;
  }

  public void run() {
    try {
      int c;
      while ((c = is.read()) != -1)
          os.print((char) c);
    } catch (IOException x) {
      // エラー処理
    }
  }
}

public class Exec {
  public static void main(String[] args)
    throws IOException, InterruptedException {

    Runtime rt = Runtime.getRuntime();
    Process proc = rt.exec("notemaker");

    // エラーメッセージがある?
    StreamGobbler errorGobbler =
        new StreamGobbler(proc.getErrorStream(), System.err);

    // 出力がある?
    StreamGobbler outputGobbler =
        new StreamGobbler(proc.getInputStream(), System.out);

    errorGobbler.start();
    outputGobbler.start();

    // エラー?
    int exitVal = proc.waitFor();
    errorGobbler.join();   // スレッドの処理が完了する前に
    outputGobbler.join();  // プロセスが終了してしまった場合の処理
  }
}
例外

FIO07-EX0: 入力ストリームからの読み込みを行わないプロセスに入力を与えなくとも害はなく、むしろ、そのほうがよいこともある。同様に、プロセスが出力ストリームやエラーストリームに出力を行わない場合に、それらのストリームからデータを吸い出さなくとも害はないし、そのほうがよいこともある。プロセスが入力、出力、エラーの各ストリームを使わないことが分かっているならば、それらのストリームは無視してよい。

リスク評価

外部プロセスの入出力ストリームを適切に管理しないと、実行時例外の発生や運用妨害の脆弱性につながる可能性がある。

ルール 深刻度 可能性 修正コスト 優先度 レベル
FIO07-J P4 L3
関連する脆弱性

GROOVY-3275

参考文献
[API 2006] Method exec()
[Daconta 2000]  
[Daconta 2003] Pitfall 1
翻訳元

これは以下のページを翻訳したものです。

FIO07-J. Do not let external processes block on IO buffers (revision 84)

Top へ

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