Reliance on Undefined, Unspecified, or Implementation-Defined Behavior
Description
Reliance on Undefined, Unspecified, or Implementation-Defined Behavior is a vulnerability where software uses an API function, data structure, or other programming entity in a way that relies on properties that are not always guaranteed to hold for that entity. Undefined behavior in languages like C/C++ means the standard places no requirements on the implementation—anything can happen. Implementation-defined behavior means behavior that may vary between platforms or compilers. When code depends on such behaviors, it may work correctly on one system but fail catastrophically on another, potentially introducing security vulnerabilities.
Risk
Reliance on undefined behavior creates serious and unpredictable security risks. Compilers may optimize code assuming undefined behavior never occurs, removing security checks the developer expected to execute. Code that "works" during testing may fail in production on different platforms or compiler versions. Common examples include signed integer overflow (undefined in C), accessing uninitialized memory, dereferencing null pointers, and returning pointers to stack variables. When these behaviors change—during compiler upgrade, platform migration, or optimization level changes—the result can be crashes, memory corruption, or security vulnerabilities that didn't exist before.
Solution
Avoid code patterns that rely on undefined, unspecified, or implementation-defined behavior. Use compiler warnings and static analysis tools to detect such patterns. Follow language standards strictly—don't assume behaviors that aren't guaranteed. Use explicit checks before potentially undefined operations (like checking for overflow before arithmetic). Never return pointers to stack-allocated variables. Initialize all variables before use. Avoid assumptions about memory layout, endianness, or type sizes unless using platform-specific code. Test on multiple platforms and with multiple compilers.
Common Consequences
| Impact | Details |
|---|---|
| Integrity | Scope: Integrity Unexpected State - Code may behave differently than expected when behavior changes. |
| Availability | Scope: Availability DoS: Crash, Exit, or Restart - Undefined behavior often leads to crashes. |
| Other | Scope: Other Reduce Maintainability - Code depending on undefined behavior is fragile and hard to maintain. |
Example Code
Vulnerable Code
// Vulnerable: Fixed address function pointer
#include <stdio.h>
int (*functionPtr)(float, char, char) = (void*)0x08040000;
void vulnerable_fixed_address() {
// Vulnerable: Assumes function exists at hardcoded address
// Address may not be valid on different systems/runs
int result = (*functionPtr)(12.0f, 'a', 'b');
// Could crash, execute arbitrary code, or behave unpredictably
// Attacker could potentially map code to this address
}
// Vulnerable: Return pointer to stack variable
char* vulnerable_stack_return() {
char name[256]; // Stack-allocated
// Fill in the name
strcpy(name, "LocalData");
// Vulnerable: Returning pointer to stack memory
return name;
// After function returns, 'name' is deallocated
// Returned pointer is dangling - undefined behavior
}
void use_vulnerable() {
char* result = vulnerable_stack_return();
// 'result' points to deallocated stack memory
printf("%s\n", result); // Undefined behavior - may crash or print garbage
}
// Vulnerable: Signed integer overflow (undefined in C)
#include <limits.h>
int vulnerable_overflow_check(int value) {
// Vulnerable: Signed overflow is undefined behavior
// Compiler may optimize this check away!
if (value + 1 < value) {
// Overflow occurred
return -1;
}
return value + 1;
}
// Compiler may transform to:
int optimized_vulnerable(int value) {
// Compiler: "Signed overflow is undefined, so it never happens"
// Check removed entirely!
return value + 1; // Overflow check gone
}
// Vulnerable: NULL pointer dereference after check
void vulnerable_null_check(int* ptr) {
int value = *ptr; // Dereference before check
if (ptr == NULL) {
// Vulnerable: Compiler may remove this check
// Reasoning: ptr was already dereferenced, so if program
// reached here, ptr must not be NULL (or undefined behavior)
return;
}
process(value);
}
// Vulnerable: Uninitialized variable
void vulnerable_uninitialized() {
int flag; // Not initialized
if (some_condition()) {
flag = 1;
}
// Vulnerable: flag may be uninitialized
if (flag) { // Undefined behavior if flag wasn't set
do_something();
}
}
// Vulnerable: Accessing array out of bounds
void vulnerable_bounds(int index) {
int array[10];
// Vulnerable: No bounds check
// If index >= 10 or < 0, undefined behavior
array[index] = 42;
}
// Vulnerable: Type punning through union (implementation-defined)
void vulnerable_type_pun() {
union {
float f;
int i;
} converter;
converter.f = 3.14f;
// Implementation-defined: reading different member than was written
int bits = converter.i;
// May work on some platforms, fail on others
}
// Vulnerable: Shift by width of type
void vulnerable_shift(unsigned int value) {
// Vulnerable: Shifting by >= width of type is undefined
unsigned int shifted = value << 32; // If int is 32 bits, undefined!
// Different compilers/platforms may:
// - Return 0
// - Return value unchanged
// - Return garbage
// - Crash
}
// Vulnerable: Modifying string literal
void vulnerable_string_literal() {
char* str = "Hello"; // Points to read-only memory (typically)
// Vulnerable: Modifying string literal is undefined
str[0] = 'J'; // May crash, may work, may corrupt other data
}
// Vulnerable: Evaluation order dependency
int vulnerable_sequence(int* p) {
// Vulnerable: Order of evaluation is unspecified
return (*p++) + (*p++); // Result varies by compiler
}
Fixed Code
// Fixed: Use function pointers properly
#include <stdio.h>
typedef int (*FunctionPtr)(float, char, char);
int secure_function(float f, char a, char b) {
return (int)(f + a + b);
}
void secure_function_pointer() {
// Fixed: Point to a known, valid function
FunctionPtr functionPtr = secure_function;
int result = functionPtr(12.0f, 'a', 'b');
printf("Result: %d\n", result);
}
// Fixed: Return heap-allocated or static memory
char* secure_return_string() {
// Fixed: Allocate on heap
char* name = malloc(256);
if (name == NULL) return NULL;
strcpy(name, "HeapAllocatedData");
return name; // Caller must free
}
// Alternative: Use static buffer (with limitations)
char* secure_static_return() {
static char name[256]; // Static storage - persists
strcpy(name, "StaticData");
return name; // Valid but not thread-safe
}
// Best: Caller provides buffer
int secure_fill_buffer(char* buffer, size_t size) {
if (buffer == NULL || size < 10) return -1;
strncpy(buffer, "SafeData", size - 1);
buffer[size - 1] = '\0';
return 0;
}
// Fixed: Safe overflow check using unsigned or builtin
#include <limits.h>
#include <stdint.h>
int secure_overflow_check(int value) {
// Fixed: Check before overflow occurs
if (value == INT_MAX) {
return -1; // Would overflow
}
return value + 1;
}
// Alternative: Use compiler builtins
int secure_overflow_builtin(int value) {
int result;
// GCC/Clang builtin for checked arithmetic
if (__builtin_add_overflow(value, 1, &result)) {
return -1; // Overflow occurred
}
return result;
}
// Fixed: Check NULL before dereference
void secure_null_check(int* ptr) {
// Fixed: Check BEFORE dereference
if (ptr == NULL) {
return;
}
int value = *ptr; // Now safe
process(value);
}
// Fixed: Initialize all variables
void secure_initialized() {
int flag = 0; // Fixed: Initialize
if (some_condition()) {
flag = 1;
}
// Now flag is always defined
if (flag) {
do_something();
}
}
// Fixed: Bounds checking
void secure_bounds(int index, int* array, size_t array_size) {
// Fixed: Validate bounds
if (index < 0 || (size_t)index >= array_size) {
return; // Out of bounds
}
array[index] = 42;
}
// Fixed: Proper type access with memcpy
#include <string.h>
void secure_type_access() {
float f = 3.14f;
int bits;
// Fixed: memcpy is well-defined for type punning
memcpy(&bits, &f, sizeof(bits));
// Now bits contains the bit representation of f
}
// Fixed: Safe shift operations
void secure_shift(unsigned int value, unsigned int shift_amount) {
// Fixed: Check shift amount
if (shift_amount >= sizeof(value) * 8) {
// Shift too large - handle appropriately
return;
}
unsigned int shifted = value << shift_amount;
}
// Fixed: Use modifiable array instead of string literal
void secure_string() {
// Fixed: Array is modifiable
char str[] = "Hello"; // Copied to stack
str[0] = 'J'; // Safe to modify
printf("%s\n", str); // Prints "Jello"
}
// Fixed: Clear evaluation order
int secure_sequence(int* p) {
// Fixed: Explicit ordering
int first = *p;
p++;
int second = *p;
p++;
return first + second;
}
CVE Examples
- CVE-2006-1902: Change in C compiler behavior caused buffer overflows in programs depending on undefined behavior.
- CVE-2008-1685: Compiler optimized away security check that relied on undefined signed overflow behavior.
References
- MITRE Corporation. "CWE-758: Reliance on Undefined, Unspecified, or Implementation-Defined Behavior." https://cwe.mitre.org/data/definitions/758.html
- ISO/IEC 9899 C Language Standard - Undefined Behavior.
- CERT C Coding Standard. "MSC15-C. Do not depend on undefined behavior."