Improper Handling of Insufficient Entropy in TRNG

Description

Improper Handling of Insufficient Entropy in TRNG is a vulnerability that occurs when a system using a True Random Number Generator (TRNG) fails to properly handle situations where the entropy source is exhausted or unavailable. Unlike Pseudo-Random Number Generators (PRNGs) that can produce unlimited output from a seed, TRNGs rely on physical entropy sources such as thermal noise, radioactive decay, or hardware timing variations, which generate randomness at a limited rate. When applications request more random data than the TRNG can provide, improper handling can cause the application to block indefinitely, crash, or worse, fall back to weaker random sources. The rate at which true random numbers can be generated is inherently limited, making it critical to use them judiciously and handle exhaustion gracefully.

Risk

Improper TRNG handling creates both availability and security risks. When applications block waiting for TRNG entropy, attackers can deliberately exhaust entropy sources to cause denial of service - a server generating session IDs from a hardware TRNG could be stalled by rapidly opening many connections. If the system crashes on entropy exhaustion, this enables straightforward DoS attacks. More dangerous is when systems silently fall back to weaker random sources - security appears maintained while actually degraded. Applications that don't limit TRNG consumption can starve other processes of entropy. In embedded systems with limited entropy sources, improper handling can prevent proper cryptographic operations entirely. The vulnerability is particularly severe in scenarios requiring continuous random number generation like TLS servers or high-volume session management.

Solution

Design applications to handle TRNG entropy exhaustion gracefully without compromising security. Use non-blocking entropy sources like /dev/urandom instead of blocking /dev/random for most cryptographic purposes - modern systems maintain sufficient entropy in urandom. Implement rate limiting for operations requiring true random numbers. Queue random number requests and process them as entropy becomes available rather than blocking or crashing. Never silently fall back to weaker random sources - either wait for proper entropy or fail the operation explicitly. Cache random numbers during periods of high entropy availability for use during exhaustion. Consider hybrid approaches using TRNG output to seed high-quality PRNGs for bulk random number generation. Monitor entropy pool levels and alert when they fall below safe thresholds.

Common Consequences

ImpactDetails
AvailabilityScope: Availability

Programs may crash, block, or hang when random number supplies are depleted, causing denial of service. Attackers can exploit this by deliberately exhausting entropy sources.
ConfidentialityScope: Confidentiality

If systems fall back to weaker random sources when TRNGs are exhausted, cryptographic keys and other security-critical values may be generated with insufficient entropy.
Access ControlScope: Access Control

Authentication tokens generated during entropy exhaustion may be predictable if the system degrades to weak random sources, enabling session hijacking or authentication bypass.

Example Code

Vulnerable Code (C/Python)

The following examples demonstrate improper TRNG handling:

// Vulnerable: Improper handling of TRNG entropy exhaustion
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

// Vulnerable: Blocking read from /dev/random
int vulnerable_get_random(unsigned char *buffer, size_t length) {
    // Vulnerable: /dev/random blocks when entropy pool is empty
    // Can cause indefinite hangs under high load
    int fd = open("/dev/random", O_RDONLY);
    if (fd < 0) return -1;

    // Vulnerable: No timeout, no fallback, no error handling
    ssize_t bytes = read(fd, buffer, length);
    close(fd);

    return bytes == (ssize_t)length ? 0 : -1;
}

// Vulnerable: Session ID generation that can exhaust entropy
void vulnerable_session_handler(int client_fd) {
    unsigned char session_id[32];

    // Vulnerable: Each connection consumes 32 bytes of true random
    // Attacker can exhaust entropy with many connections
    if (vulnerable_get_random(session_id, sizeof(session_id)) != 0) {
        // Vulnerable: Crash on entropy exhaustion
        fprintf(stderr, "Failed to get random bytes, exiting\n");
        exit(1);  // Vulnerable: DoS by crashing server
    }

    // Send session ID to client
    write(client_fd, session_id, sizeof(session_id));
}

// Vulnerable: No rate limiting on entropy consumption
void vulnerable_key_generation_loop() {
    unsigned char key[32];

    while (1) {
        // Vulnerable: Continuous entropy consumption
        // Starves other processes of entropy
        vulnerable_get_random(key, sizeof(key));

        // Process key...
    }
}

