Schleifenbedingungswert-Aktualisierung innerhalb der Schleife

Beschreibung

Schleifenbedingungswert-Aktualisierung innerhalb der Schleife tritt auf, wenn ein Produkt eine Schleife mit einer Kontrollfluss-Bedingung verwendet, die auf einem Wert basiert, der innerhalb des Schleifenkörpers aktualisiert wird. Dies erzeugt Code, der schwerer zu verstehen und zu analysieren ist, weil die Schleifenabbruchbedingung nicht sofort aus dem Schleifenkopf ersichtlich ist. Die dynamische Modifikation von Schleifenkontrollvariablen innerhalb des Schleifenkörpers erschwert es, zu verstehen, wann und wie die Schleife beendet wird.

Risiko

Das Modifizieren von Schleifenkontrollvariablen innerhalb von Schleifenkörpern hat indirekte Sicherheitsimplikationen. Der Code wird schwerer zu analysieren, was möglicherweise Fehler einschließlich Sicherheitsschwachstellen verbirgt. Schleifenverhalten wird unvorhersehbar, was die Verifizierung korrekter Beendigung erschwert. Endlosschleifen werden wahrscheinlicher, wenn Aktualisierungen falsch oder bedingt sind. Code-Reviewer können Sicherheitsprobleme aufgrund der Komplexität übersehen. Statische Analysetools können Schwierigkeiten haben, solche Schleifen zu analysieren. Off-by-one-Fehler sind bei komplexer Schleifensteuerung wahrscheinlicher. Das unvorhersagbare Schleifenverhalten kann für Denial of Service ausgenutzt werden.

Lösung

Definieren Sie Schleifengrenzen klar im Schleifenkopf, wo möglich. Verwenden Sie Standard-Schleifenmuster (for-each, Iterator), die keine manuelle Verwaltung von Kontrollvariablen erfordern. Vermeiden Sie das Modifizieren von Schleifenkontrollvariablen innerhalb des Schleifenkörpers. Wenn Modifikation notwendig ist, dokumentieren Sie klar warum und stellen Sie korrektes Verhalten sicher. Verwenden Sie break-Anweisungen für vorzeitigen Abbruch anstatt Kontrollvariablen zu modifizieren. Erwägen Sie Umstrukturierung als while-Schleife mit klaren Abbruchbedingungen. Verwenden Sie statische Analysetools, um problematische Schleifenmuster zu identifizieren. Wenden Sie Code-Review-Richtlinien an, die solche Muster kennzeichnen.

Häufige Auswirkungen

AuswirkungDetails
AndereBereich: Ändere

Reduzierte Wartbarkeit - Schleifenverhalten ist schwerer zu verstehen und sicher zu ändern.
AndereBereich: Ändere

Erhöhte analytische Komplexität - Sicherheitsanalyse wird durch dynamische Schleifensteuerung kompliziert.
VerfügbarkeitBereich: Verfügbarkeit

DoS: Endlosschleife - Falsche Aktualisierungen können Endlosschleifen verursachen.

Beispielcode

Anfälliger Code

// Anfällig: Schleifenbedingungsvariable wird im Schleifenkörper modifiziert
public class VulnerableLoopProcessor {

    public void processItems(List<String> items) {
        int i = 0;
        // Anfällig: 'i' wird im Schleifenkörper modifiziert
        while (i < items.size()) {
            String item = items.get(i);

            if (item.isEmpty()) {
                // Leere Elemente überspringen - aber dies ändert die Schleifensteuerung!
                i += 2;  // Anfällig: Schleifenvariable modifiziert
                continue;
            }

            if (item.startsWith("SKIP_")) {
                // Ein weiterer Modifikationspfad
                i = findNextNonSkip(items, i);  // Anfällig: komplexe Aktualisierung
                continue;
            }

            processItem(item);
            i++;  // Normales Inkrement im Körper versteckt
        }
        // Schwer zu wissen: Wird diese Schleife beendet? Wie viele Iterationen?
    }

