Unsachgemäßes Sperren

Beschreibung

Unsachgemäßes Sperren tritt auf, wenn Software Synchronisierungsprimitive falsch verwendet, um den Zugriff auf gemeinsam genutzte Ressourcen zu kontrollieren. Dies umfasst das Versäumnis, Sperren vor dem Zugriff auf gemeinsam genutzte Daten zu erwerben, Sperren nach der Verwendung nicht freizugeben, falschen Sperrbereich zu verwenden, Sperren zu lange zu halten, Sperrreihenfolgeverletzungen die Deadlocks verursachen, und Versuche, bereits gehaltene nicht-reentrantfähige Sperren zu sperren. Unsachgemäßes Sperren führt zu Race Conditions, Deadlocks und Datenkorruption.

Risiko

Race Conditions ermöglichen Datenkorruption oder Sicherheitsumgehung. Deadlocks verursachen Denial of Service. Doppeltes Sperren bei nicht-reentrantfähigen Sperren verursacht Einfrieren. Fehlende Entsperrungen führen zu dauerhafter Ressourcenunverfügbarkeit. Sperrkonkurrenz verschlechtert die Leistung. Prioritätsinversion verursacht Systeminstabilität.

Lösung

Paaren Sie Sperrerwerbe immer mit Freigaben. Verwenden Sie RAII-Muster für automatische Entsperrung. Implementieren Sie konsistente Sperrreihenfolge, um Deadlocks zu verhindern. Verwenden Sie angemessene Sperrgranularität. Bevorzugen Sie höherstufige Nebenläufigkeitskonstrukte. Verwenden Sie Try-Lock mit Timeouts wo angemessen. Testen Sie mit Thread-Sanitizern.

Häufige Konsequenzen

AuswirkungDetails
IntegritätBereich: Datenkorruption

Unsynchronisierter Zugriff beschädigt gemeinsamen Zustand.
VerfügbarkeitBereich: Deadlock/DoS

Sperrfehler verursachen Systemhänger.
SicherheitBereich: Race Conditions

Timing-Schwachstellen durch unsachgemäße Synchronisierung.

Beispielcode + Lösungscode

Verwundbarer Code

// VERWUNDBAR: Unsachgemäßes Sperren in C
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int shared_counter = 0;

// VERWUNDBAR: Fehlende Sperre
void increment_vulnerable() {
    // VERWUNDBAR: Keine Sperre - Race Condition
    shared_counter++;
}

// VERWUNDBAR: Sperre nie auf Fehlerpfad freigegeben
int process_data_vulnerable(int* data) {
    pthread_mutex_lock(&lock);

    if (data == NULL) {
        // VERWUNDBAR: Sperre nicht freigegeben bei Fehler
        return -1;
    }

    *data = shared_counter;
    pthread_mutex_unlock(&lock);
    return 0;
}

// VERWUNDBAR: Doppelsperre (nicht-reentrantfähig)
void outer_function_vulnerable() {
    pthread_mutex_lock(&lock);
    inner_function_vulnerable();  // Versucht auch zu sperren
    pthread_mutex_unlock(&lock);
}

void inner_function_vulnerable() {
    // VERWUNDBAR: Deadlock - Sperre bereits gehalten
    pthread_mutex_lock(&lock);
    do_work();
    pthread_mutex_unlock(&lock);
}

// VERWUNDBAR: Sperrreihenfolgeverletzung verursacht Deadlock
pthread_mutex_t lock_a = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t lock_b = PTHREAD_MUTEX_INITIALIZER;

void* thread1_vulnerable(void* arg) {
    pthread_mutex_lock(&lock_a);
    sleep(1);  // Erhöht Chance für Deadlock
    pthread_mutex_lock(&lock_b);  // Wartet auf lock_b

    do_work();

    pthread_mutex_unlock(&lock_b);
    pthread_mutex_unlock(&lock_a);
    return NULL;
}

void* thread2_vulnerable(void* arg) {
    pthread_mutex_lock(&lock_b);  // Bekommt lock_b
    sleep(1);
    pthread_mutex_lock(&lock_a);  // DEADLOCK: Wartet auf lock_a

    do_work();

    pthread_mutex_unlock(&lock_a);
    pthread_mutex_unlock(&lock_b);
    return NULL;
}

// VERWUNDBAR: Sperre während blockierender Operation gehalten
void blocking_with_lock_vulnerable() {
    pthread_mutex_lock(&lock);
    read(socket_fd, buffer, size);  // Blockiert während Sperre gehalten!
    pthread_mutex_unlock(&lock);
}
# VERWUNDBAR: Python unsachgemäßes Sperren
import threading

counter = 0
lock = threading.Lock()