// Vulnerable: Silent fallback to weak random
int vulnerable_fallback_random(unsigned char *buffer, size_t length) {
    int fd = open("/dev/random", O_RDONLY | O_NONBLOCK);
    if (fd < 0) {
        // Vulnerable: Falling back to weak random!
        srand(time(NULL));
        for (size_t i = 0; i < length; i++) {
            buffer[i] = rand() % 256;
        }
        return 0;
    }

    ssize_t bytes = read(fd, buffer, length);
    close(fd);

    if (bytes < (ssize_t)length) {
        // Vulnerable: Silent fallback for remaining bytes
        srand(time(NULL) ^ getpid());
        for (size_t i = bytes; i < length; i++) {
            buffer[i] = rand() % 256;
        }
    }

    return 0;
}
# Vulnerable: Improper TRNG handling in Python
import os
import random
import time

# Vulnerable: Reading from /dev/random (blocking)
def vulnerable_true_random(length):
    # Vulnerable: Will block if entropy exhausted
    with open('/dev/random', 'rb') as f:
        return f.read(length)

# Vulnerable: Server that exhausts entropy
class VulnerableServer:
    def handle_connection(self, client):
        # Vulnerable: Each connection uses true random
        # No rate limiting on entropy consumption
        session_id = vulnerable_true_random(32)
        client.send(session_id)

# Vulnerable: Silent fallback
def vulnerable_get_random_with_fallback(length):
    try:
        # Try to get true random
        with open('/dev/random', 'rb') as f:
            # Set non-blocking
            import fcntl
            flags = fcntl.fcntl(f.fileno(), fcntl.F_GETFL)
            fcntl.fcntl(f.fileno(), fcntl.F_SETFL, flags | os.O_NONBLOCK)

            data = f.read(length)
            if len(data) < length:
                # Vulnerable: Fill remaining with weak random!
                data += bytes([random.randint(0, 255)
                              for _ in range(length - len(data))])
            return data
    except Exception:
        # Vulnerable: Complete fallback to weak random
        random.seed(time.time())
        return bytes([random.randint(0, 255) for _ in range(length)])

# Vulnerable: Crash on entropy exhaustion
def vulnerable_critical_key_gen():
    try:
        with open('/dev/random', 'rb') as f:
            return f.read(32)
    except:
        # Vulnerable: Exception causes process termination
        raise SystemExit("Cannot generate secure key - entropy exhausted")
// Vulnerable: Improper TRNG handling in Java
import java.io.*;
import java.security.*;
import java.util.*;

public class VulnerableTRNG {

    // Vulnerable: Blocking read from /dev/random
    public byte[] vulnerableGetRandom(int length) throws IOException {
        // Vulnerable: Will block indefinitely if entropy depleted
        FileInputStream fis = new FileInputStream("/dev/random");
        byte[] buffer = new byte[length];
        fis.read(buffer);  // Vulnerable: No timeout
        fis.close();
        return buffer;
    }

    // Vulnerable: Fallback to weak random
    public byte[] vulnerableWithFallback(int length) {
        byte[] buffer = new byte[length];

        try {
            SecureRandom strong = SecureRandom.getInstanceStrong();
            strong.nextBytes(buffer);
        } catch (NoSuchAlgorithmException e) {
            // Vulnerable: Silent fallback!
            new Random().nextBytes(buffer);
        }

        return buffer;
    }

    // Vulnerable: Consuming all entropy
    public void vulnerableEntropyHog() throws Exception {
        SecureRandom sr = SecureRandom.getInstanceStrong();

        while (true) {
            byte[] data = new byte[1024];
            // Vulnerable: Continuous consumption
            sr.nextBytes(data);
            // Starves other processes
        }
    }

    // Vulnerable: No graceful degradation
    public byte[] vulnerableCrashOnFailure(int length) {
        try {
            return SecureRandom.getInstanceStrong().generateSeed(length);
        } catch (Exception e) {
            // Vulnerable: Crash instead of graceful handling
            throw new RuntimeException("Security failure - cannot continue");
        }
    }
}

Fixed Code (C/Python)

// Fixed: Proper TRNG handling
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>

