Ausgelassene Break-Anweisung in Switch

Beschreibung

Ausgelassene Break-Anweisung in Switch tritt auf, wenn ein Programmierer es versäumt, eine Break-Anweisung am Ende eines Switch-Falls einzufügen, wodurch die Ausführung zum nächsten Fall "durchfällt". In C-Familie-Sprachen enden Switch-Fälle nicht automatisch — ohne explizites break, continue, return oder throw fließt die Ausführung in nachfolgende Fälle. Während absichtliches Durchfallen gelegentlich verwendet wird, ist unbeabsichtigtes Durchfallen eine häufige Fehlerquelle, bei der mehrere Fälle ausgeführt werden, wenn nur einer sollte.

Risiko

Ausgelassene Break-Anweisungen verursachen unbeabsichtigte Codeausführung und Logikfehler. In Sicherheitskontexten kann dies zu Privilegieneskalation führen, wenn ein Fall mit niedrigeren Privilegien zu Aktionen mit höheren Privilegien durchfällt. Authentifizierungssysteme können Zugang gewähren, wenn sie ihn verweigern sollten. Daten können korrumpiert werden, wenn mehrere Modifikationsoperationen ausgeführt werden. Zustandsautomaten können in ungültige Zustände eintreten. Der Bug ist besonders gefährlich, weil der Code auf den ersten Blick korrekt erscheint — nur sorgfältige Analyse offenbart das fehlende break.

Lösung

Fügen Sie immer Break-Anweisungen oder explizite Durchfall-Kommentare ein. Verwenden Sie Compiler-Warnungen (-Wimplicit-fallthrough in GCC/Clang). Wenden Sie statische Analysetools an, die fehlende Breaks erkennen. Verwenden Sie das [[fallthrough]]-Attribut (C++17) oder Durchfall-Kommentare für absichtliches Durchfallen. Erwägen Sie die Verwendung von if-else-Ketten anstelle von Switch für komplexe Logik. Einige Codierungsstandards empfehlen, Durchfallen vollständig zu vermeiden. Überprüfen Sie bei Code-Review, dass jeder Fall angemessen endet.

Häufige Auswirkungen

AuswirkungDetails
ZugriffskontrolleUmfang: Privilegieneskalation

Durchfallen zu Fällen mit höheren Privilegien gewährt unbeabsichtigten Zugang.
IntegritätUmfang: Unbeabsichtigte Modifikationen

Mehrere Fall-Körper werden ausgeführt und verursachen Datenkorruption.
LogikUmfang: Falsches Verhalten

Programmfluss entspricht nicht der beabsichtigten Fallauswahl.

Beispielcode

Anfälliger Code

// ANFÄLLIG: Fehlendes break verursacht Privilegieneskalation
void handle_user_vulnerable(User* user, int action) {
    switch (user->role) {
        case GUEST:
            log_access(user, "guest");
            // BUG! Fehlendes break - fällt durch!

        case USER:
            allow_read_access(user);
            // BUG! Fehlendes break - fällt durch!

        case ADMIN:
            allow_full_access(user);  // Guest und User bekommen Admin-Zugang!
            break;
    }
}

// ANFÄLLIG: Zustandsautomaten-Korruption
typedef enum { INIT, CONNECTING, CONNECTED, CLOSING, CLOSED } State;

void process_state_vulnerable(Connection* conn) {
    switch (conn->state) {
        case INIT:
            start_connection(conn);
            // BUG! Fällt durch zu CONNECTING!

        case CONNECTING:
            check_connection(conn);
            // BUG! Fällt durch zu CONNECTED!

        case CONNECTED:
            process_data(conn);
            // BUG! Fällt durch zu CLOSING!

        case CLOSING:
            finish_close(conn);
            // BUG! Fällt durch zu CLOSED!

        case CLOSED:
            cleanup_connection(conn);
            break;
    }
}

