finalize()-Methode ohne super.finalize()
Beschreibung
finalize()-Methode ohne super.finalize() ist eine Schwachstelle in Java, bei der eine Klasse die finalize()-Methode überschreibt, aber nicht super.finalize() innerhalb ihrer Implementierung aufruft. Die finalize()-Methode wird vom Garbage Collector aufgerufen, bevor der Speicher eines Objekts zurückgewonnen wird. Wenn eine Unterklasse finalize() überschreibt, ohne die finalize()-Methode der Oberklasse aufzurufen, werden die in Elternklassen definierten Bereinigungsoperationen nie ausgeführt. Dies kann zu Ressourcenlecks, nicht geschlossenen Verbindungen oder anderen Bereinigungsfehlern führen, die sich im Laufe der Zeit ansammeln und schließlich die Anwendungsleistung beeinträchtigen oder Ressourcenerschöpfung verursachen.
Risiko
Das Nicht-Aufrufen von super.finalize() kann erhebliche Ressourcenverwaltungsprobleme verursachen. Elternklassen können sich auf finalize() verlassen, um native Ressourcen freizugeben, Dateihandles zu schließen oder externe Verbindungen zu bereinigen. Wenn diese Bereinigungsoperationen übersprungen werden, akkumulieren sich Ressourcen, was zu Speicherlecks, Erschöpfung von Dateideskriptoren oder Erschöpfung des Datenbankverbindungspools führt. Die Auswirkungen sind möglicherweise nicht sofort sichtbar, manifestieren sich aber unter anhaltender Last oder bei langlaufenden Anwendungen. Im Sicherheitskontext können nicht freigegebene Ressourcen sensible Daten enthalten, die länger als beabsichtigt bestehen bleiben und das Zeitfenster für potenzielle Datenexposition vergrößern.
Lösung
Rufen Sie beim Überschreiben der finalize()-Methode immer super.finalize() in einem finally-Block auf, um sicherzustellen, dass der Bereinigungscode der Elternklasse auch dann ausgeführt wird, wenn die finalize()-Methode der Unterklasse eine Exception wirft. Beachten Sie jedoch, dass finalize() seit Java 9 aufgrund inhärenter Probleme mit dem Finalisierungsmechanismus veraltet ist. Bevorzugen Sie die Verwendung von try-with-resources-Anweisungen und die Implementierung von AutoCloseable für die Ressourcenverwaltung. Erwägen Sie die Verwendung von Cleaner oder PhantomReference für die Bereinigung nativer Ressourcen in modernen Java-Anwendungen. Verlassen Sie sich nicht auf Finalisierung für sicherheitskritische Ressourcenbereinigung.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Verfügbarkeit | Bereich: Verfügbarkeit DoS: Ressourcenverbrauch - Ressourcenlecks durch verpasste Bereinigung können sich ansammeln und schließlich Systemressourcen wie Speicher, Dateihandles oder Verbindungen erschöpfen. |
| Integrität | Bereich: Integrität Unerwarteter Zustand - Anwendung kann mit inkonsistentem Zustand arbeiten, wenn der Bereinigungscode der Elternklasse nicht ausgeführt wird und Objekte teilweise finalisiert bleiben. |
Beispielcode
Verwundbarer Code
// Verwundbar: finalize() ohne super.finalize()
public class DatabaseConnection {
private Connection connection;
public DatabaseConnection(String url) throws SQLException {
this.connection = DriverManager.getConnection(url);
}
@Override
protected void finalize() {
// finalize() der Elternklasse wird nie aufgerufen!
// Wenn diese Klasse eine ressourcenverwaltende Klasse erweitert,
// wird die Bereinigung der Elternklasse übersprungen
try {
if (connection != null && !connection.isClosed()) {
connection.close();
}
} catch (SQLException e) {
// Exception geschluckt
}
// Fehlt: super.finalize();
}
}
// Verwundbar: Ressourcenverwaltende Elternklasse
public class ResourceManager {
private List<NativeResource> nativeResources = new ArrayList<>();
protected void registerResource(NativeResource resource) {
nativeResources.add(resource);
}
@Override
protected void finalize() throws Throwable {
// Native Ressourcen bereinigen
for (NativeResource resource : nativeResources) {
resource.release();
}
}
}
// Verwundbar: Kindklasse überspringt Eltern-Finalisierung
public class VulnerableFileHandler extends ResourceManager {
private FileOutputStream outputStream;
public VulnerableFileHandler(String filename) throws IOException {
this.outputStream = new FileOutputStream(filename);
registerResource(new NativeFileResource(outputStream.getFD()));
}
@Override
protected void finalize() {
// Verwundbar: Schließt nur Stream, ruft nicht super.finalize() auf
// Native Ressourcen, die in Elternklasse registriert sind, werden nie freigegeben!
try {
if (outputStream != null) {
outputStream.close();
}
} catch (IOException e) {
// Ignoriert
}
// Fehlt: super.finalize() - native Ressourcen lecken!
}
}
// Verwundbar: Exception verhindert Eltern-Finalisierung
public class VulnerableCacheManager extends ResourceManager {
private Map<String, Object> cache = new HashMap<>();
@Override
protected void finalize() {
// Wenn dies wirft, läuft super.finalize() nie
for (Object value : cache.values()) {
if (value instanceof Closeable) {
((Closeable) value).close(); // Kann IOException werfen
}
}
super.finalize(); // Wird nie erreicht wenn Exception geworfen
}
}
// Verwundbar: Komplexe Vererbungshierarchie
public class BaseResource {
private long nativeHandle;
public BaseResource() {
this.nativeHandle = allocateNative();
}
private native long allocateNative();
private native void freeNative(long handle);
@Override
protected void finalize() throws Throwable {
if (nativeHandle != 0) {
freeNative(nativeHandle);
nativeHandle = 0;
}
}
}
public class CachedResource extends BaseResource {
private byte[] buffer;
@Override
protected void finalize() throws Throwable {
buffer = null; // Puffer löschen
super.finalize(); // Korrekt - ruft Elternklasse auf
}
}
// Verwundbar: Unterbricht Finalisierungskette
public class VulnerablePooledResource extends CachedResource {
private ObjectPool pool;
@Override
protected void finalize() {
// Verwundbar: Ruft nicht super.finalize() auf
// BaseResource.finalize() läuft nie!
// Nativer Speicher leckt!
if (pool != null) {
pool.returnResource(this);
}
// Fehlt: super.finalize()
}
}
Lösungscode
// Behoben: Ordnungsgemäßes finalize() mit super.finalize() im finally-Block
public class SecureDatabaseConnection {
private Connection connection;
public SecureDatabaseConnection(String url) throws SQLException {
this.connection = DriverManager.getConnection(url);
}
@Override
protected void finalize() throws Throwable {
try {
// Bereinigung dieser Klasse durchführen
if (connection != null && !connection.isClosed()) {
connection.close();
}
} finally {
// Behoben: Immer super.finalize() im finally-Block aufrufen
super.finalize();
}
}
}
// Behoben: Ordnungsgemäße Finalisierungskette
public class SecureFileHandler extends ResourceManager {
private FileOutputStream outputStream;
public SecureFileHandler(String filename) throws IOException {
this.outputStream = new FileOutputStream(filename);
registerResource(new NativeFileResource(outputStream.getFD()));
}
@Override
protected void finalize() throws Throwable {
try {
if (outputStream != null) {
outputStream.close();
}
} finally {
// Behoben: Eltern-Bereinigung läuft immer
super.finalize();
}
}
}
// Behoben: Exception-sichere Finalisierung
public class SecureCacheManager extends ResourceManager {
private Map<String, Object> cache = new HashMap<>();
@Override
protected void finalize() throws Throwable {
try {
// Bereinigung mit individueller Exception-Behandlung
for (Object value : cache.values()) {
try {
if (value instanceof Closeable) {
((Closeable) value).close();
}
} catch (IOException e) {
// Protokollieren aber Bereinigung fortsetzen
}
}
} finally {
// Behoben: Wird immer aufgerufen auch wenn Exceptions auftreten
super.finalize();
}
}
}
// Moderner Ansatz: AutoCloseable statt finalize() verwenden
// Dies ist das empfohlene Muster seit Java 7+
public class ModernDatabaseConnection implements AutoCloseable {
private Connection connection;
public ModernDatabaseConnection(String url) throws SQLException {
this.connection = DriverManager.getConnection(url);
}
@Override
public void close() throws SQLException {
if (connection != null && !connection.isClosed()) {
connection.close();
}
}
// Verwendung mit try-with-resources (automatische Bereinigung)
public static void example() {
try (ModernDatabaseConnection conn =
new ModernDatabaseConnection("jdbc:mysql://localhost/db")) {
// Verbindung verwenden
} catch (SQLException e) {
// Exception behandeln
}
// Verbindung automatisch geschlossen
}
}
// Moderner Ansatz: Cleaner für native Ressourcenbereinigung (Java 9+)
import java.lang.ref.Cleaner;
public class ModernNativeResource implements AutoCloseable {
private static final Cleaner cleaner = Cleaner.create();
private final long nativeHandle;
private final Cleaner.Cleanable cleanable;
public ModernNativeResource() {
this.nativeHandle = allocateNative();
// Bereinigungsaktion registrieren
final long handle = this.nativeHandle;
this.cleanable = cleaner.register(this, () -> {
if (handle != 0) {
freeNative(handle);
}
});
}
@Override
public void close() {
cleanable.clean(); // Explizite Bereinigung
}
private static native long allocateNative();
private static native void freeNative(long handle);
}
// Behoben: Vererbung mit ordnungsgemäßer Bereinigungskette unter Verwendung von AutoCloseable
public abstract class BaseAutoCloseable implements AutoCloseable {
private List<AutoCloseable> resources = new ArrayList<>();
protected void registerResource(AutoCloseable resource) {
resources.add(resource);
}
@Override
public void close() throws Exception {
Exception firstException = null;
// Alle registrierten Ressourcen schließen
for (AutoCloseable resource : resources) {
try {
resource.close();
} catch (Exception e) {
if (firstException == null) {
firstException = e;
} else {
firstException.addSuppressed(e);
}
}
}
if (firstException != null) {
throw firstException;
}
}
}
public class ModernFileHandler extends BaseAutoCloseable {
private final FileOutputStream outputStream;
public ModernFileHandler(String filename) throws IOException {
this.outputStream = new FileOutputStream(filename);
registerResource(outputStream); // Wird durch Elternklasse geschlossen
}
@Override
public void close() throws Exception {
// Zusätzliche Bereinigung durchführen
// Dann Elternklasse aufrufen um registrierte Ressourcen zu schließen
super.close();
}
}
CVE-Beispiele
Keine spezifischen CVEs sind öffentlich für diese CWE dokumentiert. Jedoch haben Ressourcenleck-Schwachstellen aufgrund unsachgemäßer Finalisierung zu Denial-of-Service-Bedingungen in Java-Anwendungen beigetragen.
Referenzen
- MITRE Corporation. "CWE-568: finalize() Method Without super.finalize()." https://cwe.mitre.org/data/definitions/568.html
- Oracle. "Object.finalize() - Deprecated since Java 9."
- Effective Java von Joshua Bloch. "Item 8: Avoid finalizers and cleaners."