Unbedingte Kontrollflusstransfer außerhalb von Switch-Block

Beschreibung

Unbedingte Kontrollflusstransfer außerhalb von Switch-Block tritt auf, wenn ein Produkt unbedingte Kontrollflusstransfers (wie 'goto'-Anweisungen) in Code außerhalb einer Verzweigungsstruktur wie eines Switch-Blocks durchführt. Während Goto-Anweisungen legitime Verwendungen in Switch-Blöcken für Fall-Through-Verhalten oder in der Fehlerbehandlung in C haben, erzeugt ihre Verwendung anderswo Spaghetti-Code, der schwer zu verstehen, zu warten und auf Sicherheitsschwachstellen zu analysieren ist. Der unvorhersehbare Kontrollfluss macht es nahezu unmöglich, über Codeverhalten nachzudenken.

Risiko

Unbedingte Kontrollflusstransfers außerhalb kontrollierter Strukturen haben Sicherheitsimplikationen. Der unvorhersehbare Fluss macht Sicherheitsaudits extrem schwierig. Code-Analysetools können den Kontrollfluss möglicherweise nicht ordnungsgemäß nachverfolgen und übersehen Schwachstellen. Sicherheitsprüfungen können umgangen werden, indem über sie gesprungen wird. Ressourcenbereinigungscode kann übersprungen werden, was zu Lecks führt. Ausnahmebehandlung funktioniert möglicherweise nicht korrekt mit Goto-basiertem Kontrollfluss. Entwickler, die den Code warten, führen möglicherweise Schwachstellen ein, weil sie den Fluss missverstehen. Die erzeugte Komplexität kann Backdoors oder bösartigen Code verbergen.

Lösung

Eliminieren Sie Goto-Anweisungen außerhalb von Switch-Blöcken. Verwenden Sie strukturierten Kontrollfluss: Schleifen, Bedingungen, Funktionen. Ersetzen Sie Goto-basierte Fehlerbehandlung durch ordnungsgemäße Ausnahmebehandlung. Verwenden Sie frühe Returns anstelle von Goto für Bereinigung. Wenden Sie Extract-Method-Refactoring an, um komplexe Funktionen zu vereinfachen. Verwenden Sie RAII (Resource Acquisition Is Initialization) in C++ für Ressourcenbereinigung. In C erwägen Sie Goto nur für zentralisierte Bereinigung am Funktionsende. Verwenden Sie statische Analysetools, um Goto-Verwendung zu erkennen. Etablieren Sie Coding-Standards, die Goto einschränken oder verbieten.

Häufige Auswirkungen

AuswirkungDetails
AndereBereich: Ändere

Reduzierte Wartbarkeit - Goto-basierter Code ist extrem schwer zu verstehen und sicher zu modifizieren.
AndereBereich: Ändere

Erhöhte analytische Komplexität - Statische Analyse und Sicherheitsaudits werden unzuverlässig.
AndereBereich: Ändere

Qualitätsverschlechterung - Spaghetti-Code ist anfällig für Fehler einschließlich Sicherheitsschwachstellen.

Beispielcode

Anfälliger Code