// ANFÄLLIG: Datenkorruption
void process_command_vulnerable(int cmd, Data* data) {
    switch (cmd) {
        case CMD_RESET:
            reset_data(data);
            // BUG! Fällt durch zu CMD_MODIFY!

        case CMD_MODIFY:
            modify_data(data);
            // BUG! Fällt durch zu CMD_DELETE!

        case CMD_DELETE:
            delete_data(data);  // RESET löscht auch!
            break;
    }
}

// ANFÄLLIG: Authentifizierungsumgehung
int authenticate_vulnerable(Request* req) {
    switch (req->auth_type) {
        case AUTH_NONE:
            return AUTH_DENIED;
            // Dieser ist OK - return beendet den Fall

        case AUTH_BASIC:
            if (!check_basic_auth(req))
                return AUTH_DENIED;
            // BUG! Wenn Prüfung besteht, fällt durch!

        case AUTH_ADMIN:
            grant_admin_access(req);  // Basic-Auth-Benutzer bekommen Admin!
            return AUTH_GRANTED;
    }
    return AUTH_DENIED;
}

// ANFÄLLIG: Mehrere Aktionen ausgeführt
void handle_error_vulnerable(int error_code) {
    switch (error_code) {
        case ERR_WARNING:
            log_warning();
            // BUG! Fällt durch!

        case ERR_ERROR:
            log_error();
            // BUG! Fällt durch!

        case ERR_CRITICAL:
            notify_admin();
            // BUG! Fällt durch!

        case ERR_FATAL:
            shutdown_system();  // WARNING löst Herunterfahren aus!
            break;
    }
}

// ANFÄLLIG: Teilweise Initialisierung
void init_config_vulnerable(Config* cfg, int level) {
    switch (level) {
        case 3:
            cfg->advanced = load_advanced();
            // BUG! Fällt durch!

        case 2:
            cfg->extended = load_extended();
            // BUG! Fällt durch!

        case 1:
            cfg->basic = load_basic();
            // BUG! Fällt durch zu default!

        default:
            cfg->minimal = load_minimal();
            break;
    }
    // Level 3 lädt ALLE Configs, Level 1 lädt basic UND minimal
    // Dies könnte beabsichtigt sein aber ist unklar!
}
// ANFÄLLIG: C++ mit denselben Problemen
class VulnerableHandler {
public:
    void handle(Event event) {
        switch (event.type) {
            case EventType::CLICK:
                handleClick(event);
                // BUG! Fällt durch!

            case EventType::DOUBLE_CLICK:
                handleDoubleClick(event);  // CLICK löst auch aus!
                // BUG! Fällt durch!

            case EventType::RIGHT_CLICK:
                handleRightClick(event);  // Alle Klicks lösen aus!
                break;
        }
    }

    int getPermission(UserRole role) {
        int permission = PERM_NONE;

        switch (role) {
            case UserRole::GUEST:
                permission |= PERM_VIEW;
                // BUG! Fällt durch!

            case UserRole::USER:
                permission |= PERM_EDIT;
                // BUG! Fällt durch!

            case UserRole::ADMIN:
                permission |= PERM_DELETE;  // Jeder kann löschen!
                break;
        }

        return permission;
    }
};

// ANFÄLLIG: Ausnahmebehandlung
void processValue_vulnerable(int value) {
    switch (value) {
        case -1:
            throw std::invalid_argument("Wert kann nicht -1 sein");
            // OK - throw beendet

        case 0:
            std::cout << "Warnung: Nullwert\n";
            // BUG! Fällt durch!

        case 1:
            processNormal();  // 0 und 1 verarbeiten beide als normal!
            break;
    }
}
// JavaScript hat dasselbe Durchfall-Verhalten
// ANFÄLLIG: Rollen-Eskalation
function handleUserVulnerable(user) {
    switch (user.role) {
        case 'guest':
            logGuest(user);
            // BUG! Fällt durch!

        case 'user':
            enableBasicFeatures();
            // BUG! Fällt durch!

        case 'admin':
            enableAdminFeatures();  // Gäste bekommen Admin!
            break;
    }
}

