Missing Reference to Active Allocated Resource

Description

Missing Reference to Active Allocated Resource is a resource management vulnerability where software allocates a resource but fails to maintain a reference to it, preventing the resource from being reclaimed or properly managed. This typically occurs when a pointer or handle to an allocated resource is overwritten, goes out of scope, or is otherwise lost before the resource is freed. The orphaned resource cannot be accessed for cleanup, leading to resource leaks. This weakness may not apply in languages with automatic garbage collection, where removing all references signals that a resource is ready for reclamation.

Risk

When references to allocated resources are lost, those resources become orphaned and cannot be freed, leading to resource exhaustion. Attackers can exploit this by triggering allocation code paths repeatedly, eventually exhausting available resources (memory, file descriptors, network connections) and causing denial of service. In long-running services, even small leaks accumulate over time, degrading performance and eventually causing failures. The vulnerability is particularly severe for limited resources like file descriptors or database connections. Additionally, leaked resources may contain sensitive data that persists longer than intended.

Solution

Always maintain references to allocated resources until they are properly released. Use RAII (Resource Acquisition Is Initialization) patterns in C++ to tie resource lifetime to object scope. In other languages, use try-finally or try-with-resources patterns to ensure cleanup. Before overwriting a pointer or reference, ensure the old resource is freed first. Use smart pointers or handle classes that automatically manage resource lifetime. Implement resource pools with automatic reclamation. Set resource limits using OS facilities (e.g., POSIX setrlimit) to prevent complete exhaustion. Use memory leak detection tools during development.

Common Consequences

ImpactDetails
AvailabilityScope: Availability

DoS: Resource Consumption - Lost references prevent resource reclamation, leading to exhaustion and denial of service.
IntegrityScope: Integrity

Unexpected State - Program state becomes inconsistent as resources exist without proper tracking.
ConfidentialityScope: Confidentiality

Information Exposure - Leaked resources may retain sensitive data longer than intended.

Example Code

Vulnerable Code

// Vulnerable: Pointer overwritten without freeing original
void vulnerable_overwrite() {
    char* buffer = malloc(1024);
    strcpy(buffer, "Initial data");

    // Vulnerable: Original allocation lost
    buffer = malloc(2048);  // Original 1024 bytes leaked!
    strcpy(buffer, "New data");

    free(buffer);  // Only frees second allocation
}

// Vulnerable: Reference lost in loop
void vulnerable_loop(int count) {
    char* data;

    for (int i = 0; i < count; i++) {
        // Vulnerable: Previous allocation lost on each iteration
        data = malloc(100);
        processData(data);
        // No free before next iteration
    }

    free(data);  // Only frees last allocation
}
// Vulnerable: Reference lost on error path
int vulnerable_error_path(const char* filename) {
    FILE* file = fopen(filename, "r");
    if (file == NULL) return -1;

    char* buffer = malloc(4096);
    if (buffer == NULL) {
        // Vulnerable: file handle lost on this path
        return -1;  // File never closed!
    }

    // ... process file ...

    free(buffer);
    fclose(file);
    return 0;
}

// Vulnerable: Reference lost in conditional
void vulnerable_conditional(int size) {
    void* ptr = malloc(100);

    if (size > 100) {
        // Vulnerable: Original allocation lost
        ptr = malloc(size);  // 100-byte allocation leaked
    }

    // ... use ptr ...
    free(ptr);
}
// Vulnerable: Reference lost due to exception
void vulnerable_exception() {
    int* array = new int[1000];

    riskyOperation();  // May throw exception

    delete[] array;  // Never reached if exception thrown
}

// Vulnerable: Object overwrites its own resource
class VulnerableBuffer {
    char* data;
    size_t size;

public:
    VulnerableBuffer(size_t s) : size(s) {
        data = new char[size];
    }

    void resize(size_t newSize) {
        // Vulnerable: Old data lost without delete
        data = new char[newSize];  // Memory leak!
        size = newSize;
    }

    ~VulnerableBuffer() {
        delete[] data;
    }
};
// Vulnerable: In Java, this affects non-memory resources
public class VulnerableConnection {
    private Connection conn;

    public void connect(String url) throws SQLException {
        // Vulnerable: Previous connection lost without close
        conn = DriverManager.getConnection(url);  // Old connection leaked!
    }

    public void close() throws SQLException {
        if (conn != null) {
            conn.close();
        }
    }
}

