Missing Release of Resource after Effective Lifetime

Description

Missing Release of Resource after Effective Lifetime is a resource management vulnerability where software fails to release a resource after it is no longer needed. This applies to various resource types including memory allocations, file handles, database connections, network sockets, locks, and other system resources. When resources are not properly released, they remain allocated indefinitely, gradually consuming available resources until exhaustion occurs. This weakness affects virtually all programming languages and platforms, though languages with automatic garbage collection may handle some resource types automatically.

Risk

Failure to release resources leads to resource leaks that accumulate over time, eventually causing resource exhaustion. This can result in denial of service as the application or system runs out of available resources. Memory leaks cause applications to consume increasing amounts of memory until they crash or trigger out-of-memory conditions. File descriptor leaks prevent opening new files or network connections. Database connection leaks exhaust connection pools. Attackers can accelerate resource exhaustion by repeatedly triggering code paths that allocate without releasing. In long-running services, even small leaks become critical over time.

Solution

Ensure every allocated resource is released when no longer needed. Use RAII (Resource Acquisition Is Initialization) patterns in C++ where destructors automatically release resources. In Java and similar languages, use try-with-resources or try-finally patterns to guarantee cleanup. Close files, sockets, and connections explicitly at all exit points including error paths. Use garbage-collected languages where appropriate, but remember that GC handles memory only—other resources still need explicit release. Implement resource pools with automatic reclamation. Use OS-level resource limits (setrlimit) to prevent complete exhaustion. Monitor resource usage and implement alerts for unusual consumption patterns.

Common Consequences

ImpactDetails
AvailabilityScope: Availability

DoS: Resource Consumption (Memory) - Memory leaks exhaust available memory, causing crashes or system-wide impact.
AvailabilityScope: Availability

DoS: Resource Consumption (Other) - File descriptor, socket, or connection leaks prevent new allocations.
AvailabilityScope: Availability

DoS: Resource Consumption (CPU) - Resource tracking overhead increases as leaks accumulate.

Example Code

Vulnerable Code

// Vulnerable: File handle never explicitly closed
private void processFile(String fName) throws IOException {
    BufferedReader fil = new BufferedReader(new FileReader(fName));
    String line;
    while ((line = fil.readLine()) != null) {
        processLine(line);
    }
    // Vulnerable: fil.close() never called
    // File descriptor leaked
}

// Vulnerable: Exception prevents close
private void processFileWithException(String fName) throws IOException {
    BufferedReader fil = new BufferedReader(new FileReader(fName));
    String line;
    while ((line = fil.readLine()) != null) {
        processLine(line);  // May throw exception
    }
    fil.close();  // Never reached if exception thrown
}
// Vulnerable: File handle not closed on error path
int decodeFile(char* fName) {
    int rc;
    FILE* f = fopen(fName, "r");
    if (!f) {
        return DECODE_FAIL;
    }

    rc = setRecordType(f);
    if (rc != SUCCESS) {
        return DECODE_FAIL;  // Vulnerable: f not closed!
    }

    rc = processRecords(f);
    if (rc != SUCCESS) {
        return DECODE_FAIL;  // Vulnerable: f not closed!
    }

    fclose(f);
    return DECODE_SUCCESS;
}

// Vulnerable: Socket not closed
int connectToServer(const char* host, int port) {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) return -1;

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);

    if (connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        return -1;  // Vulnerable: sockfd leaked!
    }

    return sockfd;
}
# Vulnerable: Database connection not closed
def query_database(query):
    conn = psycopg2.connect(database="mydb")
    cursor = conn.cursor()
    cursor.execute(query)
    results = cursor.fetchall()
    # Vulnerable: connection and cursor never closed
    return results

# Vulnerable: File not closed on exception
def read_config(filename):
    f = open(filename, 'r')
    config = json.load(f)  # May raise exception
    f.close()  # Never reached if exception
    return config
// Vulnerable: Memory allocated but not freed
void processData(int size) {
    char* buffer = new char[size];

    if (!validateSize(size)) {
        return;  // Vulnerable: buffer leaked
    }

    process(buffer);
    delete[] buffer;
}

