Zuweisung anstelle von Vergleich

Beschreibung

Zuweisung anstelle von Vergleich tritt auf, wenn ein Programmierer den Zuweisungsoperator (=) verwendet, wo ein Vergleichsoperator (==) beabsichtigt war. Dieser Fehler passiert häufig in bedingten Anweisungen (if, while, for), wo die Absicht ist, Werte zu vergleichen, aber das einzelne Gleichheitszeichen stattdessen eine Zuweisung bewirkt. Der Ausdruck evaluiert zum zugewiesenen Wert, nicht zu einem booleschen Vergleichsergebnis, was zu Logik führt, die sich anders verhält als beabsichtigt.

Risiko

Dieser Fehler erzeugt schwerwiegende Sicherheitslücken und Logikfehler. Bei Authentifizierungsprüfungen kann Zuweisung anstelle von Vergleich dazu führen, dass die Bedingung immer wahr (oder falsch) ist und Sicherheitskontrollen umgangen werden. Der zugewiesene Wert ersetzt den ursprünglichen und verursacht Datenkorruption. In Schleifen kann es zu Endlosschleifen oder vorzeitiger Beendigung führen. Da der Code ohne Fehler kompiliert und oft subtil falsches Verhalten erzeugt, können diese Bugs unentdeckt durch Tests und in die Produktion gelangen.

Lösung

Aktivieren Sie Compiler-Warnungen, die Zuweisungen in bedingten Kontexten markieren (-Wparentheses in GCC/Clang). Verwenden Sie statische Analysetools, die Zuweisungen in Bedingungen erkennen. Wenden Sie Yoda-Bedingungen an (Konstante auf der linken Seite), sodass eine Zuweisung einen Kompilierfehler verursacht. Etablieren Sie Codierungsstandards, die explizite Vergleiche (== oder !=) in allen Bedingungen erfordern. Verwenden Sie Sprachen oder Linter, die Zuweisungen in Bedingungen verbieten oder davor warnen. Überprüfen Sie bei Code-Reviews speziell die Operatoren in sicherheitskritischen Bedingungen.

Häufige Auswirkungen

AuswirkungDetails
ZugriffskontrolleUmfang: Authentifizierungsumgehung

Sicherheitsprüfungen können immer bestehen oder immer fehlschlagen.
IntegritätUmfang: Datenmodifikation

Variablen werden unerwartet durch Zuweisung modifiziert.
VerfügbarkeitUmfang: Logikfehler

Programmfluss weicht vom beabsichtigten Verhalten ab.

Beispielcode

Anfälliger Code

// ANFÄLLIG: Authentifizierungsumgehung
int login_vulnerable(User* user, const char* password) {
    if (user->authenticated = check_password(user, password)) {
        // BUG! Dies WEIST das Ergebnis authenticated zu
        // Wenn check_password einen Nicht-Null-Wert zurückgibt, ist Bedingung wahr
        // Selbst Fehlercodes könnten nicht-null sein!
        return SUCCESS;
    }
    return FAILURE;
}

// ANFÄLLIG: Variablenmodifikation in Bedingung
void search_vulnerable(int* data, int size, int target) {
    for (int i = 0; i < size; i++) {
        if (data[i] = target) {  // BUG! Weist target zu data[i] zu
            // Jedes Element wird gleich target!
            // Bedingung wahr außer target ist 0
            printf("Gefunden bei Index %d\n", i);
            break;
        }
    }
}

// ANFÄLLIG: Endlosschleifen-Potenzial
void process_vulnerable(FILE* file) {
    char buffer[256];
    int done = 0;

    while (done = 0) {  // BUG! Weist immer 0 zu, Schleife wird nie ausgeführt
        if (fgets(buffer, sizeof(buffer), file) == NULL) {
            done = 1;
        }
        process_line(buffer);
    }
}

// ANFÄLLIG: Zeigerprüfung umgangen
void use_pointer_vulnerable(int* ptr) {
    if (ptr = NULL) {  // BUG! Weist NULL zu ptr zu!
        printf("Pointer ist NULL\n");
        return;
    }
    // ptr ist jetzt NULL, wird abstürzen!
    *ptr = 42;
}