// Anfällig: Goto-Anweisungen erzeugen Spaghetti-Code
int vulnerable_process_data(char* input, size_t len) {
    int result = 0;
    char* buffer = NULL;
    FILE* file = NULL;

    if (input == NULL) {
        goto label_a;  // Sprung in die Mitte des Codes
    }

    buffer = malloc(len + 1);
    if (buffer == NULL) {
        goto error;
    }

    memcpy(buffer, input, len);
    buffer[len] = '\0';

    // Anfällig: Sicherheitsprüfung könnte umgangen werden
    if (is_malicious(buffer)) {
        goto label_b;  // Springt irgendwo unerwartet hin
    }

    file = fopen("/tmp/data.txt", "w");
    if (file == NULL) {
        goto cleanup;
    }

label_a:
    // Anfällig: Kann hier mit nicht initialisiertem buffer ankommen!
    result = process_buffer(buffer);  // buffer könnte NULL sein

label_b:
    // Anfällig: Datei ist möglicherweise nicht offen
    if (file) {
        fprintf(file, "%s", buffer);
    }

    // Anfällig: Bereinigung könnte übersprungen werden
    goto done;

error:
    result = -1;
    // Fällt durch zu cleanup

cleanup:
    if (buffer) free(buffer);
    if (file) fclose(file);

done:
    return result;
}
// Anfällig: Goto springt über Sicherheitsprüfungen
int vulnerable_authenticate(const char* username, const char* password) {
    int authenticated = 0;

    if (username == NULL) {
        goto end;
    }

    // Anfällig: Diese Prüfung kann umgangen werden durch Sprung zu 'skip_check'
    if (!validate_username_format(username)) {
        goto error;
    }

    if (password == NULL) {
        goto skip_check;  // Umgeht Passwort-Validierung!
    }

    if (!verify_password(username, password)) {
        goto error;
    }

skip_check:
    // Kann hier ohne Passwort-Verifizierung ankommen!
    authenticated = 1;
    log_successful_auth(username);
    goto end;

error:
    log_failed_auth(username);

end:
    return authenticated;
}
// Anfällig: Komplexes Goto-Netz
void vulnerable_state_machine(int initial_state) {
    int state = initial_state;
    int data = 0;

    // Anfällig: Unstrukturierte Zustandsmaschine
start:
    if (state == 0) goto state_a;
    if (state == 1) goto state_b;
    if (state == 2) goto state_c;
    goto end;

state_a:
    data = read_input();
    if (data < 0) goto error;
    if (data > 100) goto state_c;  // Überspringt state_b
    state = 1;
    goto state_b;  // Verwirrend: warum nicht einfach durchfallen?

state_b:
    process_data(data);
    if (has_more_data()) goto start;  // Schleife via goto
    state = 2;
    goto state_c;

state_c:
    // Anfällig: Kann hier mit data == 0 ankommen wenn von state_a gesprungen
    finalize(data);
    goto end;

error:
    // Fehlerbehandlung in den Fluss gemischt
    log_error();
    // Fällt durch zu end - ist das beabsichtigt?

end:
    cleanup();
}

Korrigierter Code

// Korrigiert: Strukturierter Kontrollfluss mit ordnungsgemäßer Fehlerbehandlung
int fixed_process_data(char* input, size_t len) {
    int result = -1;  // Fehler annehmen
    char* buffer = NULL;
    FILE* file = NULL;

    // Korrigiert: Frühe Rückkehr für ungültige Eingabe
    if (input == NULL) {
        return -1;
    }

    // Korrigiert: Strukturierte Allokation und Bereinigung
    buffer = malloc(len + 1);
    if (buffer == NULL) {
        return -1;
    }

    memcpy(buffer, input, len);
    buffer[len] = '\0';

    // Korrigiert: Sicherheitsprüfung mit strukturiertem Fluss
    if (is_malicious(buffer)) {
        // Bösartige Eingabe explizit behandeln
        log_security_event("Bösartige Eingabe erkannt");
        goto cleanup;  // Einziges Goto ist für Bereinigung
    }

    file = fopen("/tmp/data.txt", "w");
    if (file == NULL) {
        goto cleanup;  // Konsistenter Bereinigungspfad
    }

    // Korrigiert: Verarbeitung mit gültigen, geprüften Daten
    result = process_buffer(buffer);
    if (result >= 0) {
        fprintf(file, "%s", buffer);
    }

cleanup:
    // Korrigiert: Einzelner Bereinigungspunkt am Ende der Funktion
    if (buffer) {
        free(buffer);
    }
    if (file) {
        fclose(file);
    }
    return result;
}
// Korrigiert: Strukturierte Authentifizierung ohne Goto-Umgehungsrisiko
int fixed_authenticate(const char* username, const char* password) {
    // Korrigiert: Frühe Validierung mit klarem Fluss
    if (username == NULL) {
        log_failed_auth("null_user");
        return 0;
    }

    if (!validate_username_format(username)) {
        log_failed_auth(username);
        return 0;
    }

    // Korrigiert: Passwort ist immer erforderlich
    if (password == NULL) {
        log_failed_auth(username);
        return 0;
    }

    if (!verify_password(username, password)) {
        log_failed_auth(username);
        return 0;
    }

    // Korrigiert: Nur hier erreichen wenn ALLE Prüfungen bestanden
    log_successful_auth(username);
    return 1;
}
// Korrigiert: Ordnungsgemäße Zustandsmaschine mit Switch oder Funktionszeigern
typedef enum {
    STATE_INIT,
    STATE_PROCESS,
    STATE_FINALIZE,
    STATE_ERROR,
    STATE_DONE
} State;

