Verwendung nicht-reentrantfähiger Funktionen in nebenläufigem Kontext

Beschreibung

Die Verwendung nicht-reentrantfähiger Funktionen in nebenläufigem Kontext tritt auf, wenn Code Funktionen aufruft, die internen statischen Zustand in einer Multithread-Umgebung beibehalten. Nicht-reentrantfähige Funktionen verwenden globale oder statische Variablen zum Speichern von Zustand zwischen Aufrufen, was sie unsicher macht, wenn mehrere Threads sie gleichzeitig aufrufen. Dies führt zu Race Conditions, Datenkorruption und unvorhersehbarem Verhalten.

Risiko

Datenkorruption durch gleichzeitigen Zugriff auf gemeinsamen statischen Zustand. Race Conditions erzeugen inkonsistente Ergebnisse. Sicherheitslücken durch beschädigten Authentifizierungszustand. Speicherkorruption bei String-Manipulationsfunktionen. Unvorhersehbare Abstürze in Produktionssystemen. Schwer reproduzierbare Fehler, die nur unter Last auftreten.

Lösung

Verwenden Sie reentrantfähige Versionen von Funktionen (Funktionen mit Suffix _r). Schützen Sie nicht-reentrantfähige Aufrufe mit Mutexen. Verwenden Sie Thread-lokalen Speicher wo angemessen. Bevorzugen Sie moderne thread-sichere Alternativen. Prüfen Sie Code auf Verwendung nicht-reentrantfähiger Funktionen. Testen Sie gründlich unter nebenläufiger Last.

Häufige Konsequenzen

AuswirkungDetails
IntegritätBereich: Datenkorruption

Gemeinsamer Zustand wird durch gleichzeitigen Zugriff beschädigt.
VerfügbarkeitBereich: Abstürze

Speicherkorruption verursacht unvorhersehbare Fehler.
SicherheitBereich: Authentifizierungsumgehung

Beschädigter Auth-Zustand ermöglicht unbefugten Zugriff.

Beispielcode + Lösungscode

Verwundbarer Code

// VERWUNDBAR: Nicht-reentrantfähige Funktionen in Multithread-Kontext
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <pwd.h>
#include <netdb.h>
#include <pthread.h>

// VERWUNDBAR: strtok ist nicht reentrantfähig
void* parse_tokens_vulnerable(void* arg) {
    char* input = strdup((char*)arg);

    // VERWUNDBAR: strtok verwendet statischen Puffer
    char* token = strtok(input, ",");
    while (token != NULL) {
        printf("Token: %s\n", token);
        token = strtok(NULL, ",");  // Statischer Zustand zwischen Threads geteilt!
    }

    free(input);
    return NULL;
}

// VERWUNDBAR: localtime ist nicht reentrantfähig
void* get_time_vulnerable(void* arg) {
    time_t now = time(NULL);

    // VERWUNDBAR: Gibt Pointer auf statischen Puffer zurück
    struct tm* tm_info = localtime(&now);

    // Bis wir tm_info verwenden, könnte ein anderer Thread es überschrieben haben
    printf("Stunde: %d\n", tm_info->tm_hour);

    return NULL;
}

// VERWUNDBAR: getpwnam ist nicht reentrantfähig
void* lookup_user_vulnerable(void* arg) {
    char* username = (char*)arg;

    // VERWUNDBAR: Gibt Pointer auf statischen Puffer zurück
    struct passwd* pw = getpwnam(username);

    if (pw != NULL) {
        // Statischer Puffer könnte von anderem Thread überschrieben werden
        printf("Benutzer %s hat UID %d\n", pw->pw_name, pw->pw_uid);
    }

    return NULL;
}

// VERWUNDBAR: gethostbyname ist nicht reentrantfähig
void* resolve_host_vulnerable(void* arg) {
    char* hostname = (char*)arg;

    // VERWUNDBAR: Verwendet statischen Puffer
    struct hostent* host = gethostbyname(hostname);

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

    return NULL;
}

// VERWUNDBAR: ctime ist nicht reentrantfähig
void log_event_vulnerable(const char* event) {
    time_t now = time(NULL);

    // VERWUNDBAR: ctime gibt statischen Puffer zurück
    char* time_str = ctime(&now);

    // Ein anderer Thread könnte time_str beschädigen bevor dies abgeschlossen ist
    printf("[%s] %s\n", time_str, event);
}