    public int countValidItems(List<Item> items) {
        int count = 0;
        int index = 0;

        // Anfällig: Mehrfache Modifikationen der Schleifensteuerung
        while (index < items.size()) {
            Item item = items.get(index);

            if (item.isDeleted()) {
                index++;
                continue;
            }

            if (item.isBatch()) {
                // Alle Elemente im Batch verarbeiten
                count += item.getBatchSize();
                index += item.getBatchSize();  // Vorwärts springen
            } else {
                count++;
                index++;
            }

            // Bug: Wenn getBatchSize() 0 zurückgibt, Endlosschleife!
        }
        return count;
    }
}
# Anfällig: Schleifensteuerung an mehreren Stellen modifiziert
class VulnerableDataParser:

    def parse_records(self, data):
        i = 0
        records = []

        # Anfällig: 'i' wird im gesamten Schleifenkörper modifiziert
        while i < len(data):
            if data[i] == '\n':
                i += 1  # Zeilenumbrüche überspringen
                continue

            if data[i] == '#':
                # Kommentare überspringen - Zeilenende finden
                while i < len(data) and data[i] != '\n':
                    i += 1  # Verschachtelte Modifikation!
                continue

            if data[i] == '"':
                # Zitierter String - komplexe Vorwärtsbewegung
                i += 1
                start = i
                while i < len(data) and data[i] != '"':
                    if data[i] == '\\':
                        i += 2  # Escape-Zeichen überspringen
                    else:
                        i += 1
                records.append(data[start:i])
                i += 1  # Schließendes Anführungszeichen überspringen
            else:
                # Normales Token
                start = i
                while i < len(data) and data[i] not in ' \t\n':
                    i += 1
                records.append(data[start:i])

        # Extrem schwer zu verifizieren:
        # - Wird dies für alle Eingaben beendet?
        # - Gibt es Off-by-one-Fehler?
        # - Was wenn Escape-Sequenz am Ende der Daten?
        return records

    def find_patterns(self, text, patterns):
        position = 0
        matches = []

        # Anfällig: Position wird auf komplexe Weise aktualisiert
        while position < len(text):
            matched = False

            for pattern in patterns:
                if text[position:].startswith(pattern):
                    matches.append((position, pattern))
                    position += len(pattern)  # Über Treffer hinaus vorrücken
                    matched = True
                    break

            if not matched:
                position += 1

            # Bug-Potenzial: Wenn Pattern leerer String, Endlosschleife!
        return matches
// Anfällig: C-Schleife mit modifizierter Kontrollvariable
void vulnerable_process_buffer(char* buffer, size_t size) {
    size_t i = 0;

    // Anfällig: 'i' wird im Schleifenkörper modifiziert
    while (i < size) {
        if (buffer[i] == '\0') {
            i++;  // Null-Bytes überspringen
            continue;
        }

        if (buffer[i] == ESCAPE_CHAR) {
            // Escape-Sequenz verarbeiten
            i++;  // Über Escape-Zeichen hinaus
            if (i >= size) break;  // Grenzen prüfen

            switch (buffer[i]) {
                case 'n':
                    handle_newline();
                    i++;  // Modifikation hier
                    break;
                case 'x':
                    // Hex-Escape - 2 weitere Zeichen lesen
                    i++;  // Modifikation hier
                    if (i + 2 <= size) {
                        handle_hex(buffer[i], buffer[i+1]);
                        i += 2;  // Und hier
                    }
                    break;
                default:
                    i++;  // Und hier auch
            }
        } else {
            handle_char(buffer[i]);
            i++;  // Normaler Fall
        }
    }
    // Sicherheitsbedenken: Schwer zu verifizieren, ob Grenzen in allen Pfaden respektiert werden
}

Korrigierter Code

// Korrigiert: Klare Schleifenmuster mit vorhersagbarem Kontrollfluss
public class FixedLoopProcessor {

