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
| Impact | Details |
|---|---|
| Integrity | Scope: Data Corruption Shared state corrupted by concurrent access. |
| Availability | Scope: Crashes Memory corruption causes unpredictable failures. |
| Security | Scope: 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
-
MITRE. "CWE-663: Use of a Non-reentrant Function in a Concurrent Context." https://cwe.mitre.org/data/definitions/663.html
-
POSIX Thread Safety and Reentrancy guidelines.