Improper Control of a Resource Through its Lifetime

Description

Improper Control of a Resource Through its Lifetime occurs when an application does not properly manage the complete lifecycle of a resource from allocation to release. This includes failing to initialize resources, using resources after they've been released, releasing resources multiple times, or never releasing resources at all. Resources can include memory, file handles, database connections, network sockets, and other system objects.

Risk

Memory leaks cause resource exhaustion and denial of service. Use-after-free vulnerabilities enable code execution. Double-free vulnerabilities corrupt memory allocators. Uninitialized resources contain sensitive data from previous operations. Resource leaks in long-running services cause gradual degradation. File descriptor exhaustion prevents new connections.

Solution

Follow RAII (Resource Acquisition Is Initialization) patterns. Use smart pointers and automatic resource management. Implement proper cleanup in all code paths including exceptions. Use try-with-resources or similar constructs. Track resource state explicitly. Implement resource pools with proper lifecycle management.

Common Consequences

ImpactDetails
AvailabilityScope: Resource Exhaustion

Leaked resources cause service degradation.
ConfidentialityScope: Information Disclosure

Uninitialized resources expose sensitive data.
IntegrityScope: Memory Corruption

Double-free and use-after-free corrupt memory.

Example Code + Solution Code

Vulnerable Code

// VULNERABLE: Improper resource lifecycle management
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// VULNERABLE: Memory leak on error path
char* read_file_vulnerable(const char* filename) {
    FILE* file = fopen(filename, "r");
    if (!file) {
        return NULL;
    }

    char* buffer = malloc(1024);
    if (!buffer) {
        // VULNERABLE: File handle leaked!
        return NULL;
    }

    if (fread(buffer, 1, 1024, file) == 0) {
        // VULNERABLE: Both file and buffer leaked!
        return NULL;
    }

    fclose(file);
    return buffer;  // Caller must free, but no indication
}

// VULNERABLE: Use-after-free
void use_after_free_vulnerable() {
    char* data = malloc(100);
    strcpy(data, "sensitive");

    free(data);

    // VULNERABLE: Using freed memory
    printf("Data: %s\n", data);  // Use-after-free!
}

// VULNERABLE: Double free
void double_free_vulnerable(int condition) {
    char* ptr = malloc(100);

    if (condition) {
        free(ptr);
    }

    // VULNERABLE: May free already-freed memory
    free(ptr);  // Double-free if condition was true!
}

// VULNERABLE: Uninitialized memory use
void uninitialized_memory_vulnerable() {
    char buffer[256];  // VULNERABLE: Not initialized

    // VULNERABLE: Reading uninitialized data
    if (buffer[0] == 'A') {  // Undefined behavior
        do_something();
    }

    // VULNERABLE: Sending uninitialized data
    send(socket, buffer, sizeof(buffer), 0);  // Leaks stack data
}

// VULNERABLE: Resource never released
void resource_leak_vulnerable() {
    while (1) {
        int* data = malloc(sizeof(int) * 1000);
        // VULNERABLE: Never freed - memory exhaustion
        process_data(data);
    }
}

// VULNERABLE: File descriptor leak
void fd_leak_vulnerable(const char** filenames, int count) {
    for (int i = 0; i < count; i++) {
        int fd = open(filenames[i], O_RDONLY);
        if (fd < 0) continue;

        process_file(fd);
        // VULNERABLE: fd never closed - descriptor exhaustion
    }
}
# VULNERABLE: Python resource lifecycle issues
class VulnerableResourceManager:

    def __init__(self):
        self.connection = None

    def connect(self):
        # VULNERABLE: Old connection not closed before creating new one
        self.connection = create_database_connection()

    def execute(self, query):
        # VULNERABLE: No check if connection exists
        return self.connection.execute(query)

    # VULNERABLE: No cleanup method called automatically

# VULNERABLE: File handle leaks
def read_files_vulnerable(filenames):
    results = []
    for filename in filenames:
        f = open(filename, 'r')
        # VULNERABLE: Files never closed
        results.append(f.read())
    return results

# VULNERABLE: Circular references causing memory leaks
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

def create_cycle_vulnerable():
    a = Node("A")
    b = Node("B")
    # VULNERABLE: Circular reference - potential memory leak
    a.next = b
    b.next = a
    # When function returns, circular reference may prevent collection

# VULNERABLE: Exception causing resource leak
def process_file_vulnerable(filename):
    f = open(filename, 'r')
    data = f.read()
    process(data)  # If this raises, file never closed!
    f.close()
// VULNERABLE: Java resource lifecycle issues
public class VulnerableResourceManagement {

