Omitted Break Statement in Switch
Description
Omitted Break Statement in Switch occurs when a programmer fails to include a break statement at the end of a switch case, causing execution to "fall through" to the next case. In C-family languages, switch cases don't automatically terminate—without an explicit break, continue, return, or throw, execution flows into subsequent cases. While intentional fall-through is occasionally used, unintentional fall-through is a common source of bugs where multiple cases execute when only one should.
Risk
Omitted break statements cause unintended code execution and logic errors. In security contexts, this can lead to privilege escalation when a lower-privilege case falls through to higher-privilege actions. Authentication systems may grant access when they should deny it. Data can be corrupted when multiple modification operations execute. State machines can enter invalid states. The bug is particularly dangerous because the code appears correct at first glance—only careful analysis reveals the missing break.
Solution
Always include break statements or explicit fall-through comments. Use compiler warnings (-Wimplicit-fallthrough in GCC/Clang). Apply static analysis tools that detect missing breaks. Use [[fallthrough]] attribute (C++17) or fall-through comments for intentional fall-through. Consider using if-else chains instead of switch for complex logic. Some coding standards recommend avoiding fall-through entirely. In code review, verify each case ends appropriately.
Common Consequences
| Impact | Details |
|---|---|
| Access Control | Scope: Privilege Escalation Fall-through to higher privilege cases grants unintended access. |
| Integrity | Scope: Unintended Modifications Multiple case bodies execute, causing data corruption. |
| Logic | Scope: Incorrect Behavior Program flow doesn't match intended case selection. |
Example Code + Solution Code
Vulnerable Code
// VULNERABLE: Missing break causes privilege escalation
void handle_user_vulnerable(User* user, int action) {
switch (user->role) {
case GUEST:
log_access(user, "guest");
// BUG! Missing break - falls through!
case USER:
allow_read_access(user);
// BUG! Missing break - falls through!
case ADMIN:
allow_full_access(user); // Guest and User get admin access!
break;
}
}
// VULNERABLE: State machine corruption
typedef enum { INIT, CONNECTING, CONNECTED, CLOSING, CLOSED } State;
void process_state_vulnerable(Connection* conn) {
switch (conn->state) {
case INIT:
start_connection(conn);
// BUG! Falls through to CONNECTING!
case CONNECTING:
check_connection(conn);
// BUG! Falls through to CONNECTED!
case CONNECTED:
process_data(conn);
// BUG! Falls through to CLOSING!
case CLOSING:
finish_close(conn);
// BUG! Falls through to CLOSED!
case CLOSED:
cleanup_connection(conn);
break;
}
}
// VULNERABLE: Data corruption
void process_command_vulnerable(int cmd, Data* data) {
switch (cmd) {
case CMD_RESET:
reset_data(data);
// BUG! Falls through to CMD_MODIFY!
case CMD_MODIFY:
modify_data(data);
// BUG! Falls through to CMD_DELETE!
case CMD_DELETE:
delete_data(data); // RESET also deletes!
break;
}
}
// VULNERABLE: Authentication bypass
int authenticate_vulnerable(Request* req) {
switch (req->auth_type) {
case AUTH_NONE:
return AUTH_DENIED;
// This one is OK - return ends the case
case AUTH_BASIC:
if (!check_basic_auth(req))
return AUTH_DENIED;
// BUG! If check passes, falls through!
case AUTH_ADMIN:
grant_admin_access(req); // Basic auth users get admin!
return AUTH_GRANTED;
}
return AUTH_DENIED;
}
// VULNERABLE: Multiple actions executed
void handle_error_vulnerable(int error_code) {
switch (error_code) {
case ERR_WARNING:
log_warning();
// BUG! Falls through!
case ERR_ERROR:
log_error();
// BUG! Falls through!
case ERR_CRITICAL:
notify_admin();
// BUG! Falls through!
case ERR_FATAL:
shutdown_system(); // WARNING triggers shutdown!
break;
}
}
// VULNERABLE: Partial initialization
void init_config_vulnerable(Config* cfg, int level) {
switch (level) {
case 3:
cfg->advanced = load_advanced();
// BUG! Falls through!
case 2:
cfg->extended = load_extended();
// BUG! Falls through!
case 1:
cfg->basic = load_basic();
// BUG! Falls through to default!
default:
cfg->minimal = load_minimal();
break;
}
// Level 3 loads ALL configs, level 1 loads basic AND minimal
// This might be intentional but is unclear!
}
// VULNERABLE: C++ with same issues
class VulnerableHandler {
public:
void handle(Event event) {
switch (event.type) {
case EventType::CLICK:
handleClick(event);
// BUG! Falls through!
case EventType::DOUBLE_CLICK:
handleDoubleClick(event); // CLICK also triggers!
// BUG! Falls through!
case EventType::RIGHT_CLICK:
handleRightClick(event); // All clicks trigger!
break;
}
}
int getPermission(UserRole role) {
int permission = PERM_NONE;
switch (role) {
case UserRole::GUEST:
permission |= PERM_VIEW;
// BUG! Falls through!
case UserRole::USER:
permission |= PERM_EDIT;
// BUG! Falls through!
case UserRole::ADMIN:
permission |= PERM_DELETE; // Everyone can delete!
break;
}
return permission;
}
};
// VULNERABLE: Exception handling
void processValue_vulnerable(int value) {
switch (value) {
case -1:
throw std::invalid_argument("Value cannot be -1");
// OK - throw exits
case 0:
std::cout << "Warning: zero value\n";
// BUG! Falls through!
case 1:
processNormal(); // 0 and 1 both process as normal!
break;
}
}
// JavaScript has same fall-through behavior
// VULNERABLE: Role escalation
function handleUserVulnerable(user) {
switch (user.role) {
case 'guest':
logGuest(user);
// BUG! Falls through!
case 'user':
enableBasicFeatures();
// BUG! Falls through!
case 'admin':
enableAdminFeatures(); // Guests get admin!
break;
}
}
// VULNERABLE: Status handling
function updateStatus(status) {
switch (status) {
case 'pending':
notify('Processing...');
// BUG! Falls through!
case 'processing':
startTimer();
// BUG! Falls through!
case 'completed':
showResult(); // Pending shows result immediately!
break;
}
}
Fixed Code
// SAFE: Explicit break in each case
void handle_user_safe(User* user, int action) {
switch (user->role) {
case GUEST:
log_access(user, "guest");
allow_guest_access(user);
break; // Explicit break
case USER:
log_access(user, "user");
allow_read_access(user);
break; // Explicit break
case ADMIN:
log_access(user, "admin");
allow_full_access(user);
break; // Explicit break
default:
log_access(user, "unknown");
deny_access(user);
break;
}
}
// SAFE: State machine with explicit 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;
}
}
// SAFE: Commands with explicit 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;
}
}
// SAFE: Authentication with proper flow
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;
}
}
// SAFE: Error handling with explicit 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;
}
}
// SAFE: Intentional fall-through with comment (if truly needed)
void init_config_intentional(Config* cfg, int level) {
cfg->minimal = load_minimal();
switch (level) {
case 3:
cfg->advanced = load_advanced();
/* FALLTHROUGH */ // Explicit comment for intentional fall-through
case 2:
cfg->extended = load_extended();
/* FALLTHROUGH */
case 1:
cfg->basic = load_basic();
break;
default:
break;
}
}
// Better: avoid fall-through with cumulative logic
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();
}
}
// SAFE: C++ with [[fallthrough]] attribute (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;
}
}
// If fall-through is intentional, use [[fallthrough]]
void logLevel(int level) {
switch (level) {
case 3:
std::cout << "DEBUG: ";
[[fallthrough]]; // C++17 attribute
case 2:
std::cout << "INFO: ";
[[fallthrough]];
case 1:
std::cout << "MESSAGE\n";
break;
default:
break;
}
}
};
// SAFE: Using enum class with exhaustive switch
enum class Status { PENDING, PROCESSING, COMPLETED };
std::string getStatusMessage(Status status) {
switch (status) {
case Status::PENDING:
return "Waiting...";
case Status::PROCESSING:
return "Working...";
case Status::COMPLETED:
return "Done!";
}
// Compiler warns if case is missing with enum class
}
// SAFE: JavaScript with explicit 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;
}
}
// Or use object mapping to avoid switch entirely
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 rule
// "no-fallthrough": "error"
Exploited in the Wild
Privilege Escalation via Fall-Through
Authorization systems have granted elevated privileges when lower-privilege cases fell through to higher-privilege code.
State Machine Corruption
Protocol implementations have entered invalid states due to missing breaks, causing security vulnerabilities.
Data Corruption in Handlers
Command handlers have corrupted data when multiple operations executed due to missing breaks.
Tools to test/exploit
-
GCC -Wimplicit-fallthrough — warns about implicit fall-through.
-
Clang — similar warning with -Wimplicit-fallthrough.
-
ESLint no-fallthrough — catches missing breaks in JavaScript.
-
Coverity — detects missing breaks.
CVE Examples
-
CVE-2020-0796 — SMBGhost had switch-related issues.
-
Multiple privilege escalation CVEs from fall-through bugs.
-
Various state machine corruption vulnerabilities.
References
-
MITRE. "CWE-484: Omitted Break Statement in Switch." https://cwe.mitre.org/data/definitions/484.html
-
CERT C. "MSC17-C: Finish every set of statements associated with a case label with a break statement." https://wiki.sei.cmu.edu/confluence/display/c/