Unsachgemäße Synchronisierung

Beschreibung

Unsachgemäße Synchronisierung tritt auf, wenn der gleichzeitige Zugriff auf gemeinsam genutzte Ressourcen nicht ordnungsgemäß kontrolliert wird, was zu Race Conditions, Datenkorruption oder Sicherheitslücken führt. Dies umfasst fehlende Sperren, falschen Sperrbereich, Probleme mit der Sperrreihenfolge und Time-of-Check-to-Time-of-Use (TOCTOU)-Bedingungen. Im Sicherheitskontext kann unsachgemäße Synchronisierung es Angreifern ermöglichen, den Programmzustand zwischen Prüfungen und Operationen zu manipulieren.

Risiko

Race Conditions ermöglichen Privilegieneskalation. TOCTOU-Angriffe umgehen Zugriffskontrollen. Datenkorruption durch gleichzeitige Schreibvorgänge. Deadlocks verursachen Denial of Service. Double-Spending in Finanzanwendungen. Authentifizierungsumgehung durch Timing-Angriffe. Session-Fixierung durch Race Conditions.

Lösung

Verwenden Sie geeignete Synchronisierungsprimitive. Implementieren Sie atomare Operationen für Check-and-Act-Sequenzen. Nutzen Sie Transaktionen für Datenbankoperationen. Wenden Sie Sperrreihenfolge an, um Deadlocks zu verhindern. Verwenden Sie thread-sichere Datenstrukturen. Implementieren Sie ordnungsgemäßes Session-Locking. Testen Sie auf Race Conditions mit Fuzzing.

Häufige Konsequenzen

AuswirkungDetails
IntegritätBereich: Datenkorruption

Gleichzeitiger Zugriff beschädigt gemeinsamen Zustand.
AutorisierungBereich: Umgehung

Race Conditions umgehen Sicherheitsprüfungen.
VerfügbarkeitBereich: Deadlock/DoS

Unsachgemäßes Sperren verursacht Systemhänger.

Beispielcode + Lösungscode

Verwundbarer Code

// VERWUNDBAR: Race Condition bei Authentifizierung
public class VulnerableAuthService {

    private Map<String, Session> sessions = new HashMap<>();  // Nicht thread-sicher!

    public Session login(String username, String password) {
        // VERWUNDBAR: TOCTOU Race Condition
        if (validateCredentials(username, password)) {
            // Lücke zwischen Prüfung und Session-Erstellung
            Session session = new Session(username);
            sessions.put(session.getId(), session);  // Thread-unsicheres Schreiben
            return session;
        }
        return null;
    }

    public boolean isLoggedIn(String sessionId) {
        // VERWUNDBAR: Prüfung ohne Synchronisierung
        return sessions.containsKey(sessionId);
    }
}

// VERWUNDBAR: Race Condition bei Kontostand-Prüfung
public class VulnerableBankAccount {

    private double balance;  // Nicht atomar

    public boolean withdraw(double amount) {
        // VERWUNDBAR: TOCTOU - Prüfung und Aktion nicht atomar
        if (balance >= amount) {
            // Ein anderer Thread könnte zwischen Prüfung und Update abheben
            balance -= amount;  // Nicht-atomare Operation
            return true;
        }
        return false;
    }

    // Doppelabhebungsangriff möglich:
    // Thread A: prüft Kontostand (100), sieht ausreichend Guthaben
    // Thread B: prüft Kontostand (100), sieht ausreichend Guthaben
    // Thread A: hebt 100 ab, Kontostand = 0
    // Thread B: hebt 100 ab, Kontostand = -100 (Überziehung!)
}

// VERWUNDBAR: Dateibasiertes TOCTOU
public class VulnerableFileAccess {

    public void readFile(String filename) {
        File file = new File(filename);

        // VERWUNDBAR: TOCTOU Race Condition
        if (file.exists() && file.canRead()) {
            // Lücke: Angreifer könnte Datei hier durch Symlink ersetzen
            // zwischen Prüfung und Verwendung
            return Files.readAllBytes(file.toPath());
        }
    }
}
# VERWUNDBAR: Python Race Conditions
import threading

class VulnerableCounter:
    def __init__(self):
        self.count = 0  # Nicht thread-sicher

    def increment(self):
        # VERWUNDBAR: Read-Modify-Write nicht atomar
        current = self.count
        # Kontextwechsel könnte hier passieren
        self.count = current + 1

