Expliziter Aufruf von finalize()
Beschreibung
Expliziter Aufruf von finalize() ist eine Codequalitätsschwäche in Java, bei der Anwendungscode die finalize()-Methode direkt auf einem Objekt aufruft, anstatt den Garbage Collector sie aufrufen zu lassen. Die finalize()-Methode ist dafür konzipiert, automatisch vom Garbage Collector aufgerufen zu werden, wenn ein Objekt für die Collection qualifiziert ist und keine verbleibenden Referenzen hat. Das explizite Aufrufen von finalize() verletzt diesen Vertrag, weil der Garbage Collector finalize() später trotzdem aufrufen wird, wenn er das Objekt einsammelt, was dazu führt, dass der Finalizer zweimal ausgeführt wird. Diese doppelte Ausführung kann zu Ressourcenmanagementfehlern, korrumpiertem Zustand und unvorhersehbarem Verhalten führen.
Risiko
Explizite finalize()-Aufrufe erzeugen ernsthafte Zuverlässigkeitsprobleme. Ressourcen, die beim ersten expliziten Aufruf freigegeben werden, können während der Garbage Collection erneut zugegriffen oder freigegeben werden, was Use-after-free-Szenarien, Double-Free-Fehler oder Exceptions verursacht. Objekte können nach expliziter Finalisierung in einem inkonsistenten Zustand zurückbleiben, aber in der Anwendung zugänglich bleiben. Native Ressourcen können korrumpiert werden, wenn Bereinigungscode zweimal läuft. Das Timing zwischen explizitem Aufruf und Garbage Collection ist unvorhersehbar, was Fehler intermittierend und schwer reproduzierbar macht. Sicherheitssensible Bereinigung (Löschen von Anmeldedaten, Schließen sicherer Verbindungen) kann fehlschlagen, wenn sie mehrfach aufgerufen wird.
Lösung
Rufen Sie finalize() niemals explizit aus Anwendungscode auf. Verwenden Sie die ordnungsgemäßen Ressourcenmanagement-Muster: Implementieren Sie AutoCloseable und verwenden Sie try-with-resources für deterministische Bereinigung, oder stellen Sie eine explizite close()-Methode bereit, die sicher mehrfach aufgerufen werden kann. Wenn Bereinigungslogik zwischen expliziter Bereinigung und Finalisierung geteilt werden muss, machen Sie die Bereinigungsmethode idempotent (sicher mehrfach aufzurufen). Erwägen Sie, finalize() gänzlich zu vermeiden - es ist seit Java 9 veraltet. Verwenden Sie java.lang.ref.Cleaner für native Ressourcenbereinigung in modernen Java-Anwendungen.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Integrität | Bereich: Integrität Unerwarteter Zustand - Doppelte Ausführung von Bereinigungscode kann Objekte in korrumpiertem oder inkonsistentem Zustand hinterlassen. |
| Sonstige | Bereich: Sonstige Qualitätsverschlechterung - Ressourcenmanagement wird unzuverlässig und führt zu Lecks, Double-Frees oder Abstürzen. |
Beispielcode
Verwundbarer Code
// Verwundbar: Expliziter Aufruf von finalize()
public class VulnerableExplicitFinalize {
public void cleanup(Widget widget) {
// Verwundbar: Expliziter finalize-Aufruf
widget.finalize(); // Falsch!
// Später wird GC finalize() erneut aufrufen!
}
}
// Verwundbar: Widget mit nicht-idempotentem finalize
public class Widget {
private Connection connection;
private boolean finalized = false;
public Widget() {
connection = Database.getConnection();
}
@Override
protected void finalize() throws Throwable {
try {
if (connection != null) {
connection.close(); // Erster Aufruf: OK
connection = null; // Zweiter Aufruf: connection bereits null
}
// Aber was wenn komplexere Bereinigung?
} finally {
super.finalize();
}
}
public void doWork() {
// Nach explizitem finalize() ist connection null
// aber Objekt wird noch verwendet!
connection.query("SELECT..."); // NullPointerException!
}
}
// Verwundbar: Ressourcenmanager mit explizitem finalize
public class VulnerableResourceManager {
private List<Resource> resources = new ArrayList<>();
public void addResource(Resource r) {
resources.add(r);
}
// Verwundbar: finalize auf verwalteten Ressourcen aufrufen
public void cleanupAll() {
for (Resource r : resources) {
try {
r.finalize(); // Falsch! GC wird dies später erneut aufrufen
} catch (Throwable t) {
// Finalize-Exceptions schlucken
}
}
resources.clear();
}
}
// Verwundbar: Versuch manueller Garbage Collection
public class VulnerableManualGC {
private ExpensiveObject expensive;
public void releaseNow() {
// Verwundbar: Versuch Bereinigung zu erzwingen
try {
expensive.finalize(); // Falscher Ansatz!
} catch (Throwable t) {
// Ignoriert
}
expensive = null;
System.gc(); // Hoffend dass GC läuft, aber finalize bereits aufgerufen!
}
}
// Verwundbar: Double-Free-Szenario mit nativen Ressourcen
public class VulnerableNativeResource {
private long nativeHandle;
public VulnerableNativeResource() {
nativeHandle = allocateNative();
}
private native long allocateNative();
private native void freeNative(long handle);
@Override
protected void finalize() throws Throwable {
try {
if (nativeHandle != 0) {
freeNative(nativeHandle); // Native Ressource freigeben
nativeHandle = 0;
}
} finally {
super.finalize();
}
}
}
// Angriffs-/Fehlerszenario
public class DoubleFreeBug {
public void exploit() {
VulnerableNativeResource resource = new VulnerableNativeResource();
// Explizites finalize - gibt native Ressource frei
try {
resource.finalize();
} catch (Throwable t) {}
// Ressource ist freigegeben, aber Objekt noch referenziert
// ... Zeit vergeht, GC läuft ...
// GC ruft finalize() erneut auf - Double Free!
// Könnte Speicherkorruption oder Absturz verursachen
}
}
Lösungscode
// Behoben: AutoCloseable und close() statt finalize() verwenden
public class SecureWidget implements AutoCloseable {
private Connection connection;
private boolean closed = false;
public SecureWidget() {
connection = Database.getConnection();
}
// Behoben: Öffentliche close()-Methode - kann explizit aufgerufen werden
@Override
public synchronized void close() {
if (!closed) {
if (connection != null) {
connection.close();
connection = null;
}
closed = true;
}
// Idempotent - sicher mehrfach aufzurufen
}
public void doWork() {
if (closed) {
throw new IllegalStateException("Widget ist geschlossen");
}
connection.query("SELECT...");
}
// Optional: finalize als Backup (veralteter Ansatz)
@Override
protected void finalize() throws Throwable {
try {
close(); // Ruft idempotente close-Methode auf
} finally {
super.finalize();
}
}
}
// Verwendung mit try-with-resources
public class SecureUsage {
public void process() {
try (SecureWidget widget = new SecureWidget()) {
widget.doWork();
} // Ruft automatisch close() auf
}
}
// Behoben: Ressourcenmanager mit ordnungsgemäßer Bereinigung
public class SecureResourceManager implements AutoCloseable {
private List<AutoCloseable> resources = new ArrayList<>();
private boolean closed = false;
public void addResource(AutoCloseable r) {
if (closed) {
throw new IllegalStateException("Manager ist geschlossen");
}
resources.add(r);
}
// Behoben: close() aufrufen, nicht finalize()
@Override
public void close() {
if (closed) return;
List<Exception> exceptions = new ArrayList<>();
for (AutoCloseable r : resources) {
try {
r.close(); // Ordnungsgemäße Bereinigungsmethode
} catch (Exception e) {
exceptions.add(e);
}
}
resources.clear();
closed = true;
if (!exceptions.isEmpty()) {
RuntimeException combined = new RuntimeException("Bereinigungsfehler");
for (Exception e : exceptions) {
combined.addSuppressed(e);
}
throw combined;
}
}
}
// Behoben: Ordnungsgemäße native Ressourcenbereinigung
public class SecureNativeResource implements AutoCloseable {
private long nativeHandle;
private boolean closed = false;
public SecureNativeResource() {
nativeHandle = allocateNative();
}
private native long allocateNative();
private native void freeNative(long handle);
@Override
public synchronized void close() {
if (!closed && nativeHandle != 0) {
freeNative(nativeHandle);
nativeHandle = 0;
closed = true;
}
}
// Kein explizites finalize() - stattdessen Cleaner verwenden (Java 9+)
}
// Behoben: Cleaner für native Ressourcen verwenden (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 mit erfasstem Handle registrieren
final long handle = this.nativeHandle;
this.cleanable = cleaner.register(this, () -> {
freeNative(handle);
});
}
@Override
public void close() {
cleanable.clean(); // Explizite Bereinigung, entfernt aus Cleaner
}
private static native long allocateNative();
private static native void freeNative(long handle);
}
// Behoben: Korrekter Weg Bereinigung anzufordern (nicht finalize)
public class SecureCleanupRequest {
private AutoCloseable resource;
public void releaseNow() {
if (resource != null) {
try {
resource.close(); // Korrekt: close() aufrufen
} catch (Exception e) {
// Bereinigungsfehler behandeln
}
resource = null;
}
}
}
// Behoben: Idempotentes Bereinigungsmuster
public class IdempotentResource implements AutoCloseable {
private volatile boolean closed = false;
private Resource internalResource;
@Override
public void close() {
// Check-then-act muss atomar sein für Thread-Sicherheit
if (closed) return;
synchronized (this) {
if (closed) return; // Double-Check nach Sperre
try {
if (internalResource != null) {
internalResource.release();
internalResource = null;
}
} finally {
closed = true;
}
}
}
// Sicher mehrfach aufzurufen - von explizitem close oder Finalizer
}
CVE-Beispiele
Keine spezifischen CVEs werden dieser CWE üblicherweise zugeordnet, da sie primär die Anwendungszuverlässigkeit betrifft statt Sicherheitsschwachstellen.
Referenzen
- MITRE Corporation. "CWE-586: Explicit Call to Finalize()." https://cwe.mitre.org/data/definitions/586.html
- Joshua Bloch. "Effective Java" - Item 8: Avoid finalizers and cleaners.
- Oracle. "Object.finalize() - Deprecated since Java 9."