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
| Auswirkung | Details |
|---|---|
| Andere | Bereich: Ändere Reduzierte Wartbarkeit - Goto-basierter Code ist extrem schwer zu verstehen und sicher zu modifizieren. |
| Andere | Bereich: Ändere Erhöhte analytische Komplexität - Statische Analyse und Sicherheitsaudits werden unzuverlässig. |
| Andere | Bereich: Ä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
-
MITRE Corporation. "CWE-1075: Unconditional Control Flow Transfer outside of Switch Block." https://cwe.mitre.org/data/definitions/1075.html
-
Dijkstra, Edsger. "Go To Statement Considered Harmful."
-
CERT C Coding Standard. "MEM00-C. Allocate and free memory in the same module."