Unconditional Control Flow Transfer outside of Switch Block
Description
Unconditional Control Flow Transfer outside of Switch Block occurs when a product performs unconditional control flow transfers (such as 'goto' statements) in code outside of a branching structure such as a switch block. While goto statements have legitimate uses in switch blocks for fall-through behavior or in error handling in C, their use elsewhere creates spaghetti code that is difficult to understand, maintain, and analyze for security vulnerabilities. The unpredictable control flow makes it nearly impossible to reason about code behavior.
Risk
Unconditional control flow transfers outside controlled structures have security implications. The unpredictable flow makes security auditing extremely difficult. Code analysis tools may fail to properly trace control flow, missing vulnerabilities. Security checks may be bypassed by jumping over them. Resource cleanup code may be skipped, leading to leaks. Exception handling may not work correctly with goto-based control flow. Developers maintaining the code may introduce vulnerabilities due to misunderstanding the flow. The complexity created can hide backdoors or malicious code.
Solution
Eliminate goto statements outside of switch blocks. Use structured control flow: loops, conditionals, functions. Replace goto-based error handling with proper exception handling. Use early returns instead of goto for cleanup. Apply the extract method refactoring to simplify complex functions. Use RAII (Resource Acquisition Is Initialization) in C++ for resource cleanup. In C, consider using goto only for centralized cleanup at function end. Use static analysis tools to detect goto usage. Establish coding standards that restrict or prohibit goto.
Common Consequences
| Impact | Details |
|---|---|
| Other | Scope: Other Reduce Maintainability - Goto-based code is extremely difficult to understand and modify safely. |
| Other | Scope: Other Increase Analytical Complexity - Static analysis and security auditing become unreliable. |
| Other | Scope: Other Quality Degradation - Spaghetti code is prone to bugs including security vulnerabilities. |
Example Code
Vulnerable Code
// Vulnerable: Goto statements creating 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; // Jump to middle of code
}
buffer = malloc(len + 1);
if (buffer == NULL) {
goto error;
}
memcpy(buffer, input, len);
buffer[len] = '\0';
// Vulnerable: Security check might be bypassed
if (is_malicious(buffer)) {
goto label_b; // Jumps somewhere unexpected
}
file = fopen("/tmp/data.txt", "w");
if (file == NULL) {
goto cleanup;
}
label_a:
// Vulnerable: Can reach here with uninitialized buffer!
result = process_buffer(buffer); // buffer might be NULL
label_b:
// Vulnerable: File might not be open
if (file) {
fprintf(file, "%s", buffer);
}
// Vulnerable: Cleanup might be skipped
goto done;
error:
result = -1;
// Falls through to cleanup
cleanup:
if (buffer) free(buffer);
if (file) fclose(file);
done:
return result;
}
// Vulnerable: Goto jumping over security checks
int vulnerable_authenticate(const char* username, const char* password) {
int authenticated = 0;
if (username == NULL) {
goto end;
}
// Vulnerable: This check can be bypassed by jumping to 'skip_check'
if (!validate_username_format(username)) {
goto error;
}
if (password == NULL) {
goto skip_check; // Bypasses password validation!
}
if (!verify_password(username, password)) {
goto error;
}
skip_check:
// Can reach here without password verification!
authenticated = 1;
log_successful_auth(username);
goto end;
error:
log_failed_auth(username);
end:
return authenticated;
}
// Vulnerable: Complex goto web
void vulnerable_state_machine(int initial_state) {
int state = initial_state;
int data = 0;
// Vulnerable: Unstructured state machine
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; // Skip state_b
state = 1;
goto state_b; // Confusing: why not just fall through?
state_b:
process_data(data);
if (has_more_data()) goto start; // Loop via goto
state = 2;
goto state_c;
state_c:
// Vulnerable: Can reach here with data == 0 if jumped from state_a
finalize(data);
goto end;
error:
// Error handling mixed into the flow
log_error();
// Falls through to end - is this intentional?
end:
cleanup();
}
Fixed Code
// Fixed: Structured control flow with proper error handling
int fixed_process_data(char* input, size_t len) {
int result = -1; // Assume failure
char* buffer = NULL;
FILE* file = NULL;
// Fixed: Early return for invalid input
if (input == NULL) {
return -1;
}
// Fixed: Structured allocation and cleanup
buffer = malloc(len + 1);
if (buffer == NULL) {
return -1;
}
memcpy(buffer, input, len);
buffer[len] = '\0';
// Fixed: Security check with structured flow
if (is_malicious(buffer)) {
// Handle malicious input explicitly
log_security_event("Malicious input detected");
goto cleanup; // Only goto is for cleanup
}
file = fopen("/tmp/data.txt", "w");
if (file == NULL) {
goto cleanup; // Consistent cleanup path
}
// Fixed: Process with valid, checked data
result = process_buffer(buffer);
if (result >= 0) {
fprintf(file, "%s", buffer);
}
cleanup:
// Fixed: Single cleanup point at end of function
if (buffer) {
free(buffer);
}
if (file) {
fclose(file);
}
return result;
}
// Fixed: Structured authentication without goto bypass risk
int fixed_authenticate(const char* username, const char* password) {
// Fixed: Early validation with clear flow
if (username == NULL) {
log_failed_auth("null_user");
return 0;
}
if (!validate_username_format(username)) {
log_failed_auth(username);
return 0;
}
// Fixed: Password is always required
if (password == NULL) {
log_failed_auth(username);
return 0;
}
if (!verify_password(username, password)) {
log_failed_auth(username);
return 0;
}
// Fixed: Only reach here if ALL checks pass
log_successful_auth(username);
return 1;
}
// Fixed: Proper state machine using switch or function pointers
typedef enum {
STATE_INIT,
STATE_PROCESS,
STATE_FINALIZE,
STATE_ERROR,
STATE_DONE
} State;
typedef State (*StateHandler)(int* data);
// Fixed: State handlers as separate functions
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;
}
// Fixed: Structured state machine
void fixed_state_machine(int initial_state) {
State state = (State)initial_state;
int data = 0;
// State handler lookup table
static StateHandler handlers[] = {
[STATE_INIT] = handle_init,
[STATE_PROCESS] = handle_process,
[STATE_FINALIZE] = handle_finalize,
[STATE_ERROR] = handle_error
};
// Fixed: Clear loop structure
while (state != STATE_DONE) {
if (state < STATE_DONE && handlers[state]) {
state = handlers[state](&data);
} else {
state = STATE_ERROR;
}
}
cleanup();
}
// Alternative: Switch-based state machine (goto only in switch is acceptable)
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();
}
// Fixed: C++ RAII approach (best for C++)
#include <memory>
#include <fstream>
#include <string>
class DataProcessor {
public:
int processData(const char* input, size_t len) {
// Fixed: RAII - no explicit cleanup needed
if (!input) {
return -1;
}
// Fixed: Smart pointer handles memory
std::unique_ptr<char[]> buffer(new char[len + 1]);
std::memcpy(buffer.get(), input, len);
buffer[len] = '\0';
// Fixed: Security check with normal control flow
if (isMalicious(buffer.get())) {
logSecurityEvent("Malicious input detected");
return -1; // Buffer automatically freed
}
// Fixed: RAII file handling
std::ofstream file("/tmp/data.txt");
if (!file) {
return -1; // Buffer automatically freed
}
int result = processBuffer(buffer.get());
if (result >= 0) {
file << buffer.get();
}
return result;
// Buffer and file automatically cleaned up
}
};
CVE Examples
Goto-related control flow issues have contributed to vulnerabilities, though specific CVEs typically describe the resulting weakness (buffer overflow, bypass) rather than the goto usage itself.
Related CWEs
- CWE-1120: Excessive Code Complexity (parent)
- CWE-1226: Complexity Issues (category member)
- CWE-691: Insufficient Control Flow Management (related)
References
- 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."