Ausdruck ist immer wahr

Beschreibung

Ausdruck ist immer wahr tritt auf, wenn ein bedingter Ausdruck unter allen möglichen Umständen zu wahr auswertet. Dies ist das Gegenstück zu CWE-570. Häufige Muster umfassen Vergleiche von unsigned-Werten gegen negative untere Grenzen (immer wahr), redundante Prüfungen für bereits etablierte Bedingungen, Vergleiche gegen Werte außerhalb des Bereichs eines Typs und logische Ausdrücke mit tautologischen Komponenten. Während die Bedingung immer zutrifft, wird der "else"-Zweig oder die alternative Behandlung nie ausgeführt.

Risiko

Immer-wahre Ausdrücke weisen auf Logikfehler mit potenziellen Sicherheitsimplikationen hin. Eingabevalidierung, die immer durchläuft, bietet keine Filterung. Bounds-Checks, die immer erfolgreich sind, verhindern keine Überläufe. Umgehungsbedingungen, die immer auslösen, überspringen wichtige Verarbeitung. Fehlerbehandlungsalternativen, die nie ausgeführt werden, lassen Fehler unbehandelt. Das Inverse - Code, der bedingt ausgeführt werden sollte, wird bedingungslos ausgeführt - kann unbeabsichtigte Zustandsänderungen und Sicherheitsschwachstellen verursachen.

Lösung

Aktivieren Sie Compiler-Warnungen für tautologische Vergleiche. Verwenden Sie statische Analysewerkzeuge zur Identifizierung immer-wahrer Bedingungen. Verstehen Sie Typbereiche - unsigned-Integer sind immer >= 0. Überprüfen Sie zusammengesetzte Bedingungen auf tautologische Komponenten. Entfernen Sie redundante Prüfungen, die immer durchlaufen. Wenn die Prüfung bestimmte Werte abfangen sollte, beheben Sie die Logik, um sie tatsächlich abzufangen. Verifizieren Sie, dass Sicherheitsprüfungen tatsächlich fehlschlagen können, wenn sie sollten.

Häufige Auswirkungen

AuswirkungDetails
SicherheitBereich: Keine Eingabevalidierung

Validierung, die immer durchläuft, verhindert keine bösartigen Eingaben.
LogikBereich: Tote Zweige

Else-Zweige, die nie ausgeführt werden, dienen keinem Zweck.
QualitätBereich: Irreführender Code

Bedingungen, die immer durchlaufen, geben falsches Gefühl von Prüfung.

Beispielcode + Lösungscode

Verwundbarer Code

// VERWUNDBAR: Unsigned immer >= 0
int validate_size_vulnerable(size_t size) {
    if (size >= 0) {  // IMMER WAHR! size_t ist unsigned
        return 1;  // Gibt immer gültig zurück
    }
    return 0;  // Toter Code!
}

// VERWUNDBAR: Unsigned-Vergleich mit negativ
void check_length_vulnerable(unsigned int len) {
    if (len > -1) {  // IMMER WAHR! -1 wird zu UINT_MAX konvertiert
        process(len);  // Wird immer ausgeführt
    }
    // Else wird nie genommen
}

// VERWUNDBAR: Tautologie in ODER-Bedingung
int validate_input_vulnerable(int value) {
    if (value >= 0 || value < 0) {  // IMMER WAHR!
        return 1;  // "Validierung" läuft immer durch
    }
    return 0;  // Toter Code
}

// VERWUNDBAR: Redundant nach Bounds-Check
void process_vulnerable(int value) {
    if (value >= 0 && value <= 100) {
        // value ist in [0, 100]

        if (value >= 0) {  // IMMER WAHR! Bereits geprüft!
            handle_valid();
        }
    }
}

// VERWUNDBAR: Pointer-Vergleich
void check_pointer_vulnerable(void* ptr) {
    if (ptr != NULL) {
        // ptr ist nicht NULL

        if (ptr != NULL || flag) {  // Erster Teil IMMER WAHR!
            use_ptr(ptr);
        }
    }
}

// VERWUNDBAR: String-Längenprüfung
void check_string_vulnerable(const char* str) {
    size_t len = strlen(str);

    if (len >= 0) {  // IMMER WAHR! strlen gibt size_t zurück
        process(str, len);
    }
    // Keine else-Behandlung
}

