C Unchecked Random Device Read Leading to File Descriptor Exhaustion

Medium Risk Memory Safety
cmemory-safetyfile-descriptorsrandom-generationresource-exhaustiondossystem-callserror-handlingsemgrep

What it is

A critical denial of service vulnerability where C code reads from OS random devices (/dev/random or /dev/urandom) without properly checking return values or handling errors. This can lead to file descriptor exhaustion, resource leaks, and application hangs when random data generation fails. Unchecked read() operations on random devices can cause unpredictable behavior, partial data reads, and system resource depletion under error conditions.

// VULNERABLE: Unchecked read from random device
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

int generate_random_bytes_vulnerable(unsigned char *buffer, size_t length) {
    int fd = open("/dev/urandom", O_RDONLY);
    if (fd < 0) {
        return -1;
    }
    
    // SECURITY ISSUE: Unchecked read() result
    // - May read fewer bytes than requested
    // - Ignores EINTR and EAGAIN errors
    // - File descriptor leaked on error
    read(fd, buffer, length);
    
    close(fd);
    return 0;
}

// VULNERABLE: No error handling in random key generation
void create_encryption_key_vulnerable(unsigned char *key, size_t key_len) {
    int random_fd = open("/dev/random", O_RDONLY);
    
    // SECURITY ISSUES:
    // - No check if open() succeeded
    // - No verification that full key_len bytes were read
    // - Potential partial key generation
    // - File descriptor leak if read fails
    read(random_fd, key, key_len);
    
    // close() called even if open() failed (undefined behavior)
    close(random_fd);
}

// VULNERABLE: Retry loop without bounds
int fill_random_buffer_vulnerable(void *buf, size_t buflen) {
    int fd = open("/dev/urandom", O_RDONLY);
    if (fd < 0) return -1;
    
    size_t total_read = 0;
    while (total_read < buflen) {
        // SECURITY ISSUE: Infinite retry loop on EINTR
        // No bounds on retry attempts
        ssize_t bytes_read = read(fd, (char*)buf + total_read, buflen - total_read);
        
        if (bytes_read > 0) {
            total_read += bytes_read;
        }
        // Missing: error handling, EINTR handling, retry limits
    }
    
    close(fd);
    return 0;
}
// SECURE: Proper error checking for random device reads
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

int generate_random_bytes_secure(unsigned char *buffer, size_t length) {
    int fd = open("/dev/urandom", O_RDONLY);
    if (fd < 0) {
        perror("Failed to open /dev/urandom");
        return -1;
    }
    
    size_t total_read = 0;
    while (total_read < length) {
        ssize_t bytes_read = read(fd, buffer + total_read, length - total_read);
        
        if (bytes_read > 0) {
            total_read += bytes_read;
        } else if (bytes_read == 0) {
            fprintf(stderr, "Unexpected EOF on /dev/urandom\n");
            close(fd);
            return -1;
        } else {
            // Handle errors
            if (errno == EINTR) {
                continue;  // Retry on interrupt
            } else {
                perror("Error reading /dev/urandom");
                close(fd);
                return -1;
            }
        }
    }
    
    close(fd);
    return 0;
}

void create_encryption_key_secure(unsigned char *key, size_t key_len) {
    if (!key || key_len == 0) {
        fprintf(stderr, "Invalid key parameters\n");
        return;
    }
    
    if (generate_random_bytes_secure(key, key_len) != 0) {
        fprintf(stderr, "Failed to generate encryption key\n");
        memset(key, 0, key_len);
        return;
    }
}

int fill_random_buffer_secure(void *buf, size_t buflen) {
    if (!buf || buflen == 0) {
        return -1;
    }
    
    return generate_random_bytes_secure((unsigned char*)buf, buflen);
}

💡 Why This Fix Works

The vulnerable examples show unchecked read() operations on random devices that can lead to file descriptor exhaustion and incomplete random data. The secure implementations use modern APIs like getrandom(), implement comprehensive error handling, and ensure proper resource management.

â„šī¸ Configuration Fix

Configuration changes required - see explanation below.

💡 Explanation

This code demonstrates secure cross-platform random number generation using arc4random() on BSD systems, getrandom() on Linux, and fallback implementations. These APIs avoid file descriptor usage and provide better security guarantees.

Why it happens

C code reads from /dev/random or /dev/urandom without checking the return value of read() system calls. This commonly occurs when developers assume read() operations always succeed or return the requested number of bytes. Unchecked reads can result in partial data, error conditions being ignored, and file descriptors remaining open on error paths.

Root causes

Unchecked read() System Call Results

C code reads from /dev/random or /dev/urandom without checking the return value of read() system calls. This commonly occurs when developers assume read() operations always succeed or return the requested number of bytes. Unchecked reads can result in partial data, error conditions being ignored, and file descriptors remaining open on error paths.

Missing Error Handling for EINTR and EAGAIN

Random device reads fail to handle interruption signals (EINTR) or would-block conditions (EAGAIN) properly. This leads to incomplete random data generation, retry loops without bounds, and potential resource exhaustion when the code doesn't gracefully handle these recoverable error conditions.

File Descriptor Leaks on Error Paths

Code opens random devices but fails to close file descriptors when read operations encounter errors. This commonly happens when error handling paths don't include proper cleanup, leading to file descriptor exhaustion over time as failed operations accumulate without releasing resources.

Fixes

1

Use Modern Random APIs

Replace manual /dev/random and /dev/urandom access with modern APIs like getrandom() on Linux or arc4random() on BSD/macOS systems. These APIs provide better error handling, avoid file descriptor usage, and handle interruptions automatically. They eliminate the need for manual file descriptor management and provide more robust random number generation.

2

Implement Proper Error Checking

Always check the return value of read() operations on random devices. Handle EINTR by retrying the operation, handle partial reads by accumulating bytes until the desired amount is obtained, and implement retry limits to prevent infinite loops. Close file descriptors on all error paths to prevent leaks.

3

Add Resource Management and Bounds

Implement proper resource management with automatic cleanup using RAII patterns or explicit cleanup blocks. Set maximum retry counts for recoverable errors, implement timeouts for blocking operations, and ensure file descriptors are closed in all code paths including error conditions.

Detect This Vulnerability in Your Code

Sourcery automatically identifies c unchecked random device read leading to file descriptor exhaustion and many other security issues in your codebase.