Zuweisung an Variable ohne Verwendung

Beschreibung

Zuweisung an Variable ohne Verwendung tritt auf, wenn einer Variablen ein Wert zugewiesen wird, der nie nachfolgend gelesen oder verwendet wird, bevor er überschrieben wird oder der Gültigkeitsbereich endet. Dieses Muster, auch "ungenutzte Zuweisung" oder "toter Speicher" genannt, weist auf verschwendete Berechnung, Logikfehler oder Sicherheitsprobleme hin. Der Programmierer könnte beabsichtigt haben, den Wert zu verwenden, hat es aber vergessen, oder es könnte einen Bug geben, bei dem einer anderen Variablen zugewiesen werden sollte. Die Berechnung zur Erzeugung des Werts wird trotzdem ausgeführt und verbraucht Ressourcen ohne Wirkung.

Risiko

Während ungenutzte Zuweisungen selbst keine direkten Sicherheitsschwachstellen verursachen, deuten sie stark auf Programmierfehler hin, die Sicherheitsimplikationen haben können. Ein berechneter sicherheitskritischer Wert, der ungenutzt bleibt, legt nahe, dass die Sicherheitsprüfung nie durchgeführt wurde. Sensible Daten, die berechnet aber nicht gelöscht werden, weisen auf ein Datenoffenlegungsrisiko hin. In leistungskritischem Code verschwenden ungenutzte Berechnungen Ressourcen. Am wichtigsten ist, dass tote Speicher oft Logikfehler offenbaren - der Programmierer erwartete, dass der Wert verwendet wird, machte aber einen Fehler, der die beabsichtigte Funktionalität unterbrach.

Lösung

Aktivieren Sie Compiler-Warnungen für ungenutzte Variablen und tote Speicher (-Wunused-variable, -Wunused-but-set-variable). Verwenden Sie statische Analysewerkzeuge, die tote Speicher erkennen. Untersuchen Sie jeden Fall - bestimmen Sie, ob der Wert hätte verwendet werden sollen, ob die Zuweisung entfernt werden sollte, oder ob ein Bug vorliegt. Entfernen Sie wirklich unnötige Zuweisungen. Verwenden Sie das volatile-Schlüsselwort, wenn Seiteneffekte beabsichtigt sind. Erwägen Sie Code-Reviews, um Zuweisungen zu finden, die verwendet werden sollten, aber nicht werden.

Häufige Auswirkungen

AuswirkungDetails
QualitätBereich: Code-Klarheit

Tote Speicher machen Code schwerer verständlich und wartbar.
SicherheitBereich: Fehlende Prüfungen

Sicherheitswerte, die berechnet aber nicht verwendet werden, können auf umgangene Prüfungen hinweisen.
LeistungBereich: Verschwendete Berechnung

CPU-Zyklen werden für die Berechnung von Werten ausgegeben, die nie verwendet werden.

Beispielcode + Lösungscode

Verwundbarer Code

// VERWUNDBAR: Berechneter Wert nie verwendet
int process_data_vulnerable(int* data, int size) {
    int sum = 0;
    int max = 0;  // TOTER SPEICHER: max wird nie verwendet!

    for (int i = 0; i < size; i++) {
        sum += data[i];
        if (data[i] > max) {
            max = data[i];  // TOTER SPEICHER: max berechnet aber nie verwendet!
        }
    }

    return sum;  // Sollte max auch zurückgegeben werden?
}

// VERWUNDBAR: Ergebnis der Sicherheitsprüfung ignoriert
int authenticate_vulnerable(User* user, const char* password) {
    int result = check_password(user, password);
    // TOTER SPEICHER: result wird nie verwendet!

    // Bug: sollte result geprüft haben!
    user->authenticated = 1;  // Authentifiziert immer!
    return SUCCESS;
}

