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

AuswirkungDetails
IntegritätBereich: Integrität

Unerwarteter Zustand - Doppelte Ausführung von Bereinigungscode kann Objekte in korrumpiertem oder inkonsistentem Zustand hinterlassen.
SonstigeBereich: 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

  1. MITRE Corporation. "CWE-586: Explicit Call to Finalize()." https://cwe.mitre.org/data/definitions/586.html
  2. Joshua Bloch. "Effective Java" - Item 8: Avoid finalizers and cleaners.
  3. Oracle. "Object.finalize() - Deprecated since Java 9."