Incorrect Short Circuit Evaluation
Description
Incorrect Short Circuit Evaluation is a logic vulnerability where conditional statements with multiple expressions contain side effects in non-leading expressions that may not execute due to short-circuit evaluation. In most programming languages, logical AND (&&) stops evaluating if the first operand is false, and logical OR (||) stops if the first operand is true. When subsequent expressions contain side effects (assignments, function calls, increments), these side effects may not occur, leading to unexpected program state. This creates subtle bugs that are difficult to detect and may result in security-relevant inconsistencies.
Risk
Short-circuit evaluation bugs create unpredictable program states that can have security implications. When security-critical operations (like permission checks, audit logging, or state initialization) are placed in non-leading conditional expressions, they may be skipped unexpectedly. This can lead to uninitialized variables being used, security checks being bypassed, or incomplete state updates. The vulnerability is particularly insidious because the code may work correctly in most cases but fail under specific conditions. Attackers who understand the logic flaw can craft inputs that trigger the short-circuit path, bypassing intended operations.
Solution
Avoid placing expressions with side effects in conditional statements, especially in non-leading positions. Extract side-effect operations to separate statements before the conditional. If a function call is genuinely part of the condition, ensure it has no critical side effects or that its execution is not dependent on short-circuit behavior. Use explicit conditional checks instead of relying on evaluation order. Review code for increment/decrement operators, assignments, and function calls within conditional expressions. Static analysis tools can help identify potential short-circuit evaluation issues.
Common Consequences
| Impact | Details |
|---|---|
| Integrity | Scope: Integrity Unexpected State - Program enters undefined or inconsistent state when side effects don't execute due to short-circuiting. |
| Confidentiality | Scope: Confidentiality Information Exposure - Uninitialized variables may contain sensitive residual data. |
| Availability | Scope: Availability DoS: Crash - Use of uninitialized or incorrectly initialized data may cause crashes. |
Example Code
Vulnerable Code
// Vulnerable: Decrement in condition may not execute
#define ADMIN_PRIV 0
#define STANDARD_PRIV 1
#define NUM_USERS 5
int privileges[NUM_USERS];
void initializePrivileges() {
int i = NUM_USERS;
// Vulnerable: Assignment in second operand skipped when i becomes 0
while (--i && (privileges[i] = STANDARD_PRIV)) {
// When --i evaluates to 0 (false), the assignment doesn't happen
// privileges[0] is never initialized!
}
// privileges[0] contains uninitialized/previous value
// Could be ADMIN_PRIV or garbage
}
// Vulnerable: Security check in second operand may be skipped
int isValidRequest = 0;
int isAuthenticated = 0;
int auditLogged = 0;
void processRequest(int requestType, int userId) {
// Vulnerable: audit logging skipped if first condition is true
if (requestType == FAST_PATH || (auditLogged = logRequest(userId))) {
// For FAST_PATH requests, audit logging is skipped!
processData();
}
}
// Vulnerable: Permission check skipped under certain conditions
void accessResource(int urgentMode, int userId) {
// Vulnerable: checkPermission() not called if urgentMode is true
if (urgentMode || checkPermission(userId)) {
// Urgent mode bypasses permission check!
readSensitiveData();
}
}
// Vulnerable: Initialization skipped due to short-circuit
class ConnectionManager {
bool isConnected;
Socket* socket;
public:
void connect(const char* host, bool skipValidation) {
// Vulnerable: socket initialization skipped if skipValidation is true
if (skipValidation || (socket = createSocket(host)) != nullptr) {
isConnected = true; // May be true with null socket!
}
}
void send(const char* data) {
if (isConnected) {
socket->write(data); // NULL pointer dereference!
}
}
};
// Vulnerable: Counter update skipped in OR condition
public class RateLimiter {
private int requestCount = 0;
private static final int MAX_REQUESTS = 100;
private boolean bypassEnabled = false;
public boolean allowRequest() {
// Vulnerable: requestCount not incremented when bypass enabled
if (bypassEnabled || ++requestCount <= MAX_REQUESTS) {
return true;
}
return false;
}
// requestCount becomes inaccurate when bypassEnabled=true
public int getRequestCount() {
return requestCount; // May be incorrect
}
}
Fixed Code
// Fixed: Side effects moved outside conditional
#define ADMIN_PRIV 0
#define STANDARD_PRIV 1
#define NUM_USERS 5
int privileges[NUM_USERS];
void initializePrivileges() {
// Fixed: Initialize all elements explicitly
for (int i = 0; i < NUM_USERS; i++) {
privileges[i] = STANDARD_PRIV;
}
// All elements properly initialized
}
// Alternative fix: Separate decrement from condition
void initializePrivilegesV2() {
int i = NUM_USERS;
while (i > 0) {
i--; // Decrement happens regardless
privileges[i] = STANDARD_PRIV; // Assignment always occurs
}
}
// Fixed: Security operations always execute
int isValidRequest = 0;
int isAuthenticated = 0;
int auditLogged = 0;
void processRequest(int requestType, int userId) {
// Fixed: Audit logging always happens first
auditLogged = logRequest(userId);
if (requestType == FAST_PATH || auditLogged) {
processData();
}
}
// Fixed: Permission check always performed
void accessResource(int urgentMode, int userId) {
// Fixed: Always check permission, use urgentMode for priority only
int hasPermission = checkPermission(userId);
if (hasPermission) {
if (urgentMode) {
readSensitiveDataUrgent();
} else {
readSensitiveData();
}
}
}
// Fixed: Initialization always occurs
class ConnectionManager {
bool isConnected;
Socket* socket;
public:
ConnectionManager() : isConnected(false), socket(nullptr) {}
void connect(const char* host, bool skipValidation) {
// Fixed: Always create socket first
socket = createSocket(host);
if (socket != nullptr) {
if (skipValidation || validateConnection(socket)) {
isConnected = true;
} else {
delete socket;
socket = nullptr;
}
}
}
void send(const char* data) {
if (isConnected && socket != nullptr) {
socket->write(data);
}
}
};
// Fixed: Counter always updated
public class RateLimiter {
private int requestCount = 0;
private static final int MAX_REQUESTS = 100;
private boolean bypassEnabled = false;
public boolean allowRequest() {
// Fixed: Always increment counter
requestCount++;
// Then check limits
if (bypassEnabled || requestCount <= MAX_REQUESTS) {
return true;
}
return false;
}
// Alternative: Explicit control flow
public boolean allowRequestV2() {
requestCount++;
if (bypassEnabled) {
return true;
}
return requestCount <= MAX_REQUESTS;
}
public int getRequestCount() {
return requestCount; // Always accurate
}
}
Detection Methods
- Automated Static Analysis: SAST tools can detect side effects in conditional expressions, especially assignments and increment/decrement operators.
- Code Review: Look for function calls, assignments, and operators with side effects in && and || conditions.
References
- MITRE Corporation. "CWE-768: Incorrect Short Circuit Evaluation." https://cwe.mitre.org/data/definitions/768.html
- CERT C Coding Standard. "EXP02-C. Be aware of the short-circuit behavior of the logical AND and OR operators."
- CERT C++ Coding Standard. "EXP02-CPP. Be aware of the short-circuit behavior of logical expressions."