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
| Impact | Details |
|---|---|
| Availability | Scope: Availability DoS: Resource Consumption - Resource leaks from missed cleanup can accumulate, eventually exhausting system resources like memory, file handles, or connections. |
| Integrity | Scope: 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
- 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 by Joshua Bloch. "Item 8: Avoid finalizers and cleaners."