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

FIO04-J. 不要になったリソースは解放する

Java のガベージコレクタは、誰からも参照されずまだ解放されていないメモリ領域を解放するために呼び出される。しかし、ガベージコレクタは、オープンされたファイルディスクリプタやデータベース接続といった、メモリ以外のリソースを解放することはできない。そのようなリソースの解放をプログラマが行っていない場合、リソース枯渇攻撃を受ける可能性がある。さらに、ファイナライザによって Lock オブジェクトや Semaphore オブジェクトなどのリソースが解放されるのをプログラムが待っている間に、リソース枯渇状態が発生する可能性もある。このような問題が発生する理由は、Java では、ファイナライザが実行されるのは「プログラムが終了する前のどこか」であり、「いつ」実行されるかが明確には規定されていないからである。また、出力ストリームはオブジェクトへの参照をキャッシュする可能性があり、キャッシュされた情報は出力ストリームがクローズされるまでガベージコレクションされることはない。したがって、出力ストリームは使用後ただちにクローズすべきである。

システムリソースの解放をファイナライザに頼っていたり、プログラムのどの部分で解放を行うか誤解している場合、リソースリークが発生する可能性がある。負荷の高いシステムにおいては、ファイナライザが呼び出されるまでの遅延は、攻撃者がサービス運用妨害を行う余地を与えてしまう。結論としては、メモリ以外のシステムリソースは明示的に、ファイナライザ以外の方法で解放しなくてはならない。ファイナライザの使用を避けるべきそのほかの理由については、「MET12-J. ファイナライザは使わない」を参照。

Windows システムにおいては、オープンされているファイルを削除しようとするとエラーになる。詳しくは、「FIO03-J. 一時ファイルはプログラムの終了前に削除する」を参照。

違反コード (ファイルハンドル)

以下の違反コード例では、ファイルをオープンして使用するが、明示的にクローズしていない。

public int processFile(String fileName)
                       throws IOException, FileNotFoundException {
  FileInputStream stream = new FileInputStream(fileName);
  BufferedReader bufRead =
      new BufferedReader(new InputStreamReader(stream));
  String line;
  while ((line = bufRead.readLine()) != null) {
    sendLine(line);
  }
  return 1;
}
適合コード

以下の適合コードでは、いかなる例外の発生にも関係なく、取得したすべてのリソースを解放する。bufRead を参照すると例外が発生するかもしれないが、そのような場合でも FileInputStream オブジェクトはクローズされる。

try {
  final FileInputStream stream = new FileInputStream(fileName);
  try {
    final BufferedReader bufRead =
        new BufferedReader(new InputStreamReader(stream));

    String line;
    while ((line = bufRead.readLine()) != null) {
      sendLine(line);
    }
  } finally {
    if (stream != null) {
      try {
        stream.close();
      } catch (IOException e) {
        // ハンドラに処理を移す
      }
    }
  }
} catch (IOException e) {
  // ハンドラに処理を移す
}
適合コード (try-with-resources)

以下の適合コードでは、Java SE 7 にて導入された try-with-resources 構文を使用することで、処理中にどのような例外が発生しても、取得したすべてのリソースを解放する。

try (FileInputStream stream = new FileInputStream(fileName);
     BufferedReader bufRead =
         new BufferedReader(new InputStreamReader(stream))) {

  String line;
  while ((line = bufRead.readLine()) != null) {
    sendLine(line);
  }
} catch (IOException e) {
  // ハンドラに処理を移す
}

try-with-resources 構文を使った前述のコードでは、リソース割り当て(すなわち FileInputStreamBufferedReader の作成)時に発生する例外、while ループの実行中に発生する IOException 例外、bufReadstream をクローズする際に発生する IOException を含め、すべての IOExceptioncatch 節に送り、catch 節において例外ハンドラに処理を移している。

違反コード (データベースへの接続)

データベース接続におけるリソースプールの枯渇は、より深刻な問題となる。データベースサーバの多くは、サーバの設定やライセンスによって、ある決まった数の接続しか許さない。それゆえ、接続を解放しないと利用可能な接続はすぐに枯渇してしまうであろう。以下の違反コード例では、SQL文の実行中あるいはその結果の処理中にエラーが発生した場合、データベース接続をクローズしていない。

public void getResults(String sqlQuery) {
  try {
    Connection conn = getConnection();
    Statement stmt = conn.createStatement();
    ResultSet rs = stmt.executeQuery(sqlQuery);
    processResults(rs);
    stmt.close(); conn.close();
  } catch (SQLException e) { /* ハンドラに処理を移す */ }
}
違反コード

