Missing Release of File Descriptor or Handle after Effective Lifetime
Description
Missing Release of File Descriptor or Handle after Effective Lifetime is a resource management vulnerability where software fails to close file descriptors or handles after they are no longer needed. Unlike losing a reference to a descriptor (CWE-773), this weakness involves maintaining the reference but simply not calling the close operation. The descriptor remains open, consuming system resources. When descriptors accumulate without being released, the available descriptor pool is eventually exhausted, causing denial of service for the application and potentially other processes.
Risk
File descriptors are a limited system resource. Each process has a maximum number of descriptors it can hold, and there's also a system-wide limit. When descriptors aren't released after use, they accumulate over time. In long-running applications, even small leaks eventually cause exhaustion. This results in inability to open new files, accept network connections, create pipes, or perform other I/O operations. Attackers can accelerate exhaustion by repeatedly triggering operations that open descriptors without proper cleanup. The impact extends beyond the vulnerable application to other processes sharing resource limits.
Solution
Always close file descriptors and handles when they are no longer needed. Use RAII patterns in C++ with wrapper classes that close descriptors in their destructors. In Java, use try-with-resources for automatic cleanup. Ensure cleanup occurs on all code paths, including error paths and exception handlers. Use tools like Valgrind or file descriptor trackers during development to identify leaks. Set process file descriptor limits using setrlimit() to contain damage. Implement monitoring to detect gradual descriptor accumulation. Review code for missing close() calls, particularly on error paths. Consider using higher-level abstractions that manage descriptor lifetime automatically.
Common Consequences
| Impact | Details |
|---|---|
| Availability | Scope: Availability DoS: Resource Consumption - Unreleased file descriptors exhaust available system resources. |
| Availability | Scope: Availability DoS: System Instability - File descriptor exhaustion causes application and potentially system-wide failures. |
Example Code
Vulnerable Code
// Vulnerable: File descriptor not closed
void vulnerable_read_file(const char* filename) {
int fd = open(filename, O_RDONLY);
if (fd < 0) return;
char buffer[4096];
ssize_t bytes = read(fd, buffer, sizeof(buffer));
// Vulnerable: fd never closed
// Process buffer...
}
// Vulnerable: Socket not closed after use
void vulnerable_client_connection(const char* host, int port) {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) return;
struct sockaddr_in addr;
// ... setup addr ...
if (connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == 0) {
send(sockfd, "Hello", 5, 0);
char buffer[100];
recv(sockfd, buffer, sizeof(buffer), 0);
}
// Vulnerable: sockfd never closed
}
// Vulnerable: File not closed on error paths
int vulnerable_process_file(const char* filename) {
int fd = open(filename, O_RDONLY);
if (fd < 0) {
return -1;
}
struct stat st;
if (fstat(fd, &st) < 0) {
return -1; // Vulnerable: fd not closed
}
if (st.st_size > MAX_FILE_SIZE) {
return -1; // Vulnerable: fd not closed
}
char* buffer = malloc(st.st_size);
if (buffer == NULL) {
return -1; // Vulnerable: fd not closed
}
if (read(fd, buffer, st.st_size) < 0) {
free(buffer);
return -1; // Vulnerable: fd not closed
}
processBuffer(buffer, st.st_size);
free(buffer);
close(fd); // Only closed on success path
return 0;
}
// Vulnerable: Stream not closed
public void vulnerableReadFile(String filename) throws IOException {
FileInputStream fis = new FileInputStream(filename);
byte[] buffer = new byte[4096];
int bytesRead = fis.read(buffer);
// Process buffer...
// Vulnerable: fis.close() never called
}
// Vulnerable: Close not in finally block
public void vulnerableWithException(String filename) throws IOException {
FileInputStream fis = new FileInputStream(filename);
processStream(fis); // May throw exception
fis.close(); // Not reached if exception thrown
}
# Vulnerable: File not closed
def vulnerable_read_file(filename):
f = open(filename, 'r')
content = f.read()
# Process content...
return content
# f.close() never called
# Vulnerable: Close not called on exception
def vulnerable_process_file(filename):
f = open(filename, 'r')
try:
data = json.load(f) # May raise exception
process(data)
except json.JSONDecodeError:
return None # f not closed
f.close()
return data
Fixed Code
// Fixed: Always close file descriptor
void fixed_read_file(const char* filename) {
int fd = open(filename, O_RDONLY);
if (fd < 0) return;
char buffer[4096];
ssize_t bytes = read(fd, buffer, sizeof(buffer));
close(fd); // Fixed: Always close
// Process buffer...
}
// Fixed: Close socket after use
void fixed_client_connection(const char* host, int port) {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) return;
struct sockaddr_in addr;
// ... setup addr ...
if (connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == 0) {
send(sockfd, "Hello", 5, 0);
char buffer[100];
recv(sockfd, buffer, sizeof(buffer), 0);
}
close(sockfd); // Fixed: Always close
}
// Fixed: Close on all paths using cleanup pattern
int fixed_process_file(const char* filename) {
int result = -1;
int fd = -1;
char* buffer = NULL;
fd = open(filename, O_RDONLY);
if (fd < 0) {
goto cleanup;
}
struct stat st;
if (fstat(fd, &st) < 0) {
goto cleanup;
}
if (st.st_size > MAX_FILE_SIZE) {
goto cleanup;
}
buffer = malloc(st.st_size);
if (buffer == NULL) {
goto cleanup;
}
if (read(fd, buffer, st.st_size) < 0) {
goto cleanup;
}
processBuffer(buffer, st.st_size);
result = 0;
cleanup:
free(buffer); // free(NULL) is safe
if (fd >= 0) {
close(fd); // Fixed: Close on all paths
}
return result;
}
// Fixed: RAII wrapper automatically closes
#include <unistd.h>
#include <fcntl.h>
class FileDescriptor {
int fd_;
public:
explicit FileDescriptor(const char* path, int flags = O_RDONLY)
: fd_(open(path, flags)) {}
~FileDescriptor() {
if (fd_ >= 0) close(fd_);
}
int get() const { return fd_; }
bool isValid() const { return fd_ >= 0; }
FileDescriptor(const FileDescriptor&) = delete;
FileDescriptor& operator=(const FileDescriptor&) = delete;
};
int fixed_with_raii(const char* filename) {
FileDescriptor fd(filename);
if (!fd.isValid()) return -1;
char buffer[4096];
read(fd.get(), buffer, sizeof(buffer));
// Automatic close when fd goes out of scope
return 0;
}
// Fixed: Using try-with-resources (Java 7+)
public void fixedReadFile(String filename) throws IOException {
try (FileInputStream fis = new FileInputStream(filename)) {
byte[] buffer = new byte[4096];
int bytesRead = fis.read(buffer);
// Process buffer...
} // Automatically closed
}
// Fixed: Using try-finally
public void fixedWithFinally(String filename) throws IOException {
FileInputStream fis = new FileInputStream(filename);
try {
processStream(fis);
} finally {
fis.close(); // Always executed
}
}
# Fixed: Using context manager
def fixed_read_file(filename):
with open(filename, 'r') as f:
content = f.read()
return content # f automatically closed
# Fixed: Explicit try-finally
def fixed_process_file(filename):
f = open(filename, 'r')
try:
data = json.load(f)
process(data)
return data
except json.JSONDecodeError:
return None
finally:
f.close() # Always closes
CVE Examples
- CVE-2007-0897: Anti-virus product failed to close file descriptor when encountering malformed input, causing descriptor exhaustion and scan failures.
Detection Methods
- Static Analysis: SAST tools can identify missing close() calls on file descriptors.
- Runtime Analysis: Tools like Valgrind (memcheck), lsof, or custom tracking detect descriptor leaks.
- Code Review: Manually verify close() calls on all code paths, especially error paths.
References
- MITRE Corporation. "CWE-775: Missing Release of File Descriptor or Handle after Effective Lifetime." https://cwe.mitre.org/data/definitions/775.html
- CERT C Coding Standard. "FIO42-C. Close files when they are no longer needed."
- C++ Core Guidelines. "R.1: Manage resources automatically using resource handles and RAII."