# VERWUNDBAR: Keine Sperrung
def increment_vulnerable():
    global counter
    # VERWUNDBAR: Race Condition
    counter += 1

# VERWUNDBAR: Sperre nicht freigegeben bei Exception
def process_vulnerable(data):
    lock.acquire()

    if not data:
        # VERWUNDBAR: Sperre nicht freigegeben
        raise ValueError("Keine Daten")

    result = process(data)
    lock.release()
    return result

# VERWUNDBAR: Doppelter Erwerb (nicht-reentrantfähig)
def outer_vulnerable():
    lock.acquire()
    inner_vulnerable()  # VERWUNDBAR: Deadlock
    lock.release()

def inner_vulnerable():
    lock.acquire()  # Deadlock - dieselbe Sperre bereits gehalten
    do_work()
    lock.release()

# VERWUNDBAR: Sperrreihenfolge
lock_a = threading.Lock()
lock_b = threading.Lock()

def thread1_vulnerable():
    with lock_a:
        time.sleep(0.1)
        with lock_b:  # Möglicher Deadlock
            work()

def thread2_vulnerable():
    with lock_b:  # Andere Reihenfolge!
        time.sleep(0.1)
        with lock_a:  # DEADLOCK
            work()

# VERWUNDBAR: Falscher Sperrbereich
class VulnerableCounter:
    def __init__(self):
        self.value = 0
        self.lock = threading.Lock()

    def increment_and_get(self):
        with self.lock:
            self.value += 1

        # VERWUNDBAR: Lesen außerhalb der Sperre
        return self.value  # Race Condition
// VERWUNDBAR: Java unsachgemäßes Sperren
public class VulnerableLocking {

    private int counter = 0;
    private final Object lock = new Object();

    // VERWUNDBAR: Keine Synchronisierung
    public void incrementVulnerable() {
        counter++;  // Race Condition
    }

    // VERWUNDBAR: Sperre nicht freigegeben bei Exception
    public void processVulnerable(Object data) {
        synchronized(lock) {
            if (data == null) {
                throw new IllegalArgumentException();
                // Sperre durch synchronized-Block freigegeben, ABER...
            }
            process(data);
        }
    }

    // VERWUNDBAR: Doppelsperre (explizite Sperren)
    private final ReentrantLock reentrantLock = new ReentrantLock();

    // Hinweis: ReentrantLock handhabt das tatsächlich, aber konzeptionell:
    // Nicht-reentrantfähige Sperre würde hier deadlocken

    // VERWUNDBAR: Sperrreihenfolgeverletzung
    private final Object lockA = new Object();
    private final Object lockB = new Object();

    public void method1() {
        synchronized(lockA) {
            try { Thread.sleep(100); } catch (Exception e) {}
            synchronized(lockB) {  // Wartet auf lockB
                work();
            }
        }
    }

    public void method2() {
        synchronized(lockB) {  // Andere Reihenfolge
            try { Thread.sleep(100); } catch (Exception e) {}
            synchronized(lockA) {  // DEADLOCK
                work();
            }
        }
    }

    // VERWUNDBAR: Grobkörniges Sperren
    private List<User> users = new ArrayList<>();
    private List<Order> orders = new ArrayList<>();

    public synchronized void updateUser(User u) {
        // Hält Sperre sogar für unabhängige Daten
        users.add(u);
    }

    public synchronized void updateOrder(Order o) {
        // Dieselbe Sperre, aber andere Daten
        orders.add(o);  // Unnötige Konkurrenz
    }
}
// VERWUNDBAR: JavaScript unsachgemäßes Sperren (async Kontext)
class VulnerableLocking {
    constructor() {
        this.data = {};
        this.processing = false;
    }

    // VERWUNDBAR: Kein ordnungsgemäßes Sperren für async Operationen
    async updateData(key, value) {
        // VERWUNDBAR: Check-then-act Race Condition
        if (!this.processing) {
            this.processing = true;

            // Async Lücke - ein anderer Aufruf könnte hier eintreten
            await someAsyncOperation();

            this.data[key] = value;
            this.processing = false;
        }
    }

    // VERWUNDBAR: Sperre nicht freigegeben bei Fehler
    async processWithLock() {
        this.locked = true;

        const result = await fetchData();  // Könnte werfen
        // VERWUNDBAR: Wenn fetchData wirft, bleibt locked true

        this.locked = false;
        return result;
    }

    // VERWUNDBAR: Kein Warten auf Sperre
    async accessShared() {
        if (this.locked) {
            return null;  // Scheitert einfach statt zu warten
        }

        this.locked = true;
        // ... Arbeit ...
        this.locked = false;
    }
}