    public void processItems(List<String> items) {
        // Korrigiert: for-each verwenden, wenn Modifikation nicht nötig
        for (String item : items) {
            if (item.isEmpty()) {
                continue;  // Nur überspringen, keine Indizes modifizieren
            }

            if (item.startsWith("SKIP_")) {
                continue;
            }

            processItem(item);
        }
    }

    // Korrigiert: Wenn Index-Manipulation nötig, klare Struktur verwenden
    public void processWithSkips(List<String> items) {
        // Korrigiert: Expliziten Iterator mit klarer Steuerung verwenden
        ListIterator<String> iterator = items.listIterator();

        while (iterator.hasNext()) {
            String item = iterator.next();

            if (item.isEmpty()) {
                continue;  // Iterator handhabt Vorrücken
            }

            if (item.startsWith("BATCH_")) {
                int batchSize = getBatchSize(item);
                processBatchItem(item);

                // Korrigiert: Klare, begrenzte Überspringoperation
                skipItems(iterator, batchSize - 1);  // -1 weil wir schon eins verbraucht haben
                continue;
            }

            processItem(item);
        }
    }

    private void skipItems(ListIterator<?> iterator, int count) {
        for (int i = 0; i < count && iterator.hasNext(); i++) {
            iterator.next();
        }
    }

    public int countValidItems(List<Item> items) {
        // Korrigiert: Stream mit klarer Logik verwenden
        return (int) items.stream()
            .filter(item -> !item.isDeleted())
            .mapToInt(item -> item.isBatch() ? item.getBatchSize() : 1)
            .filter(size -> size > 0)  // Schutz gegen Batches mit Größe Null
            .sum();
    }
}
# Korrigiert: Klare Schleifenmuster mit vorhersagbarer Steuerung
class FixedDataParser:

    def parse_records(self, data: str) -> list:
        # Korrigiert: Generator/Iterator-Pattern verwenden
        return list(self._tokenize(data))

    def _tokenize(self, data: str):
        """Generator, der Tokens mit klarem Zustandsmanagement liefert."""
        pos = 0
        length = len(data)

        while pos < length:
            char = data[pos]

            if char in ' \t\n':
                pos += 1
                continue

            if char == '#':
                pos = self._skip_to_newline(data, pos, length)
                continue

            if char == '"':
                token, pos = self._parse_quoted_string(data, pos, length)
                yield token
            else:
                token, pos = self._parse_token(data, pos, length)
                yield token

    def _skip_to_newline(self, data: str, pos: int, length: int) -> int:
        """Position nach Zeilenumbruch zurückgeben. Klare, einzweckige Funktion."""
        while pos < length and data[pos] != '\n':
            pos += 1
        return pos + 1 if pos < length else pos

    def _parse_quoted_string(self, data: str, pos: int, length: int) -> tuple:
        """Zitierten String parsen. Gibt (token, neue_position) zurück."""
        assert data[pos] == '"'
        pos += 1  # Öffnendes Anführungszeichen überspringen
        chars = []

        while pos < length and data[pos] != '"':
            if data[pos] == '\\' and pos + 1 < length:
                chars.append(self._unescape(data[pos + 1]))
                pos += 2
            else:
                chars.append(data[pos])
                pos += 1

        end_pos = pos + 1 if pos < length else pos  # Schließendes Anführungszeichen überspringen
        return ''.join(chars), end_pos

    def _parse_token(self, data: str, pos: int, length: int) -> tuple:
        """Unzitierten Token parsen. Gibt (token, neue_position) zurück."""
        start = pos
        while pos < length and data[pos] not in ' \t\n':
            pos += 1
        return data[start:pos], pos

    def find_patterns(self, text: str, patterns: list) -> list:
        # Korrigiert: Schutz gegen leere Patterns und klare Logik
        valid_patterns = [p for p in patterns if p]  # Leere entfernen

        if not valid_patterns:
            return []

        matches = []
        position = 0

        while position < len(text):
            # Korrigiert: Hilfsmethode mit klarer Semantik verwenden
            match = self._find_pattern_at(text, position, valid_patterns)

            if match:
                pattern, length = match
                matches.append((position, pattern))
                position += length
            else:
                position += 1

        return matches

    def _find_pattern_at(self, text: str, pos: int, patterns: list):
        """Pattern an Position finden. Gibt (pattern, lange) oder None zurück."""
        for pattern in patterns:
            if text[pos:pos + len(pattern)] == pattern:
                return pattern, len(pattern)
        return None