// VERWUNDBAR: Fehlercode ignoriert
void process_file_vulnerable(const char* path) {
    int error = 0;  // TOTER SPEICHER wenn nicht verwendet

    FILE* f = fopen(path, "r");
    error = validate_file(f);
    // TOTER SPEICHER: error wird nie geprüft!

    // Fährt fort auch wenn Validierung fehlschlug!
    read_contents(f);
    fclose(f);
}

// VERWUNDBAR: Überschrieben vor Verwendung
int calculate_vulnerable(int a, int b) {
    int result = a * b;  // TOTER SPEICHER!
    result = a + b;  // Überschreibt vorherigen Wert

    return result;  // Multiplikation war sinnlos
}

// VERWUNDBAR: Zuweisung im falschen Gültigkeitsbereich
void search_vulnerable(int* arr, int size, int target) {
    int found = 0;

    for (int i = 0; i < size; i++) {
        if (arr[i] == target) {
            int found = 1;  // TOTER SPEICHER: verdeckt äußeres 'found'!
            break;
        }
    }

    if (found) {  // Immer 0! Inneres 'found' verdeckte äußeres
        printf("Gefunden!\n");  // Wird nie ausgeführt
    }
}

// VERWUNDBAR: Sensible Daten nicht gelöscht
void process_secret_vulnerable(const char* secret) {
    char buffer[256];
    strncpy(buffer, secret, sizeof(buffer));

    // Verarbeite das Geheimnis...
    compute_hash(buffer);

    // Versuch zu löschen - aber Ergebnis ungenutzt!
    memset(buffer, 0, sizeof(buffer));  // Kann wegoptimiert werden!
}

// VERWUNDBAR: Rückgabewert berechnet aber ignoriert
int validate_vulnerable(Data* data) {
    int valid;

    valid = check_format(data);
    valid = check_content(data);  // Überschreibt Formatprüfung!
    // TOTER SPEICHER: Erstes 'valid' wurde überschrieben!

    return valid;  // Gibt nur Inhaltsprüfung zurück!
}
// VERWUNDBAR: C++ mit toten Speichern
class VulnerableProcessor {
public:
    int process(const std::vector<int>& data) {
        int total = 0;
        int count = 0;  // TOTER SPEICHER: nie verwendet!

        for (int val : data) {
            total += val;
            count++;  // Berechnet aber nie verwendet!
        }

        return total;  // Sollte count für Durchschnitt verwendet werden?
    }

    std::string format(int value) {
        std::string result = "Wert: ";
        result = std::to_string(value);  // Überschreibt vorheriges!
        // TOTER SPEICHER: "Wert: " war sinnlos

        return result;
    }

    bool validate(const Request& req) {
        bool isValid = true;

        isValid = checkFormat(req);
        // TOTER SPEICHER: checkFormat-Ergebnis sofort überschrieben!

        isValid = checkContent(req);
        // Sollten beide Prüfungen UND-verknüpft werden?

        return isValid;
    }
};

// VERWUNDBAR: RAII-Guard zugewiesen aber nicht verwendet
void process_vulnerable() {
    auto guard = std::lock_guard<std::mutex>(mutex);  // OK, RAII

    int result = compute();
    result = compute_again();  // TOTER SPEICHER: erstes compute ungenutzt!

    // Was sollte compute() tun?
}

// VERWUNDBAR: Optional-Wert ignoriert
void handle_vulnerable(std::optional<int> value) {
    int result = 0;

    if (value) {
        result = *value;  // TOTER SPEICHER wenn unten nicht verwendet!
    }

    // result wird nie verwendet!
    do_something_else();
}
// VERWUNDBAR: JavaScript tote Speicher
function processVulnerable(data) {
    let sum = 0;
    let count = 0;  // Toter Speicher - nie verwendet!

    for (let item of data) {
        sum += item;
        count++;  // Berechnet aber nie verwendet!
    }

    return sum;
}

// VERWUNDBAR: Sofort überschrieben
function calculateVulnerable(a, b) {
    let result = a * b;  // Toter Speicher!
    result = a + b;  // Überschreibt

    return result;
}

