Use of a Non-reentrant Function in a Concurrent Context

Description

Use of a Non-reentrant Function in a Concurrent Context occurs when code calls functions that maintain internal static state in a multi-threaded environment. Non-reentrant functions use global or static variables to store state between calls, making them unsafe when multiple threads call them simultaneously. This leads to race conditions, data corruption, and unpredictable behavior.

Risk

Data corruption from concurrent access to shared static state. Race conditions produce inconsistent results. Security vulnerabilities from corrupted authentication state. Memory corruption in string manipulation functions. Unpredictable crashes in production systems. Difficult-to-reproduce bugs that only manifest under load.

Solution

Use reentrant versions of functions (functions ending in _r). Protect non-reentrant calls with mutexes. Use thread-local storage where appropriate. Prefer modern thread-safe alternatives. Audit code for non-reentrant function usage. Test thoroughly under concurrent load.

Common Consequences

ImpactDetails
IntegrityScope: Data Corruption

Shared state corrupted by concurrent access.
AvailabilityScope: Crashes

Memory corruption causes unpredictable failures.
SecurityScope: Authentication Bypass

Corrupted auth state allows unauthorized access.

Example Code + Solution Code

Vulnerable Code

// VULNERABLE: Non-reentrant functions in multi-threaded context
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <pwd.h>
#include <netdb.h>
#include <pthread.h>

// VULNERABLE: strtok is not reentrant
void* parse_tokens_vulnerable(void* arg) {
    char* input = strdup((char*)arg);

    // VULNERABLE: strtok uses static buffer
    char* token = strtok(input, ",");
    while (token != NULL) {
        printf("Token: %s\n", token);
        token = strtok(NULL, ",");  // Static state shared between threads!
    }

    free(input);
    return NULL;
}

// VULNERABLE: localtime is not reentrant
void* get_time_vulnerable(void* arg) {
    time_t now = time(NULL);

    // VULNERABLE: Returns pointer to static buffer
    struct tm* tm_info = localtime(&now);

    // By the time we use tm_info, another thread may have overwritten it
    printf("Hour: %d\n", tm_info->tm_hour);

    return NULL;
}

// VULNERABLE: getpwnam is not reentrant
void* lookup_user_vulnerable(void* arg) {
    char* username = (char*)arg;

    // VULNERABLE: Returns pointer to static buffer
    struct passwd* pw = getpwnam(username);

    if (pw != NULL) {
        // Static buffer may be overwritten by another thread
        printf("User %s has UID %d\n", pw->pw_name, pw->pw_uid);
    }

    return NULL;
}

// VULNERABLE: gethostbyname is not reentrant
void* resolve_host_vulnerable(void* arg) {
    char* hostname = (char*)arg;

    // VULNERABLE: Uses static buffer
    struct hostent* host = gethostbyname(hostname);

    if (host != NULL) {
        printf("Host: %s\n", host->h_name);
    }

    return NULL;
}

// VULNERABLE: ctime is not reentrant
void log_event_vulnerable(const char* event) {
    time_t now = time(NULL);

    // VULNERABLE: ctime returns static buffer
    char* time_str = ctime(&now);

    // Another thread could corrupt time_str before this completes
    printf("[%s] %s\n", time_str, event);
}

// VULNERABLE: rand is not thread-safe
void* generate_random_vulnerable(void* arg) {
    // VULNERABLE: rand() uses global state
    int random_value = rand();
    printf("Random: %d\n", random_value);
    return NULL;
}

int main() {
    pthread_t threads[10];
    char* inputs[] = {"a,b,c", "1,2,3", "x,y,z"};
    char* usernames[] = {"root", "admin", "user"};

    // All these threads will interfere with each other
    for (int i = 0; i < 3; i++) {
        pthread_create(&threads[i], NULL, parse_tokens_vulnerable, inputs[i]);
        pthread_create(&threads[i+3], NULL, lookup_user_vulnerable, usernames[i]);
    }

    for (int i = 0; i < 6; i++) {
        pthread_join(threads[i], NULL);
    }

    return 0;
}
# Python: Non-reentrant patterns
import threading
import time

# VULNERABLE: Global state shared between threads
_last_result = None

def compute_and_store(value):
    global _last_result
    # VULNERABLE: Race condition - another thread could modify between compute and store
    result = expensive_computation(value)
    _last_result = result
    time.sleep(0.001)  # Simulates work, increases race window
    return _last_result  # May not be the result we just computed!

