Verwendung einer nicht initialisierten Variablen
Beschreibung
Verwendung einer nicht initialisierten Variablen tritt auf, wenn Code den Wert einer Variablen liest, bevor dieser ein sinnvoller Wert zugewiesen wurde. Während diese Schwäche eng mit CWE-456 (Fehlende Initialisierung) verwandt ist, behandelt sie spezifisch das Lesen/Verwenden solcher Variablen. Die nicht initialisierte Variable enthält unbestimmte Daten—welche Bits auch immer zufällig an dieser Speicherstelle waren. Dies führt zu unvorhersehbarem Programmverhalten, das zwischen Ausführungen, Compilern, Optimierungsstufen und Systemen variieren kann.
Risiko
Das Lesen nicht initialisierter Variablen führt zu schwerwiegenden Sicherheits- und Zuverlässigkeitsrisiken. Der unbestimmte Wert kann falsche Programmlogik verursachen, die zu Sicherheitsumgehungen führt, wenn der Müllwert bedingte Prüfungen erfüllt. Speicherpreisgabe tritt auf, wenn nicht initialisierte Puffer übertragen oder protokolliert werden. Die Verwendung nicht initialisierter Pointer verursacht Abstürze oder ausnutzbare Speicherkorruption. In kryptografischen Kontexten kompromittieren nicht initialisierte Werte Zufälligkeitsannahmen. Die nicht-deterministische Natur macht Schwachstellen schwer zu erkennen und zu reproduzieren.
Lösung
Aktivieren Sie Compiler-Warnungen und behandeln Sie sie als Fehler (-Wuninitialized -Werror). Verwenden Sie statische Analysewerkzeuge, um Pfade zu erkennen, in denen Variablen vor der Initialisierung verwendet werden. Setzen Sie Speicher-Sanitizer während des Testens ein (MSan, Valgrind). Initialisieren Sie Variablen bei der Deklaration. Verwenden Sie moderne Sprachfeatures, die Initialisierung erzwingen. Gestalten Sie Code so, dass bedingte Initialisierung vermieden wird, bei der einige Pfade Variablen undefiniert lassen. Implementieren Sie defensive Programmierung mit expliziten Standardwerten.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Umfang: Informationspreisgabe Das Lesen nicht initialisierten Speichers legt vorherige Daten an dieser Stelle offen. |
| Integrität | Umfang: Logikfehler Unbestimmte Werte verursachen unvorhersehbares Programmverhalten. |
| Sicherheit | Umfang: Umgehung Müllwerte können versehentlich Sicherheitsprüfungen erfüllen. |
Beispielcode
Anfälliger Code
// ANFÄLLIG: Verwendung nicht initialisierter Variable in Bedingung
int authenticate_vulnerable(const char* username, const char* password) {
int authenticated; // Nicht initialisiert!
if (verify_credentials(username, password)) {
authenticated = 1;
}
// Wenn Verifizierung fehlschlägt, ist authenticated Müll!
if (authenticated) { // Kann durch Müll wahr sein!
grant_access();
return 1;
}
return 0;
}
// ANFÄLLIG: Nicht initialisierter Zähler
void process_items_vulnerable(Item* items, int count) {
int processed; // Nicht initialisiert!
int i;
for (i = 0; i < count; i++) {
if (items[i].valid) {
handle_item(&items[i]);
processed++; // Inkrementiert Müll!
}
}
printf("Verarbeitet: %d Elemente\n", processed); // Falsche Anzahl!
}
// ANFÄLLIG: Nicht initialisierter Puffer verwendet
void send_response_vulnerable(int socket, int status) {
char buffer[1024]; // Nicht initialisiert!
int len;
if (status == 200) {
len = sprintf(buffer, "HTTP/1.1 200 OK\r\n");
}
// Wenn status != 200, enthält buffer Müll!
send(socket, buffer, 1024, 0); // Informationsleck!
}
// ANFÄLLIG: Dereferenzierung nicht initialisierter Pointer
void lookup_vulnerable(int key) {
struct Record* record; // Nicht initialisierter Pointer!
if (key > 0) {
record = find_record(key);
}
// Wenn key <= 0, ist record Müll-Pointer!
printf("Gefunden: %s\n", record->name); // Absturz oder beliebiges Lesen!
}
// ANFÄLLIG: Schleifenvariablen-Bereichsproblem
void sum_array_vulnerable(int* arr, int size) {
int sum; // Nicht initialisiert!
for (int i = 0; i < size; i++) {
sum += arr[i]; // Addiert zu Müll!
}
printf("Summe: %d\n", sum);
}
// ANFÄLLIG: Fehlerpfad lässt Variable nicht initialisiert
int get_config_value_vulnerable(const char* key) {
int value; // Nicht initialisiert!
FILE* file = fopen("config.txt", "r");
if (file == NULL) {
return value; // Gibt Müll zurück!
}
if (fscanf(file, "%d", &value) != 1) {
fclose(file);
return value; // Immer noch Müll wenn Parsing fehlschlägt!
}
fclose(file);
return value;
}
// ANFÄLLIG: C++ mit Verwendung nicht initialisierter Mitglieder
class VulnerableSession {
bool authenticated; // Nicht initialisiert!
int userId; // Nicht initialisiert!
time_t lastAccess; // Nicht initialisiert!
public:
VulnerableSession() {
// Konstruktor initialisiert Mitglieder nicht!
}
bool isAuthenticated() {
return authenticated; // Liest Müll!
}
int getUserId() {
return userId; // Liest Müll!
}
};
// ANFÄLLIG: Virtuelle Methode auf nicht initialisiertem Objekt
class Base {
public:
virtual void process() = 0;
};
void dangerous_polymorphism() {
Base* obj; // Nicht initialisierter Pointer!
// Irgendein Code-Pfad, der obj nicht immer setzt
if (some_condition()) {
obj = new DerivedClass();
}
obj->process(); // Vtable-Korruption/Absturz!
}
// ANFÄLLIG: std::optional Missbrauch (Vor-Prüfung)
#include <optional>
void misuse_optional_vulnerable() {
std::optional<int> value; // Leer
// Vergessen has_value() zu prüfen!
int x = *value; // Undefiniertes Verhalten!
// Oder Zugriff nach move
auto other = std::move(value);
if (value.has_value()) { // Kann immer noch wahr sein!
int y = *value; // Müll!
}
}
// ANFÄLLIG: Exception lässt Objekt teilweise initialisiert
class PartialInit {
std::string name;
Resource* resource; // Kann nicht initialisiert sein!
public:
PartialInit(const std::string& n) : name(n) {
resource = acquireResource(); // Kann werfen!
}
~PartialInit() {
delete resource; // Löscht Müll wenn Ktor warf!
}
};
// Java verhindert generell nicht initialisierte lokale Variablen,
// aber Probleme existieren dennoch
// ANFÄLLIG: Bedingte Initialisierung in Java
public class VulnerableJava {
// Compiler fängt dies ab, aber ähnliche Logikprobleme existieren
public int processVulnerable(boolean flag) {
int result; // Muss vor Verwendung initialisiert werden
if (flag) {
result = computeValue();
}
// Compiler-Fehler wenn wir versuchen result hier zurückzugeben
// Aber Logik könnte fehlerhaft sein
// Simulation der Schwachstelle mit Standard
int value = 0; // Standard könnte falsch sein
if (flag) {
value = computeValue();
}
// Wenn !flag, ist Rückgabe von 0 vielleicht nicht beabsichtigt
return value;
}
// ANFÄLLIG: Null-Mitglieds-Zugriff
private String data; // Standardmäßig null
public int getDataLength() {
return data.length(); // NullPointerException!
}
// ANFÄLLIG: Array-Element nicht initialisiert
public void processArray() {
Object[] items = new Object[10];
// Array gefüllt mit null!
for (Object item : items) {
item.toString(); // NPE bei null-Elementen!
}
}
}
Korrigierter Code
// SICHER: Initialisieren und konsistent verwenden
int authenticate_safe(const char* username, const char* password) {
int authenticated = 0; // Expliziter Standard: nicht authentifiziert
if (verify_credentials(username, password)) {
authenticated = 1;
}
if (authenticated) {
grant_access();
return 1;
}
return 0;
}
// SICHER: Zähler initialisieren
void process_items_safe(Item* items, int count) {
int processed = 0; // Auf null initialisiert!
for (int i = 0; i < count; i++) {
if (items[i].valid) {
handle_item(&items[i]);
processed++;
}
}
printf("Verarbeitet: %d Elemente\n", processed);
}
// SICHER: Puffer initialisieren
void send_response_safe(int socket, int status) {
char buffer[1024] = {0}; // Null-initialisiert!
int len = 0;
if (status == 200) {
len = sprintf(buffer, "HTTP/1.1 200 OK\r\n");
} else if (status == 404) {
len = sprintf(buffer, "HTTP/1.1 404 Not Found\r\n");
} else {
len = sprintf(buffer, "HTTP/1.1 500 Error\r\n");
}
send(socket, buffer, len, 0); // Sendet nur tatsächlichen Inhalt
}
// SICHER: Pointer initialisieren und prüfen
void lookup_safe(int key) {
struct Record* record = NULL; // Initialisiert!
if (key > 0) {
record = find_record(key);
}
if (record != NULL) {
printf("Gefunden: %s\n", record->name);
} else {
printf("Datensatz nicht gefunden\n");
}
}
// SICHER: Summe initialisieren
void sum_array_safe(int* arr, int size) {
int sum = 0; // Initialisiert!
for (int i = 0; i < size; i++) {
sum += arr[i];
}
printf("Summe: %d\n", sum);
}
// SICHER: Alle Fehlerpfade behandeln
int get_config_value_safe(const char* key, int default_value) {
int value = default_value; // Sicherer Standard!
FILE* file = fopen("config.txt", "r");
if (file == NULL) {
return value; // Gibt Standard zurück
}
if (fscanf(file, "%d", &value) != 1) {
value = default_value; // Bei Parse-Fehler auf Standard zurücksetzen
}
fclose(file);
return value;
}
// SICHER: Output-Parameter mit Rückgabestatus verwenden
int get_config_value_robust(const char* key, int* out_value) {
FILE* file = fopen("config.txt", "r");
if (file == NULL) {
return -1; // Fehlerindikator
}
if (fscanf(file, "%d", out_value) != 1) {
fclose(file);
return -1;
}
fclose(file);
return 0; // Erfolg
}
// SICHER: C++ mit ordnungsgemäßer Initialisierung
class SafeSession {
bool authenticated = false; // In-Class-Initialisierer
int userId = -1;
time_t lastAccess = 0;
public:
SafeSession() = default; // Verwendet In-Class-Initialisierer
// Oder expliziter Konstruktor
SafeSession(int id)
: authenticated(false)
, userId(id)
, lastAccess(std::time(nullptr)) {}
bool isAuthenticated() const {
return authenticated; // Immer gültig
}
int getUserId() const {
return userId;
}
};
// SICHER: Pointer initialisieren, vor Verwendung prüfen
void safe_polymorphism() {
std::unique_ptr<Base> obj; // Standardmäßig nullptr
if (some_condition()) {
obj = std::make_unique<DerivedClass>();
}
if (obj) { // Vor Verwendung prüfen
obj->process();
} else {
handle_no_object();
}
}
// SICHER: Ordnungsgemäße std::optional Verwendung
#include <optional>
void use_optional_safe() {
std::optional<int> value;
// Immer vor Zugriff prüfen
if (value.has_value()) {
int x = *value;
}
// Oder value_or für Standard verwenden
int y = value.value_or(0);
// Vor Verwendung zuweisen
value = compute_value();
if (value) {
process(*value);
}
}
// SICHER: Exception-sichere Initialisierung
class SafeInit {
std::string name;
std::unique_ptr<Resource> resource; // Smart-Pointer!
public:
SafeInit(const std::string& n)
: name(n)
, resource(nullptr) // Explizite Init
{
resource = std::make_unique<Resource>();
// Wenn dies wirft, behandelt unique_ptr die Bereinigung
}
// Destruktor behandelt automatisch Ressourcen-Bereinigung
};
// SICHER: std::variant für typsichere Unions verwenden
#include <variant>
std::variant<int, std::string, std::monostate> getValue() {
if (hasInt) return 42;
if (hasString) return "hello";
return std::monostate{}; // Explizit "kein Wert"
}
void processVariant() {
auto value = getValue();
std::visit([](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, int>) {
processInt(arg);
} else if constexpr (std::is_same_v<T, std::string>) {
processString(arg);
} else {
// monostate - kein Wert
}
}, value);
}
// SICHER: Java mit ordnungsgemäßer Initialisierung
public class SafeJava {
// SICHER: Explizite Initialisierung in allen Pfaden
public int processSafe(boolean flag) {
int result;
if (flag) {
result = computeValue();
} else {
result = getDefaultValue(); // Immer initialisiert
}
return result;
}
// SICHER: Mitglied initialisieren oder auf null prüfen
private String data = ""; // Nicht-null Standard
public int getDataLength() {
return data.length(); // Sicher
}
// Oder Optional verwenden
private Optional<String> optionalData = Optional.empty();
public int getOptionalDataLength() {
return optionalData.map(String::length).orElse(0);
}
// SICHER: Array-Elemente initialisieren
public void processArraySafe() {
Object[] items = new Object[10];
// Alle Elemente initialisieren
for (int i = 0; i < items.length; i++) {
items[i] = createDefault();
}
// Oder auf null prüfen
for (Object item : items) {
if (item != null) {
item.toString();
}
}
}
// SICHER: Objects.requireNonNull verwenden
public void setData(String newData) {
this.data = Objects.requireNonNull(newData, "data darf nicht null sein");
}
// SICHER: Builder-Pattern stellt vollständige Initialisierung sicher
public static class Config {
private final String host;
private final int port;
private final boolean secure;
private Config(Builder builder) {
this.host = Objects.requireNonNull(builder.host);
this.port = builder.port;
this.secure = builder.secure;
}
public static class Builder {
private String host;
private int port = 80; // Standard
private boolean secure = false; // Standard
public Builder host(String host) {
this.host = host;
return this;
}
public Config build() {
if (host == null) {
throw new IllegalStateException("host ist erforderlich");
}
return new Config(this);
}
}
}
}
Ausgenutzt in der Praxis
Linux-Kernel nicht initialisierter Speicher
Mehrere Linux-Kernel-Schwachstellen beinhalteten das Lesen nicht initialisierten Stack- oder Heap-Speichers, was zu Informationspreisgabe vom Kernel-Space zum User-Space führte.
OpenSSL Speicherpreisgabe
Nicht initialisierte Speicherprobleme in kryptografischen Bibliotheken haben sensibles kryptografisches Material und private Daten offengelegt.
Browser-Engine-Schwachstellen
Web-Browser-Engines hatten Schwachstellen mit nicht initialisierten Variablen, die zu Informationslecks und Remote-Code-Ausführung führten.
Werkzeuge zum Testen/Ausnutzen
-
Valgrind Memcheck — erkennt Verwendung nicht initialisierter Werte.
-
LLVM MemorySanitizer — Laufzeiterkennung nicht initialisierter Lesezugriffe.
-
Coverity — statische Analyse für nicht initialisierte Variablen.
-
Clang Static Analyzer — erkennt Pfade mit nicht initialisierten Variablen.
CVE-Beispiele
-
CVE-2021-3156 — sudo Heap-basierter Buffer Overflow mit nicht initialisiertem Speicher.
-
CVE-2020-0796 — SMBGhost mit nicht initialisiertem Speicher in Kompression.
-
CVE-2019-7304 — snapd nicht initialisierte Variable Privilegieneskalation.
Referenzen
-
MITRE. "CWE-457: Use of Uninitialized Variable." https://cwe.mitre.org/data/definitions/457.html
-
CERT C. "EXP33-C: Do not read uninitialized memory." https://wiki.sei.cmu.edu/confluence/display/c/