typedef State (*StateHandler)(int* data);

// Korrigiert: Zustandshandler als separate Funktionen
State handle_init(int* data) {
    *data = read_input();
    if (*data < 0) {
        return STATE_ERROR;
    }
    return (*data > 100) ? STATE_FINALIZE : STATE_PROCESS;
}

State handle_process(int* data) {
    process_data(*data);
    return has_more_data() ? STATE_INIT : STATE_FINALIZE;
}

State handle_finalize(int* data) {
    finalize(*data);
    return STATE_DONE;
}

State handle_error(int* data) {
    (void)data;
    log_error();
    return STATE_DONE;
}

// Korrigiert: Strukturierte Zustandsmaschine
void fixed_state_machine(int initial_state) {
    State state = (State)initial_state;
    int data = 0;

    // Zustandshandler-Lookup-Tabelle
    static StateHandler handlers[] = {
        [STATE_INIT] = handle_init,
        [STATE_PROCESS] = handle_process,
        [STATE_FINALIZE] = handle_finalize,
        [STATE_ERROR] = handle_error
    };

    // Korrigiert: Klare Schleifenstruktur
    while (state != STATE_DONE) {
        if (state < STATE_DONE && handlers[state]) {
            state = handlers[state](&data);
        } else {
            state = STATE_ERROR;
        }
    }

    cleanup();
}

// Alternative: Switch-basierte Zustandsmaschine (Goto nur in Switch ist akzeptabel)
void fixed_state_machine_switch(int initial_state) {
    State state = (State)initial_state;
    int data = 0;
    bool running = true;

    while (running) {
        switch (state) {
            case STATE_INIT:
                data = read_input();
                state = (data < 0) ? STATE_ERROR :
                        (data > 100) ? STATE_FINALIZE : STATE_PROCESS;
                break;

            case STATE_PROCESS:
                process_data(data);
                state = has_more_data() ? STATE_INIT : STATE_FINALIZE;
                break;

            case STATE_FINALIZE:
                finalize(data);
                state = STATE_DONE;
                break;

            case STATE_ERROR:
                log_error();
                state = STATE_DONE;
                break;

            case STATE_DONE:
                running = false;
                break;
        }
    }

    cleanup();
}
// Korrigiert: C++ RAII-Ansatz (beste Lösung für C++)
#include <memory>
#include <fstream>
#include <string>

class DataProcessor {
public:
    int processData(const char* input, size_t len) {
        // Korrigiert: RAII - keine explizite Bereinigung nötig
        if (!input) {
            return -1;
        }

        // Korrigiert: Smart Pointer behandelt Speicher
        std::unique_ptr<char[]> buffer(new char[len + 1]);
        std::memcpy(buffer.get(), input, len);
        buffer[len] = '\0';

        // Korrigiert: Sicherheitsprüfung mit normalem Kontrollfluss
        if (isMalicious(buffer.get())) {
            logSecurityEvent("Bösartige Eingabe erkannt");
            return -1;  // Buffer automatisch freigegeben
        }

        // Korrigiert: RAII-Dateibehandlung
        std::ofstream file("/tmp/data.txt");
        if (!file) {
            return -1;  // Buffer automatisch freigegeben
        }

        int result = processBuffer(buffer.get());
        if (result >= 0) {
            file << buffer.get();
        }

        return result;
        // Buffer und Datei automatisch bereinigt
    }
};

CVE-Beispiele

Goto-bezogene Kontrollflussprobleme haben zu Schwachstellen beigetragen, obwohl spezifische CVEs typischerweise die resultierende Schwäche (Buffer Overflow, Umgehung) beschreiben und nicht die Goto-Verwendung selbst.


Verwandte CWEs

  • CWE-1120: Excessive Code Complexity (Eltern)
  • CWE-1226: Complexity Issues (Kategoriemitglied)
  • CWE-691: Insufficient Control Flow Management (verwandt)

Referenzen

  1. MITRE Corporation. "CWE-1075: Unconditional Control Flow Transfer outside of Switch Block." https://cwe.mitre.org/data/definitions/1075.html

  2. Dijkstra, Edsger. "Go To Statement Considered Harmful."

  3. CERT C Coding Standard. "MEM00-C. Allocate and free memory in the same module."