// VERWUNDBAR: Nach Zuweisung
void use_result_vulnerable(int input) {
    int result = compute(input);

    if (result = result) {  // IMMER WAHR! (auch Zuweisungsbug)
        use(result);
    }
}

// VERWUNDBAR: Enum-Bereich
typedef enum { A = 0, B = 1, C = 2 } Type;

void handle_type_vulnerable(Type t) {
    if (t == A || t == B || t == C) {  // IMMER WAHR für gültiges Enum
        process(t);
    }
    // Enum-Werte außerhalb des Bereichs sind UB, aber dies sieht aus wie Validierung
}
// VERWUNDBAR: C++ mit immer-wahren Bedingungen
void validateSize_vulnerable(std::size_t size) {
    if (size >= 0) {  // IMMER WAHR!
        processValid(size);
    } else {
        handleInvalid();  // Toter Code!
    }
}

// VERWUNDBAR: Vector-Größenprüfung
void processVector_vulnerable(const std::vector<int>& vec) {
    if (vec.size() >= 0) {  // IMMER WAHR!
        // "Validierung" läuft immer durch
        for (int val : vec) {
            process(val);
        }
    }
}

// VERWUNDBAR: Optional-Wert
void checkOptional_vulnerable(std::optional<int> opt) {
    if (opt.has_value() || !opt.has_value()) {  // IMMER WAHR!
        // Tautologie
        handleAny();
    }
}

// VERWUNDBAR: Smart-Pointer
void checkPointer_vulnerable(std::shared_ptr<Object> ptr) {
    if (ptr || !ptr) {  // IMMER WAHR!
        doSomething();
    }
}

// VERWUNDBAR: Nach Initialisierung
void useValue_vulnerable() {
    int value = 42;

    if (value >= 42 || value < 42) {  // IMMER WAHR!
        process(value);
    }
}
// VERWUNDBAR: JavaScript immer-wahr
function validateVulnerable(arr) {
    if (arr.length >= 0 || arr.length < 0) {  // IMMER WAHR!
        return true;
    }
    return false;  // Toter Code
}

// VERWUNDBAR: Typkonversion
function checkVulnerable(value) {
    if (value == value) {  // Normalerweise wahr (außer NaN)
        process(value);
    }
}

// VERWUNDBAR: Nach Null-Prüfung
function useObjectVulnerable(obj) {
    if (obj !== null) {
        // obj ist nicht null

        if (obj !== null || config.enabled) {  // Erster Teil IMMER WAHR!
            obj.doSomething();
        }
    }
}

// VERWUNDBAR: Array-Prüfung
function processVulnerable(items) {
    if (Array.isArray(items)) {
        // items ist Array

        if (items.length >= 0) {  // IMMER WAHR für Arrays!
            process(items);
        }
    }
}

Lösungscode

// SICHER: Auf spezifische ungültige Werte prüfen
int validate_size_safe(size_t size) {
    if (size == 0) {
        return 0;  // Ungültig: Nullgröße
    }
    if (size > MAX_ALLOWED_SIZE) {
        return 0;  // Ungültig: zu groß
    }
    return 1;  // Gültig
}

// SICHER: Ordnungsgemäße unsigned-Prüfung
void check_length_safe(unsigned int len) {
    if (len > 0 && len <= MAX_LENGTH) {
        process(len);
    } else {
        handle_invalid_length();
    }
}

// SICHER: Sinnvolle Validierung
int validate_input_safe(int value) {
    if (value >= MIN_VALUE && value <= MAX_VALUE) {
        return 1;
    }
    return 0;
}

// SICHER: Redundante Prüfung entfernen
void process_safe(int value) {
    if (value >= 0 && value <= 100) {
        handle_valid();  // Keine redundante innere Prüfung
    }
}

// SICHER: Zusammengesetzte Bedingung vereinfachen
void check_pointer_safe(void* ptr) {
    if (ptr != NULL) {
        use_ptr(ptr);  // Keine redundante Bedingung
    }
}

// SICHER: Tatsächliche String-Einschränkungen prüfen
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);
}