// Vulnerable: Multiple resources, one leaked
void handleRequest() {
    FILE* inputFile = fopen("input.txt", "r");
    FILE* outputFile = fopen("output.txt", "w");

    if (inputFile == NULL || outputFile == NULL) {
        // Vulnerable: If one succeeded, it's leaked
        return;
    }

    // ... process ...

    fclose(inputFile);
    fclose(outputFile);
}

Fixed Code

// Fixed: Using try-with-resources (Java 7+)
private void processFile(String fName) throws IOException {
    try (BufferedReader fil = new BufferedReader(new FileReader(fName))) {
        String line;
        while ((line = fil.readLine()) != null) {
            processLine(line);
        }
    }  // Automatically closed, even on exception
}

// Fixed: Using try-finally (older Java)
private void processFileLegacy(String fName) throws IOException {
    BufferedReader fil = null;
    try {
        fil = new BufferedReader(new FileReader(fName));
        String line;
        while ((line = fil.readLine()) != null) {
            processLine(line);
        }
    } finally {
        if (fil != null) {
            fil.close();
        }
    }
}
// Fixed: Close file on all paths
int decodeFile(char* fName) {
    int rc;
    int result = DECODE_SUCCESS;
    FILE* f = fopen(fName, "r");
    if (!f) {
        return DECODE_FAIL;
    }

    rc = setRecordType(f);
    if (rc != SUCCESS) {
        result = DECODE_FAIL;
        goto cleanup;
    }

    rc = processRecords(f);
    if (rc != SUCCESS) {
        result = DECODE_FAIL;
        goto cleanup;
    }

cleanup:
    fclose(f);  // Always close
    return result;
}

// Fixed: Close socket on connect failure
int connectToServer(const char* host, int port) {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) return -1;

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);

    if (connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        close(sockfd);  // Fixed: Close on failure
        return -1;
    }

    return sockfd;
}
# Fixed: Using context manager
def query_database(query):
    with psycopg2.connect(database="mydb") as conn:
        with conn.cursor() as cursor:
            cursor.execute(query)
            results = cursor.fetchall()
    return results  # Connection automatically closed

# Fixed: File with context manager
def read_config(filename):
    with open(filename, 'r') as f:
        config = json.load(f)  # File closed even on exception
    return config

# Fixed: Using try-finally for resources without context manager
def process_resource():
    resource = acquire_resource()
    try:
        do_work(resource)
    finally:
        release_resource(resource)
// Fixed: Using RAII with smart pointer or wrapper
#include <memory>
#include <fstream>

void processData(int size) {
    std::unique_ptr<char[]> buffer(new char[size]);
    // Or: auto buffer = std::make_unique<char[]>(size);

    if (!validateSize(size)) {
        return;  // buffer automatically freed
    }

    process(buffer.get());
    // buffer automatically freed when going out of scope
}

// Fixed: RAII wrapper for FILE*
class FileHandle {
    FILE* file;
public:
    FileHandle(const char* path, const char* mode)
        : file(fopen(path, mode)) {}

    ~FileHandle() {
        if (file) fclose(file);
    }

    FILE* get() { return file; }
    bool isValid() { return file != nullptr; }

    // Prevent copying
    FileHandle(const FileHandle&) = delete;
    FileHandle& operator=(const FileHandle&) = delete;
};

void handleRequest() {
    FileHandle inputFile("input.txt", "r");
    FileHandle outputFile("output.txt", "w");

    if (!inputFile.isValid() || !outputFile.isValid()) {
        return;  // Both automatically closed
    }

    // ... process ...
}  // Both automatically closed

CVE Examples

  • CVE-2007-0897: Anti-virus product failed to close file descriptor when encountering malformed files, leading to file descriptor exhaustion and failed scans.
  • CVE-2001-0830: Sockets remained open during repeated connect/disconnect cycles, exhausting available sockets.
  • CVE-2009-2054: File descriptor exhaustion from processing large TCP packet volumes.

References

  1. MITRE Corporation. "CWE-772: Missing Release of Resource after Effective Lifetime." https://cwe.mitre.org/data/definitions/772.html
  2. CERT C Coding Standard. "FIO42-C. Close files when they are no longer needed."
  3. C++ Core Guidelines. "R.1: Manage resources automatically using resource handles and RAII."