// Fixed: Use /dev/urandom for most purposes
int secure_get_random(unsigned char *buffer, size_t length) {
    // Fixed: /dev/urandom doesn't block and is suitable for crypto
    int fd = open("/dev/urandom", O_RDONLY);
    if (fd < 0) return -1;

    ssize_t total = 0;
    while (total < (ssize_t)length) {
        ssize_t bytes = read(fd, buffer + total, length - total);
        if (bytes < 0) {
            if (errno == EINTR) continue;  // Retry on interrupt
            close(fd);
            return -1;
        }
        total += bytes;
    }

    close(fd);
    return 0;
}

// Fixed: Rate-limited TRNG usage
typedef struct {
    unsigned char pool[256];
    size_t pool_offset;
    time_t last_refill;
} EntropyPool;

static EntropyPool entropy_pool = {0};

int secure_rate_limited_random(unsigned char *buffer, size_t length) {
    // Fixed: Refill pool at most once per second
    time_t now = time(NULL);

    if (now > entropy_pool.last_refill) {
        // Get fresh entropy from urandom
        if (secure_get_random(entropy_pool.pool, sizeof(entropy_pool.pool)) == 0) {
            entropy_pool.pool_offset = 0;
            entropy_pool.last_refill = now;
        }
    }

    // Serve from pool
    if (entropy_pool.pool_offset + length <= sizeof(entropy_pool.pool)) {
        memcpy(buffer, entropy_pool.pool + entropy_pool.pool_offset, length);
        entropy_pool.pool_offset += length;
        return 0;
    }

    // Pool exhausted, get directly but don't block
    return secure_get_random(buffer, length);
}

// Fixed: Graceful handling of entropy exhaustion
int secure_session_id(unsigned char *session_id, size_t length) {
    // Fixed: Try urandom first (won't block)
    if (secure_get_random(session_id, length) == 0) {
        return 0;
    }

    // Fixed: Return error rather than crash or fallback
    return -1;
}

// Fixed: Monitor entropy availability
int check_entropy_available(size_t needed) {
    #ifdef __linux__
    FILE *f = fopen("/proc/sys/kernel/random/entropy_avail", "r");
    if (f) {
        int available;
        if (fscanf(f, "%d", &available) == 1) {
            fclose(f);
            return available >= (int)needed * 8;  // Convert bytes to bits
        }
        fclose(f);
    }
    #endif
    return 1;  // Assume available on non-Linux
}
# Fixed: Proper TRNG handling in Python
import os
import secrets
import threading
import time

# Fixed: Use os.urandom which handles entropy properly
def secure_random_bytes(length):
    # Fixed: os.urandom is non-blocking and cryptographically secure
    return os.urandom(length)

# Fixed: Use secrets module
def secure_token():
    # Fixed: secrets module handles entropy correctly
    return secrets.token_hex(32)

# Fixed: Rate-limited entropy pool
class SecureEntropyPool:
    def __init__(self, pool_size=1024):
        self._pool = bytearray(pool_size)
        self._offset = pool_size  # Start empty
        self._lock = threading.Lock()
        self._refill()

    def _refill(self):
        # Fixed: Refill from urandom
        new_entropy = os.urandom(len(self._pool))
        with self._lock:
            self._pool[:] = new_entropy
            self._offset = 0

    def get_bytes(self, length):
        with self._lock:
            if self._offset + length > len(self._pool):
                self._refill()

            result = bytes(self._pool[self._offset:self._offset + length])
            self._offset += length
            return result

# Fixed: Server with rate limiting
class SecureServer:
    def __init__(self):
        self._entropy_pool = SecureEntropyPool()
        self._rate_limiter = {}

    def handle_connection(self, client_addr):
        # Fixed: Rate limit per client
        now = time.time()
        if client_addr in self._rate_limiter:
            if now - self._rate_limiter[client_addr] < 0.1:  # 100ms min
                raise RateLimitError("Too many requests")

        self._rate_limiter[client_addr] = now

        # Fixed: Use pooled entropy
        session_id = self._entropy_pool.get_bytes(32)
        return session_id