# VERWUNDBAR: Authentifizierungs-Race Condition
class VulnerableAuth:
    def __init__(self):
        self.sessions = {}  # Nicht thread-sichere Dict-Operationen

    def login(self, user_id, password):
        # VERWUNDBAR: Check then act
        if self.validate(user_id, password):
            # Race: anderer Thread könnte zwischen Prüfung und Erstellung invalidieren
            session = create_session(user_id)
            self.sessions[session.id] = session
            return session

    def check_session(self, session_id):
        # VERWUNDBAR: Keine Synchronisierung
        if session_id in self.sessions:
            # Race: Session könnte zwischen Prüfung und Verwendung entfernt werden
            return self.sessions[session_id]

# VERWUNDBAR: TOCTOU Dateizugriff
import os

def process_file_vulnerable(filepath):
    # VERWUNDBAR: Check then use
    if os.path.exists(filepath) and os.access(filepath, os.R_OK):
        # Angreifer könnte Datei hier durch Symlink ersetzen
        with open(filepath, 'r') as f:
            return f.read()

# VERWUNDBAR: Race Condition bei Rate-Limiting
class VulnerableRateLimiter:
    def __init__(self):
        self.counts = {}

    def is_allowed(self, user_id):
        # VERWUNDBAR: Nicht atomar
        if user_id not in self.counts:
            self.counts[user_id] = 0

        current = self.counts[user_id]
        if current < 100:
            self.counts[user_id] = current + 1  # Race Condition
            return True
        return False
// VERWUNDBAR: Node.js Race Conditions
class VulnerableService {
    constructor() {
        this.data = {};
        this.sessions = new Map();
    }

    // VERWUNDBAR: Async Race Condition
    async withdraw(accountId, amount) {
        const account = await this.getAccount(accountId);

        // VERWUNDBAR: Check then act mit async Lücke
        if (account.balance >= amount) {
            // Eine andere Anfrage könnte zwischen Prüfung und Update verarbeiten
            account.balance -= amount;
            await this.saveAccount(account);
            return true;
        }
        return false;
    }

    // VERWUNDBAR: Session Race Condition
    async createSession(userId) {
        const existingSession = await this.findSession(userId);

        // VERWUNDBAR: Race Condition
        if (!existingSession) {
            // Zwei Anfragen könnten beide keine Session sehen und Duplikate erstellen
            const session = { id: generateId(), userId };
            this.sessions.set(session.id, session);
            return session;
        }
        return existingSession;
    }

    // VERWUNDBAR: Zähler Race
    async incrementCounter(key) {
        const value = await this.redis.get(key) || 0;
        // VERWUNDBAR: Nicht atomar
        await this.redis.set(key, parseInt(value) + 1);
    }
}

// VERWUNDBAR: Dateiprüfung dann Verwendung
const fs = require('fs').promises;

async function readFileVulnerable(filepath) {
    try {
        await fs.access(filepath, fs.constants.R_OK);
        // Lücke: Datei könnte sich zwischen Prüfung und Lesen ändern
        return await fs.readFile(filepath);
    } catch (e) {
        return null;
    }
}
// VERWUNDBAR: C Race Conditions
#include <pthread.h>

// VERWUNDBAR: Ungeschützter gemeinsamer Zähler
int global_counter = 0;

void* increment_vulnerable(void* arg) {
    for (int i = 0; i < 1000000; i++) {
        // VERWUNDBAR: Read-Modify-Write nicht atomar
        global_counter++;  // Nicht thread-sicher
    }
    return NULL;
}

// VERWUNDBAR: TOCTOU Dateizugriff
int access_file_vulnerable(const char* filename) {
    struct stat st;

    // VERWUNDBAR: Check then use
    if (stat(filename, &st) == 0) {
        if (st.st_uid == getuid()) {
            // Angreifer könnte Datei zwischen Prüfung und Öffnen austauschen
            int fd = open(filename, O_RDONLY);
            // ...
        }
    }
}

// VERWUNDBAR: Double-Free Race Condition
void* shared_ptr = NULL;
pthread_mutex_t mutex;  // Aber nicht korrekt verwendet

void free_shared_vulnerable() {
    // VERWUNDBAR: Check then act ohne gehaltene Sperre
    if (shared_ptr != NULL) {
        // Ein anderer Thread könnte zwischen Prüfung und Free freigeben
        free(shared_ptr);
        shared_ptr = NULL;
    }
}

Lösungscode

