Missing Reference to Active File Descriptor or Handle

Description

Missing Reference to Active File Descriptor or Handle is a resource management vulnerability where software fails to properly maintain references to file descriptors or handles, preventing them from being reclaimed. This occurs when a file descriptor is opened but the variable holding it is overwritten, goes out of scope, or is otherwise lost before the descriptor is closed. The orphaned file descriptor cannot be closed because the program no longer has a way to reference it. This leads to file descriptor exhaustion, preventing the application and potentially other processes from opening new files or connections.

Risk

File descriptors are a limited system resource. When references to active file descriptors are lost, they become orphaned and consume resources indefinitely. Repeated occurrences exhaust the available file descriptor pool, causing denial of service. Applications may fail to open new files, establish network connections, or perform I/O operations. Since file descriptor limits are typically system-wide or per-process, exhaustion can affect other applications on the same system. Attackers can trigger descriptor leaks to intentionally exhaust resources. The vulnerability is particularly severe in servers that handle many concurrent connections.

Solution

Always maintain references to file descriptors until they are explicitly closed. Never overwrite a file descriptor variable without first closing the existing descriptor. Use RAII patterns in C++ or try-with-resources in Java to tie descriptor lifetime to scope. Implement wrapper classes that track descriptor state and ensure cleanup. Use resource pools that track active descriptors. Set process-level file descriptor limits using setrlimit to contain damage from leaks. Monitor file descriptor usage and alert on unusual patterns. Implement proper error handling when file descriptor operations fail. Consider using languages or frameworks that automatically manage descriptors.

Common Consequences

ImpactDetails
AvailabilityScope: Availability

DoS: Resource Consumption - File descriptor exhaustion prevents opening files, sockets, and other I/O operations.
AvailabilityScope: Availability

DoS: Instability - System becomes unstable when file descriptors are exhausted.

Example Code

Vulnerable Code

// Vulnerable: File descriptor reference lost
void vulnerable_fd_overwrite() {
    int fd = open("file1.txt", O_RDONLY);
    if (fd < 0) return;

    // ... use fd ...

    // Vulnerable: fd overwritten without closing
    fd = open("file2.txt", O_RDONLY);  // Original fd leaked!
    if (fd < 0) return;

    // ... use fd ...

    close(fd);  // Only closes file2.txt
}

// Vulnerable: Reference lost in loop
void vulnerable_loop() {
    int fd;
    for (int i = 0; i < 100; i++) {
        char filename[64];
        snprintf(filename, sizeof(filename), "file%d.txt", i);

        // Vulnerable: Previous fd lost on each iteration
        fd = open(filename, O_RDONLY);
        if (fd < 0) continue;

        processFile(fd);
        // No close before next iteration
    }
    close(fd);  // Only closes last file
}
// Vulnerable: Descriptor lost on error path
int vulnerable_error_path(const char* filename) {
    int fd = open(filename, O_RDONLY);
    if (fd < 0) return -1;

    char* buffer = malloc(4096);
    if (buffer == NULL) {
        // Vulnerable: fd reference lost, descriptor leaked
        return -1;
    }

    ssize_t bytes = read(fd, buffer, 4096);
    if (bytes < 0) {
        free(buffer);
        // Vulnerable: fd not closed
        return -1;
    }

    free(buffer);
    close(fd);
    return 0;
}
// Vulnerable: Reference lost due to early return
int vulnerable_multiple_files(const char* file1, const char* file2) {
    int fd1 = open(file1, O_RDONLY);
    if (fd1 < 0) return -1;

    int fd2 = open(file2, O_RDONLY);
    if (fd2 < 0) {
        // Vulnerable: fd1 leaked on this path
        return -1;
    }

    // ... process both files ...

    close(fd1);
    close(fd2);
    return 0;
}
// Vulnerable: Descriptor lost in function
class FileProcessor {
    int currentFd;

public:
    void openFile(const char* filename) {
        // Vulnerable: Previous fd lost without close
        currentFd = open(filename, O_RDONLY);
    }

    void process() {
        // ... use currentFd ...
    }

    ~FileProcessor() {
        if (currentFd >= 0) close(currentFd);
    }
};

// If openFile called twice, first fd is leaked

Fixed Code

