Expression is Always True
Description
Expression is Always True occurs when a conditional expression evaluates to true under all possible circumstances. This is the counterpart to CWE-570. Common patterns include comparing unsigned values against negative lower bounds (always true), redundant checks for conditions already established, comparisons against values outside a type's range, and logical expressions with tautological components. While the condition always passes, the "else" branch or alternative handling never executes.
Risk
Always-true expressions indicate logic errors with potential security implications. Input validation that always passes provides no filtering. Bounds checks that always succeed don't prevent overflows. Bypass conditions that always trigger skip important processing. Error handling alternatives that never execute leave errors unhandled. The inverse—code that should execute conditionally executing unconditionally—can cause unintended state changes and security vulnerabilities.
Solution
Enable compiler warnings for tautological comparisons. Use static analysis tools to identify always-true conditions. Understand type ranges—unsigned integers are always >= 0. Review compound conditions for tautological components. Remove redundant checks that always pass. If the check was meant to catch certain values, fix the logic to actually catch them. Verify that security checks can actually fail when they should.
Common Consequences
| Impact | Details |
|---|---|
| Security | Scope: No Input Validation Validation that always passes doesn't prevent malicious input. |
| Logic | Scope: Dead Branches Else branches that never execute serve no purpose. |
| Quality | Scope: Misleading Code Conditions that always pass give false sense of checking. |
Example Code + Solution Code
Vulnerable Code
// VULNERABLE: Unsigned always >= 0
int validate_size_vulnerable(size_t size) {
if (size >= 0) { // ALWAYS TRUE! size_t is unsigned
return 1; // Always returns valid
}
return 0; // Dead code!
}
// VULNERABLE: Unsigned comparison to negative
void check_length_vulnerable(unsigned int len) {
if (len > -1) { // ALWAYS TRUE! -1 converts to UINT_MAX
process(len); // Always executes
}
// Else never taken
}
// VULNERABLE: Tautology in OR condition
int validate_input_vulnerable(int value) {
if (value >= 0 || value < 0) { // ALWAYS TRUE!
return 1; // "Validation" always passes
}
return 0; // Dead code
}
// VULNERABLE: Redundant after bounds check
void process_vulnerable(int value) {
if (value >= 0 && value <= 100) {
// value is in [0, 100]
if (value >= 0) { // ALWAYS TRUE! Already checked!
handle_valid();
}
}
}
// VULNERABLE: Pointer comparison
void check_pointer_vulnerable(void* ptr) {
if (ptr != NULL) {
// ptr is not NULL
if (ptr != NULL || flag) { // First part ALWAYS TRUE!
use_ptr(ptr);
}
}
}
// VULNERABLE: String length check
void check_string_vulnerable(const char* str) {
size_t len = strlen(str);
if (len >= 0) { // ALWAYS TRUE! strlen returns size_t
process(str, len);
}
// No else handling
}
// VULNERABLE: After assignment
void use_result_vulnerable(int input) {
int result = compute(input);
if (result = result) { // ALWAYS TRUE! (also assignment bug)
use(result);
}
}
// VULNERABLE: Enum range
typedef enum { A = 0, B = 1, C = 2 } Type;
void handle_type_vulnerable(Type t) {
if (t == A || t == B || t == C) { // ALWAYS TRUE for valid enum
process(t);
}
// Enum values outside range are UB, but this looks like validation
}
// VULNERABLE: C++ with always-true conditions
void validateSize_vulnerable(std::size_t size) {
if (size >= 0) { // ALWAYS TRUE!
processValid(size);
} else {
handleInvalid(); // Dead code!
}
}
// VULNERABLE: Vector size check
void processVector_vulnerable(const std::vector<int>& vec) {
if (vec.size() >= 0) { // ALWAYS TRUE!
// "Validation" always passes
for (int val : vec) {
process(val);
}
}
}
// VULNERABLE: Optional value
void checkOptional_vulnerable(std::optional<int> opt) {
if (opt.has_value() || !opt.has_value()) { // ALWAYS TRUE!
// Tautology
handleAny();
}
}
// VULNERABLE: Smart pointer
void checkPointer_vulnerable(std::shared_ptr<Object> ptr) {
if (ptr || !ptr) { // ALWAYS TRUE!
doSomething();
}
}
// VULNERABLE: After initialization
void useValue_vulnerable() {
int value = 42;
if (value >= 42 || value < 42) { // ALWAYS TRUE!
process(value);
}
}
// VULNERABLE: JavaScript always-true
function validateVulnerable(arr) {
if (arr.length >= 0 || arr.length < 0) { // ALWAYS TRUE!
return true;
}
return false; // Dead code
}
// VULNERABLE: Type coercion
function checkVulnerable(value) {
if (value == value) { // Usually true (except NaN)
process(value);
}
}
// VULNERABLE: After null check
function useObjectVulnerable(obj) {
if (obj !== null) {
// obj is not null
if (obj !== null || config.enabled) { // First part ALWAYS TRUE!
obj.doSomething();
}
}
}
// VULNERABLE: Array check
function processVulnerable(items) {
if (Array.isArray(items)) {
// items is array
if (items.length >= 0) { // ALWAYS TRUE for arrays!
process(items);
}
}
}
Fixed Code
// SAFE: Check for specific invalid values
int validate_size_safe(size_t size) {
if (size == 0) {
return 0; // Invalid: zero size
}
if (size > MAX_ALLOWED_SIZE) {
return 0; // Invalid: too large
}
return 1; // Valid
}
// SAFE: Proper unsigned check
void check_length_safe(unsigned int len) {
if (len > 0 && len <= MAX_LENGTH) {
process(len);
} else {
handle_invalid_length();
}
}
// SAFE: Meaningful validation
int validate_input_safe(int value) {
if (value >= MIN_VALUE && value <= MAX_VALUE) {
return 1;
}
return 0;
}
// SAFE: Remove redundant check
void process_safe(int value) {
if (value >= 0 && value <= 100) {
handle_valid(); // No redundant inner check
}
}
// SAFE: Simplify compound condition
void check_pointer_safe(void* ptr) {
if (ptr != NULL) {
use_ptr(ptr); // No redundant condition
}
}
// SAFE: Check actual string constraints
void check_string_safe(const char* str) {
if (str == NULL) {
handle_null();
return;
}
size_t len = strlen(str);
if (len == 0) {
handle_empty();
return;
}
if (len > MAX_STRING_LENGTH) {
handle_too_long();
return;
}
process(str, len);
}
// SAFE: Fix assignment bug and comparison
void use_result_safe(int input) {
int result = compute(input);
if (result != 0) { // Meaningful check
use(result);
}
}
// SAFE: Explicit enum validation if needed
void handle_type_safe(Type t) {
switch (t) {
case A:
case B:
case C:
process(t);
break;
default:
handle_invalid_type();
break;
}
}
// SAFE: Check meaningful conditions
void validateSize_safe(std::size_t size) {
if (size == 0) {
handleEmpty();
return;
}
if (size > MAX_SIZE) {
handleTooLarge();
return;
}
processValid(size);
}
// SAFE: Check empty vector
void processVector_safe(const std::vector<int>& vec) {
if (vec.empty()) {
handleEmptyVector();
return;
}
for (int val : vec) {
process(val);
}
}
// SAFE: Proper optional handling
void checkOptional_safe(std::optional<int> opt) {
if (opt.has_value()) {
handleValue(*opt);
} else {
handleEmpty();
}
}
// SAFE: Clear smart pointer logic
void checkPointer_safe(std::shared_ptr<Object> ptr) {
if (ptr) {
ptr->doSomething();
} else {
handleNullPointer();
}
}
// SAFE: Meaningful value check
void useValue_safe(int value) {
if (value > 0) { // Meaningful condition
processPositive(value);
} else if (value == 0) {
processZero();
} else {
processNegative(value);
}
}
// SAFE: Use std::variant for type safety
using SafeType = std::variant<TypeA, TypeB, TypeC>;
void handle_safe(const SafeType& t) {
std::visit([](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, TypeA>) {
processA(arg);
} else if constexpr (std::is_same_v<T, TypeB>) {
processB(arg);
} else {
processC(arg);
}
}, t);
}
// SAFE: Meaningful array validation
function validateSafe(arr) {
if (!Array.isArray(arr)) {
return false;
}
if (arr.length === 0) {
return false; // Require non-empty
}
return true;
}
// SAFE: Explicit NaN check
function checkValueSafe(value) {
if (typeof value !== 'number' || Number.isNaN(value)) {
handleInvalid();
return;
}
process(value);
}
// SAFE: Simplified logic
function useObjectSafe(obj) {
if (obj !== null && obj !== undefined) {
obj.doSomething();
}
}
// SAFE: Proper array processing
function processSafe(items) {
if (!Array.isArray(items)) {
throw new TypeError('Expected array');
}
if (items.length === 0) {
return []; // Handle empty case
}
return items.map(process);
}
// Use TypeScript for better type checking
// TypeScript will catch many of these at compile time
Exploited in the Wild
Input Validation Bypasses
Security filters that always evaluated to "valid" have allowed malicious input to pass through.
Size Check Bypasses
Buffer size validations that always passed have led to buffer overflow vulnerabilities.
Access Control Failures
Permission checks that always granted access due to tautological conditions have caused authorization bypasses.
Tools to test/exploit
-
GCC/Clang — -Wtype-limits, -Wtautological-compare.
-
Coverity — detects always-true conditions.
-
PVS-Studio — catches tautological expressions.
-
clang-tidy — readability-simplify-boolean-expr.
CVE Examples
-
Input validation bypasses where checks always passed.
-
Buffer overflow CVEs from ineffective size validation.
-
Various security filter bypasses from tautological conditions.
References
-
MITRE. "CWE-571: Expression is Always True." https://cwe.mitre.org/data/definitions/571.html
-
CERT C. "INT31-C: Ensure that integer conversions do not result in lost or misinterpreted data." https://wiki.sei.cmu.edu/confluence/display/c/