Übermäßige Verwendung unbedingter Verzweigungen

Beschreibung

Übermäßige Verwendung unbedingter Verzweigungen tritt auf, wenn Code zu viele unbedingte Verzweigungen wie 'goto'-Anweisungen, uneingeschränkte Sprünge oder ähnliche Konstrukte verwendet, die den Kontrollfluss ohne Bedingungen übertragen. Obwohl goto in bestimmten Kontexten angemessen eingesetzt werden kann (Fehlerbehandlung in C, Ausbrechen aus verschachtelten Schleifen), führt übermäßige Verwendung zu Spaghetti-Code, der schwer zu verstehen, zu warten und auf Sicherheitslücken zu prüfen ist. Der unstrukturierte Kontrollfluss macht es schwierig, über den Programmzustand zu urteilen und Sicherheitsprobleme zu identifizieren.

Risiko

Übermäßige unbedingte Verzweigungen haben indirekte Sicherheitsauswirkungen. Code wird schwerer auf Sicherheitslücken zu prüfen. Kontrollfluss-Analysetools können ungenaue Ergebnisse liefern. Sicherheitsinvarianten sind über Sprungziele hinweg schwerer zu verifizieren. Code-Reviewer übersehen möglicherweise Schwachstellen im verworrenen Kontrollfluss. Wartungsänderungen können Sicherheitsbugs einführen. Zustandsmanagement wird bei willkürlichen Sprüngen fehleranfällig. Testabdeckung ist schwer zu erreichen. Statische Analysetools produzieren möglicherweise Falsch-Positive oder übersehen echte Probleme.

Lösung

Verwenden Sie strukturierte Programmierkonstrukte (if/else, while, for, switch). Begrenzen Sie goto-Verwendung auf spezifische Muster wie Fehler-Bereinigung in C. Refaktorisieren Sie tief verschachtelten Code in kleinere Funktionen. Verwenden Sie frühe Returns statt goto für Fehlerfälle. Verwenden Sie Exception-Handling wo die Sprache es unterstützt. Wenden Sie zyklomatische Komplexitätsgrenzen an. Verwenden Sie break/continue für Schleifensteuerung statt goto. Refaktorisieren Sie switch-Anweisungen mit Fall-Through. Verwenden Sie Zustandsautomaten für komplexe Zustandsübergänge. Wenden Sie Linting-Regeln an, um goto zu begrenzen oder zu verbieten.

Häufige Auswirkungen

AuswirkungDetails
SonstigesBereich: Sonstiges

Reduzierte Wartbarkeit -- Macht es schwieriger, das Produkt zu verstehen und zu warten, was die Sicherheit indirekt beeinträchtigt, indem Schwachstellen schwerer zu finden und zu beheben sind.
SonstigesBereich: Sonstiges

Erhöhte analytische Komplexität -- Komplexer Kontrollfluss macht Sicherheitsanalyse schwierig und fehleranfällig.

Beispielcode und Lösung

Verwundbarer Code

// Verwundbar: Übermäßige Verwendung von goto erzeugt Spaghetti-Code

int process_request(Request *req) {
    int result = 0;
    char *buffer = NULL;
    FILE *file = NULL;
    Connection *conn = NULL;

    // Spaghetti-Code mit übermäßigen gotos
    if (!req) goto error1;

    buffer = malloc(1024);
    if (!buffer) goto error2;

    if (req->type == TYPE_FILE) goto handle_file;
    if (req->type == TYPE_NETWORK) goto handle_network;
    goto error3;

handle_file:
    file = fopen(req->path, "r");
    if (!file) goto error4;
    // Einige Verarbeitung...
    if (req->needs_validation) goto validate;
    goto process_data;

handle_network:
    conn = connect(req->host, req->port);
    if (!conn) goto error5;
    if (req->secure) goto secure_connect;
    // Einige Verarbeitung...
    goto process_data;

secure_connect:
    if (!setup_tls(conn)) goto error6;
    goto process_data;

validate:
    if (!validate_data(buffer)) goto error7;
    // Durchfall zu process_data

process_data:
    // Daten verarbeiten...
    if (req->needs_logging) goto log_request;
    goto cleanup;

log_request:
    log_entry(req, buffer);
    // Durchfall zu cleanup

cleanup:
    if (conn) close_connection(conn);
    if (file) fclose(file);
    if (buffer) free(buffer);
    return result;

error1:
    result = ERR_NULL_REQUEST;
    goto cleanup;

error2:
    result = ERR_NO_MEMORY;
    goto cleanup;

error3:
    result = ERR_INVALID_TYPE;
    goto cleanup;

error4:
    result = ERR_FILE_OPEN;
    goto cleanup;

error5:
    result = ERR_CONNECT;
    goto cleanup;

error6:
    result = ERR_TLS;
    goto cleanup;

error7:
    result = ERR_VALIDATION;
    goto cleanup;
}
// Verwundbar: Verschachtelte gotos machen Kontrollfluss unverständlich

