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

FIO06-J. 1つの InputStream に対して複数のバッファ付きラッパーを作成しない

Javaの入力クラスであるScannerBufferedInputStreamは、入力ストリームをバッファリングすることで、高速かつノンブロッキングな I/O を提供する。また、単一のInputStreamに対しては、複数のラッパーを作成することができる。ラッパーが先読み(look-ahead)を許可するかどうかにもよるが、単一の入力ストリームに対して複数のラッパーを使用するプログラムの動作は不安定になることがある。攻撃者は、このような不安定な動作を悪用するために、たとえばSystem.in をファイルからの入力にリダイレクトしたり、System.setIn()メソッドを使用してリダイレクトするかもしれない。一般に、ノンブロッキングバッファ付き I/O をサポートする入力ストリームはすべて、この手の悪用の影響を受ける。

入力ストリームは2つ以上のバッファー付きラッパーを持ってはならない。必要とするメソッドには引数として渡す、あるいはクラス変数として宣言することで、1つの入力につき1つだけラッパを作成し、使用しなくてはならない。

違反コード

以下の違反コード例では、BufferedInputStreamの宣言はひとつしか存在しないが、System.inに対して複数のBufferedInputStreamを作成している。getChar()メソッドは、呼び出されるたびにBufferedInputStreamを新規作成する。ある呼び出しの実行中にストリームから読み込まれ、バッファに取り込まれたデータは、ストリームに戻すことができないため、次の呼び出しはこれにアクセスできない。したがって、getChar()の実行終了時にバッファに残っているデータは失われてしまう。以下の違反コードでは BufferedInputStreamを使用しているが、バッファ付きラッパーはすべて危険であり、たとえば Scanner を使用している場合も攻撃可能である。

public final class InputLibrary {
  public static char getChar() throws EOFException, IOException {
    BufferedInputStream in = new BufferedInputStream(System.in); // ラッパー
    int input = in.read();
    if (input == -1) {
      throw new EOFException();
    }
    // InputStream は read() する範囲が 0から255 であることを保証するため
    // -1 でなければダウンキャストしてもよい
    return (char) input;
  }

  public static void main(String[] args) {
    try {
      // コンソールからの入力をリダイレクトするか、
      // System.setIn(new FileInputStream("input.dat")); を使用する
      System.out.print("Enter first initial: ");
      char first = getChar();
      System.out.println("Your first initial is " + first);
      System.out.print("Enter last initial: ");
      char last = getChar();
      System.out.println("Your last initial is " + last);
    } catch (EOFException e) {
      System.err.println("ERROR");
      // ハンドラに処理を移す
    } catch (IOException e) {
      System.err.println("ERROR");
      // ハンドラに処理を移す
    }
  }
}
実装の詳細 (POSIX)

このプログラムを Java 1.6.0 でコンパイルしコマンドラインから実行すると、プログラムは正常に2つの文字を入力として受け取り、出力する。しかし、標準入力をファイルにリダイレクトして実行すると、2度目のgetChar()呼び出しでストリームの終端に達した際に読み取る文字が存在しないため、プログラムはEOFExceptionを投げる。

BufferedInputStreamクラスのmarkメソッドやreset()メソッドを使えば入力したバイトを入力ストリームに書き戻すことができるのではないかと考えるかもしれない。しかしこれらのメソッドは、直接ストリームを操作するのではなく、BufferedInputStreamの内部バッファに対して動作することで先読みを行う。前述のコード例では、getChar()が呼び出されるたびに新規のBufferedInputStreamが作成され、前に作られたBufferedInputStreamは失われている。

適合コード (クラス変数)

System.inに対して作成し使用するBufferedInputStreamは1つだけに限定せよ。以下の解決法では、BufferedInputStreamをクラス変数として宣言することで、すべてのメソッドがこのBufferedInputStreamにアクセスできるようにしている。

public final class InputLibrary {
  private static BufferedInputStream in = 
      new BufferedInputStream(System.in);

  public static char getChar() throws EOFException, IOException {
    int input = in.read();
    if (input == -1) {
      throw new EOFException();
    }
    in.skip(1); // 改行コードをスキップして次の行に進む
                // 前述の違反コード例ではこの文がなくても
                // 動作していた
    return (char) input;
  }

  public static void main(String[] args) {
    try {
      System.out.print("Enter first initial: ");
      char first = getChar();
      System.out.println("Your first initial is " + first);
      System.out.print("Enter last initial: ");
      char last = getChar();
      System.out.println("Your last initial is " + last);
    } catch (EOFException e) {
      System.err.println("ERROR");
      // ハンドラに処理を移す
    } catch (IOException e) {
       System.err.println("ERROR");
       // ハンドラに処理を移す
    }
  }
}
実装の詳細 (POSIX)

このプログラムを Java 1.6.0 でコンパイルし、コマンドラインから実行すると、2つの文字を正常に受け取り、それらを出力する。前述の違反コード例とは異なり、このプログラムは、標準入力がファイルにリダイレクトされている場合でも、正しい結果を出力する。

適合コード (アクセス可能なクラス変数)

以下の解決法はSystem.inInputLibraryの両方を使用し、System.inのバッファ付きラッパーを作成している。InputLibrary クラスとその他のプログラムが単一のバッファ付きラッパーを共有しなくてはならない場合、InputLibraryクラスはそのラッパーへの参照を提供しなくてはならない。InputLibraryクラスの外部のコードは、System.inのバッファ付きラッパーを独自に作成して使うのではなく、提供されたラッパーを使用しなくてはならない。

public final class InputLibrary {
  private static BufferedInputStream in = 
      new BufferedInputStream(System.in);

  static BufferedInputStream getBufferedWrapper() {
    return in;
  }

  // ...そのほかのメソッド
}


// System.in からのユーザ入力を必要とするなんらかのコード
class AppCode {
  private static BufferedInputStream in;

  AppCode() {
    in = InputLibrary.getBufferedWrapper();
  }

  // ...そのほかのメソッド
}

ストリームからの読取りはデフォルトではスレッドセーフではないことに注意。したがってこの解決法は、マルチスレッド環境では適切ではないであろう。そのような場合は、明示的な同期を行う必要がある。

リスク評価

単一のInputStreamに対して複数のバッファ付きラッパーを作成すると、InputStreamがリダイレクトされた場合に予期せぬプログラムの動作を引き起こす恐れがある。

ルール 深刻度 可能性 修正コスト 優先度 レベル
FIO06-J P2 L3
自動検出

この脆弱性を自動検出するのは一般には難しい。ヒューリスティックなアプローチが有効であろう。

参考文献
[API 2006] method read
[API 2006] class BufferedInputStream
翻訳元

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

FIO06-J. Do not create multiple buffered wrappers on a single InputStream (revision 89)

Top へ

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