// ANFÄLLIG: String-Vergleichsfehler
int verify_token_vulnerable(const char* token) {
    char* valid_token = get_valid_token();

    if (token = valid_token) {  // BUG! Weist valid_token zu token zu
        // Immer wahr (außer valid_token ist NULL)
        return 1;  // Sicherheitsumgehung!
    }
    return 0;
}

// ANFÄLLIG: Verschachtelte Bedingungszuweisung
int complex_check_vulnerable(int a, int b, int c) {
    if ((a = b) && (b == c)) {  // BUG! Erster Teil weist zu
        // a ist jetzt gleich b
        // Bedingung basiert darauf, ob b nicht-null ist UND b gleich c
        return 1;
    }
    return 0;
}

// ANFÄLLIG: Schleifenzähler-Modifikation
void iterate_vulnerable(int* arr, int count) {
    for (int i = 0; i < count; i++) {
        if (i = arr[i]) {  // BUG! Weist arr[i] zu i zu
            // Schleifenzähler korrumpiert!
            printf("Wert: %d\n", i);
        }
    }
}

// ANFÄLLIG: Enum-Vergleich
typedef enum { STATE_INIT, STATE_RUN, STATE_DONE } State;

int check_state_vulnerable(State* state) {
    if (*state = STATE_DONE) {  // BUG! Weist STATE_DONE zu
        // state ist jetzt STATE_DONE unabhängig vom vorherigen Wert
        // Bedingung immer wahr (STATE_DONE != 0)
        return 1;
    }
    return 0;
}
// ANFÄLLIG: C++ Klassenmember-Zuweisung
class VulnerableAccount {
    bool locked;
    int balance;

public:
    bool canWithdraw(int amount) {
        if (locked = false) {  // BUG! Weist false zu locked zu
            // Konto ist jetzt entsperrt!
            // Bedingung immer falsch
            return false;  // Aber Logik ist trotzdem falsch
        }
        return balance >= amount;
    }

    bool isOverdrawn() {
        if (balance = 0) {  // BUG! Setzt balance auf 0
            // Kontostand genullt!
            // Bedingung immer falsch (0 ist falsy)
            return false;
        }
        return balance < 0;  // Wird nie erreicht, und balance ist jetzt 0
    }
};

// ANFÄLLIG: Smart-Pointer-Zuweisung
void process_vulnerable(std::shared_ptr<Object> obj) {
    std::shared_ptr<Object> other = getOther();

    if (obj = other) {  // BUG! Weist other zu obj zu
        // obj zeigt jetzt auf other
        // Das ursprüngliche Objekt von obj könnte zerstört werden!
        obj->process();
    }
}

// ANFÄLLIG: Boolescher Ausdruck
bool validate_vulnerable(int value, bool flag) {
    if (flag = (value > 0)) {  // BUG! Weist Ergebnis zu flag zu
        // flag ist modifiziert
        // Ursprünglicher flag-Wert verloren
        return true;
    }
    return false;
}

// ANFÄLLIG: Ausnahmebehandlung
void handle_error_vulnerable(int* error_code) {
    try {
        doRiskyOperation();
    } catch (const std::exception& e) {
        if (*error_code = ERROR_EXCEPTION) {  // BUG! Weist Fehlercode zu
            // error_code ist gesetzt, aber Bedingung immer wahr
            // (außer ERROR_EXCEPTION ist 0)
            log_error(e.what());
        }
    }
}
// JavaScript erlaubt Zuweisung in Bedingungen
// ANFÄLLIG: Authentifizierungsprüfung
function loginVulnerable(user, password) {
    if (user.authenticated = checkPassword(password)) {
        // BUG! Weist Ergebnis zu user.authenticated zu
        // Truthy-Rückgabewert = authentifiziert
        return { success: true };
    }
    return { success: false };
}