// Korrigiert: Klare Schleifenstruktur mit explizitem Zustandsautomaten
typedef enum {
    STATE_NORMAL,
    STATE_ESCAPE,
    STATE_HEX_FIRST,
    STATE_HEX_SECOND
} ParseState;

void fixed_process_buffer(char* buffer, size_t size) {
    ParseState state = STATE_NORMAL;
    char hex_first = 0;

    // Korrigiert: Einfache for-Schleife mit klarem Inkrement
    for (size_t i = 0; i < size; i++) {
        char c = buffer[i];

        switch (state) {
            case STATE_NORMAL:
                if (c == '\0') {
                    continue;
                } else if (c == ESCAPE_CHAR) {
                    state = STATE_ESCAPE;
                } else {
                    handle_char(c);
                }
                break;

            case STATE_ESCAPE:
                if (c == 'n') {
                    handle_newline();
                    state = STATE_NORMAL;
                } else if (c == 'x') {
                    state = STATE_HEX_FIRST;
                } else {
                    // Unbekanntes Escape - als Literal behandeln
                    handle_char(c);
                    state = STATE_NORMAL;
                }
                break;

            case STATE_HEX_FIRST:
                hex_first = c;
                state = STATE_HEX_SECOND;
                break;

            case STATE_HEX_SECOND:
                handle_hex(hex_first, c);
                state = STATE_NORMAL;
                break;
        }
    }

    // Korrigiert: Unvollständige Escape-Sequenzen am Ende behandeln
    if (state != STATE_NORMAL) {
        handle_incomplete_sequence(state);
    }
}

// Korrigiert: Alternative mit explizitem Positionsmanagement
typedef struct {
    const char* data;
    size_t size;
    size_t pos;
} BufferReader;

char reader_peek(BufferReader* r) {
    return r->pos < r->size ? r->data[r->pos] : '\0';
}

char reader_advance(BufferReader* r) {
    return r->pos < r->size ? r->data[r->pos++] : '\0';
}

bool reader_has_more(BufferReader* r) {
    return r->pos < r->size;
}

void fixed_process_with_reader(char* buffer, size_t size) {
    BufferReader reader = { buffer, size, 0 };

    // Korrigiert: Klare Operationen mit Reader-Abstraktion
    while (reader_has_more(&reader)) {
        char c = reader_advance(&reader);

        if (c == '\0') {
            continue;
        }

        if (c == ESCAPE_CHAR) {
            process_escape_sequence(&reader);
        } else {
            handle_char(c);
        }
    }
}

void process_escape_sequence(BufferReader* r) {
    if (!reader_has_more(r)) {
        handle_incomplete_sequence(STATE_ESCAPE);
        return;
    }

    char c = reader_advance(r);
    if (c == 'n') {
        handle_newline();
    } else if (c == 'x') {
        process_hex_escape(r);
    } else {
        handle_char(c);
    }
}

CVE-Beispiele

Diese CWE ist für direkte CVE-Zuordnung als VERBOTEN markiert, da sie ein Codequalitäts-/Wartbarkeitsproblem und keine direkte Sicherheitsschwachstelle darstellt.


Verwandte CWEs

  • CWE-1120: Excessive Code Complexity (Eltern)
  • CWE-1226: Complexity Issues (Kategoriemitglied)
  • CWE-835: Loop with Unreachable Exit Condition (verwandt)

Referenzen

  1. MITRE Corporation. "CWE-1095: Loop Condition Value Update within the Loop." https://cwe.mitre.org/data/definitions/1095.html
  2. CISQ Quality Measures - Maintainability.
  3. Martin, Robert C. "Clean Code" - Simple Loop Structures.