Explicit Call to Finalize()

Description

Explicit Call to Finalize() is a code quality weakness in Java where application code directly calls the finalize() method on an object instead of letting the garbage collector invoke it. The finalize() method is designed to be called automatically by the garbage collector when an object becomes eligible for collection and has no remaining references. Calling finalize() explicitly violates this contract because the garbage collector will still call finalize() later when it collects the object, resulting in the finalizer being executed twice. This double invocation can lead to resource management errors, corrupted state, and unpredictable behavior.

Risk

Explicit finalize() calls create serious reliability problems. Resources released in the first explicit call may be accessed or released again during garbage collection, causing use-after-free scenarios, double-free errors, or exceptions. Objects may be left in an inconsistent state after explicit finalization but remain accessible in the application. Native resources can be corrupted if cleanup code runs twice. The timing between explicit call and garbage collection is unpredictable, making bugs intermittent and hard to reproduce. Security-sensitive cleanup (clearing credentials, closing secure connections) may fail to work correctly when called multiple times.

Solution

Never call finalize() explicitly from application code. Use the proper resource management patterns: implement AutoCloseable and use try-with-resources for deterministic cleanup, or provide an explicit close() method that can be safely called multiple times. If cleanup logic must be shared between explicit cleanup and finalization, make the cleanup method idempotent (safe to call multiple times). Consider avoiding finalize() entirely—it has been deprecated since Java 9. Use java.lang.ref.Cleaner for native resource cleanup in modern Java applications.

Common Consequences

ImpactDetails
IntegrityScope: Integrity

Unexpected State - Double execution of cleanup code can leave objects in corrupted or inconsistent state.
OtherScope: Other

Quality Degradation - Resource management becomes unreliable, leading to leaks, double-frees, or crashes.

Example Code

Vulnerable Code

// Vulnerable: Explicit call to finalize()
public class VulnerableExplicitFinalize {

    public void cleanup(Widget widget) {
        // Vulnerable: Explicit finalize call
        widget.finalize();  // Wrong!

        // Later, GC will call finalize() again!
    }
}

// Vulnerable: Widget with non-idempotent 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();  // First call: OK
                connection = null;   // Second call: connection already null
            }
            // But what if there's more complex cleanup?
        } finally {
            super.finalize();
        }
    }

    public void doWork() {
        // After explicit finalize(), connection is null
        // but object is still being used!
        connection.query("SELECT...");  // NullPointerException!
    }
}

// Vulnerable: Resource manager with explicit finalize
public class VulnerableResourceManager {
    private List<Resource> resources = new ArrayList<>();

    public void addResource(Resource r) {
        resources.add(r);
    }

    // Vulnerable: Calling finalize on managed resources
    public void cleanupAll() {
        for (Resource r : resources) {
            try {
                r.finalize();  // Wrong! GC will call this again later
            } catch (Throwable t) {
                // Swallowing finalize exceptions
            }
        }
        resources.clear();
    }
}

// Vulnerable: Attempting manual garbage collection
public class VulnerableManualGC {
    private ExpensiveObject expensive;

    public void releaseNow() {
        // Vulnerable: Trying to force cleanup
        try {
            expensive.finalize();  // Wrong approach!
        } catch (Throwable t) {
            // Ignored
        }
        expensive = null;
        System.gc();  // Hoping GC runs, but finalize already called!
    }
}

// Vulnerable: Double-free scenario with native resources
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);  // Free native resource
                nativeHandle = 0;
            }
        } finally {
            super.finalize();
        }
    }
}

// Attack/Bug scenario
public class DoubleFreeBug {
    public void exploit() {
        VulnerableNativeResource resource = new VulnerableNativeResource();

        // Explicit finalize - frees native resource
        try {
            resource.finalize();
        } catch (Throwable t) {}

        // Resource is freed, but object still referenced
        // ... time passes, GC runs ...

        // GC calls finalize() again - double free!
        // Could cause memory corruption or crash
    }
}

Fixed Code

// Fixed: Use AutoCloseable and close() instead of finalize()
public class SecureWidget implements AutoCloseable {
    private Connection connection;
    private boolean closed = false;

    public SecureWidget() {
        connection = Database.getConnection();
    }

    // Fixed: Public close() method - can be called explicitly
    @Override
    public synchronized void close() {
        if (!closed) {
            if (connection != null) {
                connection.close();
                connection = null;
            }
            closed = true;
        }
        // Idempotent - safe to call multiple times
    }

    public void doWork() {
        if (closed) {
            throw new IllegalStateException("Widget is closed");
        }
        connection.query("SELECT...");
    }

    // Optional: finalize as backup (deprecated approach)
    @Override
    protected void finalize() throws Throwable {
        try {
            close();  // Calls idempotent close method
        } finally {
            super.finalize();
        }
    }
}

// Usage with try-with-resources
public class SecureUsage {
    public void process() {
        try (SecureWidget widget = new SecureWidget()) {
            widget.doWork();
        }  // Automatically calls close()
    }
}

// Fixed: Resource manager with proper cleanup
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 is closed");
        }
        resources.add(r);
    }

    // Fixed: Call close(), not finalize()
    @Override
    public void close() {
        if (closed) return;

        List<Exception> exceptions = new ArrayList<>();
        for (AutoCloseable r : resources) {
            try {
                r.close();  // Proper cleanup method
            } catch (Exception e) {
                exceptions.add(e);
            }
        }
        resources.clear();
        closed = true;

        if (!exceptions.isEmpty()) {
            RuntimeException combined = new RuntimeException("Cleanup errors");
            for (Exception e : exceptions) {
                combined.addSuppressed(e);
            }
            throw combined;
        }
    }
}

// Fixed: Proper native resource cleanup
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;
        }
    }

    // No explicit finalize() - use Cleaner instead (Java 9+)
}

// Fixed: Using Cleaner for native resources (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();

        // Register cleaning action with captured handle
        final long handle = this.nativeHandle;
        this.cleanable = cleaner.register(this, () -> {
            freeNative(handle);
        });
    }

    @Override
    public void close() {
        cleanable.clean();  // Explicit cleanup, removes from cleaner
    }

    private static native long allocateNative();
    private static native void freeNative(long handle);
}

// Fixed: Correct way to request cleanup (not finalize)
public class SecureCleanupRequest {
    private AutoCloseable resource;

    public void releaseNow() {
        if (resource != null) {
            try {
                resource.close();  // Correct: call close()
            } catch (Exception e) {
                // Handle cleanup error
            }
            resource = null;
        }
    }
}

// Fixed: Idempotent cleanup pattern
public class IdempotentResource implements AutoCloseable {
    private volatile boolean closed = false;
    private Resource internalResource;

    @Override
    public void close() {
        // Check-then-act must be atomic for thread safety
        if (closed) return;

        synchronized (this) {
            if (closed) return;  // Double-check after lock

            try {
                if (internalResource != null) {
                    internalResource.release();
                    internalResource = null;
                }
            } finally {
                closed = true;
            }
        }
    }

    // Safe to call multiple times - from explicit close or finalizer
}

CVE Examples

No specific CVEs are commonly attributed to this CWE, as it primarily affects application reliability rather than security vulnerabilities.


References

  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."