java.io.OutputStream クラスで定義されている write() メソッドは int 型の引数をとるが、その値は 0 から 255 の範囲でなければならない。int 型の値はこの範囲を越えることがあるので、範囲チェックを怠ると、引数の上位ビットの切り捨てを招く。
write() メソッドの一般契約は、出力ストリームに1バイト書き込む、というものである。書き込むべき1バイトには、write() メソッドの引数 b の下位8ビットが使われ、残り24ビットは無視される(詳しくは [API 2006] java.io.OutputStream.write() を参照)。
違反コード
以下の違反コード例は、ユーザ入力を検証せずに使っている。0から255の範囲に収まらない値の場合切り捨てが発生する。たとえば ASCII ベースのシステムにおいては、write(303) という呼び出しは / を表示する。なぜならば、303 の上位24ビットは無視され、下位8ビットが使われるからだ(303 % 256 = 47 であり、これは / の文字コード)。つまり、結果は 256 で割った余りとなっている。
class ConsoleWrite { public static void main(String[] args) { // 入力が 255 より大きいと予期せぬ出力となる System.out.write(Integer.valueOf(args[0])); System.out.flush(); } }
適合コード (入力の範囲チェック)
以下の適合コードでは、入力された整数が適切な範囲の値であるときのみ、その値に対応する文字を出力する。入力が int 型で表現できない範囲の値だったときには、Integer.valueOf() メソッドは NumberFormatException 例外を投げる。入力が int 型で表現できる値であるが、write() が想定する範囲の値でない場合には、このコードは ArithmeticException 例外を投げる。
class FileWrite { public static void main(String[] args) throws NumberFormatException, IOException { // 範囲チェック int value = Integer.valueOf(args[0]); if (value < 0 || value > 255) { throw new ArithmeticException("Value is out of range"); } System.out.write(value); System.out.flush(); } }
適合コード (writeInt())
以下の適合コードでは、DataOutputStream クラスの writeInt() メソッドを使っている。このメソッドは、int 型で表現できるすべての値に対応できる。
class FileWrite { public static void main(String[] args) throws NumberFormatException, IOException { DataOutputStream dos = new DataOutputStream(System.out); dos.writeInt(Integer.valueOf(args[0].toString())); System.out.flush(); } }
リスク評価
0から255の範囲に収まらない整数値の出力に write() メソッドを使うと、切り捨てが発生する。
ルール | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
---|---|---|---|---|---|
FIO09-J | 低 | 低 | 中 | P2 | L3 |
自動検出
write() メソッドの使用を自動検出するのは簡単である。切り捨ての発生が、プログラマが意図した動作かどうかを適切に判別するのは、一般には難しい。ヒューリスティックなチェックが役に立つかもしれない。
関連ガイドライン
MITRE CWE | CWE-252. Unchecked return value |
参考文献
[API 2006] | Method write() |
[Harold 1999] |
翻訳元
これは以下のページを翻訳したものです。
FIO09-J. Do not rely on the write() method to output integers outside the range 0 to 255 (revision 72)