// Fixed: Close before reassigning
void fixed_fd_overwrite() {
    int fd = open("file1.txt", O_RDONLY);
    if (fd < 0) return;

    // ... use fd ...

    close(fd);  // Fixed: Close before reassigning

    fd = open("file2.txt", O_RDONLY);
    if (fd < 0) return;

    // ... use fd ...

    close(fd);
}

// Fixed: Close in each iteration
void fixed_loop() {
    for (int i = 0; i < 100; i++) {
        char filename[64];
        snprintf(filename, sizeof(filename), "file%d.txt", i);

        int fd = open(filename, O_RDONLY);
        if (fd < 0) continue;

        processFile(fd);
        close(fd);  // Fixed: Close each file
    }
}
// Fixed: Close on all error paths
int fixed_error_path(const char* filename) {
    int fd = open(filename, O_RDONLY);
    if (fd < 0) return -1;

    char* buffer = malloc(4096);
    if (buffer == NULL) {
        close(fd);  // Fixed: Close on error
        return -1;
    }

    ssize_t bytes = read(fd, buffer, 4096);
    if (bytes < 0) {
        free(buffer);
        close(fd);  // Fixed: Close on error
        return -1;
    }

    free(buffer);
    close(fd);
    return 0;
}

// Fixed: Using cleanup label pattern
int fixed_cleanup_pattern(const char* filename) {
    int result = -1;
    int fd = -1;
    char* buffer = NULL;

    fd = open(filename, O_RDONLY);
    if (fd < 0) goto cleanup;

    buffer = malloc(4096);
    if (buffer == NULL) goto cleanup;

    ssize_t bytes = read(fd, buffer, 4096);
    if (bytes < 0) goto cleanup;

    result = 0;

cleanup:
    free(buffer);  // free(NULL) is safe
    if (fd >= 0) close(fd);
    return result;
}
// Fixed: Properly handle multiple files
int fixed_multiple_files(const char* file1, const char* file2) {
    int fd1 = -1, fd2 = -1;
    int result = -1;

    fd1 = open(file1, O_RDONLY);
    if (fd1 < 0) goto cleanup;

    fd2 = open(file2, O_RDONLY);
    if (fd2 < 0) goto cleanup;

    // ... process both files ...

    result = 0;

cleanup:
    if (fd1 >= 0) close(fd1);
    if (fd2 >= 0) close(fd2);
    return result;
}
// Fixed: RAII wrapper for file descriptors
#include <unistd.h>
#include <fcntl.h>

class FileDescriptor {
    int fd;

public:
    explicit FileDescriptor(const char* filename, int flags = O_RDONLY)
        : fd(open(filename, flags)) {}

    ~FileDescriptor() {
        if (fd >= 0) close(fd);
    }

    int get() const { return fd; }
    bool isValid() const { return fd >= 0; }

    // Move semantics for transfer of ownership
    FileDescriptor(FileDescriptor&& other) noexcept : fd(other.fd) {
        other.fd = -1;
    }

    FileDescriptor& operator=(FileDescriptor&& other) noexcept {
        if (this != &other) {
            if (fd >= 0) close(fd);
            fd = other.fd;
            other.fd = -1;
        }
        return *this;
    }

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

// Fixed: Class properly manages descriptor
class FileProcessor {
    FileDescriptor currentFd;

public:
    void openFile(const char* filename) {
        // Previous fd automatically closed by assignment
        currentFd = FileDescriptor(filename);
    }

    void process() {
        if (currentFd.isValid()) {
            // ... use currentFd.get() ...
        }
    }

    // Destructor automatically closes fd
};

Detection Methods

  • Static Analysis: SAST tools can identify code paths where file descriptor variables are reassigned without closing.
  • Runtime Analysis: Tools like lsof, /proc/[pid]/fd monitoring, or custom tracking can detect descriptor leaks.
  • Resource Monitoring: Track file descriptor count over time to identify gradual leaks.

References

  1. MITRE Corporation. "CWE-773: Missing Reference to Active File Descriptor or Handle." https://cwe.mitre.org/data/definitions/773.html
  2. CERT C Coding Standard. "FIO42-C. Close files when they are no longer needed."
  3. Linux Manual. "getrlimit, setrlimit - get/set resource limits."