InputStreamクラスおよびReaderクラスとそれらのサブクラスのread()メソッドの一般契約は、バイト配列もしくは文字配列にどう書き込むかという点において複雑である。Java API 仕様 [API 2006] には、InputStreamクラスの read(byte[], int, int) メソッドは以下の動作をすると記されている。
このメソッドのデフォルトの実装は、入力データが要求された長さに達するか、EOFを検出するか、例外が投げられるまでブロックする。サブクラスではこのメソッドをより効率的な実装にすることが推奨される。
一方、read(byte[])メソッドについては以下のように記されている。
入力ストリームから、あるバイト数だけ読み取り、バッファ配列bにデータを格納する。実際に読み取られたバイト数が整数値で返される。読み取られるバイト数は、最大で bの長さに等しい。
read() メソッド群は、読み取り可能な入力データを見つけるやいなやリターンする。つまり、配列の要素すべてを満たすほどの数のデータがなかった場合、これらのメソッドはその時点で読み込みを終了する場合がある。
read() メソッドの返り値を無視することは、「EXP00-J. メソッドの返り値を無視しない」に違反する。たとえ返り値を検証していても、セキュリティ上の問題が発生する可能性はある。なぜなら、read()メソッドのデフォルトの動作は、配列全体がデータで埋められることを保証しないからである。したがって、read()メソッドを使って配列を埋める場合、read() の返り値をチェックし、配列の一部しかデータで埋められていない場合にも対処しなくてはならない。その場合の選択肢としては、残りの配列をデータで埋める、データで満たされている配列の一部のみを使って動作する、例外を投げるなどが考えられるであろう。
このルールは、配列を引数にとるread()メソッドにのみ適用される。1バイト読み取る場合は、引数を取らずintを返り値とするInputStream.read()メソッドを呼び出せばよい。1文字読み取る場合は、引数を取らず読み取った文字をintとして返す Reader.read() メソッドを呼び出せばよい。
違反コード (read())
以下の違反コード例では、UTF-8にエンコードされた1024バイトのデータをInputStreamから読み取り、Stringとして返そうとしている。String を作成する際に明示的に文字エンコーディングを指定しており、「IDS13-J. ファイル入出力やネットワーク入出力の両端で互換性のある文字エンコーディングを使う」に適合している。
public static String readBytes(InputStream in) throws IOException { byte[] data = new byte[1024]; if (in.read(data) == -1) { throw new EOFException(); } return new String(data, "UTF-8"); }
プログラマはread()メソッドの一般契約を正しく理解しておらず、意図したデータを完全に読み取れない可能性がある。読み取るデータは1024バイトより少なく、続きのデータがまだ 入力ストリームに残っているかもしれない。
適合コード (複数の read() 呼出し)
以下の解決法では、読み取られた総バイト数に応じて残りバイト数のオフセットを調整し、求められるすべてのバイトをバッファに読み込んでいる。また、データが完全に読み取られてから文字列を作成することで、マルチバイト文字を分割してしまうことを避けている。(詳しくは「IDS10-J. 一文字を構成するデータを分割しない」を参照。)
public static String readBytes(InputStream in) throws IOException { int offset = 0; int bytesRead = 0; byte[] data = new byte[1024]; while ((bytesRead = in.read(data, offset, data.length - offset)) != -1) { offset += bytesRead; if (offset >= data.length) { break; } } String str = new String(data, "UTF-8"); return str; }
適合コード (readFully())
引数がないあるいは引数が1つのDataInputStreamクラスのreadFully()メソッドは、要求されたすべてのデータを読み取るか、そうでなければ例外を投げる。これらのメソッドは、必要なバイト数を読み取る前に入力の終端に達した場合、EOFException を投げる。また、なんらかの I/O エラーが生じた場合は IOException を投げる。
public static String readBytes(FileInputStream fis) throws IOException { byte[] data = new byte[1024]; DataInputStream dis = new DataInputStream(fis); dis.readFully(data); String str = new String(data, "UTF-8"); return str; }
リスク評価
read()メソッドの使用を誤ると、読み取るバイト数が期待したものと異なったり、文字データの並びが誤って解釈されることにつながる。
ルール | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
---|---|---|---|---|---|
FIO10-J | 低 | 低 | 中 | P2 | L3 |
関連ガイドライン
MITRE CWE | CWE-135. Incorrect calculation of multi-byte string length |
参考文献
[API 2006] | Class InputStream, DataInputStream |
[Chess 2007] | 8.1 Handling Errors with Return Codes |
[Harold 1999] | Chapter 7: Data Streams, Reading Byte Arrays |
[Phillips 2005] |
翻訳元
これは以下のページを翻訳したものです。
FIO10-J. Ensure the array is filled when using read() to fill an array (revision 85)