// SICHER: Ordnungsgemäße Synchronisierung
public class SafeAuthService {

    private final ConcurrentHashMap<String, Session> sessions = new ConcurrentHashMap<>();

    public Session login(String username, String password) {
        if (validateCredentials(username, password)) {
            Session session = new Session(username);
            // SICHER: Atomare put-Operation
            sessions.put(session.getId(), session);
            return session;
        }
        return null;
    }

    public boolean isLoggedIn(String sessionId) {
        // SICHER: Thread-sichere Prüfung
        return sessions.containsKey(sessionId);
    }
}

// SICHER: Atomare Kontostand-Operationen
public class SafeBankAccount {

    private final AtomicReference<BigDecimal> balance;
    private final Object withdrawLock = new Object();

    public SafeBankAccount(BigDecimal initialBalance) {
        this.balance = new AtomicReference<>(initialBalance);
    }

    // SICHER: Compare-and-Swap verwenden
    public boolean withdrawCAS(BigDecimal amount) {
        while (true) {
            BigDecimal current = balance.get();
            if (current.compareTo(amount) < 0) {
                return false;  // Unzureichendes Guthaben
            }
            BigDecimal newBalance = current.subtract(amount);
            if (balance.compareAndSet(current, newBalance)) {
                return true;  // Erfolgreich abgehoben
            }
            // Wiederholen falls anderer Thread modifiziert hat
        }
    }

    // Oder mit synchronized
    public synchronized boolean withdrawSync(BigDecimal amount) {
        if (balance.get().compareTo(amount) >= 0) {
            balance.set(balance.get().subtract(amount));
            return true;
        }
        return false;
    }
}

// SICHER: TOCTOU mit atomaren Operationen vermeiden
public class SafeFileAccess {

    public byte[] readFile(Path path) {
        try {
            // SICHER: Einzelne atomare Operation, kein TOCTOU
            return Files.readAllBytes(path);
        } catch (NoSuchFileException | AccessDeniedException e) {
            // Zugriffsprobleme behandeln
            return null;
        }
    }

    // SICHER: Dateisperren für exklusiven Zugriff verwenden
    public void writeFileExclusive(Path path, byte[] data) throws IOException {
        try (FileChannel channel = FileChannel.open(path,
                StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
            // SICHER: Exklusive Sperre
            try (FileLock lock = channel.lock()) {
                channel.write(ByteBuffer.wrap(data));
            }
        }
    }
}
# SICHER: Python mit ordnungsgemäßer Synchronisierung
import threading
from threading import Lock, RLock
import fcntl

class SafeCounter:
    def __init__(self):
        self.count = 0
        self.lock = Lock()

    def increment(self):
        # SICHER: Atomare Operation mit Sperre
        with self.lock:
            self.count += 1

# SICHER: Thread-sichere Authentifizierung
class SafeAuth:
    def __init__(self):
        self.sessions = {}
        self.lock = RLock()

    def login(self, user_id, password):
        if self.validate(user_id, password):
            # SICHER: Synchronisierte Session-Erstellung
            with self.lock:
                session = create_session(user_id)
                self.sessions[session.id] = session
                return session
        return None

    def check_session(self, session_id):
        # SICHER: Synchronisierter Zugriff
        with self.lock:
            return self.sessions.get(session_id)

# SICHER: TOCTOU mit atomaren Operationen vermeiden
def process_file_safe(filepath):
    try:
        # SICHER: Einzelne atomare Operation
        with open(filepath, 'r') as f:
            return f.read()
    except (FileNotFoundError, PermissionError):
        return None

# SICHER: Dateisperren für exklusiven Zugriff
def write_file_safe(filepath, content):
    with open(filepath, 'w') as f:
        # SICHER: Exklusive Dateisperre
        fcntl.flock(f.fileno(), fcntl.LOCK_EX)
        try:
            f.write(content)
        finally:
            fcntl.flock(f.fileno(), fcntl.LOCK_UN)

# SICHER: Atomares Rate-Limiting mit Redis
import redis

class SafeRateLimiter:
    def __init__(self):
        self.redis = redis.Redis()

    def is_allowed(self, user_id, limit=100, window=60):
        key = f"ratelimit:{user_id}"

        # SICHER: Atomares Inkrement mit Lua-Skript
        script = """
        local current = redis.call('INCR', KEYS[1])
        if current == 1 then
            redis.call('EXPIRE', KEYS[1], ARGV[1])
        end
        return current
        """