// VERWUNDBAR: rand ist nicht thread-sicher
void* generate_random_vulnerable(void* arg) {
    // VERWUNDBAR: rand() verwendet globalen Zustand
    int random_value = rand();
    printf("Zufallszahl: %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"};

    // Alle diese Threads werden sich gegenseitig storen
    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: Nicht-reentrantfähige Muster
import threading
import time

# VERWUNDBAR: Globaler Zustand zwischen Threads geteilt
_last_result = None

def compute_and_store(value):
    global _last_result
    # VERWUNDBAR: Race Condition - anderer Thread könnte zwischen Berechnung und Speichern modifizieren
    result = expensive_computation(value)
    _last_result = result
    time.sleep(0.001)  # Simuliert Arbeit, vergrößert Race-Fenster
    return _last_result  # Könnte nicht das Ergebnis sein, das wir gerade berechnet haben!

# VERWUNDBAR: Klasse mit statischem/klassenweitem veränderlichem Zustand
class VulnerableCache:
    _cache = {}  # Klassenweit geteilter Zustand

    @classmethod
    def get_or_compute(cls, key, compute_func):
        # VERWUNDBAR: Check-then-act Race Condition
        if key not in cls._cache:
            # Ein anderer Thread könnte hier denselben Schlüssel hinzufügen
            cls._cache[key] = compute_func(key)
        return cls._cache[key]

# VERWUNDBAR: Verwendung nicht-thread-sicherer Datei-Handles
class VulnerableLogger:
    def __init__(self, filename):
        self.file = open(filename, 'a')  # Geteiltes Datei-Handle

    def log(self, message):
        # VERWUNDBAR: Mehrere Threads schreiben ohne Synchronisierung
        timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
        self.file.write(f"[{timestamp}] {message}\n")
        self.file.flush()  # Schreibvorgänge können sich überlappen
// VERWUNDBAR: Nicht-reentrantfähige Muster in Java
public class VulnerableNonReentrant {

    // VERWUNDBAR: SimpleDateFormat ist nicht thread-sicher
    private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");

    public static String formatDate(Date date) {
        // VERWUNDBAR: Mehrere Threads verwenden denselben Formatter
        return dateFormat.format(date);  // Race Condition!
    }

    // VERWUNDBAR: Statischer StringBuilder
    private static StringBuilder sharedBuffer = new StringBuilder();

    public static String buildMessage(String[] parts) {
        // VERWUNDBAR: Mehrere Threads modifizieren denselben Puffer
        sharedBuffer.setLength(0);
        for (String part : parts) {
            sharedBuffer.append(part);
        }
        return sharedBuffer.toString();  // Könnte Daten von anderen Threads enthalten
    }

    // VERWUNDBAR: Statisches NumberFormat
    private static NumberFormat currencyFormat = NumberFormat.getCurrencyInstance();

    public static String formatCurrency(double amount) {
        // VERWUNDBAR: NumberFormat ist nicht thread-sicher
        return currencyFormat.format(amount);
    }
}

Lösungscode

// SICHER: Verwendung reentrantfähiger Funktionen
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <pwd.h>
#include <netdb.h>
#include <pthread.h>

// SICHER: Verwendung von strtok_r (reentrantfähige Version)
void* parse_tokens_safe(void* arg) {
    char* input = strdup((char*)arg);
    char* saveptr;  // Thread-lokaler Zustand

    // SICHER: strtok_r verwendet vom Aufrufer bereitgestellten Zustand
    char* token = strtok_r(input, ",", &saveptr);
    while (token != NULL) {
        printf("Token: %s\n", token);
        token = strtok_r(NULL, ",", &saveptr);
    }

    free(input);
    return NULL;
}

// SICHER: Verwendung von localtime_r (reentrantfähige Version)
void* get_time_safe(void* arg) {
    time_t now = time(NULL);
    struct tm tm_info;  // Thread-lokaler Puffer

    // SICHER: localtime_r verwendet vom Aufrufer bereitgestellten Puffer
    localtime_r(&now, &tm_info);

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

    return NULL;
}

// SICHER: Verwendung von getpwnam_r (reentrantfähige Version)
void* lookup_user_safe(void* arg) {
    char* username = (char*)arg;
    struct passwd pwd;
    struct passwd* result;
    char buffer[1024];  // Thread-lokaler Puffer

    // SICHER: getpwnam_r verwendet vom Aufrufer bereitgestellte Puffer
    int ret = getpwnam_r(username, &pwd, buffer, sizeof(buffer), &result);

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

    return NULL;
}

// SICHER: Verwendung von getaddrinfo (thread-sicherer Ersatz)
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;

    // SICHER: getaddrinfo ist thread-sicher
    int ret = getaddrinfo(hostname, NULL, &hints, &res);

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

    return NULL;
}

