Vergleich anstelle von Zuweisung
Beschreibung
Vergleich anstelle von Zuweisung tritt auf, wenn ein Programmierer den Vergleichsoperator (==) verwendet, wo ein Zuweisungsoperator (=) beabsichtigt war. Dies ist die Umkehrung von CWE-481. Obwohl weniger häufig, da Vergleiche in einem Anweisungskontext oft Warnungen auslösen, kann dieser Fehler in komplexen Ausdrücken, Return-Anweisungen oder wenn das Vergleichsergebnis fälschlicherweise für eine Zuweisung gehalten wird, auftreten. Der beabsichtigte Nebeneffekt (Wertzuweisung) findet nie statt, wodurch Variablen in ihrem ursprünglichen Zustand verbleiben.
Risiko
Vergleich anstelle von Zuweisung bewirkt, dass Variablen unbeabsichtigte Werte behalten, was zu Logikfehlern und potenziellen Sicherheitslücken führt. In Initialisierungscode können Objekte uninitialisiert bleiben. Bei Zustandsverwaltung finden Übergänge nicht statt. In Sicherheitskontexten werden Flags möglicherweise nicht richtig gesetzt, was Systeme in unsicheren Zuständen belässt. Fehlerbehandlung kann fehlschlagen, Fehlercodes zu erfassen. Im Gegensatz zu Zuweisung-anstelle-von-Vergleich schlägt dieser Bug oft lautlos fehl — das Programm fährt mit veralteten oder falschen Werten fort.
Lösung
Aktivieren Sie Compiler-Warnungen für Anweisungen ohne Effekt. Verwenden Sie statische Analysetools, die Vergleiche mit ungenutzten Ergebnissen erkennen. Machen Sie Zuweisungen explizit und in separaten Zeilen wenn möglich. Vermeiden Sie komplexe Ausdrücke, die Zuweisungen und Vergleiche mischen. Verwenden Sie Code-Review, um Zustandsübergänge und Wertzuweisungen zu verifizieren. Beim Debugging überprüfen Sie, dass erwartete Variablenwerte sich ändern. Erwägen Sie IDE-Funktionen, die ungenutzte Ausdrucksergebnisse hervorheben.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Integrität | Umfang: Falscher Zustand Variablen behalten falsche Werte und korrumpieren den Programmzustand. |
| Sicherheit | Umfang: Flag nicht gesetzt Sicherheits-Flags können im unsicheren Standardzustand verbleiben. |
| Zuverlässigkeit | Umfang: Logikfehler Programmlogik schlägt aufgrund nicht durchgeführter Zuweisungen fehl. |
Beispielcode
Anfälliger Code
// ANFÄLLIG: Vergleich anstelle von Zuweisung
void initialize_vulnerable(Config* config) {
config->enabled == 1; // BUG! Vergleicht aber weist nicht zu!
config->timeout == 30; // BUG! Config-Werte unverändert!
config->retries == 3; // BUG! Ursprüngliche Werte bleiben!
}
// ANFÄLLIG: Fehler-Flag nicht gesetzt
int process_data_vulnerable(char* data, int* error_flag) {
if (data == NULL) {
*error_flag == ERROR_NULL; // BUG! Setzt Flag nicht!
return -1;
}
if (!validate(data)) {
*error_flag == ERROR_INVALID; // BUG! Flag unverändert!
return -1;
}
return 0;
}
// ANFÄLLIG: Zustandsübergang schlägt fehl
typedef enum { INIT, RUNNING, STOPPED } State;
void start_service_vulnerable(Service* svc) {
if (svc->state == INIT) {
svc->state == RUNNING; // BUG! Zustand ändert sich nicht!
// Dienst denkt er ist gestartet aber Zustand ist noch INIT
}
}
// ANFÄLLIG: Pointer nicht zugewiesen
int* get_buffer_vulnerable(int size) {
int* buffer;
buffer == malloc(size * sizeof(int)); // BUG! Vergleich!
// buffer ist uninitialisiert, Speicher verloren!
return buffer; // Gibt Müll-Pointer zurück!
}
// ANFÄLLIG: Zähler nicht inkrementiert
void count_items_vulnerable(Item* items, int count, int* total) {
*total = 0;
for (int i = 0; i < count; i++) {
if (items[i].valid) {
*total == *total + 1; // BUG! Inkrementiert nicht!
// *total bleibt 0
}
}
}
// ANFÄLLIG: Rückgabewert-Vergleich
int calculate_vulnerable(int a, int b) {
int result;
result == a + b; // BUG! result ist uninitialisiert/unverändert
return result; // Gibt Müll zurück!
}
// ANFÄLLIG: In ternärem Ausdruck
void set_mode_vulnerable(int* mode, int condition) {
condition ? (*mode == MODE_A) : (*mode == MODE_B);
// BUG! Beide Zweige vergleichen, keiner weist zu!
// mode ist unverändert
}
// ANFÄLLIG: Sicherheits-Flag nicht gesetzt
int authenticate_vulnerable(User* user, const char* password) {
if (check_password(user, password)) {
user->authenticated == 1; // BUG! Nie authentifiziert!
user->login_time == time(NULL); // BUG! Zeit nicht erfasst!
return SUCCESS;
}
return FAILURE;
}
// ANFÄLLIG: C++ Member-Initialisierung
class VulnerableClass {
int value;
bool initialized;
public:
void init(int v) {
value == v; // BUG! value unverändert
initialized == true; // BUG! noch uninitialisiert
}
void reset() {
value == 0; // BUG! nicht zurückgesetzt
initialized == false; // BUG! Flag nicht gelöscht
}
};
// ANFÄLLIG: Smart-Pointer-Zuweisung
void create_resource_vulnerable() {
std::unique_ptr<Resource> ptr;
ptr == std::make_unique<Resource>(); // BUG! Vergleich!
// ptr ist noch nullptr!
if (ptr) {
ptr->use(); // Wird nie erreicht
}
}
// ANFÄLLIG: String-Zuweisung
void update_name_vulnerable(std::string& name, const std::string& newName) {
name == newName; // BUG! name unverändert!
}
// ANFÄLLIG: Vector-Element-Update
void update_element_vulnerable(std::vector<int>& vec, int index, int value) {
if (index < vec.size()) {
vec[index] == value; // BUG! Vergleich, keine Zuweisung!
}
}
// ANFÄLLIG: Objekt-Zustand
class VulnerableState {
State current;
public:
void transition(State next) {
if (isValidTransition(current, next)) {
current == next; // BUG! Zustand unverändert!
}
}
};
// JavaScript - Vergleichsergebnis ignoriert
// ANFÄLLIG: Eigenschaftszuweisung schlägt fehl
function initConfigVulnerable(config) {
config.enabled == true; // BUG! Vergleich!
config.timeout == 30; // BUG! Eigenschaften unverändert!
}
// ANFÄLLIG: Variablenzuweisung schlägt fehl
function processVulnerable(data) {
let result;
result == processData(data); // BUG! result ist undefined!
return result;
}
// ANFÄLLIG: Objekt-Eigenschafts-Update
function updateUserVulnerable(user, newData) {
user.name == newData.name; // BUG! name nicht aktualisiert
user.email == newData.email; // BUG! email nicht aktualisiert
}
// ANFÄLLIG: Zähler in Schleife
function countValidVulnerable(items) {
let count = 0;
for (let item of items) {
if (item.valid) {
count == count + 1; // BUG! count bleibt 0
}
}
return count; // Gibt immer 0 zurück
}
Korrigierter Code
// SICHER: Korrekte Zuweisung
void initialize_safe(Config* config) {
config->enabled = 1; // Korrekte Zuweisung
config->timeout = 30; // Werte ordnungsgemäß gesetzt
config->retries = 3;
}
// SICHER: Fehler-Flag ordnungsgemäß gesetzt
int process_data_safe(char* data, int* error_flag) {
if (data == NULL) {
*error_flag = ERROR_NULL; // Korrekte Zuweisung
return -1;
}
if (!validate(data)) {
*error_flag = ERROR_INVALID; // Flag ordnungsgemäß gesetzt
return -1;
}
*error_flag = 0; // Bei Erfolg löschen
return 0;
}
// SICHER: Zustandsübergang funktioniert
void start_service_safe(Service* svc) {
if (svc->state == INIT) {
svc->state = RUNNING; // Korrekte Zuweisung
log_transition(INIT, RUNNING);
}
}
// SICHER: Pointer ordnungsgemäß zugewiesen
int* get_buffer_safe(int size) {
int* buffer = malloc(size * sizeof(int)); // Zuweisung in Deklaration
if (buffer == NULL) {
return NULL;
}
return buffer;
}
// Oder zur Klarheit aufteilen
int* get_buffer_safe_v2(int size) {
int* buffer;
buffer = malloc(size * sizeof(int)); // Klare Zuweisung
return buffer;
}
// SICHER: Zähler ordnungsgemäß inkrementiert
void count_items_safe(Item* items, int count, int* total) {
*total = 0;
for (int i = 0; i < count; i++) {
if (items[i].valid) {
*total = *total + 1; // Korrekte Zuweisung
// Oder: (*total)++;
}
}
}
// SICHER: Rückgabewert ordnungsgemäß zugewiesen
int calculate_safe(int a, int b) {
int result = a + b; // Initialisierung in Deklaration
return result;
}
// SICHER: Ternär mit Zuweisung
void set_mode_safe(int* mode, int condition) {
*mode = condition ? MODE_A : MODE_B; // Korrekte Zuweisung
}
// SICHER: Sicherheits-Flags ordnungsgemäß gesetzt
int authenticate_safe(User* user, const char* password) {
if (check_password(user, password)) {
user->authenticated = 1; // Korrekte Zuweisung
user->login_time = time(NULL); // Zeit erfasst
log_successful_login(user);
return SUCCESS;
}
user->authenticated = 0; // Explizit bei Fehlschlag löschen
return FAILURE;
}
// SICHER: Verwendung von assert zur Verifizierung von Zuweisungen
void initialize_with_verify(Config* config) {
config->enabled = 1;
config->timeout = 30;
config->retries = 3;
// In Debug-Builds verifizieren
assert(config->enabled == 1);
assert(config->timeout == 30);
assert(config->retries == 3);
}
// SICHER: C++ mit korrekten Zuweisungen
class SafeClass {
int value = 0; // In Deklaration initialisieren
bool initialized = false;
public:
void init(int v) {
value = v; // Korrekte Zuweisung
initialized = true; // Flag ordnungsgemäß gesetzt
}
void reset() {
value = 0; // Korrektes Zurücksetzen
initialized = false; // Flag gelöscht
}
// Getter zur Verifizierung verwenden
bool isInitialized() const {
return initialized;
}
};
// SICHER: Smart-Pointer-Zuweisung
void create_resource_safe() {
auto ptr = std::make_unique<Resource>(); // Direkte Initialisierung
// Oder:
std::unique_ptr<Resource> ptr2;
ptr2 = std::make_unique<Resource>(); // Korrekte Zuweisung
if (ptr) {
ptr->use(); // Funktioniert korrekt
}
}
// SICHER: String-Zuweisung
void update_name_safe(std::string& name, const std::string& newName) {
name = newName; // Korrekte Zuweisung
}
// SICHER: Vector-Element-Update
void update_element_safe(std::vector<int>& vec, int index, int value) {
if (index < vec.size()) {
vec[index] = value; // Korrekte Zuweisung
}
}
// Oder at() für Grenzprüfung verwenden
void update_element_safe_v2(std::vector<int>& vec, size_t index, int value) {
vec.at(index) = value; // Wirft bei Überschreitung
}
// SICHER: Zustandsverwaltung
class SafeState {
State current = State::INIT;
public:
bool transition(State next) {
if (isValidTransition(current, next)) {
current = next; // Korrekte Zuweisung
return true;
}
return false;
}
State getState() const {
return current;
}
};
// SICHER: Verwendung von structured bindings (C++17)
auto createAndInit() {
Config config;
config.enabled = true;
config.timeout = 30;
return config;
}
// SICHER: JavaScript mit korrekten Zuweisungen
// SICHER: Eigenschaftszuweisung
function initConfigSafe(config) {
config.enabled = true; // Korrekte Zuweisung
config.timeout = 30;
}
// SICHER: Variablenzuweisung
function processSafe(data) {
const result = processData(data); // Korrekte Zuweisung
return result;
}
// SICHER: Objekt-Eigenschafts-Update
function updateUserSafe(user, newData) {
user.name = newData.name; // Korrekte Zuweisung
user.email = newData.email;
}
// Oder Object.assign verwenden
function updateUserSafe2(user, newData) {
Object.assign(user, {
name: newData.name,
email: newData.email
});
}
// Oder Spread-Operator
function updateUserSafe3(user, newData) {
return { ...user, ...newData };
}
// SICHER: Zähler ordnungsgemäß inkrementiert
function countValidSafe(items) {
let count = 0;
for (const item of items) {
if (item.valid) {
count = count + 1; // Korrekte Zuweisung
// Oder: count++;
// Oder: count += 1;
}
}
return count;
}
// Oder funktionaler Ansatz
function countValidFunctional(items) {
return items.filter(item => item.valid).length;
}
// ESLint fängt no-unused-expressions ab
// "use strict" hilft einige Probleme zu fangen
Ausgenutzt in der Praxis
Initialisierungsfehler
Software wurde mit nicht initialisierten Konfigurationen ausgeliefert aufgrund von Vergleich-anstelle-von-Zuweisung-Fehlern, was unerwartetes Verhalten in der Produktion verursachte.
Sicherheitsflag-Umgehungen
Sicherheitsflags, die aufgrund dieses Fehlers nie richtig gesetzt wurden, haben Systeme in unsicheren Standardzuständen belassen.
State-Machine-Fehler
Kritische Systeme hatten Zustandsübergänge, die still fehlschlugen, was zu inkorrektem Verhalten führte, wenn Zustände nicht aktualisiert wurden.
CVE-Beispiele
-
Verschiedene Software-Bugs, die auf Initialisierungsfehler durch dieses Fehlermuster zurückzuführen sind.
-
Zustandsverwaltungsfehler in kritischen Systemen.
-
Konfigurationsfehler durch fehlgeschlagene Zuweisungen.
Referenzen
-
MITRE Corporation. "CWE-482: Comparing instead of Assigning." https://cwe.mitre.org/data/definitions/482.html
-
CERT C Secure Coding Standard. "MSC12-C: Detect and remove code that has no effect or is never executed." https://wiki.sei.cmu.edu/confluence/display/c/