void process_data(Data *data) {
    int i, j, k;

start:
    if (!data->initialized) {
        initialize(data);
        goto start;  // Schleife über goto
    }

    for (i = 0; i < data->rows; i++) {
next_row:
        for (j = 0; j < data->cols; j++) {
            if (data->matrix[i][j] < 0) goto skip_negative;

            for (k = 0; k < data->depth; k++) {
                if (should_abort(data, i, j, k)) goto abort_all;
                if (special_case(data, i, j, k)) goto handle_special;

                process_cell(data, i, j, k);
                continue;

handle_special:
                handle_special_case(data, i, j, k);
                if (retry_needed(data)) goto start;  // Springt weit zurück!
            }
            continue;

skip_negative:
            log_skipped(i, j);
        }
    }
    return;

abort_all:
    cleanup(data);
    goto start;  // Alles wiederholen?!
}

Sichere Lösung

// SICHER: Strukturierte Fehlerbehandlung ohne übermäßiges goto
// (Ein goto pro Funktion zur Bereinigung ist in C akzeptabel)

int process_request(Request *req) {
    int result = 0;
    char *buffer = NULL;
    FILE *file = NULL;
    Connection *conn = NULL;

    // Eingabe validieren
    if (!req) {
        return ERR_NULL_REQUEST;
    }

    // Ressourcen zuweisen
    buffer = malloc(1024);
    if (!buffer) {
        return ERR_NO_MEMORY;
    }

    // Verarbeitung basierend auf Typ mit strukturiertem Kontrollfluss
    switch (req->type) {
        case TYPE_FILE:
            result = handle_file_request(req, buffer, &file);
            break;
        case TYPE_NETWORK:
            result = handle_network_request(req, buffer, &conn);
            break;
        default:
            result = ERR_INVALID_TYPE;
            break;
    }

    // Auf Fehler aus Handlern prüfen
    if (result != 0) {
        goto cleanup;  // Einzelnes Bereinigung-goto ist akzeptabel
    }

    // Bei Bedarf validieren
    if (req->needs_validation && !validate_data(buffer)) {
        result = ERR_VALIDATION;
        goto cleanup;
    }

    // Daten verarbeiten
    result = process_data(buffer);
    if (result != 0) {
        goto cleanup;
    }

    // Bei Bedarf protokollieren
    if (req->needs_logging) {
        log_entry(req, buffer);
    }

cleanup:
    // Alle Ressourcen bereinigen
    if (conn) {
        close_connection(conn);
    }
    if (file) {
        fclose(file);
    }
    free(buffer);

    return result;
}

// Separate Funktion für Dateibehandlung
static int handle_file_request(Request *req, char *buffer, FILE **file_out) {
    FILE *file = fopen(req->path, "r");
    if (!file) {
        return ERR_FILE_OPEN;
    }

    // Daten in Buffer lesen
    if (fread(buffer, 1, 1024, file) == 0 && ferror(file)) {
        fclose(file);
        return ERR_FILE_READ;
    }

    *file_out = file;
    return 0;
}