// SICHER: Verwendung von ctime_r (reentrantfähige Version)
void log_event_safe(const char* event) {
    time_t now = time(NULL);
    char time_buffer[26];  // Thread-lokaler Puffer

    // SICHER: ctime_r verwendet vom Aufrufer bereitgestellten Puffer
    ctime_r(&now, time_buffer);
    time_buffer[24] = '\0';  // Zeilenumbruch entfernen

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

// SICHER: Verwendung von rand_r oder besseren Alternativen
void* generate_random_safe(void* arg) {
    unsigned int seed = (unsigned int)(uintptr_t)arg;

    // SICHER: rand_r verwendet thread-spezifischen Seed
    int random_value = rand_r(&seed);
    printf("Zufallszahl: %d\n", random_value);

    return NULL;
}

// SICHER: Nicht-reentrantfähige Funktionen mit Mutex schützen
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);
    // SICHER: Mutex schützt Zugriff auf statischen Puffer
    char* result = ctime(&now);
    strncpy(time_str, result, sizeof(time_str) - 1);
    pthread_mutex_unlock(&time_mutex);

    printf("Zeit: %s", time_str);
    return NULL;
}
# SICHER: Thread-sichere Muster in Python
import threading
import time
from threading import Lock, local

# SICHER: Thread-lokaler Speicher
_thread_local = local()

def get_connection():
    # SICHER: Jeder Thread bekommt seine eigene Verbindung
    if not hasattr(_thread_local, 'connection'):
        _thread_local.connection = create_connection()
    return _thread_local.connection

# SICHER: Thread-sicherer Cache mit Sperren
class ThreadSafeCache:
    def __init__(self):
        self._cache = {}
        self._lock = Lock()

    def get_or_compute(self, key, compute_func):
        # SICHER: Sperre schützt Check-and-Act
        with self._lock:
            if key not in self._cache:
                self._cache[key] = compute_func(key)
            return self._cache[key]

# SICHER: Thread-sicherer Logger
class ThreadSafeLogger:
    def __init__(self, filename):
        self.filename = filename
        self._lock = Lock()

    def log(self, message):
        # SICHER: Sperre schützt Dateischreibvorgänge
        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")

# SICHER: concurrent.futures für sichere nebenläufige Ausführung verwenden
from concurrent.futures import ThreadPoolExecutor

def process_items_safely(items):
    def process_item(item):
        # Jeder Aufruf ist unabhängig, kein geteilter Zustand
        return transform(item)

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

public class SafeReentrant {

    // SICHER: ThreadLocal für nicht-thread-sichere Formatter
    private static final ThreadLocal<SimpleDateFormat> dateFormat =
        ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

    public static String formatDateThreadLocal(Date date) {
        // SICHER: Jeder Thread bekommt seinen eigenen Formatter
        return dateFormat.get().format(date);
    }

    // SICHER: Thread-sicheren DateTimeFormatter verwenden
    private static final DateTimeFormatter formatter =
        DateTimeFormatter.ofPattern("yyyy-MM-dd");

    public static String formatDateSafe(LocalDate date) {
        // SICHER: DateTimeFormatter ist unveränderlich und thread-sicher
        return date.format(formatter);
    }

    // SICHER: Lokaler StringBuilder pro Methodenaufruf
    public static String buildMessage(String[] parts) {
        // SICHER: Lokale Variable, nicht geteilt
        StringBuilder buffer = new StringBuilder();
        for (String part : parts) {
            buffer.append(part);
        }
        return buffer.toString();
    }

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

    public static String formatCurrency(double amount) {
        // SICHER: Jeder Thread bekommt seinen eigenen Formatter
        return currencyFormat.get().format(amount);
    }

    // SICHER: synchronized zum Schutz verwenden
    private static final SimpleDateFormat sharedFormat =
        new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static synchronized String formatDateSync(Date date) {
        // SICHER: Synchronisierte Methode stellt Single-Thread-Zugriff sicher
        return sharedFormat.format(date);
    }
}

Ausgenutzt in der Praxis

DNS-Auflösungskorruption

gethostbyname Race Conditions verursachten DNS-Cache-Poisoning.

Authentifizierungsumgehung

getpwnam-Korruption ermöglichte Privilegieneskalation.

Datenkorruption

SimpleDateFormat-Races verursachten falsche Zeitstempel.


Werkzeuge zum Testen/Ausnutzen

  • Thread-Sanitizer (TSan, Helgrind).

  • Statische Analyse zur Erkennung nicht-reentrantfähiger Funktionen.

  • Nebenläufige Stresstestwerkzeuge.


CVE-Beispiele

  • CVE-2014-3566: Thread-Sicherheitsprobleme in kryptographischen Bibliotheken.

  • Mehrere CVEs durch Verwendung nicht-reentrantfähiger Funktionen in System-Utilities.


Referenzen

  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-Sicherheits- und Reentrantfähigkeits-Richtlinien.