Schleife mit unerreichbarer Abbruchbedingung ('Endlosschleife')

Beschreibung

Schleife mit unerreichbarer Abbruchbedingung, allgemein bekannt als Endlosschleife, ist eine Kontrollfluss-Schwachstelle, bei der Software eine Iteration oder Schleife mit einer Abbruchbedingung enthält, die niemals erfüllt werden kann, wodurch die Schleife unbegrenzt läuft. Dies unterscheidet sich von CWE-834 (Übermäßige Iteration) dadurch, dass die Schleife keinen gültigen Pfad zur Beendigung hat - die Abbruchbedingung ist logisch unmöglich zu erreichen. Häufige Ursachen sind Schleifenvariablen, die nie geändert werden, Abbruchbedingungen, die aufgrund von Eingabewerten nie wahr werden können, und Logikfehler in komplexen Beendigungsbedingungen.

Risiko

Endlosschleifen verursachen Denial of Service durch unbegrenzten CPU-Ressourcenverbrauch. Der betroffene Thread oder Prozess wird vollständig nicht mehr reaktionsfähig und kann keine anderen Aufgaben oder Anfragen bearbeiten. In Single-Thread-Anwendungen friert das gesamte Programm ein. In Servern können Endlosschleifen Thread-Pools erschöpfen, wenn Threads hängen bleiben. Speicher kann auch verbraucht werden, wenn die Schleife Ressourcen allokiert. Im Gegensatz zu übermäßiger Iteration, bei der die Schleife irgendwann terminiert, erfordern Endlosschleifen externen Eingriff (Beenden des Prozesses) zum Stoppen. Die Schwachstelle ist besonders gefährlich, wenn sie durch spezifische Eingabewerte ausgelöst wird, die Angreifer bereitstellen können.

Lösung

Implementieren Sie maximale Iterationslimits als Sicherheitsnetz, auch für Schleifen, die normalerweise terminieren sollten. Validieren Sie Eingabeparameter, die Schleifenbedingungen beeinflussen - lehnen Sie Werte wie Null ab, die Endlosschleifen verursachen könnten. Stellen Sie sicher, dass Schleifenvariablen bei jeder Iteration ordnungsgemäß geändert werden, um Fortschritt zur Abbruchbedingung zu machen. Verwenden Sie Timeout-Mechanismen für Schleifen, die lange laufen könnten. Fügen Sie Watchdog-Mechanismen in kritischen Code-Pfaden hinzu. Implementieren Sie Gesundheitsprüfungen, die feststeckende Threads erkennen. Verifizieren Sie bei Code-Reviews, dass jede Schleife eine erreichbare Abbruchbedingung hat. Verwenden Sie statische Analysetools, die potenzielle Endlosschleifen erkennen können.

Häufige Auswirkungen

AuswirkungDetails
VerfügbarkeitBereich: Verfügbarkeit

DoS: Ressourcenverbrauch - Endlosschleifen verbrauchen unbegrenzt CPU-Zyklen und verhindern, dass der Thread nützliche Arbeit verrichtet.
VerfügbarkeitBereich: Verfügbarkeit

DoS: Langsame Antwort - Andere Operationen werden von CPU-Zeit ausgehungert, was langsame Antwort oder vollständige Nicht-Reaktionsfähigkeit verursacht.
VerfügbarkeitBereich: Verfügbarkeit

DoS: Systemstillstand - In schweren Fällen kann die gesamte Anwendung oder das System nicht mehr reaktionsfähig werden.

Beispielcode

Anfälliger Code

// Anfällig: Server-Verbindungsschleife ohne Versuchslimit
int connected;
struct sockaddr_in servaddr;
int servsock;

// Anfällig: Keine maximalen Versuche, läuft ewig wenn Server nicht antwortet
do {
    connected = connect(servsock, (struct sockaddr *)&servaddr, sizeof(servaddr));
} while (connected < 0);

// Wird unendlich schleifen, wenn Server nie Verbindung akzeptiert
// Anfällig: Inventar-Schleife mit Division-durch-Null-Potenzial
public class VulnerableInventoryCheck {

