Ü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
| Auswirkung | Details |
|---|---|
| Sonstiges | Bereich: 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. |
| Sonstiges | Bereich: 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
- MITRE Corporation. "CWE-1119: Excessive Use of Unconditional Branching." https://cwe.mitre.org/data/definitions/1119.html
- Dijkstra, E. W. "Go To Statement Considered Harmful" (1968)
- Linux Kernel Coding Style Guidelines (akzeptable goto-Muster)