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
| Auswirkung | Details |
|---|---|
| Sicherheit | Bereich: Keine Eingabevalidierung Validierung, die immer durchläuft, verhindert keine bösartigen Eingaben. |
| Logik | Bereich: Tote Zweige Else-Zweige, die nie ausgeführt werden, dienen keinem Zweck. |
| Qualität | Bereich: 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
- 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 löst or misinterpreted data." https://wiki.sei.cmu.edu/confluence/display/c/