    // VULNERABLE: Connection leak on exception
    public String queryDatabase(String sql) throws SQLException {
        Connection conn = DriverManager.getConnection(url);
        Statement stmt = conn.createStatement();

        // VULNERABLE: If exception thrown, conn and stmt never closed
        ResultSet rs = stmt.executeQuery(sql);
        String result = rs.getString(1);

        conn.close();  // Only reached if no exception
        return result;
    }

    // VULNERABLE: Stream not closed
    public String readFile(String path) throws IOException {
        FileInputStream fis = new FileInputStream(path);
        byte[] data = new byte[1024];
        fis.read(data);
        // VULNERABLE: fis never closed
        return new String(data);
    }

    // VULNERABLE: Lock never released
    private final ReentrantLock lock = new ReentrantLock();

    public void processWithLock() {
        lock.lock();
        doProcessing();  // If exception thrown, lock never released!
        lock.unlock();
    }

    // VULNERABLE: Thread pool never shutdown
    private ExecutorService executor = Executors.newFixedThreadPool(10);

    public void submitTasks(List<Runnable> tasks) {
        for (Runnable task : tasks) {
            executor.submit(task);
        }
        // VULNERABLE: executor.shutdown() never called
    }
}
// VULNERABLE: Node.js resource lifecycle issues
class VulnerableResourceManager {

    // VULNERABLE: Event listener leak
    setupListeners() {
        // VULNERABLE: Listeners accumulate, never removed
        this.emitter.on('data', this.handleData);
        this.emitter.on('error', this.handleError);
        // Called multiple times without cleanup = memory leak
    }

    // VULNERABLE: Timer leak
    startPolling() {
        // VULNERABLE: Interval never cleared
        setInterval(() => {
            this.poll();
        }, 1000);
    }

    // VULNERABLE: Stream not properly handled
    async readFile(path) {
        const stream = fs.createReadStream(path);
        const chunks = [];

        // VULNERABLE: If error occurs, stream may not be closed
        for await (const chunk of stream) {
            chunks.push(chunk);
        }

        return Buffer.concat(chunks);
    }

    // VULNERABLE: Database connection leak
    async query(sql) {
        const client = await pool.connect();
        // VULNERABLE: If query throws, client never released
        const result = await client.query(sql);
        client.release();
        return result;
    }

    // VULNERABLE: Child process leak
    spawnProcess(command) {
        const child = spawn(command);
        // VULNERABLE: Child process never killed on shutdown
        return child;
    }
}

Fixed Code

// SAFE: Proper resource lifecycle management
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// SAFE: Proper cleanup on all paths
char* read_file_safe(const char* filename) {
    FILE* file = NULL;
    char* buffer = NULL;

    file = fopen(filename, "r");
    if (!file) {
        goto cleanup;
    }

    buffer = malloc(1024);
    if (!buffer) {
        goto cleanup;
    }

    if (fread(buffer, 1, 1024, file) == 0) {
        free(buffer);
        buffer = NULL;
        goto cleanup;
    }

cleanup:
    if (file) {
        fclose(file);
    }
    return buffer;  // Caller must free non-NULL return
}

// SAFE: Clear pointer after free
void safe_free_pattern() {
    char* data = malloc(100);
    if (!data) return;

    strcpy(data, "sensitive");

    // SAFE: Clear and nullify
    memset(data, 0, 100);  // Clear sensitive data
    free(data);
    data = NULL;  // Prevent use-after-free

    // Safe: NULL check prevents issues
    if (data) {
        printf("Data: %s\n", data);
    }
}

// SAFE: Track state to prevent double-free
typedef struct {
    char* data;
    int freed;
} ManagedBuffer;

void safe_release(ManagedBuffer* buf) {
    // SAFE: Check state before freeing
    if (buf && buf->data && !buf->freed) {
        free(buf->data);
        buf->data = NULL;
        buf->freed = 1;
    }
}

// SAFE: Initialize all memory
void initialized_memory_safe() {
    char buffer[256] = {0};  // SAFE: Zero-initialized

    // Or use memset
    char buffer2[256];
    memset(buffer2, 0, sizeof(buffer2));

    // Or use calloc for heap
    char* heap_buf = calloc(256, 1);  // SAFE: Zero-initialized
    if (heap_buf) {
        use_buffer(heap_buf);
        free(heap_buf);
    }
}

// SAFE: Always release resources
void resource_managed_safe() {
    int* data = NULL;

    while (should_continue()) {
        data = malloc(sizeof(int) * 1000);
        if (!data) break;

        process_data(data);

        // SAFE: Free after each use
        free(data);
        data = NULL;
    }
}