    public boolean isReorder(int currentCount, int rateSold) {
        boolean isReorder = false;

        // Anfällig: Wenn rateSold 0 ist, nimmt currentCount nie ab
        while (currentCount > 0) {
            if (currentCount < 10) {
                isReorder = true;
            }
            currentCount = currentCount - rateSold;  // 0 - 0 = 0 für immer
        }

        return isReorder;
    }
}
# Anfällig: Schleifenvariable wird nie aktualisiert
def vulnerable_search(items, target):
    i = 0
    found = False

    while not found and i < len(items):
        if items[i] == target:
            found = True
        # Anfällig: Vergessen i zu inkrementieren!
        # Schleife läuft ewig wenn Ziel nicht bei Index 0 ist

    return found
// Anfällig: Off-by-one verursacht Endlosschleife
void vulnerable_array_process(int *arr, int size) {
    int i = 0;

    // Anfällig: Sollte i < size sein, nicht i <= size
    // Wenn i == size, ist Bedingung wahr aber kein Fortschritt wird gemacht
    while (i <= size) {
        process(arr[i]);
        if (arr[i] == SENTINEL) {
            break;
        }
        // Wenn Sentinel nicht bis Index size-1 gefunden, Endlosschleife
        i++;
    }
}
// Anfällig: Fließkomma-Vergleich trifft nie zu
function vulnerableFloatLoop() {
    let value = 0.1;
    let sum = 0;

    // Anfällig: Wegen Fließkomma-Fehlern wird value nie genau 1.0 entsprechen
    while (value !== 1.0) {
        sum += value;
        value += 0.1;  // 0.1 + 0.1 + ... entspricht nicht genau 1.0
    }

    return sum;
}
// Anfällig: Signed/Unsigned-Vergleichsproblem
void vulnerable_countdown(unsigned int count) {
    int i = count;

    // Wenn count sehr groß ist (> INT_MAX), wrapt i zu negativ
    // Anfällig: i-- wird wrappen, erreicht nie 0
    while (i >= 0) {
        process(i);
        i--;  // Wenn i INT_MIN ist, wrapt i-- zu INT_MAX
    }
}
// Anfällig: Zustandsmaschine mit unerreichbarem Ausgang
<?php
function vulnerable_state_machine($input) {
    $state = 'START';

    while ($state !== 'END') {
        switch ($state) {
            case 'START':
                $state = 'PROCESSING';
                break;
            case 'PROCESSING':
                process($input);
                // Anfällig: 'END'-Zustand wird nie gesetzt!
                // Bleibt für immer in PROCESSING
                break;
        }
    }
}
?>

Korrigierter Code

// Korrigiert: Verbindungsschleife mit maximalen Versuchen
#define MAX_ATTEMPTS 10

int connected;
struct sockaddr_in servaddr;
int servsock;
int count = 0;

// Korrigiert: Anzahl der Verbindungsversuche begrenzen
do {
    connected = connect(servsock, (struct sockaddr *)&servaddr, sizeof(servaddr));
    count++;

    if (connected < 0 && count < MAX_ATTEMPTS) {
        sleep(1);  // Zurückweichen vor erneutem Versuch
    }
} while (connected < 0 && count < MAX_ATTEMPTS);

if (connected < 0) {
    handle_connection_failure();
}
// Korrigiert: Parameter validieren um Endlosschleife zu verhindern
public class FixedInventoryCheck {

    private static final int MAX_ITERATIONS = 10000;

    public boolean isReorder(int currentCount, int rateSold) {
        // Korrigiert: rateSold validieren um Endlosschleife zu verhindern
        if (rateSold < 1) {
            // Kann Nachbestellstatus nicht mit ungültiger Rate bestimmen
            return currentCount < 10;  // Einfache Schwellwertprüfung stattdessen
        }

        boolean isReorder = false;
        int iterations = 0;

        // Korrigiert: Auch Iterationslimit als Sicherheitsnetz hinzufügen
        while (currentCount > 0 && iterations < MAX_ITERATIONS) {
            if (currentCount < 10) {
                isReorder = true;
            }
            currentCount = currentCount - rateSold;
            iterations++;
        }

        return isReorder;
    }
}
# Korrigiert: Sicherstellen, dass Schleifenvariable aktualisiert wird
def fixed_search(items, target):
    i = 0
    found = False

    while not found and i < len(items):
        if items[i] == target:
            found = True
        i += 1  # Korrigiert: Immer i inkrementieren

    return found