// ANFÄLLIG: Status-Behandlung
function updateStatus(status) {
    switch (status) {
        case 'pending':
            notify('Verarbeitung...');
            // BUG! Fällt durch!

        case 'processing':
            startTimer();
            // BUG! Fällt durch!

        case 'completed':
            showResult();  // Pending zeigt sofort Ergebnis!
            break;
    }
}

Korrigierter Code

// SICHER: Explizites break in jedem Fall
void handle_user_safe(User* user, int action) {
    switch (user->role) {
        case GUEST:
            log_access(user, "guest");
            allow_guest_access(user);
            break;  // Explizites break

        case USER:
            log_access(user, "user");
            allow_read_access(user);
            break;  // Explizites break

        case ADMIN:
            log_access(user, "admin");
            allow_full_access(user);
            break;  // Explizites break

        default:
            log_access(user, "unknown");
            deny_access(user);
            break;
    }
}

// SICHER: Zustandsautomat mit expliziten breaks
void process_state_safe(Connection* conn) {
    switch (conn->state) {
        case INIT:
            start_connection(conn);
            conn->state = CONNECTING;
            break;

        case CONNECTING:
            if (check_connection(conn)) {
                conn->state = CONNECTED;
            }
            break;

        case CONNECTED:
            process_data(conn);
            break;

        case CLOSING:
            finish_close(conn);
            conn->state = CLOSED;
            break;

        case CLOSED:
            cleanup_connection(conn);
            break;
    }
}

// SICHER: Befehle mit expliziten breaks
void process_command_safe(int cmd, Data* data) {
    switch (cmd) {
        case CMD_RESET:
            reset_data(data);
            break;

        case CMD_MODIFY:
            modify_data(data);
            break;

        case CMD_DELETE:
            delete_data(data);
            break;

        default:
            handle_unknown_command(cmd);
            break;
    }
}

// SICHER: Authentifizierung mit ordnungsgemäßem Fluss
int authenticate_safe(Request* req) {
    switch (req->auth_type) {
        case AUTH_NONE:
            return AUTH_DENIED;

        case AUTH_BASIC:
            if (check_basic_auth(req)) {
                return AUTH_GRANTED;
            }
            return AUTH_DENIED;

        case AUTH_ADMIN:
            if (check_admin_auth(req)) {
                grant_admin_access(req);
                return AUTH_GRANTED;
            }
            return AUTH_DENIED;

        default:
            return AUTH_DENIED;
    }
}

// SICHER: Fehlerbehandlung mit expliziten breaks
void handle_error_safe(int error_code) {
    switch (error_code) {
        case ERR_WARNING:
            log_warning();
            break;

        case ERR_ERROR:
            log_error();
            break;

        case ERR_CRITICAL:
            log_error();
            notify_admin();
            break;

        case ERR_FATAL:
            log_error();
            notify_admin();
            shutdown_system();
            break;
    }
}

// SICHER: Absichtliches Durchfallen mit Kommentar (wenn wirklich nötig)
void init_config_intentional(Config* cfg, int level) {
    cfg->minimal = load_minimal();

    switch (level) {
        case 3:
            cfg->advanced = load_advanced();
            /* FALLTHROUGH */  // Expliziter Kommentar für absichtliches Durchfallen

        case 2:
            cfg->extended = load_extended();
            /* FALLTHROUGH */

        case 1:
            cfg->basic = load_basic();
            break;

        default:
            break;
    }
}