// ANFÄLLIG: Array-Suche
function findVulnerable(arr, target) {
    for (let i = 0; i < arr.length; i++) {
        if (arr[i] = target) {  // BUG! Modifiziert Array!
            return i;
        }
    }
    return -1;
}

// ANFÄLLIG: Objekt-Eigenschaftsprüfung
function checkPermission(user) {
    if (user.role = 'admin') {  // BUG! Macht jeden zum Admin!
        return true;
    }
    return false;
}

Korrigierter Code

// SICHER: Korrekter Vergleichsoperator
int login_safe(User* user, const char* password) {
    int result = check_password(user, password);
    if (result == SUCCESS) {  // Korrekter Vergleich
        user->authenticated = 1;  // Explizite Zuweisung wenn beabsichtigt
        return SUCCESS;
    }
    return FAILURE;
}

// SICHER: Yoda-Bedingung verhindert Zuweisungsfehler
void search_safe(int* data, int size, int target) {
    for (int i = 0; i < size; i++) {
        if (target == data[i]) {  // Yoda: Konstante auf der linken Seite
            // Wenn Sie versehentlich = schreiben: "target = data[i]" verursacht Fehler
            // weil Sie keinem Literal zuweisen können
            printf("Gefunden bei Index %d\n", i);
            break;
        }
    }
}

// SICHER: Korrekter Vergleich in while
void process_safe(FILE* file) {
    char buffer[256];
    int done = 0;

    while (done == 0) {  // Korrekter Vergleich
        if (fgets(buffer, sizeof(buffer), file) == NULL) {
            done = 1;
            continue;
        }
        process_line(buffer);
    }
}

// Besser: Bedingung direkt verwenden
void process_safe_v2(FILE* file) {
    char buffer[256];

    while (fgets(buffer, sizeof(buffer), file) != NULL) {
        process_line(buffer);
    }
}

// SICHER: Korrekte NULL-Prüfung
void use_pointer_safe(int* ptr) {
    if (ptr == NULL) {  // Korrekter Vergleich
        printf("Pointer ist NULL\n");
        return;
    }
    *ptr = 42;  // Sicher, ptr ist gültig
}

// Yoda-Stil NULL-Prüfung
void use_pointer_yoda(int* ptr) {
    if (NULL == ptr) {  // Kann NULL nicht zuweisen
        printf("Pointer ist NULL\n");
        return;
    }
    *ptr = 42;
}

// SICHER: String-Vergleich mit strcmp
int verify_token_safe(const char* token) {
    char* valid_token = get_valid_token();

    if (token != NULL && valid_token != NULL &&
        strcmp(token, valid_token) == 0) {  // Korrekter String-Vergleich
        return 1;
    }
    return 0;
}

// SICHER: Expliziter Vergleich in allen Bedingungen
int complex_check_safe(int a, int b, int c) {
    if ((a == b) && (b == c)) {  // Beide sind Vergleiche
        return 1;
    }
    return 0;
}

// SICHER: Schleifenzähler erhalten
void iterate_safe(int* arr, int count) {
    for (int i = 0; i < count; i++) {
        if (i == arr[i]) {  // Vergleich, keine Zuweisung
            printf("Wert bei Index %d gleich Index\n", i);
        }
    }
}

// SICHER: Enum-Vergleich
int check_state_safe(State* state) {
    if (*state == STATE_DONE) {  // Korrekter Vergleich
        return 1;
    }
    return 0;
}

// Yoda-Stil für Enums
int check_state_yoda(State* state) {
    if (STATE_DONE == *state) {  // Kann Enum-Konstante nicht zuweisen
        return 1;
    }
    return 0;
}
// SICHER: C++ mit korrekten Operatoren
class SafeAccount {
    bool locked;
    int balance;

public:
    bool canWithdraw(int amount) const {  // const verhindert Modifikation
        if (locked == false) {  // Korrekter Vergleich
            // Oder besser: if (!locked)
            return balance >= amount;
        }
        return false;
    }

    bool isOverdrawn() const {
        if (balance == 0) {  // Korrekter Vergleich
            return false;
        }
        return balance < 0;
    }