以下の違反コード例では、finally ブロックの中に、リソースをクローズするコードを追加することで、データベース接続の枯渇に対応しようとしている。しかし、rsstmt、あるいは connnull である場合、finally ブロックにあるコードは NullPointerException を投げる。

Statement stmt = null;
ResultSet rs = null;
Connection conn = getConnection();
try {
  stmt = conn.createStatement();
  rs = stmt.executeQuery(sqlQuery);
  processResults(rs);
} catch(SQLException e) {
  // ハンドラに処理を移す
} finally {
  rs.close();
  stmt.close(); conn.close();
}
違反コード

以下の違反コード例では、rs.close() あるいは stmt.close() 呼び出しにおいて SQLException が発生する可能性がある。その場合、conn.close() は呼び出されない。このコードは「ERR05-J. チェック例外を finally ブロックの外に伝播させない」に違反している。

Statement stmt = null;
ResultSet rs = null;
Connection conn = getConnection();
try {
  stmt = conn.createStatement();
  rs = stmt.executeQuery(sqlQuery);
  processResults(rs);
} catch (SQLException e) {
  // ハンドラに処理を移す
} finally {
  if (rs != null) {
    rs.close();
  }
  if (stmt != null) {
    stmt.close();
  } if (conn !=null) {
       conn.close();
    }
}
適合コード

以下の適合コードでは、リソースは確実に解放される。

Statement stmt = null;
ResultSet rs = null;
Connection conn = getConnection();
try {
  stmt = conn.createStatement();
  rs = stmt.executeQuery(sqlQuery);
  processResults(rs);
} catch (SQLException e) {
  // ハンドラに処理を移す
} finally {
  try {
    if (rs != null) {rs.close();}
  } catch (SQLException e) {
    // ハンドラに処理を移す
  } finally {
    try {
      if (stmt != null) {stmt.close();}
    } catch (SQLException e) {
        // ハンドラに処理を移す
    } finally {
      try {
        if (conn != null) {conn.close();}
      } catch (SQLException e) {
        // ハンドラに処理を移す
      }
    }
  }
}
適合コード (try-with-resources)

以下の適合コードでは、Java SE 7 で導入された try-with-resources 構文を使用し、リソースを確実に解放している。

try (Connection conn = getConnection();
     Statement stmt = conn.createStatement();
     ResultSet rs = stmt.executeQuery(sqlQuery)) {
  processResults(rs);
} catch (SQLException e) {
  // 処理を移す
}

try-with-resources 構文によって、すべての SQLExceptioncatch 節に送られ、例外ハンドラに処理が移される。リソースの割り当て時(ConnectionStatementResultSet の作成時)に発生する例外や、processResults() が投げる SQLException、さらに rsstmtconn のクローズ時に発生する SQLException が対象となる。

リスク評価

メモリ以外のシステムリソースを、不要になったとき明示的に解放しないと、リソースの枯渇につながる恐れがある。

ルール

深刻度

可能性

修正コスト

優先度

レベル

FIO04-J

P4

L3

自動検出

この脆弱性の自動検出は一般には難しいが、特定のいくつかのケースについては検出できる。

静的解析ツールの中には、ソケットリソースや、ファイルやその他のシステムリソースを表すストリームが漏えいする場合に検知できるものもある。

ツール
バージョン
チェッカー
説明
Coverity 7.5

ITERATOR
JDBC_CONNECTION
RESOURCE_LEAK

実装済み
Parasoft Jtest 10.3 BD.RES.LEAKS, OPT.CIO, OPT.CCR 実装済み
SonarQube 6.7 S2095 実装済み
関連ガイドライン

CERT C コーディングスタンダード

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

SEI CERT C++ Coding Standard

FIO51-CPP. Close files when they are no longer needed

MITRE CWE

CWE-404, Improper Resource Shutdown or Release
CWE-405, Asymmetric Resource Consumption (Amplification)
CWE-459, Incomplete Cleanup
CWE-770, Allocation of Resources without Limits or Throttling

Android Implementation Details

適合コード(try-with-resources)は APIレベル18(Android 4.3) ではサポートされていない。

参考文献

[API 2014]

Class Object

[Goetz 2006b]

 

[J2SE 2011]

The try-with-resources Statement

翻訳元

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

FIO04-J. Release resources when they are no longer needed (revision 125)

Top へ

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