Lösungscode

// SICHER: Ordnungsgemäßes Sperren in C
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int shared_counter = 0;

// SICHER: Ordnungsgemäßes Sperren
void increment_safe() {
    pthread_mutex_lock(&lock);
    shared_counter++;
    pthread_mutex_unlock(&lock);
}

// SICHER: Sperre auf allen Pfaden freigegeben
int process_data_safe(int* data) {
    int result = 0;

    pthread_mutex_lock(&lock);

    if (data == NULL) {
        result = -1;
        goto cleanup;
    }

    *data = shared_counter;

cleanup:
    pthread_mutex_unlock(&lock);
    return result;
}

// SICHER: Rekursiven Mutex für Reentrantfähigkeit verwenden
pthread_mutex_t recursive_lock;

void init_recursive_lock() {
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&recursive_lock, &attr);
    pthread_mutexattr_destroy(&attr);
}

void outer_function_safe() {
    pthread_mutex_lock(&recursive_lock);
    inner_function_safe();  // Sicher - rekursiver Mutex
    pthread_mutex_unlock(&recursive_lock);
}

void inner_function_safe() {
    pthread_mutex_lock(&recursive_lock);  // OK mit rekursivem
    do_work();
    pthread_mutex_unlock(&recursive_lock);
}

// SICHER: Konsistente Sperrreihenfolge
pthread_mutex_t lock_a = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t lock_b = PTHREAD_MUTEX_INITIALIZER;

// Immer in Reihenfolge erwerben: lock_a, dann lock_b
void* thread1_safe(void* arg) {
    pthread_mutex_lock(&lock_a);  // Erste
    pthread_mutex_lock(&lock_b);  // Zweite

    do_work();

    pthread_mutex_unlock(&lock_b);
    pthread_mutex_unlock(&lock_a);
    return NULL;
}

void* thread2_safe(void* arg) {
    pthread_mutex_lock(&lock_a);  // Dieselbe Reihenfolge!
    pthread_mutex_lock(&lock_b);

    do_work();

    pthread_mutex_unlock(&lock_b);
    pthread_mutex_unlock(&lock_a);
    return NULL;
}

// SICHER: Minimaler Sperrbereich
void process_with_minimal_lock() {
    char buffer[1024];

    // Lesen ohne Sperre (wenn Socket thread-lokal ist)
    read(socket_fd, buffer, sizeof(buffer));

    // Nur für gemeinsamen Zustandszugriff sperren
    pthread_mutex_lock(&lock);
    update_shared_state(buffer);
    pthread_mutex_unlock(&lock);
}
# SICHER: Python ordnungsgemäßes Sperren
import threading
from contextlib import contextmanager

counter = 0
lock = threading.Lock()

# SICHER: with-Anweisung verwenden
def increment_safe():
    global counter
    with lock:
        counter += 1

# SICHER: Sperre bei Exception freigegeben (Context-Manager)
def process_safe(data):
    with lock:
        if not data:
            raise ValueError("Keine Daten")  # Sperre automatisch freigegeben
        return process(data)

# SICHER: RLock für reentrantfähiges Sperren verwenden
rlock = threading.RLock()

def outer_safe():
    with rlock:
        inner_safe()  # Sicher mit RLock

def inner_safe():
    with rlock:  # RLock erlaubt reentrantfähigen Erwerb
        do_work()

# SICHER: Konsistente Sperrreihenfolge
lock_a = threading.Lock()
lock_b = threading.Lock()

def acquire_both():
    """Immer in alphabetischer Reihenfolge erwerben"""
    with lock_a:
        with lock_b:
            return work()

def thread1_safe():
    acquire_both()

def thread2_safe():
    acquire_both()  # Dieselbe Reihenfolge

# SICHER: Sperrreihenfolge mit Context-Manager
@contextmanager
def ordered_locks(*locks):
    """Mehrere Sperren in konsistenter Reihenfolge erwerben"""
    sorted_locks = sorted(locks, key=id)
    try:
        for lock in sorted_locks:
            lock.acquire()
        yield
    finally:
        for lock in reversed(sorted_locks):
            lock.release()

def safe_multi_lock():
    with ordered_locks(lock_a, lock_b):
        # Reihenfolge durch id bestimmt, immer konsistent
        work()

# SICHER: Korrekter Sperrbereich
class SafeCounter:
    def __init__(self):
        self.value = 0
        self.lock = threading.Lock()

    def increment_and_get(self):
        with self.lock:
            self.value += 1
            return self.value  # Lesen innerhalb der Sperre
// SICHER: Java ordnungsgemäßes Sperren
public class SafeLocking {

    private int counter = 0;
    private final Object lock = new Object();