// SAFE: Close file descriptors
void fd_managed_safe(const char** filenames, int count) {
    for (int i = 0; i < count; i++) {
        int fd = open(filenames[i], O_RDONLY);
        if (fd < 0) continue;

        process_file(fd);

        // SAFE: Always close
        close(fd);
    }
}
# SAFE: Python resource lifecycle management
class SafeResourceManager:

    def __init__(self):
        self.connection = None

    def connect(self):
        # SAFE: Close old connection before creating new
        if self.connection:
            self.connection.close()
        self.connection = create_database_connection()

    def execute(self, query):
        if not self.connection:
            raise RuntimeError("Not connected")
        return self.connection.execute(query)

    # SAFE: Context manager protocol for automatic cleanup
    def __enter__(self):
        self.connect()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.connection:
            self.connection.close()
            self.connection = None
        return False

# SAFE: Using context managers
def read_files_safe(filenames):
    results = []
    for filename in filenames:
        # SAFE: Context manager ensures file is closed
        with open(filename, 'r') as f:
            results.append(f.read())
    return results

# SAFE: Using weakref to break cycles
import weakref

class SafeNode:
    def __init__(self, data):
        self.data = data
        self._next = None

    @property
    def next(self):
        return self._next() if self._next else None

    @next.setter
    def next(self, node):
        # SAFE: Weak reference breaks cycle
        self._next = weakref.ref(node) if node else None

# SAFE: Exception-safe file processing
def process_file_safe(filename):
    with open(filename, 'r') as f:
        data = f.read()
        process(data)  # File closed even if exception raised
// SAFE: Java resource lifecycle with try-with-resources
public class SafeResourceManagement {

    // SAFE: Try-with-resources ensures cleanup
    public String queryDatabase(String sql) throws SQLException {
        try (Connection conn = DriverManager.getConnection(url);
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery(sql)) {

            return rs.getString(1);
            // SAFE: All resources closed automatically
        }
    }

    // SAFE: Stream closed automatically
    public String readFile(String path) throws IOException {
        try (FileInputStream fis = new FileInputStream(path)) {
            byte[] data = new byte[1024];
            fis.read(data);
            return new String(data);
        }
    }

    // SAFE: Lock released in finally block
    private final ReentrantLock lock = new ReentrantLock();

    public void processWithLock() {
        lock.lock();
        try {
            doProcessing();
        } finally {
            // SAFE: Always released
            lock.unlock();
        }
    }

    // SAFE: Thread pool with proper shutdown
    private ExecutorService executor = Executors.newFixedThreadPool(10);

    public void shutdown() {
        executor.shutdown();
        try {
            if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                executor.shutdownNow();
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}
// SAFE: Node.js resource lifecycle management
class SafeResourceManager {

    constructor() {
        this.listeners = [];
        this.intervals = [];
        this.children = [];
    }

    // SAFE: Track and cleanup listeners
    setupListeners() {
        const dataHandler = this.handleData.bind(this);
        const errorHandler = this.handleError.bind(this);

        this.emitter.on('data', dataHandler);
        this.emitter.on('error', errorHandler);

        // SAFE: Track for cleanup
        this.listeners.push(
            { event: 'data', handler: dataHandler },
            { event: 'error', handler: errorHandler }
        );
    }

    // SAFE: Track timers for cleanup
    startPolling() {
        const intervalId = setInterval(() => {
            this.poll();
        }, 1000);

        this.intervals.push(intervalId);
        return intervalId;
    }

    // SAFE: Proper stream handling
    async readFile(path) {
        const stream = fs.createReadStream(path);
        const chunks = [];

        try {
            for await (const chunk of stream) {
                chunks.push(chunk);
            }
            return Buffer.concat(chunks);
        } finally {
            // SAFE: Ensure stream is destroyed
            stream.destroy();
        }
    }

    // SAFE: Database connection with proper release
    async query(sql) {
        const client = await pool.connect();
        try {
            return await client.query(sql);
        } finally {
            // SAFE: Always release
            client.release();
        }
    }

    // SAFE: Cleanup all resources
    async cleanup() {
        // Remove all listeners
        for (const { event, handler } of this.listeners) {
            this.emitter.off(event, handler);
        }
        this.listeners = [];

        // Clear all intervals
        for (const id of this.intervals) {
            clearInterval(id);
        }
        this.intervals = [];

        // Kill all child processes
        for (const child of this.children) {
            child.kill();
        }
        this.children = [];
    }
}

Exploited in the Wild

Use-After-Free Exploits

Memory corruption enabling remote code execution.

Resource Exhaustion

Memory leaks causing denial of service.

Information Disclosure

Uninitialized memory exposing sensitive data.


Tools to test/exploit

  • Memory sanitizers (ASan, MSan).

  • Valgrind for memory leak detection.

  • Static analyzers for resource lifecycle.


CVE Examples

  • CVE-2021-3156: Use-after-free in sudo.

  • CVE-2019-14287: Resource management flaw in sudo.


References

  1. MITRE. "CWE-664: Improper Control of a Resource Through its Lifetime." https://cwe.mitre.org/data/definitions/664.html

  2. RAII pattern documentation.