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 構文を使った前述のコードでは、リソース割り当て(すなわち FileInputStream
や BufferedReader
の作成)時に発生する例外、while
ループの実行中に発生する IOException
例外、bufRead
や stream
をクローズする際に発生する IOException
を含め、すべての IOException
を catch
節に送り、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
ブロックの中に、リソースをクローズするコードを追加することで、データベース接続の枯渇に対応しようとしている。しかし、rs
、stmt
、あるいは conn
が null
である場合、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 構文によって、すべての SQLException
が catch
節に送られ、例外ハンドラに処理が移される。リソースの割り当て時(Connection
、Statement
、ResultSet
の作成時)に発生する例外や、processResults()
が投げる SQLException
、さらに rs
、stmt
、conn
のクローズ時に発生する SQLException
が対象となる。
リスク評価
メモリ以外のシステムリソースを、不要になったとき明示的に解放しないと、リソースの枯渇につながる恐れがある。
ルール |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
FIO04-J |
低 |
中 |
中 |
P4 |
L3 |
自動検出
この脆弱性の自動検出は一般には難しいが、特定のいくつかのケースについては検出できる。
静的解析ツールの中には、ソケットリソースや、ファイルやその他のシステムリソースを表すストリームが漏えいする場合に検知できるものもある。
ツール
|
バージョン
|
チェッカー
|
説明
|
---|---|---|---|
Coverity | 7.5 |
ITERATOR |
実装済み |
Parasoft Jtest | 10.3 | BD.RES.LEAKS, OPT.CIO, OPT.CCR | 実装済み |
SonarQube | 6.7 | S2095 | 実装済み |
関連ガイドライン
SEI CERT C++ Coding Standard |
FIO51-CPP. Close files when they are no longer needed |
CWE-404, Improper Resource Shutdown or Release |
Android Implementation Details
適合コード(try-with-resources)は APIレベル18(Android 4.3) ではサポートされていない。
参考文献
[API 2014] |
|
[Goetz 2006b] |
|
[J2SE 2011] |
The |
翻訳元
これは以下のページを翻訳したものです。
FIO04-J. Release resources when they are no longer needed (revision 125)