// Besser: Durchfallen mit kumulativer Logik vermeiden
void init_config_safe(Config* cfg, int level) {
    cfg->minimal = load_minimal();

    if (level >= 1) {
        cfg->basic = load_basic();
    }
    if (level >= 2) {
        cfg->extended = load_extended();
    }
    if (level >= 3) {
        cfg->advanced = load_advanced();
    }
}
// SICHER: C++ mit [[fallthrough]]-Attribut (C++17)
class SafeHandler {
public:
    void handle(Event event) {
        switch (event.type) {
            case EventType::CLICK:
                handleClick(event);
                break;

            case EventType::DOUBLE_CLICK:
                handleDoubleClick(event);
                break;

            case EventType::RIGHT_CLICK:
                handleRightClick(event);
                break;

            default:
                handleUnknown(event);
                break;
        }
    }

    int getPermission(UserRole role) {
        switch (role) {
            case UserRole::ADMIN:
                return PERM_VIEW | PERM_EDIT | PERM_DELETE;

            case UserRole::USER:
                return PERM_VIEW | PERM_EDIT;

            case UserRole::GUEST:
                return PERM_VIEW;

            default:
                return PERM_NONE;
        }
    }

    // Wenn Durchfallen beabsichtigt ist, [[fallthrough]] verwenden
    void logLevel(int level) {
        switch (level) {
            case 3:
                std::cout << "DEBUG: ";
                [[fallthrough]];  // C++17-Attribut

            case 2:
                std::cout << "INFO: ";
                [[fallthrough]];

            case 1:
                std::cout << "NACHRICHT\n";
                break;

            default:
                break;
        }
    }
};

// SICHER: Verwendung von enum class mit vollständigem Switch
enum class Status { PENDING, PROCESSING, COMPLETED };

std::string getStatusMessage(Status status) {
    switch (status) {
        case Status::PENDING:
            return "Warten...";

        case Status::PROCESSING:
            return "Arbeiten...";

        case Status::COMPLETED:
            return "Fertig!";
    }
    // Compiler warnt wenn Fall mit enum class fehlt
}
// SICHER: JavaScript mit expliziten breaks
function handleUserSafe(user) {
    switch (user.role) {
        case 'guest':
            logGuest(user);
            enableGuestFeatures();
            break;

        case 'user':
            logUser(user);
            enableBasicFeatures();
            break;

        case 'admin':
            logAdmin(user);
            enableAdminFeatures();
            break;

        default:
            logUnknown(user);
            break;
    }
}

// Oder Objekt-Mapping verwenden um Switch ganz zu vermeiden
const roleHandlers = {
    guest: (user) => {
        logGuest(user);
        enableGuestFeatures();
    },
    user: (user) => {
        logUser(user);
        enableBasicFeatures();
    },
    admin: (user) => {
        logAdmin(user);
        enableAdminFeatures();
    }
};

function handleUserModern(user) {
    const handler = roleHandlers[user.role];
    if (handler) {
        handler(user);
    } else {
        logUnknown(user);
    }
}

// ESLint: no-fallthrough-Regel
// "no-fallthrough": "error"

Ausgenutzt in der Praxis

Privilegieneskalation durch Fall-Through

Autorisierungssysteme haben erhöhte Berechtigungen gewährt, wenn niedrigere Berechtigungsfälle in höher privilegierten Code durchfielen.

State-Machine-Korruption

Protokollimplementierungen sind aufgrund fehlender breaks in ungültige Zustände geraten, was Sicherheitslücken verursachte.

Datenkorruption in Handlern

Befehlshandler haben Daten korrumpiert, wenn mehrere Operationen aufgrund fehlender breaks ausgeführt wurden.


CVE-Beispiele

  • CVE-2020-0796 — SMBGhost hatte Switch-bezogene Probleme.

  • Mehrere Privilegieneskalations-CVEs durch Durchfall-Bugs.

  • Verschiedene Zustandsautomaten-Korruptionsschwachstellen.


Referenzen

  1. MITRE Corporation. "CWE-484: Omitted Break Statement in Switch." https://cwe.mitre.org/data/definitions/484.html

  2. CERT C Secure Coding Standard. "MSC17-C: Finish every set of statements associated with a case label with a break statement." https://wiki.sei.cmu.edu/confluence/display/c/