// Separate Funktion für Netzwerkbehandlung
static int handle_network_request(Request *req, char *buffer, Connection **conn_out) {
    Connection *conn = connect(req->host, req->port);
    if (!conn) {
        return ERR_CONNECT;
    }

    if (req->secure) {
        if (!setup_tls(conn)) {
            close_connection(conn);
            return ERR_TLS;
        }
    }

    // Daten in Buffer empfangen
    if (receive_data(conn, buffer, 1024) < 0) {
        close_connection(conn);
        return ERR_RECEIVE;
    }

    *conn_out = conn;
    return 0;
}
// SICHER: Verwendung strukturierter Schleifen und früher Returns

void process_data(Data *data) {
    // Bei Bedarf initialisieren (keine goto-Schleife)
    if (!data->initialized) {
        initialize(data);
    }

    // Verarbeitung mit ordnungsgemäßen verschachtelten Schleifen
    for (int i = 0; i < data->rows; i++) {
        for (int j = 0; j < data->cols; j++) {
            // Negative Werte überspringen
            if (data->matrix[i][j] < 0) {
                log_skipped(i, j);
                continue;  // continue statt goto verwenden
            }

            if (!process_row_column(data, i, j)) {
                // Fehler behandeln durch Bereinigung und Return
                cleanup(data);
                return;
            }
        }
    }
}

// Separate Funktion für innere Verarbeitung
static bool process_row_column(Data *data, int i, int j) {
    for (int k = 0; k < data->depth; k++) {
        if (should_abort(data, i, j, k)) {
            return false;  // Abbruch an Aufrufer signalisieren
        }

        if (special_case(data, i, j, k)) {
            handle_special_case(data, i, j, k);
            // Wenn Wiederholung nötig, false zurückgeben und Aufrufer entscheiden lassen
            if (retry_needed(data)) {
                return false;
            }
        } else {
            process_cell(data, i, j, k);
        }
    }
    return true;
}
# SICHER: Python mit strukturiertem Kontrollfluss

def process_data(data):
    """Verarbeitet Daten mit sauberem, strukturiertem Kontrollfluss."""
    if not data.initialized:
        data.initialize()

    for i, row in enumerate(data.rows):
        for j, cell in enumerate(row):
            if cell < 0:
                log_skipped(i, j)
                continue

            result = process_cell(data, i, j)
            if result == ProcessResult.ABORT:
                data.cleanup()
                return
            elif result == ProcessResult.RETRY:
                # Wiederholung auf geeigneter Ebene behandeln
                return process_data(data)  # Rekursive Wiederholung wenn nötig


def process_request(request):
    """Verarbeitet Anfrage mit frühen Returns bei Fehlern."""
    if not request:
        return Error.NULL_REQUEST

    # Context-Manager für automatische Bereinigung verwenden
    with allocate_buffer(1024) as buffer:
        # Basierend auf Typ behandeln
        if request.type == RequestType.FILE:
            result = handle_file_request(request, buffer)
        elif request.type == RequestType.NETWORK:
            result = handle_network_request(request, buffer)
        else:
            return Error.INVALID_TYPE

        if result.is_error():
            return result.error

        # Bei Bedarf validieren
        if request.needs_validation:
            if not validate_data(buffer):
                return Error.VALIDATION

        # Verarbeiten und optional protokollieren
        process_data(buffer)

        if request.needs_logging:
            log_entry(request, buffer)

    return Success()

CVE-Beispiele

Diese CWE ist als VERBOTEN für direkte CVE-Zuordnung markiert, da sie ein Codequalitätsproblem darstellt und keine direkte Sicherheitslücke.


Verwandte CWEs

  • CWE-1120: Übermäßige Code-Komplexität (Eltern)
  • CWE-1226: Komplexitätsprobleme (Kategoriemitglied)
  • CWE-1121: Übermäßige McCabe-zyklomatische Komplexität (verwandt)

Referenzen

  1. MITRE Corporation. "CWE-1119: Excessive Use of Unconditional Branching." https://cwe.mitre.org/data/definitions/1119.html
  2. Dijkstra, E. W. "Go To Statement Considered Harmful" (1968)
  3. Linux Kernel Coding Style Guidelines (akzeptable goto-Muster)