Loop Condition Value Update within the Loop
Description
Loop Condition Value Update within the Loop occurs when a product uses a loop with a control flow condition based on a value that is updated within the body of the loop. This creates code that is harder to understand and analyze because the loop termination condition is not immediately apparent from the loop header. The dynamic modification of loop control variables within the loop body makes it difficult to reason about when and how the loop will terminate.
Risk
Modifying loop control variables within loop bodies has indirect security implications. The code becomes harder to analyze, potentially hiding bugs including security vulnerabilities. Loop behavior becomes unpredictable, making it harder to verify correct termination. Infinite loops become more likely if updates are incorrect or conditional. Code reviewers may miss security issues due to the complexity. Static analysis tools may have difficulty analyzing such loops. Off-by-one errors are more likely with complex loop control. The unpredictable loop behavior can be exploited for denial of service.
Solution
Define loop boundaries clearly in the loop header where possible. Use standard loop patterns (for-each, iterator) that don't require manual control variable management. Avoid modifying loop control variables within the loop body. If modification is necessary, document clearly why and ensure correct behavior. Use break statements for early exit rather than modifying control variables. Consider restructuring as a while loop with clear termination conditions. Use static analysis tools to identify problematic loop patterns. Apply code review guidelines that flag such patterns.
Common Consequences
| Impact | Details |
|---|---|
| Other | Scope: Other Reduce Maintainability - Loop behavior is harder to understand and modify safely. |
| Other | Scope: Other Increase Analytical Complexity - Security analysis is complicated by dynamic loop control. |
| Availability | Scope: Availability DoS: Infinite Loop - Incorrect updates can cause infinite loops. |
Example Code
Vulnerable Code
// Vulnerable: Loop condition variable modified inside loop body
public class VulnerableLoopProcessor {
public void processItems(List<String> items) {
int i = 0;
// Vulnerable: 'i' is modified inside the loop body
while (i < items.size()) {
String item = items.get(i);
if (item.isEmpty()) {
// Skip empty items - but this changes loop control!
i += 2; // Vulnerable: modifying loop variable
continue;
}
if (item.startsWith("SKIP_")) {
// Another modification path
i = findNextNonSkip(items, i); // Vulnerable: complex update
continue;
}
processItem(item);
i++; // Normal increment hidden in body
}
// Hard to know: Will this loop terminate? How many iterations?
}
public int countValidItems(List<Item> items) {
int count = 0;
int index = 0;
// Vulnerable: Multiple modifications of loop control
while (index < items.size()) {
Item item = items.get(index);
if (item.isDeleted()) {
index++;
continue;
}
if (item.isBatch()) {
// Process all items in batch
count += item.getBatchSize();
index += item.getBatchSize(); // Jump forward
} else {
count++;
index++;
}
// Bug: If getBatchSize() returns 0, infinite loop!
}
return count;
}
}
# Vulnerable: Loop control modified in multiple places
class VulnerableDataParser:
def parse_records(self, data):
i = 0
records = []
# Vulnerable: 'i' modified throughout loop body
while i < len(data):
if data[i] == '\n':
i += 1 # Skip newlines
continue
if data[i] == '#':
# Skip comments - find end of line
while i < len(data) and data[i] != '\n':
i += 1 # Nested modification!
continue
if data[i] == '"':
# Quoted string - complex advancement
i += 1
start = i
while i < len(data) and data[i] != '"':
if data[i] == '\\':
i += 2 # Skip escaped char
else:
i += 1
records.append(data[start:i])
i += 1 # Skip closing quote
else:
# Regular token
start = i
while i < len(data) and data[i] not in ' \t\n':
i += 1
records.append(data[start:i])
# Extremely difficult to verify:
# - Does this terminate for all inputs?
# - Are there off-by-one errors?
# - What if escape sequence at end of data?
return records
def find_patterns(self, text, patterns):
position = 0
matches = []
# Vulnerable: Position updated in complex ways
while position < len(text):
matched = False
for pattern in patterns:
if text[position:].startswith(pattern):
matches.append((position, pattern))
position += len(pattern) # Advance past match
matched = True
break
if not matched:
position += 1
# Bug potential: If pattern is empty string, infinite loop!
return matches
// Vulnerable: C loop with modified control variable
void vulnerable_process_buffer(char* buffer, size_t size) {
size_t i = 0;
// Vulnerable: 'i' modified inside loop
while (i < size) {
if (buffer[i] == '\0') {
i++; // Skip null bytes
continue;
}
if (buffer[i] == ESCAPE_CHAR) {
// Process escape sequence
i++; // Move past escape char
if (i >= size) break; // Bounds check
switch (buffer[i]) {
case 'n':
handle_newline();
i++; // Modification here
break;
case 'x':
// Hex escape - read 2 more chars
i++; // Modification here
if (i + 2 <= size) {
handle_hex(buffer[i], buffer[i+1]);
i += 2; // And here
}
break;
default:
i++; // And here too
}
} else {
handle_char(buffer[i]);
i++; // Normal case
}
}
// Security concern: Hard to verify bounds are respected in all paths
}
Fixed Code
// Fixed: Clear loop patterns with predictable control flow
public class FixedLoopProcessor {
public void processItems(List<String> items) {
// Fixed: Use for-each when modification not needed
for (String item : items) {
if (item.isEmpty()) {
continue; // Just skip, don't modify indices
}
if (item.startsWith("SKIP_")) {
continue;
}
processItem(item);
}
}
// Fixed: When index manipulation is needed, use clear structure
public void processWithSkips(List<String> items) {
// Fixed: Use explicit iterator with clear control
ListIterator<String> iterator = items.listIterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (item.isEmpty()) {
continue; // Iterator handles advancement
}
if (item.startsWith("BATCH_")) {
int batchSize = getBatchSize(item);
processBatchItem(item);
// Fixed: Clear, bounded skip operation
skipItems(iterator, batchSize - 1); // -1 because we already consumed one
continue;
}
processItem(item);
}
}
private void skipItems(ListIterator<?> iterator, int count) {
for (int i = 0; i < count && iterator.hasNext(); i++) {
iterator.next();
}
}
public int countValidItems(List<Item> items) {
// Fixed: Use stream with clear logic
return (int) items.stream()
.filter(item -> !item.isDeleted())
.mapToInt(item -> item.isBatch() ? item.getBatchSize() : 1)
.filter(size -> size > 0) // Guard against zero-size batches
.sum();
}
}
# Fixed: Clear loop patterns with predictable control
class FixedDataParser:
def parse_records(self, data: str) -> list:
# Fixed: Use generator/iterator pattern
return list(self._tokenize(data))
def _tokenize(self, data: str):
"""Generator that yields tokens with clear state management."""
pos = 0
length = len(data)
while pos < length:
char = data[pos]
if char in ' \t\n':
pos += 1
continue
if char == '#':
pos = self._skip_to_newline(data, pos, length)
continue
if char == '"':
token, pos = self._parse_quoted_string(data, pos, length)
yield token
else:
token, pos = self._parse_token(data, pos, length)
yield token
def _skip_to_newline(self, data: str, pos: int, length: int) -> int:
"""Return position after newline. Clear, single-purpose function."""
while pos < length and data[pos] != '\n':
pos += 1
return pos + 1 if pos < length else pos
def _parse_quoted_string(self, data: str, pos: int, length: int) -> tuple:
"""Parse quoted string. Returns (token, new_position)."""
assert data[pos] == '"'
pos += 1 # Skip opening quote
chars = []
while pos < length and data[pos] != '"':
if data[pos] == '\\' and pos + 1 < length:
chars.append(self._unescape(data[pos + 1]))
pos += 2
else:
chars.append(data[pos])
pos += 1
end_pos = pos + 1 if pos < length else pos # Skip closing quote
return ''.join(chars), end_pos
def _parse_token(self, data: str, pos: int, length: int) -> tuple:
"""Parse unquoted token. Returns (token, new_position)."""
start = pos
while pos < length and data[pos] not in ' \t\n':
pos += 1
return data[start:pos], pos
def find_patterns(self, text: str, patterns: list) -> list:
# Fixed: Guard against empty patterns and use clear logic
valid_patterns = [p for p in patterns if p] # Remove empty
if not valid_patterns:
return []
matches = []
position = 0
while position < len(text):
# Fixed: Use helper method with clear semantics
match = self._find_pattern_at(text, position, valid_patterns)
if match:
pattern, length = match
matches.append((position, pattern))
position += length
else:
position += 1
return matches
def _find_pattern_at(self, text: str, pos: int, patterns: list):
"""Find pattern at position. Returns (pattern, length) or None."""
for pattern in patterns:
if text[pos:pos + len(pattern)] == pattern:
return pattern, len(pattern)
return None
// Fixed: Clear loop structure with explicit state machine
typedef enum {
STATE_NORMAL,
STATE_ESCAPE,
STATE_HEX_FIRST,
STATE_HEX_SECOND
} ParseState;
void fixed_process_buffer(char* buffer, size_t size) {
ParseState state = STATE_NORMAL;
char hex_first = 0;
// Fixed: Simple for loop with clear increment
for (size_t i = 0; i < size; i++) {
char c = buffer[i];
switch (state) {
case STATE_NORMAL:
if (c == '\0') {
continue;
} else if (c == ESCAPE_CHAR) {
state = STATE_ESCAPE;
} else {
handle_char(c);
}
break;
case STATE_ESCAPE:
if (c == 'n') {
handle_newline();
state = STATE_NORMAL;
} else if (c == 'x') {
state = STATE_HEX_FIRST;
} else {
// Unknown escape - treat as literal
handle_char(c);
state = STATE_NORMAL;
}
break;
case STATE_HEX_FIRST:
hex_first = c;
state = STATE_HEX_SECOND;
break;
case STATE_HEX_SECOND:
handle_hex(hex_first, c);
state = STATE_NORMAL;
break;
}
}
// Fixed: Handle incomplete escape sequences at end
if (state != STATE_NORMAL) {
handle_incomplete_sequence(state);
}
}
// Fixed: Alternative using explicit position management
typedef struct {
const char* data;
size_t size;
size_t pos;
} BufferReader;
char reader_peek(BufferReader* r) {
return r->pos < r->size ? r->data[r->pos] : '\0';
}
char reader_advance(BufferReader* r) {
return r->pos < r->size ? r->data[r->pos++] : '\0';
}
bool reader_has_more(BufferReader* r) {
return r->pos < r->size;
}
void fixed_process_with_reader(char* buffer, size_t size) {
BufferReader reader = { buffer, size, 0 };
// Fixed: Clear operations with reader abstraction
while (reader_has_more(&reader)) {
char c = reader_advance(&reader);
if (c == '\0') {
continue;
}
if (c == ESCAPE_CHAR) {
process_escape_sequence(&reader);
} else {
handle_char(c);
}
}
}
void process_escape_sequence(BufferReader* r) {
if (!reader_has_more(r)) {
handle_incomplete_sequence(STATE_ESCAPE);
return;
}
char c = reader_advance(r);
if (c == 'n') {
handle_newline();
} else if (c == 'x') {
process_hex_escape(r);
} else {
handle_char(c);
}
}
CVE Examples
This CWE is marked as PROHIBITED for direct CVE mapping as it represents a code quality/maintainability concern rather than a direct security vulnerability.
Related CWEs
- CWE-1120: Excessive Code Complexity (parent)
- CWE-1226: Complexity Issues (category member)
- CWE-835: Loop with Unreachable Exit Condition (related)
References
- MITRE Corporation. "CWE-1095: Loop Condition Value Update within the Loop." https://cwe.mitre.org/data/definitions/1095.html
- CISQ Quality Measures - Maintainability.
- Martin, Robert C. "Clean Code" - Simple Loop Structures.