# Besser: Eingebaute Funktionen verwenden
def better_search(items, target):
    return target in items
// Korrigiert: Korrekte Schleifenbedingung
void fixed_array_process(int *arr, int size) {
    int i = 0;

    // Korrigiert: Korrekte Grenzbedingung
    while (i < size) {  // <= zu < geändert
        process(arr[i]);
        if (arr[i] == SENTINEL) {
            break;
        }
        i++;
    }
}
// Korrigiert: Epsilon-Vergleich für Fließkomma verwenden
function fixedFloatLoop() {
    let value = 0.1;
    let sum = 0;
    const epsilon = 0.0001;
    const maxIterations = 1000;
    let iterations = 0;

    // Korrigiert: Ungefähren Vergleich und Iterationslimit verwenden
    while (Math.abs(value - 1.0) > epsilon && iterations < maxIterations) {
        sum += value;
        value += 0.1;
        iterations++;
    }

    return sum;
}
// Korrigiert: Passende Typen für Vergleich verwenden
void fixed_countdown(unsigned int count) {
    // Korrigiert: Gleichen Typ wie Parameter verwenden
    unsigned int i = count;

    // Korrigiert: Auf Unterlauf-Bedingung prüfen
    while (i > 0) {
        process(i);
        i--;  // Sicher: Stoppt wenn i 0 erreicht
    }
    process(0);  // Null-Fall verarbeiten wenn nötig
}
// Korrigiert: Zustandsmaschine mit gültigen Transitionen zum Ausgang
<?php
function fixed_state_machine($input) {
    $state = 'START';
    $iterations = 0;
    $maxIterations = 1000;

    while ($state !== 'END' && $iterations < $maxIterations) {
        switch ($state) {
            case 'START':
                $state = 'PROCESSING';
                break;
            case 'PROCESSING':
                $result = process($input);
                // Korrigiert: Transition zu END-Zustand
                $state = ($result === true) ? 'END' : 'ERROR';
                break;
            case 'ERROR':
                handle_error();
                $state = 'END';  // Korrigiert: Error-Zustand führt zu END
                break;
        }
        $iterations++;
    }

    if ($iterations >= $maxIterations) {
        throw new Exception('Zustandsmaschinen-Timeout');
    }
}
?>
// Best Practice: Watchdog-Muster für lang laufende Schleifen
#include <signal.h>
#include <setjmp.h>

static sigjmp_buf timeout_jump;
static volatile sig_atomic_t timeout_occurred = 0;

void timeout_handler(int sig) {
    timeout_occurred = 1;
    siglongjmp(timeout_jump, 1);
}

int safe_long_operation(void *data) {
    // Timeout einrichten
    signal(SIGALRM, timeout_handler);
    alarm(30);  // 30 Sekunden Timeout

    if (sigsetjmp(timeout_jump, 1) != 0) {
        // Timeout aufgetreten
        return -1;
    }

    // Operation durchführen, die möglicherweise übermäßig schleift
    while (process_more(data)) {
        // Verarbeitung...
    }

    // Alarm abbrechen
    alarm(0);
    return 0;
}

Verwandte CWEs

  • CWE-834: Übermäßige Iteration (Eltern)
  • CWE-691: Unzureichendes Kontrollfluss-Management (Großeltern)
  • CWE-674: Unkontrollierte Rekursion (Geschwister)
  • CWE-400: Unkontrollierter Ressourcenverbrauch (verwandt)

Referenzen

  1. MITRE Corporation. "CWE-835: Loop with Unreachable Exit Condition ('Infinite Loop')." https://cwe.mitre.org/data/definitions/835.html
  2. CERT C Secure Coding Standard. "MSC21-C. Use robust loop termination conditions."
  3. OWASP. "Denial of Service." https://owasp.org/www-community/attacks/Denial_of_Service