// VERWUNDBAR: Bedingungsergebnis ungenutzt
function validateVulnerable(input) {
    let valid = true;

    valid = checkFormat(input);
    valid = checkContent(input);  // Überschreibt Formatprüfung!

    return valid;  // Nur Inhaltsprüfung zurückgegeben!
}

// VERWUNDBAR: Schattenvariable
function searchVulnerable(arr, target) {
    let found = false;

    for (let item of arr) {
        if (item === target) {
            let found = true;  // Verdeckt äußere! Toter Speicher!
            break;
        }
    }

    return found;  // Immer false!
}

Lösungscode

// SICHER: Alle berechneten Werte verwenden
int process_data_safe(int* data, int size, int* out_max) {
    int sum = 0;
    int max = 0;

    for (int i = 0; i < size; i++) {
        sum += data[i];
        if (data[i] > max) {
            max = data[i];
        }
    }

    *out_max = max;  // Jetzt wird max verwendet!
    return sum;
}

// Oder ungenutzte Berechnung entfernen
int process_data_simple(int* data, int size) {
    int sum = 0;

    for (int i = 0; i < size; i++) {
        sum += data[i];
    }

    return sum;  // Nur berechnen was benötigt wird
}

// SICHER: Ergebnis der Sicherheitsprüfung verwenden
int authenticate_safe(User* user, const char* password) {
    int result = check_password(user, password);

    if (result == SUCCESS) {  // Ergebnis wird verwendet!
        user->authenticated = 1;
        return SUCCESS;
    }

    user->authenticated = 0;
    return FAILURE;
}

// SICHER: Fehlercode prüfen
void process_file_safe(const char* path) {
    FILE* f = fopen(path, "r");
    if (f == NULL) {
        log_error("Datei könnte nicht geöffnet werden");
        return;
    }

    int error = validate_file(f);
    if (error != 0) {  // Fehler wird geprüft!
        log_error("Validierung fehlgeschlagen");
        fclose(f);
        return;
    }

    read_contents(f);
    fclose(f);
}

// SICHER: Keine ungenutzten Werte berechnen
int calculate_safe(int a, int b) {
    int result = a + b;  // Nur berechnen was benötigt wird
    return result;
}

// SICHER: Korrekter Variablen-Gültigkeitsbereich
void search_safe(int* arr, int size, int target) {
    int found = 0;

    for (int i = 0; i < size; i++) {
        if (arr[i] == target) {
            found = 1;  // Gleicher Gültigkeitsbereich, keine Verdeckung
            break;
        }
    }

    if (found) {
        printf("Gefunden!\n");
    }
}

// SICHER: Optimierung von sicherheitssensitivem Löschen verhindern
void process_secret_safe(const char* secret) {
    volatile char buffer[256];
    strncpy((char*)buffer, secret, sizeof(buffer));

    compute_hash((char*)buffer);

    // explicit_bzero oder volatile verwenden um Optimierung zu verhindern
    explicit_bzero((char*)buffer, sizeof(buffer));
}

// Oder plattformspezifisches sicheres Nullen verwenden
#ifdef _WIN32
    SecureZeroMemory(buffer, sizeof(buffer));
#else
    explicit_bzero(buffer, sizeof(buffer));
#endif

// SICHER: Validierungsergebnisse kombinieren
int validate_safe(Data* data) {
    int format_valid = check_format(data);
    int content_valid = check_content(data);

    // Beide Ergebnisse verwendet!
    return format_valid && content_valid;
}
// SICHER: C++ mit ordnungsgemäßer Werteverwendung
class SafeProcessor {
public:
    std::pair<int, int> process(const std::vector<int>& data) {
        int total = 0;
        int count = 0;

        for (int val : data) {
            total += val;
            count++;
        }

        return {total, count};  // Beide Werte verwendet!
    }

    // Oder ungenutzte Variable entfernen
    int processTotalOnly(const std::vector<int>& data) {
        int total = 0;
        for (int val : data) {
            total += val;
        }
        return total;
    }