// Vulnerable: Stream lost on error
public void vulnerableStream(String filename) throws IOException {
    FileInputStream fis = new FileInputStream(filename);

    if (!validateFile(fis)) {
        // Vulnerable: Stream not closed on early return
        return;  // File descriptor leaked
    }

    // ... process file ...
    fis.close();
}

Fixed Code

// Fixed: Free before reassigning
void fixed_overwrite() {
    char* buffer = malloc(1024);
    strcpy(buffer, "Initial data");

    // Fixed: Free old allocation first
    free(buffer);
    buffer = malloc(2048);
    strcpy(buffer, "New data");

    free(buffer);
}

// Fixed: Free in loop
void fixed_loop(int count) {
    for (int i = 0; i < count; i++) {
        char* data = malloc(100);
        processData(data);
        free(data);  // Free each allocation
    }
}

// Fixed: Helper function to properly reassign
void safe_realloc(char** ptr, size_t new_size) {
    char* new_ptr = realloc(*ptr, new_size);
    if (new_ptr != NULL) {
        *ptr = new_ptr;
    }
    // Note: If realloc fails, original pointer is still valid
}
// Fixed: Proper cleanup on all paths
int fixed_error_path(const char* filename) {
    FILE* file = fopen(filename, "r");
    if (file == NULL) return -1;

    char* buffer = malloc(4096);
    if (buffer == NULL) {
        fclose(file);  // Fixed: Close file before return
        return -1;
    }

    // ... process file ...

    free(buffer);
    fclose(file);
    return 0;
}

// Fixed: Maintain reference to all allocations
void fixed_conditional(int size) {
    void* ptr = malloc(100);

    if (size > 100) {
        free(ptr);  // Fixed: Free original first
        ptr = malloc(size);
    }

    // ... use ptr ...
    free(ptr);
}
// Fixed: Use RAII with smart pointers
#include <memory>

void fixed_exception() {
    std::unique_ptr<int[]> array(new int[1000]);
    // Or: auto array = std::make_unique<int[]>(1000);

    riskyOperation();  // Exception safe - array freed automatically

    // No manual delete needed
}

// Fixed: Proper resource management in class
class FixedBuffer {
    std::unique_ptr<char[]> data;
    size_t size;

public:
    FixedBuffer(size_t s) : size(s), data(std::make_unique<char[]>(s)) {}

    void resize(size_t newSize) {
        // unique_ptr automatically frees old data
        data = std::make_unique<char[]>(newSize);
        size = newSize;
    }

    // No destructor needed - unique_ptr handles cleanup
};

// Alternative: Manual management with proper cleanup
class FixedBufferManual {
    char* data;
    size_t size;

public:
    FixedBufferManual(size_t s) : size(s) {
        data = new char[size];
    }

    void resize(size_t newSize) {
        delete[] data;  // Free old data first
        data = new char[newSize];
        size = newSize;
    }

    ~FixedBufferManual() {
        delete[] data;
    }
};
// Fixed: Close connection before reassigning
public class FixedConnection implements AutoCloseable {
    private Connection conn;

    public void connect(String url) throws SQLException {
        // Fixed: Close existing connection first
        if (conn != null && !conn.isClosed()) {
            conn.close();
        }
        conn = DriverManager.getConnection(url);
    }

    @Override
    public void close() throws SQLException {
        if (conn != null) {
            conn.close();
        }
    }
}

// Fixed: Use try-with-resources
public void fixedStream(String filename) throws IOException {
    try (FileInputStream fis = new FileInputStream(filename)) {
        if (!validateFile(fis)) {
            return;  // Stream automatically closed
        }
        // ... process file ...
    }  // Stream automatically closed
}

Detection Methods

  • Memory Leak Detection: Tools like Valgrind, AddressSanitizer, or Dr. Memory can detect leaked allocations.
  • Static Analysis: SAST tools can identify code paths where pointers are overwritten without freeing.
  • Resource Monitoring: Track resource usage over time to identify gradual leaks.

References

  1. MITRE Corporation. "CWE-771: Missing Reference to Active Allocated Resource." https://cwe.mitre.org/data/definitions/771.html
  2. CERT C Coding Standard. "MEM31-C. Free dynamically allocated memory when no longer needed."
  3. C++ Core Guidelines. "R.11: Avoid calling new and delete explicitly."