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
| Auswirkung | Details |
|---|---|
| Qualität | Bereich: Code-Klarheit Tote Speicher machen Code schwerer verständlich und wartbar. |
| Sicherheit | Bereich: Fehlende Prüfungen Sicherheitswerte, die berechnet aber nicht verwendet werden, können auf umgangene Prüfungen hinweisen. |
| Leistung | Bereich: 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
- MITRE. "CWE-563: Assignment to Variable without Use." https://cwe.mitre.org/data/definitions/563.html
- CERT C. "MSC13-C: Detect and remove unused values." https://wiki.sei.cmu.edu/confluence/display/c/