    std::string format(int value) {
        return "Wert: " + std::to_string(value);  // Kein toter Speicher
    }

    bool validate(const Request& req) {
        // Ergebnisse ordnungsgemäß kombinieren
        return checkFormat(req) && checkContent(req);
    }
};

// SICHER: Optional ordnungsgemäß verwenden
void handle_safe(std::optional<int> value) {
    if (value) {
        int result = *value;
        process(result);  // result wird verwendet!
    }
}

// Oder value_or direkt verwenden
void handle_safe_v2(std::optional<int> value) {
    process(value.value_or(0));  // Kein zwischenzeitlicher toter Speicher
}

// SICHER: [[maybe_unused]] für absichtlich Ungenutztes verwenden
void debug_function([[maybe_unused]] int debug_value) {
#ifdef DEBUG
    log(debug_value);  // Nur in Debug-Builds verwendet
#endif
}

// SICHER: std::ignore für absichtlich Ignoriertes verwenden
void ignore_properly() {
    auto [used, ignored] = get_pair();
    std::ignore = ignored;  // Explizit als absichtlich ungenutzt markieren
    process(used);
}
// SICHER: JavaScript mit ordnungsgemäßer Werteverwendung
function processSafe(data) {
    let sum = 0;

    for (let item of data) {
        sum += item;
    }

    return sum;  // Nur berechnen was benötigt wird
}

// Oder beide Werte zurückgeben
function processWithCount(data) {
    let sum = 0;
    let count = 0;

    for (let item of data) {
        sum += item;
        count++;
    }

    return { sum, count };  // Beide Werte zurückgegeben
}

// SICHER: Einzelne Zuweisung
function calculateSafe(a, b) {
    return a + b;
}

// SICHER: Validierungen kombinieren
function validateSafe(input) {
    return checkFormat(input) && checkContent(input);
}

// SICHER: Keine Verdeckung
function searchSafe(arr, target) {
    let found = false;

    for (let item of arr) {
        if (item === target) {
            found = true;  // Aktualisiert äußere Variable
            break;
        }
    }

    return found;
}

// Oder eingebaute Methoden verwenden
function searchModern(arr, target) {
    return arr.includes(target);
}

// ESLint-Regeln:
// - "no-unused-vars"
// - "no-useless-assignment"

Ausgenutzt in der Praxis

Sicherheitsprüfungs-Umgehungen

Authentifizierungs- und Autorisierungssysteme wurden umgangen, wenn Ergebnisse von Sicherheitsprüfungen berechnet aber nicht verwendet wurden.

Offenlegung sensibler Daten

Sensible Daten, die berechnet aber nicht ordnungsgemäß gelöscht wurden, wurden offengelegt, als Compiler-Optimierungen "ungenutzte" Löschoperationen entfernten.

Logikfehler in kritischen Systemen

Kritische Systeme haben Fehlfunktionen erlitten, wenn berechnete Werte, die das Verhalten hätten beeinflussen sollen, nie verwendet wurden.


Tools zum Testen

  • GCC/Clang — -Wunused-variable, -Wunused-but-set-variable.
  • Coverity — erkennt tote Speicher und ungenutzte Werte.
  • ESLint — no-unused-vars Regel.
  • PVS-Studio — statische Analyse für toten Code.

CVE-Beispiele

  • Verschiedene CVEs, bei denen Ergebnisse von Sicherheitsprüfungen nicht verwendet wurden.
  • Authentifizierungsumgehungen durch ungenutzte Validierungsergebnisse.
  • Informationsoffenlegung durch nicht ordnungsgemäß gelöschte Daten.

Referenzen

  1. MITRE. "CWE-563: Assignment to Variable without Use." https://cwe.mitre.org/data/definitions/563.html
  2. CERT C. "MSC13-C: Detect and remove unused values." https://wiki.sei.cmu.edu/confluence/display/c/