    // SICHER: Synchronisierte Methode
    public synchronized void incrementSafe() {
        counter++;
    }

    // SICHER: Sperre mit try-finally
    private final ReentrantLock reentrantLock = new ReentrantLock();

    public void processWithLock(Object data) {
        reentrantLock.lock();
        try {
            if (data == null) {
                throw new IllegalArgumentException();
            }
            process(data);
        } finally {
            // SICHER: Immer freigegeben
            reentrantLock.unlock();
        }
    }

    // SICHER: Konsistente Sperrreihenfolge
    private final Object lockA = new Object();
    private final Object lockB = new Object();

    private void acquireBothLocks() {
        // Immer dieselbe Reihenfolge basierend auf Identity-Hash
        Object first = System.identityHashCode(lockA) < System.identityHashCode(lockB)
            ? lockA : lockB;
        Object second = first == lockA ? lockB : lockA;

        synchronized(first) {
            synchronized(second) {
                work();
            }
        }
    }

    // SICHER: Feinkörniges Sperren
    private final List<User> users = new ArrayList<>();
    private final List<Order> orders = new ArrayList<>();
    private final Object usersLock = new Object();
    private final Object ordersLock = new Object();

    public void updateUser(User u) {
        synchronized(usersLock) {
            users.add(u);
        }
    }

    public void updateOrder(Order o) {
        synchronized(ordersLock) {
            // Andere Sperre - keine Konkurrenz
            orders.add(o);
        }
    }

    // SICHER: Nebenläufige Collections verwenden
    private final ConcurrentHashMap<String, User> userMap = new ConcurrentHashMap<>();

    public void addUser(String id, User user) {
        // Thread-sicher ohne explizites Sperren
        userMap.put(id, user);
    }

    // SICHER: Read-Write-Lock für leseintensive Workloads
    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
    private List<String> cache = new ArrayList<>();

    public List<String> readCache() {
        rwLock.readLock().lock();
        try {
            return new ArrayList<>(cache);
        } finally {
            rwLock.readLock().unlock();
        }
    }

    public void updateCache(List<String> newData) {
        rwLock.writeLock().lock();
        try {
            cache = new ArrayList<>(newData);
        } finally {
            rwLock.writeLock().unlock();
        }
    }
}
// SICHER: JavaScript ordnungsgemäßes async Sperren
const { Mutex, withTimeout } = require('async-mutex');

class SafeLocking {
    constructor() {
        this.data = {};
        this.mutex = new Mutex();
    }

    // SICHER: Mutex für async Operationen verwenden
    async updateData(key, value) {
        const release = await this.mutex.acquire();
        try {
            await someAsyncOperation();
            this.data[key] = value;
        } finally {
            // SICHER: Immer freigegeben
            release();
        }
    }

    // SICHER: runExclusive-Helfer verwenden
    async processWithLock() {
        return await this.mutex.runExclusive(async () => {
            const result = await fetchData();
            // Sperre automatisch freigegeben auch bei Fehler
            return result;
        });
    }

    // SICHER: Timeout um Deadlock zu verhindern
    async accessWithTimeout() {
        try {
            return await withTimeout(this.mutex, 5000).runExclusive(async () => {
                return await slowOperation();
            });
        } catch (e) {
            if (e.message === 'timeout') {
                console.log('Sperrerwerb Timeout');
                return null;
            }
            throw e;
        }
    }
}

// SICHER: Semaphore für begrenzte Nebenläufigkeit
const { Semaphore } = require('async-mutex');

class ConnectionPool {
    constructor(maxConnections) {
        this.semaphore = new Semaphore(maxConnections);
        this.connections = [];
    }

    async withConnection(fn) {
        const [value, release] = await this.semaphore.acquire();
        try {
            const conn = await this.getConnection();
            return await fn(conn);
        } finally {
            release();
        }
    }
}

Ausgenutzt in der Praxis

Deadlock-Angriffe

Auslösen von Sperrreihenfolgeverletzungen um DoS zu verursachen.

Race-Condition-Exploits

Ausnutzung fehlender Sperren für Privilegieneskalation.

Prioritätsinversion

Echtzeit-Systemausfälle durch unsachgemäßes Sperren.


Werkzeuge zum Testen/Ausnutzen

  • Thread-Sanitizer (TSan, Helgrind).

  • Deadlock-Detektoren.

  • Sperranalysewerkzeuge.


CVE-Beispiele

  • CVE-2016-9576: Kernel-Sperrreihenfolge-Deadlock.

  • CVE-2019-12379: Race Condition durch fehlende Sperre.


Referenzen

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

  2. "The Art of Multiprocessor Programming" - Sperrreihenfolge.