        current = self.redis.eval(script, 1, key, window)
        return current <= limit
// SICHER: Node.js mit ordnungsgemäßer Synchronisierung
const { Mutex } = require('async-mutex');

class SafeService {
    constructor() {
        this.mutex = new Mutex();
        this.sessions = new Map();
    }

    // SICHER: Mutex für atomare Operationen verwenden
    async withdraw(accountId, amount) {
        const release = await this.mutex.acquire();
        try {
            const account = await this.getAccount(accountId);

            if (account.balance >= amount) {
                account.balance -= amount;
                await this.saveAccount(account);
                return true;
            }
            return false;
        } finally {
            release();
        }
    }

    // SICHER: Datenbanktransaktion für Atomarität
    async withdrawWithTransaction(accountId, amount) {
        return await this.db.transaction(async (trx) => {
            // SICHER: Zeilensperren
            const account = await trx('accounts')
                .where({ id: accountId })
                .forUpdate()  // Zeile sperren
                .first();

            if (account.balance >= amount) {
                await trx('accounts')
                    .where({ id: accountId })
                    .update({ balance: account.balance - amount });
                return true;
            }
            return false;
        });
    }

    // SICHER: Atomare Session-Erstellung
    async createSession(userId) {
        const release = await this.mutex.acquire();
        try {
            // Atomar prüfen und erstellen
            for (const [id, session] of this.sessions) {
                if (session.userId === userId) {
                    return session;
                }
            }

            const session = { id: generateId(), userId };
            this.sessions.set(session.id, session);
            return session;
        } finally {
            release();
        }
    }

    // SICHER: Atomare Redis-Operationen
    async incrementCounter(key) {
        // SICHER: INCR ist atomar in Redis
        return await this.redis.incr(key);
    }
}

// SICHER: Atomare Dateioperationen
const fs = require('fs').promises;
const lockfile = require('proper-lockfile');

async function writeFileSafe(filepath, content) {
    // SICHER: Dateisperren
    const release = await lockfile.lock(filepath);
    try {
        await fs.writeFile(filepath, content);
    } finally {
        await release();
    }
}
// SICHER: C mit ordnungsgemäßer Synchronisierung
#include <pthread.h>
#include <stdatomic.h>

// SICHER: Atomarer Zähler
atomic_int safe_counter = 0;

void* increment_safe(void* arg) {
    for (int i = 0; i < 1000000; i++) {
        // SICHER: Atomares Inkrement
        atomic_fetch_add(&safe_counter, 1);
    }
    return NULL;
}

// SICHER: Mutex für komplexe Operationen verwenden
pthread_mutex_t balance_mutex = PTHREAD_MUTEX_INITIALIZER;
double account_balance = 0;

int withdraw_safe(double amount) {
    // SICHER: Sperren vor Check-and-Act
    pthread_mutex_lock(&balance_mutex);

    int success = 0;
    if (account_balance >= amount) {
        account_balance -= amount;
        success = 1;
    }

    pthread_mutex_unlock(&balance_mutex);
    return success;
}

// SICHER: TOCTOU mit ordnungsgemäßen Dateioperationen vermeiden
int access_file_safe(const char* filename) {
    // SICHER: Erst öffnen, dann Eigentum prüfen
    int fd = open(filename, O_RDONLY);
    if (fd < 0) {
        return -1;
    }

    struct stat st;
    // SICHER: fstat auf geöffnetem Dateideskriptor
    if (fstat(fd, &st) < 0) {
        close(fd);
        return -1;
    }

    if (st.st_uid != getuid()) {
        close(fd);
        return -1;
    }

    // Jetzt sicher fd zu verwenden
    return fd;
}

Ausgenutzt in der Praxis

Double Spending

Race Conditions in Zahlungssystemen.

Privilegieneskalation

TOCTOU-Angriffe auf setuid-Programme.

Session-Hijacking

Race Conditions in Session-Management.


Werkzeuge zum Testen/Ausnutzen

  • Thread-Sanitizer (TSan).

  • Race-Condition-Fuzzer.

  • Statische Analyse für Nebenläufigkeitsfehler.


CVE-Beispiele

  • CVE-2016-2782: Kernel-TOCTOU-Race-Condition.

  • CVE-2019-15666: Race Condition in setuid.


Referenzen

  1. MITRE. "CWE-662: Improper Synchronization." https://cwe.mitre.org/data/definitions/662.html

  2. "The Art of Multiprocessor Programming."