# VULNERABLE: Class with static/class-level mutable state
class VulnerableCache:
    _cache = {}  # Class-level shared state

    @classmethod
    def get_or_compute(cls, key, compute_func):
        # VULNERABLE: Check-then-act race condition
        if key not in cls._cache:
            # Another thread could add the same key here
            cls._cache[key] = compute_func(key)
        return cls._cache[key]

# VULNERABLE: Using non-thread-safe file handles
class VulnerableLogger:
    def __init__(self, filename):
        self.file = open(filename, 'a')  # Shared file handle

    def log(self, message):
        # VULNERABLE: Multiple threads writing without synchronization
        timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
        self.file.write(f"[{timestamp}] {message}\n")
        self.file.flush()  # Writes may interleave
// VULNERABLE: Non-reentrant patterns in Java
public class VulnerableNonReentrant {

    // VULNERABLE: SimpleDateFormat is not thread-safe
    private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");

    public static String formatDate(Date date) {
        // VULNERABLE: Multiple threads using same formatter
        return dateFormat.format(date);  // Race condition!
    }

    // VULNERABLE: Static StringBuilder
    private static StringBuilder sharedBuffer = new StringBuilder();

    public static String buildMessage(String[] parts) {
        // VULNERABLE: Multiple threads modifying same buffer
        sharedBuffer.setLength(0);
        for (String part : parts) {
            sharedBuffer.append(part);
        }
        return sharedBuffer.toString();  // May contain data from other threads
    }

    // VULNERABLE: Static NumberFormat
    private static NumberFormat currencyFormat = NumberFormat.getCurrencyInstance();

    public static String formatCurrency(double amount) {
        // VULNERABLE: NumberFormat is not thread-safe
        return currencyFormat.format(amount);
    }
}

Fixed Code

// SAFE: Using reentrant functions
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <pwd.h>
#include <netdb.h>
#include <pthread.h>

// SAFE: Using strtok_r (reentrant version)
void* parse_tokens_safe(void* arg) {
    char* input = strdup((char*)arg);
    char* saveptr;  // Thread-local state

    // SAFE: strtok_r uses caller-provided state
    char* token = strtok_r(input, ",", &saveptr);
    while (token != NULL) {
        printf("Token: %s\n", token);
        token = strtok_r(NULL, ",", &saveptr);
    }

    free(input);
    return NULL;
}

// SAFE: Using localtime_r (reentrant version)
void* get_time_safe(void* arg) {
    time_t now = time(NULL);
    struct tm tm_info;  // Thread-local buffer

    // SAFE: localtime_r uses caller-provided buffer
    localtime_r(&now, &tm_info);

    printf("Hour: %d\n", tm_info.tm_hour);

    return NULL;
}

// SAFE: Using getpwnam_r (reentrant version)
void* lookup_user_safe(void* arg) {
    char* username = (char*)arg;
    struct passwd pwd;
    struct passwd* result;
    char buffer[1024];  // Thread-local buffer

    // SAFE: getpwnam_r uses caller-provided buffers
    int ret = getpwnam_r(username, &pwd, buffer, sizeof(buffer), &result);

    if (ret == 0 && result != NULL) {
        printf("User %s has UID %d\n", pwd.pw_name, pwd.pw_uid);
    }

    return NULL;
}

// SAFE: Using getaddrinfo (thread-safe replacement)
void* resolve_host_safe(void* arg) {
    char* hostname = (char*)arg;
    struct addrinfo hints, *res;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    // SAFE: getaddrinfo is thread-safe
    int ret = getaddrinfo(hostname, NULL, &hints, &res);

    if (ret == 0) {
        printf("Resolved: %s\n", hostname);
        freeaddrinfo(res);
    }

    return NULL;
}

// SAFE: Using ctime_r (reentrant version)
void log_event_safe(const char* event) {
    time_t now = time(NULL);
    char time_buffer[26];  // Thread-local buffer

    // SAFE: ctime_r uses caller-provided buffer
    ctime_r(&now, time_buffer);
    time_buffer[24] = '\0';  // Remove newline

    printf("[%s] %s\n", time_buffer, event);
}

// SAFE: Using rand_r or better alternatives
void* generate_random_safe(void* arg) {
    unsigned int seed = (unsigned int)(uintptr_t)arg;

    // SAFE: rand_r uses thread-specific seed
    int random_value = rand_r(&seed);
    printf("Random: %d\n", random_value);

    return NULL;
}

