Expected Behavior Violation
Description
Expected Behavior Violation is a vulnerability where a feature, API, or function does not perform according to its specification or documented behavior. Software developers rely on documented behavior to build secure and reliable systems. When a component behaves differently than specified, callers may make incorrect assumptions that lead to security vulnerabilities, data corruption, or system failures. This affects all layers from hardware registers to high-level APIs, and can be particularly dangerous in security-critical code that depends on specific behavioral guarantees.
Risk
Expected behavior violations undermine the fundamental trust model of software development. Security code that depends on specific return values, error handling, or state changes may fail silently when components don't behave as documented. Third-party libraries with undocumented behavior variations can introduce vulnerabilities into otherwise secure code. Hardware behavior violations can enable privilege escalation when security controls operate differently than documented. The silent nature of these failures makes them difficult to detect through testing and particularly dangerous in production environments where assumptions are relied upon for security.
Solution
Thoroughly test that components behave according to their specifications. Use defensive programming to verify assumptions rather than trusting documentation alone. Implement runtime assertions to detect behavioral violations early. When using third-party components, create integration tests that verify expected behavior. Document any known behavioral deviations and their security implications. For security-critical code, verify behavior through multiple independent mechanisms. Consider using formal verification for hardware components where behavior must be guaranteed. Report behavioral violations to vendors and track them as potential security issues.
Common Consequences
| Impact | Details |
|---|---|
| Other | Scope: Other Quality Degradation - Software behaves incorrectly when components don't meet specifications, leading to unreliable operation. |
| Other | Scope: Other Varies by Context - Security implications depend on the specific behavioral violation and how the caller handles the unexpected behavior. |
Example Code
Vulnerable Code
// Vulnerable: Relies on documented strncpy behavior
#include <string.h>
#include <stdio.h>
void vulnerable_string_copy(const char *input) {
char buffer[64];
// Vulnerable: strncpy behavior differs between implementations
// Some guarantee null termination, others don't
// Linux libc: does NOT null-terminate if source >= n
// Some embedded libraries: always null-terminate
strncpy(buffer, input, sizeof(buffer));
// Vulnerable: Assumes buffer is null-terminated
// If input >= 64 chars, buffer is NOT null-terminated on Linux
printf("Copied: %s\n", buffer); // May read beyond buffer
}
// Vulnerable: Relies on timeout behavior
int vulnerable_network_read(int socket, char *buffer, size_t len) {
// Vulnerable: Assumes SO_RCVTIMEO works as documented
// Some implementations ignore it for certain socket types
// Some return different error codes than documented
struct timeval tv = {.tv_sec = 5, .tv_usec = 0};
setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
// Vulnerable: May block indefinitely if timeout not honored
ssize_t bytes = read(socket, buffer, len);
return bytes;
}
// Vulnerable: Hardware register with unexpected behavior
// Example based on actual RISC-V vulnerability
module vulnerable_csr_regfile (
input wire clk,
input wire rst_n,
input wire [11:0] csr_addr,
input wire [63:0] csr_wdata,
input wire csr_we,
output reg [63:0] csr_rdata
);
reg [63:0] mie; // Machine Interrupt Enable
reg [63:0] utval; // User Trap Value
// Vulnerable: utval incorrectly affects mie calculation
// Specification says utval should only store trap information
// But this implementation uses it in mie_d assignment
wire [63:0] mie_d = csr_wdata | utval; // VULNERABLE: utval included
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
mie <= 64'b0;
end else if (csr_we && csr_addr == 12'h304) begin
mie <= mie_d; // User can corrupt mie via utval
end
end
// Attack: User writes to utval, then triggers mie write
// Results in privilege escalation or DoS
endmodule
# Vulnerable: Relies on library's documented behavior
import json
import yaml
def vulnerable_parse_config(config_string, format_type):
# Vulnerable: Assumes parsing behavior matches documentation
if format_type == 'json':
# Vulnerable: Some JSON parsers handle duplicates differently
# Documentation may say "last value wins"
# But implementation might use first value
# {"admin": false, "admin": true} - which wins?
config = json.loads(config_string)
elif format_type == 'yaml':
# Vulnerable: yaml.load() behavior changed between versions
# Old: dangerous, allows code execution
# New: may reject certain inputs
config = yaml.load(config_string) # Unsafe!
# Vulnerable: Assumes 'get' returns None for missing keys
# Some dict-like objects raise KeyError instead
is_admin = config.get('is_admin', False)
return config, is_admin
// Vulnerable: Relies on substring behavior
public class VulnerableStringHandler {
public String extractToken(String input) {
// Vulnerable: substring behavior changed in Java 7
// Java 6: substring shares backing array (memory efficient, security issue)
// Java 7+: substring creates new array (secure but different)
String fullString = readLargeSecret(); // Contains sensitive data
// Vulnerable on Java 6: token shares memory with fullString
// Sensitive data remains accessible through token's backing array
String token = fullString.substring(0, 32);
fullString = null; // Attempts to clear sensitive data
// But on Java 6, data still accessible through token!
return token;
}
// Vulnerable: Relies on hashCode behavior
public boolean checkCache(String key) {
// Vulnerable: String hashCode implementation may vary
// Attack: craft strings with same hashCode to cause DoS
// Java's hashCode is documented but consistent collisions
// are possible, causing HashMap degradation
return cache.containsKey(key);
}
}
Fixed Code
// Fixed: Defensive string handling
#include <string.h>
#include <stdio.h>
void secure_string_copy(const char *input) {
char buffer[64];
// Fixed: Explicitly handle null termination
size_t input_len = strlen(input);
size_t copy_len = input_len < sizeof(buffer) - 1 ?
input_len : sizeof(buffer) - 1;
memcpy(buffer, input, copy_len);
buffer[copy_len] = '\0'; // Fixed: Always null-terminate
printf("Copied: %s\n", buffer);
}
// Alternative: Use strlcpy where available
void secure_string_copy_strlcpy(const char *input) {
char buffer[64];
// Fixed: strlcpy always null-terminates
size_t result = strlcpy(buffer, input, sizeof(buffer));
if (result >= sizeof(buffer)) {
// Fixed: Detect truncation
log_warning("Input truncated from %zu to %zu", result, sizeof(buffer) - 1);
}
printf("Copied: %s\n", buffer);
}
// Fixed: Defensive timeout handling
int secure_network_read(int socket, char *buffer, size_t len) {
struct pollfd pfd = {
.fd = socket,
.events = POLLIN
};
// Fixed: Use poll for reliable timeout
int poll_result = poll(&pfd, 1, 5000); // 5 second timeout
if (poll_result == 0) {
return -1; // Timeout
} else if (poll_result < 0) {
return -2; // Error
}
// Fixed: Now safe to read - data is available
ssize_t bytes = read(socket, buffer, len);
return bytes;
}
// Fixed: Hardware register with correct behavior
module secure_csr_regfile (
input wire clk,
input wire rst_n,
input wire [11:0] csr_addr,
input wire [63:0] csr_wdata,
input wire csr_we,
output reg [63:0] csr_rdata
);
reg [63:0] mie; // Machine Interrupt Enable
reg [63:0] utval; // User Trap Value
// Fixed: mie calculation does not include utval
// utval is only used for its specified purpose
wire [63:0] mie_d = csr_wdata; // FIXED: Only uses write data
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
mie <= 64'b0;
utval <= 64'b0;
end else if (csr_we) begin
case (csr_addr)
12'h304: mie <= mie_d; // mie register
12'h043: utval <= csr_wdata; // utval register (separate)
endcase
end
end
// Fixed: User cannot corrupt mie via utval
// Each register operates independently per specification
endmodule
# Fixed: Defensive parsing with behavior verification
import json
import yaml
from typing import Any, Dict, Tuple
def secure_parse_config(config_string: str, format_type: str) -> Tuple[Dict, bool]:
config: Dict[str, Any] = {}
if format_type == 'json':
# Fixed: Use strict parsing mode
config = json.loads(config_string, strict=True)
# Fixed: Verify no duplicate keys
# Custom decoder that detects duplicates
def detect_duplicates(pairs):
seen = set()
result = {}
for key, value in pairs:
if key in seen:
raise ValueError(f"Duplicate key in JSON: {key}")
seen.add(key)
result[key] = value
return result
config = json.loads(config_string, object_pairs_hook=detect_duplicates)
elif format_type == 'yaml':
# Fixed: Use safe loader
config = yaml.safe_load(config_string)
# Fixed: Verify it's a dict
if not isinstance(config, dict):
raise ValueError("Config must be a dictionary")
# Fixed: Explicit default handling
if 'is_admin' not in config:
is_admin = False
else:
is_admin = bool(config['is_admin'])
return config, is_admin
# Fixed: Verify behavior at startup
def verify_json_behavior():
"""Verify JSON parsing behavior matches expectations."""
test_input = '{"key": 1, "key": 2}'
result = json.loads(test_input)
# Document actual behavior
if result['key'] == 2:
print("JSON parser uses last-value-wins for duplicates")
else:
print("JSON parser uses first-value-wins for duplicates")
# Our secure parser should reject this
try:
secure_parse_config(test_input, 'json')
raise AssertionError("Expected duplicate key rejection")
except ValueError:
print("Duplicate key rejection working correctly")
// Fixed: Defensive string handling
import java.security.SecureRandom;
import java.util.Arrays;
public class SecureStringHandler {
public String extractToken(String input) {
byte[] fullData = readLargeSecretAsBytes();
// Fixed: Create independent copy
byte[] tokenBytes = Arrays.copyOfRange(fullData, 0, 32);
// Fixed: Clear original data
Arrays.fill(fullData, (byte) 0);
// Fixed: Create new String from independent byte array
String token = new String(tokenBytes, StandardCharsets.UTF_8);
// Fixed: Clear token bytes
Arrays.fill(tokenBytes, (byte) 0);
return token;
}
// Fixed: Use collision-resistant key handling
public boolean checkCache(String key) {
// Fixed: Use TreeMap for guaranteed O(log n) regardless of hashCode
// Or use key hashing that's collision-resistant
String hashedKey = computeSecureHash(key);
return cache.containsKey(hashedKey);
}
private String computeSecureHash(String input) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hash = md.digest(input.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(hash);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("SHA-256 not available", e);
}
}
}
CVE Examples
- CVE-2003-0187 - Timeout functionality behaved inconsistently, causing security checks to be bypassed.
- CVE-2003-0465 - strncpy behavior differences between Linux kernel and libc led to security issues.
- CVE-2005-3265 - Third-party library lacked expected buffer overflow protection, causing vulnerability in calling code.
References
- MITRE Corporation. "CWE-440: Expected Behavior Violation." https://cwe.mitre.org/data/definitions/440.html
- RISC-V Foundation. "RISC-V Privileged Architecture Specification."