    // Besserer Stil: direkter Boolean
    bool isLocked() const {
        return locked;  // Keine Bedingung nötig
    }
};

// SICHER: Smart-Pointer-Vergleich
void process_safe(std::shared_ptr<Object> obj) {
    std::shared_ptr<Object> other = getOther();

    if (obj == other) {  // Korrekter Vergleich
        obj->process();
    }
}

// SICHER: Boolean ohne Zuweisung
bool validate_safe(int value, bool flag) {
    bool isPositive = value > 0;  // Separate Zuweisung

    if (flag && isPositive) {  // Logischer Vergleich
        return true;
    }
    return false;
}

// Oder direkt
bool validate_safe_v2(int value, bool flag) {
    return flag && (value > 0);
}

// SICHER: Ausnahmebehandlung
void handle_error_safe(int* error_code) {
    try {
        doRiskyOperation();
    } catch (const std::exception& e) {
        *error_code = ERROR_EXCEPTION;  // Explizite Zuweisung
        log_error(e.what());
    }
}

// SICHER: Verwendung von [[nodiscard]] und const
class SafeResult {
public:
    [[nodiscard]] bool operator==(const SafeResult& other) const;
    // Compiler warnt wenn Ergebnis ignoriert wird
    // const verhindert versehentliche Zuweisung in Vergleichen
};
// SICHER: JavaScript mit korrekten Operatoren

// Strikte Gleichheit verwenden
function loginSafe(user, password) {
    const result = checkPassword(password);
    if (result === true) {  // Strikter Vergleich
        user.authenticated = true;  // Explizite Zuweisung
        return { success: true };
    }
    return { success: false };
}

// SICHER: Array bei Suche nicht modifizieren
function findSafe(arr, target) {
    for (let i = 0; i < arr.length; i++) {
        if (arr[i] === target) {  // Strikte Gleichheit
            return i;
        }
    }
    return -1;
}

// Oder Array-Methoden verwenden
function findSafeModern(arr, target) {
    return arr.indexOf(target);
    // Oder: arr.findIndex(x => x === target);
}

// SICHER: Ordnungsgemäße Berechtigungsprüfung
function checkPermission(user) {
    if (user.role === 'admin') {  // Strikter Vergleich
        return true;
    }
    return false;
}

// Noch besser: direkte Rückgabe
function checkPermissionConcise(user) {
    return user.role === 'admin';
}

// ESLint-Regel: no-cond-assign
// Fängt Zuweisung in Bedingungen ab

Ausgenutzt in der Praxis

SSL/TLS-Implementierungsfehler

Mehrere SSL/TLS-Implementierungen hatten Schwachstellen, bei denen Zuweisung anstelle von Vergleich zu fehlerhafter Zertifikatsvalidierung führte.

Authentifizierungssystem-Umgehungen

Login-Systeme wurden aufgrund von Zuweisungsfehlern umgangen, die dazu führten, dass Authentifizierungsprüfungen immer erfolgreich waren.

Zugriffskontrollfehler

Zugriffskontrollsysteme versagten, wenn Berechtigungsprüfungen Zuweisung anstelle von Vergleich verwendeten.


Tools zum Testen

  • GCC/Clang Warnings — -Wparentheses warnt vor Zuweisung in Bedingungen.

  • ESLint — no-cond-assign-Regel für JavaScript.

  • Coverity — erkennt Zuweisungs-/Vergleichsverwechslung.

  • SonarQube — Regel für Zuweisung in Bedingungen.


CVE-Beispiele

  • CVE-2003-0161 — Sendmail prescan() Zuweisungsfehler.

  • Verschiedene Authentifizierungsumgehungs-CVEs durch Zuweisungsfehler.

  • Mehrere CVEs in eingebetteten Systemen durch Vergleichsfehler.


Referenzen

  1. MITRE Corporation. "CWE-481: Assigning instead of Comparing." https://cwe.mitre.org/data/definitions/481.html

  2. CERT C Secure Coding Standard. "EXP45-C: Do not perform assignments in selection statements." https://wiki.sei.cmu.edu/confluence/display/c/