// SICHER: Zuweisungsbug und Vergleich beheben
void use_result_safe(int input) {
    int result = compute(input);

    if (result != 0) {  // Sinnvolle Prüfung
        use(result);
    }
}

// SICHER: Explizite Enum-Validierung wenn nötig
void handle_type_safe(Type t) {
    switch (t) {
        case A:
        case B:
        case C:
            process(t);
            break;
        default:
            handle_invalid_type();
            break;
    }
}
// SICHER: Sinnvolle Bedingungen prüfen
void validateSize_safe(std::size_t size) {
    if (size == 0) {
        handleEmpty();
        return;
    }

    if (size > MAX_SIZE) {
        handleTooLarge();
        return;
    }

    processValid(size);
}

// SICHER: Leeren Vector prüfen
void processVector_safe(const std::vector<int>& vec) {
    if (vec.empty()) {
        handleEmptyVector();
        return;
    }

    for (int val : vec) {
        process(val);
    }
}

// SICHER: Ordnungsgemäße Optional-Behandlung
void checkOptional_safe(std::optional<int> opt) {
    if (opt.has_value()) {
        handleValue(*opt);
    } else {
        handleEmpty();
    }
}

// SICHER: Klare Smart-Pointer-Logik
void checkPointer_safe(std::shared_ptr<Object> ptr) {
    if (ptr) {
        ptr->doSomething();
    } else {
        handleNullPointer();
    }
}

// SICHER: Sinnvolle Wertprüfung
void useValue_safe(int value) {
    if (value > 0) {  // Sinnvolle Bedingung
        processPositive(value);
    } else if (value == 0) {
        processZero();
    } else {
        processNegative(value);
    }
}

// SICHER: std::variant für Typsicherheit verwenden
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);
}
// SICHER: Sinnvolle Array-Validierung
function validateSafe(arr) {
    if (!Array.isArray(arr)) {
        return false;
    }

    if (arr.length === 0) {
        return false;  // Nicht-leer erforderlich
    }

    return true;
}

// SICHER: Explizite NaN-Prüfung
function checkValueSafe(value) {
    if (typeof value !== 'number' || Number.isNaN(value)) {
        handleInvalid();
        return;
    }

    process(value);
}

// SICHER: Vereinfachte Logik
function useObjectSafe(obj) {
    if (obj !== null && obj !== undefined) {
        obj.doSomething();
    }
}

// SICHER: Ordnungsgemäße Array-Verarbeitung
function processSafe(items) {
    if (!Array.isArray(items)) {
        throw new TypeError('Array erwartet');
    }

    if (items.length === 0) {
        return [];  // Leeren Fall behandeln
    }

    return items.map(process);
}

// TypeScript für bessere Typprüfung verwenden
// TypeScript fängt viele davon zur Compile-Zeit ab

Ausgenutzt in der Praxis

Eingabevalidierungsumgehungen

Sicherheitsfilter, die immer zu "gültig" auswerteten, haben bösartigen Eingaben ermöglicht durchzukommen.

Größenprüfungsumgehungen

Puffergrößen-Validierungen, die immer durchliefen, haben zu Buffer Overflow-Schwachstellen geführt.

Zugriffskontrollfehler

Berechtigungsprüfungen, die aufgrund tautologischer Bedingungen immer Zugriff gewährten, haben Autorisierungsumgehungen verursacht.


Tools zum Testen

  • GCC/Clang — -Wtype-limits, -Wtautological-compare.
  • Coverity — erkennt immer-wahre Bedingungen.
  • PVS-Studio — fängt tautologische Ausdrücke ab.
  • clang-tidy — readability-simplify-boolean-expr.

CVE-Beispiele

  • Eingabevalidierungsumgehungen wo Prüfungen immer durchliefen.
  • Buffer Overflow-CVEs durch ineffektive Größenvalidierung.
  • Verschiedene Sicherheitsfilter-Umgehungen durch tautologische Bedingungen.

Referenzen

  1. MITRE. "CWE-571: Expression is Always True." https://cwe.mitre.org/data/definitions/571.html
  2. CERT C. "INT31-C: Ensure that integer conversions do not result in löst or misinterpreted data." https://wiki.sei.cmu.edu/confluence/display/c/