# Fixed: Explicit failure, no silent degradation
def secure_key_generation():
    try:
        # Fixed: Use secrets which won't degrade
        return secrets.token_bytes(32)
    except Exception as e:
        # Fixed: Explicit failure with logging
        import logging
        logging.error(f"Key generation failed: {e}")
        raise SecurityError("Unable to generate secure key") from e

# Fixed: Check entropy availability on Linux
def check_entropy_health():
    try:
        with open('/proc/sys/kernel/random/entropy_avail', 'r') as f:
            available = int(f.read().strip())
            return available >= 256  # Minimum safe level
    except:
        return True  # Non-Linux systems handle differently
// Fixed: Proper TRNG handling in Java
import java.security.*;
import java.util.concurrent.*;

public class SecureTRNG {

    private final SecureRandom secureRandom;
    private final byte[] entropyPool;
    private int poolOffset;
    private final Object poolLock = new Object();

    public SecureTRNG() throws NoSuchAlgorithmException {
        // Fixed: Use standard SecureRandom which handles entropy
        this.secureRandom = new SecureRandom();
        this.entropyPool = new byte[1024];
        this.poolOffset = entropyPool.length;  // Start empty
    }

    // Fixed: Non-blocking random with proper handling
    public byte[] getSecureRandom(int length) {
        byte[] result = new byte[length];
        // Fixed: SecureRandom handles entropy internally
        secureRandom.nextBytes(result);
        return result;
    }

    // Fixed: Rate-limited pool for high-frequency requests
    public byte[] getPooledRandom(int length) {
        synchronized (poolLock) {
            if (poolOffset + length > entropyPool.length) {
                // Refill pool
                secureRandom.nextBytes(entropyPool);
                poolOffset = 0;
            }

            byte[] result = new byte[length];
            System.arraycopy(entropyPool, poolOffset, result, 0, length);
            poolOffset += length;
            return result;
        }
    }

    // Fixed: Async random generation with timeout
    public byte[] getRandomWithTimeout(int length, long timeoutMs)
            throws TimeoutException {

        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<byte[]> future = executor.submit(() -> {
            byte[] result = new byte[length];
            SecureRandom.getInstanceStrong().nextBytes(result);
            return result;
        });

        try {
            return future.get(timeoutMs, TimeUnit.MILLISECONDS);
        } catch (InterruptedException | ExecutionException e) {
            // Fixed: Explicit failure
            throw new SecurityException("Failed to generate random bytes", e);
        } finally {
            executor.shutdownNow();
        }
    }

    // Fixed: Graceful degradation with notification
    public byte[] getRandomGraceful(int length) {
        try {
            // Try strong random first
            return SecureRandom.getInstanceStrong().generateSeed(length);
        } catch (NoSuchAlgorithmException e) {
            // Fixed: Use default SecureRandom, but log the fallback
            System.err.println("Warning: Using default SecureRandom");
            byte[] result = new byte[length];
            secureRandom.nextBytes(result);
            return result;
        }
    }
}

The fix uses non-blocking entropy sources, implements rate limiting, and fails explicitly rather than silently degrading.


Exploited in the Wild

Embedded Device Entropy Exhaustion

Various embedded devices with limited entropy sources have experienced crashes or security degradation when cryptographic operations exhausted available entropy faster than it could be replenished.

Server DoS via Entropy Exhaustion

Web servers using blocking reads from /dev/random for session ID generation have been subjected to denial of service attacks by rapidly opening many connections to exhaust the entropy pool.


Tools to Test/Exploit

  • rngtest — Tests random number generator quality and entropy.

  • haveged — Entropy harvesting daemon for Linux systems.

  • Entropy monitoring scripts — Scripts to monitor /proc/sys/kernel/random/entropy_avail.


CVE Examples

No specific CVEs are widely documented for this CWE, but the vulnerability pattern appears in many embedded systems and server applications that improperly rely on blocking TRNG sources.


References

  1. MITRE Corporation. "CWE-333: Improper Handling of Insufficient Entropy in TRNG." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/333.html

  2. NIST. "Recommendation for the Entropy Sources Used for Random Bit Generation." SP 800-90B. https://csrc.nist.gov/publications/detail/sp/800-90b/final

  3. Linux Kernel Documentation. "Random Number Generation." https://www.kernel.org/doc/html/latest/admin-guide/hw-random.html