finalize() Method Without super.finalize()

Description

finalize() Method Without super.finalize() is a vulnerability in Java where a class overrides the finalize() method but fails to call super.finalize() within its implementation. The finalize() method is called by the garbage collector before reclaiming an object's memory. When a subclass overrides finalize() without calling the superclass's finalize() method, the cleanup operations defined in parent classes are never executed. This can lead to resource leaks, unclosed connections, or other cleanup failures that accumulate over time, eventually degrading application performance or causing resource exhaustion.

Risk

Failing to call super.finalize() can cause significant resource management problems. Parent classes may rely on finalize() to release native resources, close file handles, or clean up external connections. When these cleanup operations are skipped, resources accumulate, leading to memory leaks, file descriptor exhaustion, or database connection pool depletion. The effects may not be immediately visible but manifest under sustained load or long-running applications. In security contexts, unreleased resources might contain sensitive data that persists longer than intended, increasing the window for potential data exposure.

Solution

Always call super.finalize() in a finally block when overriding the finalize() method to ensure parent class cleanup code executes even if the subclass finalize() throws an exception. However, note that finalize() is deprecated since Java 9 due to inherent problems with the finalization mechanism. Prefer using try-with-resources statements and implementing AutoCloseable for resource management. Consider using Cleaner or PhantomReference for cleanup of native resources in modern Java applications. Avoid relying on finalization for security-critical resource cleanup.

Common Consequences

ImpactDetails
AvailabilityScope: Availability

DoS: Resource Consumption - Resource leaks from missed cleanup can accumulate, eventually exhausting system resources like memory, file handles, or connections.
IntegrityScope: Integrity

Unexpected State - Application may operate with inconsistent state when parent class cleanup code fails to execute, leaving objects partially finalized.

Example Code

Vulnerable Code

// Vulnerable: finalize() without super.finalize()
public class DatabaseConnection {
    private Connection connection;

    public DatabaseConnection(String url) throws SQLException {
        this.connection = DriverManager.getConnection(url);
    }

    @Override
    protected void finalize() {
        // Parent class finalize() is never called!
        // If this class extends a resource-managing class,
        // the parent's cleanup is skipped
        try {
            if (connection != null && !connection.isClosed()) {
                connection.close();
            }
        } catch (SQLException e) {
            // Swallowed exception
        }
        // Missing: super.finalize();
    }
}

// Vulnerable: Resource-managing parent class
public class ResourceManager {
    private List<NativeResource> nativeResources = new ArrayList<>();

    protected void registerResource(NativeResource resource) {
        nativeResources.add(resource);
    }

    @Override
    protected void finalize() throws Throwable {
        // Clean up native resources
        for (NativeResource resource : nativeResources) {
            resource.release();
        }
    }
}

// Vulnerable: Child class skips parent finalization
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() {
        // Vulnerable: Only closes stream, doesn't call super.finalize()
        // Native resources registered in parent are never released!
        try {
            if (outputStream != null) {
                outputStream.close();
            }
        } catch (IOException e) {
            // Ignored
        }
        // Missing: super.finalize() - native resources leak!
    }
}

// Vulnerable: Exception prevents parent finalization
public class VulnerableCacheManager extends ResourceManager {
    private Map<String, Object> cache = new HashMap<>();

    @Override
    protected void finalize() {
        // If this throws, super.finalize() never runs
        for (Object value : cache.values()) {
            if (value instanceof Closeable) {
                ((Closeable) value).close();  // May throw IOException
            }
        }
        super.finalize();  // Never reached if exception thrown
    }
}
// Vulnerable: Complex inheritance hierarchy
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;  // Clear buffer
        super.finalize();  // Correct - calls parent
    }
}

// Vulnerable: Breaks finalization chain
public class VulnerablePooledResource extends CachedResource {
    private ObjectPool pool;

    @Override
    protected void finalize() {
        // Vulnerable: Doesn't call super.finalize()
        // BaseResource.finalize() never runs!
        // Native memory leaks!
        if (pool != null) {
            pool.returnResource(this);
        }
        // Missing: super.finalize()
    }
}

Fixed Code

// Fixed: Proper finalize() with super.finalize() in 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 {
            // Perform this class's cleanup
            if (connection != null && !connection.isClosed()) {
                connection.close();
            }
        } finally {
            // Fixed: Always call super.finalize() in finally block
            super.finalize();
        }
    }
}

// Fixed: Proper finalization chain
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 {
            // Fixed: Parent cleanup always runs
            super.finalize();
        }
    }
}

// Fixed: Exception-safe finalization
public class SecureCacheManager extends ResourceManager {
    private Map<String, Object> cache = new HashMap<>();

    @Override
    protected void finalize() throws Throwable {
        try {
            // Cleanup with individual exception handling
            for (Object value : cache.values()) {
                try {
                    if (value instanceof Closeable) {
                        ((Closeable) value).close();
                    }
                } catch (IOException e) {
                    // Log but continue cleanup
                }
            }
        } finally {
            // Fixed: Always called even if exceptions occur
            super.finalize();
        }
    }
}
// Modern approach: Use AutoCloseable instead of finalize()
// This is the recommended pattern since 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();
        }
    }

    // Usage with try-with-resources (automatic cleanup)
    public static void example() {
        try (ModernDatabaseConnection conn =
                new ModernDatabaseConnection("jdbc:mysql://localhost/db")) {
            // Use connection
        } catch (SQLException e) {
            // Handle exception
        }
        // Connection automatically closed
    }
}

// Modern approach: Use Cleaner for native resource cleanup (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 cleanup action
        final long handle = this.nativeHandle;
        this.cleanable = cleaner.register(this, () -> {
            if (handle != 0) {
                freeNative(handle);
            }
        });
    }

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

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

// Fixed: Inheritance with proper cleanup chain using 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;

        // Close all registered resources
        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);  // Will be closed by parent
    }

    @Override
    public void close() throws Exception {
        // Perform any additional cleanup
        // Then call parent to close registered resources
        super.close();
    }
}

CVE Examples

No specific CVEs are publicly documented for this CWE. However, resource leak vulnerabilities stemming from improper finalization have contributed to denial-of-service conditions in Java applications.


References

  1. MITRE Corporation. "CWE-568: finalize() Method Without super.finalize()." https://cwe.mitre.org/data/definitions/568.html
  2. Oracle. "Object.finalize() - Deprecated since Java 9."
  3. Effective Java by Joshua Bloch. "Item 8: Avoid finalizers and cleaners."