// SAFE: Protecting non-reentrant functions with mutex
pthread_mutex_t time_mutex = PTHREAD_MUTEX_INITIALIZER;

void* get_time_mutex_protected(void* arg) {
    time_t now = time(NULL);
    char time_str[64];

    pthread_mutex_lock(&time_mutex);
    // SAFE: Mutex protects access to static buffer
    char* result = ctime(&now);
    strncpy(time_str, result, sizeof(time_str) - 1);
    pthread_mutex_unlock(&time_mutex);

    printf("Time: %s", time_str);
    return NULL;
}
# SAFE: Thread-safe patterns in Python
import threading
import time
from threading import Lock, local

# SAFE: Thread-local storage
_thread_local = local()

def get_connection():
    # SAFE: Each thread gets its own connection
    if not hasattr(_thread_local, 'connection'):
        _thread_local.connection = create_connection()
    return _thread_local.connection

# SAFE: Thread-safe cache with locking
class ThreadSafeCache:
    def __init__(self):
        self._cache = {}
        self._lock = Lock()

    def get_or_compute(self, key, compute_func):
        # SAFE: Lock protects check-and-act
        with self._lock:
            if key not in self._cache:
                self._cache[key] = compute_func(key)
            return self._cache[key]

# SAFE: Thread-safe logger
class ThreadSafeLogger:
    def __init__(self, filename):
        self.filename = filename
        self._lock = Lock()

    def log(self, message):
        # SAFE: Lock protects file writes
        with self._lock:
            with open(self.filename, 'a') as f:
                timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
                f.write(f"[{timestamp}] {message}\n")

# SAFE: Using concurrent.futures for safe concurrent execution
from concurrent.futures import ThreadPoolExecutor

def process_items_safely(items):
    def process_item(item):
        # Each call is independent, no shared state
        return transform(item)

    with ThreadPoolExecutor(max_workers=4) as executor:
        results = list(executor.map(process_item, items))
    return results
// SAFE: Thread-safe alternatives in Java
import java.text.SimpleDateFormat;
import java.time.format.DateTimeFormatter;
import java.time.LocalDate;
import java.text.NumberFormat;

public class SafeReentrant {

    // SAFE: ThreadLocal for non-thread-safe formatters
    private static final ThreadLocal<SimpleDateFormat> dateFormat =
        ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

    public static String formatDateThreadLocal(Date date) {
        // SAFE: Each thread gets its own formatter
        return dateFormat.get().format(date);
    }

    // SAFE: Use thread-safe DateTimeFormatter
    private static final DateTimeFormatter formatter =
        DateTimeFormatter.ofPattern("yyyy-MM-dd");

    public static String formatDateSafe(LocalDate date) {
        // SAFE: DateTimeFormatter is immutable and thread-safe
        return date.format(formatter);
    }

    // SAFE: Local StringBuilder per method call
    public static String buildMessage(String[] parts) {
        // SAFE: Local variable, not shared
        StringBuilder buffer = new StringBuilder();
        for (String part : parts) {
            buffer.append(part);
        }
        return buffer.toString();
    }

    // SAFE: ThreadLocal NumberFormat
    private static final ThreadLocal<NumberFormat> currencyFormat =
        ThreadLocal.withInitial(() -> NumberFormat.getCurrencyInstance());

    public static String formatCurrency(double amount) {
        // SAFE: Each thread gets its own formatter
        return currencyFormat.get().format(amount);
    }

    // SAFE: Using synchronized for protection
    private static final SimpleDateFormat sharedFormat =
        new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static synchronized String formatDateSync(Date date) {
        // SAFE: Synchronized method ensures single-threaded access
        return sharedFormat.format(date);
    }
}

Exploited in the Wild

DNS Resolution Corruption

gethostbyname race conditions caused DNS cache poisoning.

Authentication Bypass

getpwnam corruption allowed privilege escalation.

Data Corruption

SimpleDateFormat races caused incorrect timestamps.


Tools to test/exploit

  • Thread sanitizers (TSan, Helgrind).

  • Static analysis for non-reentrant function detection.

  • Concurrent stress testing tools.


CVE Examples

  • CVE-2014-3566: Thread-safety issues in cryptographic libraries.

  • Multiple CVEs from non-reentrant function usage in system utilities.


References

  1. MITRE. "CWE-663: Use of a Non-reentrant Function in a Concurrent Context." https://cwe.mitre.org/data/definitions/663.html

  2. POSIX Thread Safety and Reentrancy guidelines.