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
| Impact | Details |
|---|---|
| Availability | Scope: Resource Exhaustion Leaked resources cause service degradation. |
| Confidentiality | Scope: Information Disclosure Uninitialized resources expose sensitive data. |
| Integrity | Scope: 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
-
MITRE. "CWE-664: Improper Control of a Resource Through its Lifetime." https://cwe.mitre.org/data/definitions/664.html
-
RAII pattern documentation.