Fehlende Synchronisation

Beschreibung

Fehlende Synchronisation ist eine Nebenläufigkeits-Schwachstelle, bei der Software eine gemeinsam genutzte Ressource nebenläufig verwendet, aber keinen Synchronisationsmechanismus implementiert, um den Zugriff auf diese Ressource zu kontrollieren. Wenn mehrere Threads, Prozesse oder Ausführungskontexte gleichzeitig auf gemeinsame Daten zugreifen, ohne Koordination, kann die Ressource unerwartete oder inkonsistente Zustände erreichen. Dies unterscheidet sich von falscher Synchronisation (CWE-821), wo Synchronisation versucht, aber falsch implementiert wird - hier wird keine Synchronisation überhaupt versucht, was den nebenläufigen Zugriff völlig unkontrolliert lässt.

Risiko

Ohne Synchronisation werden Race Conditions bei nebenläufigem Zugriff auf gemeinsame Ressourcen unvermeidlich. Die Konsequenzen reichen von beschädigten Daten und inkonsistentem Zustand bis zu Sicherheitsschwachstellen, wenn Angreifer das Timing beeinflussen können. Die Datenintegrität wird kompromittiert, wenn mehrere Schreiber dieselbe Ressource gleichzeitig modifizieren und sie potenziell in einem teilweise aktualisierten Zustand hinterlassen. Die Vertraulichkeit kann verletzt werden, wenn Leser während Aktualisierungen auf Daten zugreifen und inkonsistente oder sensible Zwischenwerte sehen. In Sicherheitskontexten können Angreifer die fehlende Synchronisation ausnutzen, um Prüfungen zu umgehen, kritische Variablen zu modifizieren oder gefährliche Ausführungspfade durch sorgfältig getimte Interaktionen mit dem System auszulösen.

Lösung

Identifizieren Sie alle gemeinsam genutzten Ressourcen, auf die nebenläufig zugegriffen werden kann, und implementieren Sie geeignete Synchronisationsmechanismen. Verwenden Sie Mutexe, Semaphore oder kritische Abschnitte zum Schutz gemeinsamer Daten. Verwenden Sie in objektorientierten Sprachen synchronisierte Methoden oder Blöcke. Wenden Sie das Prinzip des minimalen gemeinsamen Zustands an - bevorzugen Sie thread-lokalen Speicher oder unveränderliche Daten, wenn möglich. Verwenden Sie atomare Operationen für einfache Zähler oder Flags. Wenn Sie Bedingungsvariablen verwenden, paaren Sie sie immer mit ordnungsgemäßem Mutex-Schutz. Erwägen Sie die Verwendung von Abstraktionen höherer Ebene wie nebenläufige Sammlungen oder Message Passing. Überprüfen Sie den Code auf alle gemeinsam genutzten Ressourcen und stellen Sie sicher, dass jede dokumentierte Synchronisationsanforderungen hat.

Häufige Auswirkungen

AuswirkungDetails
IntegritätBereich: Integrität

Anwendungsdaten modifizieren - Nebenläufige unsynchronisierte Schreibvorgänge können gemeinsame Daten beschädigen und in inkonsistenten Zuständen hinterlassen.
VertraulichkeitBereich: Vertraulichkeit

Anwendungsdaten lesen - Leser können teilweise Aktualisierungen oder sensible Zwischenwerte während unsynchronisiertem Zugriff beobachten.
SonstigesBereich: Sonstiges

Ausführungslogik ändern - Race Conditions können Angreifern ermöglichen, den Kontrollfluss durch Manipulation des Timings beim Zugriff auf gemeinsame Ressourcen zu beeinflussen.

Beispielcode

Anfälliger Code

// Anfällig: Keine Synchronisation auf gemeinsamen stdout
#include <stdio.h>
#include <unistd.h>

int main(void) {
    pid_t pid = fork();

    if (pid == 0) {
        // Kindprozess
        // Anfällig: Unsynchronisierte Schreibvorgänge auf gemeinsamen stdout
        printf("k");
        printf("i");
        printf("n");
        printf("d");
        printf("\n");
    } else {
        // Elternprozess
        // Anfällig: Unsynchronisierte Schreibvorgänge auf gemeinsamen stdout
        printf("E");
        printf("L");
        printf("T");
        printf("E");
        printf("R");
        printf("N");
        printf("\n");
    }
    return 0;
}

// Ausgabe kann verschachtelt sein: "EkLinTdE\nRN" statt ordentlicher Zeilen
// Anfällig: Gemeinsamer Zähler ohne Synchronisation
public class VulnerableCounter {
    private int count = 0;  // Gemeinsamer Zustand

    // Anfällig: Keine Synchronisation
    public void increment() {
        count++;  // Nicht atomar: Read-Modify-Write Race Condition
    }

    public int getCount() {
        return count;
    }
}

// Mit mehreren Threads kann der Endzähler niedriger als erwartet sein
// Thread 1: liest count=5
// Thread 2: liest count=5
// Thread 1: schreibt count=6
// Thread 2: schreibt count=6  (sollte 7 sein!)
# Anfällig: Gemeinsame Liste ohne Synchronisation
import threading

shared_list = []

def vulnerable_append(item):
    # Anfällig: Kein Lock-Schutz
    if item not in shared_list:  # Prüfung
        shared_list.append(item)  # Dann handeln - Race Condition!

# Zwei Threads können beide sehen, dass item nicht in der Liste ist, und beide fügen es hinzu

Korrigierter Code

// Korrigiert: Mutex zur Synchronisation der Ausgabe verwenden
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/wait.h>

pthread_mutex_t stdout_mutex = PTHREAD_MUTEX_INITIALIZER;

void safe_print(const char* message) {
    pthread_mutex_lock(&stdout_mutex);
    printf("%s\n", message);
    fflush(stdout);
    pthread_mutex_unlock(&stdout_mutex);
}
// Korrigiert: Synchronisierter Zähler
public class FixedCounter {
    private int count = 0;

    // Korrigiert: Synchronisierte Methode
    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}

// Oder AtomicInteger für bessere Leistung verwenden
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicCounter {
    private final AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        count.incrementAndGet();  // Atomare Operation
    }

    public int getCount() {
        return count.get();
    }
}
# Korrigiert: Lock für gemeinsamen Listenzugriff verwenden
import threading

shared_list = []
list_lock = threading.Lock()

def fixed_append(item):
    # Korrigiert: Lock schützt Check-then-Act
    with list_lock:
        if item not in shared_list:
            shared_list.append(item)

# Oder thread-sichere Sammlungen verwenden
from queue import Queue

thread_safe_queue = Queue()

def fixed_queue_append(item):
    thread_safe_queue.put(item)  # Thread-sicher durch Design

Verwandte CWEs

  • CWE-662: Unzureichende Synchronisation (Eltern)
  • CWE-821: Falsche Synchronisation (Geschwister)
  • CWE-362: Nebenläufige Ausführung mit gemeinsamer Ressource ohne ordnungsgemäße Synchronisation (verwandt)
  • CWE-543: Verwendung des Singleton-Musters ohne Synchronisation in Multithread-Kontext (verwandt)
  • CWE-567: Unsynchronisierter Zugriff auf gemeinsame Daten in Multithread-Kontext (verwandt)

Referenzen

  1. MITRE Corporation. "CWE-820: Missing Synchronization." https://cwe.mitre.org/data/definitions/820.html
  2. CERT Oracle Secure Coding Standard. "LCK05-J. Synchronize access to static fields that can be modified by untrusted code."
  3. CERT C Secure Coding Standard. "CON32-C. Prevent data races when accessing bit-fields from multiple threads."