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

AuswirkungDetails
VertraulichkeitUmfang: Informationspreisgabe

Das Lesen nicht initialisierten Speichers legt vorherige Daten an dieser Stelle offen.
IntegritätUmfang: Logikfehler

Unbestimmte Werte verursachen unvorhersehbares Programmverhalten.
SicherheitUmfang: 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


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

  1. MITRE. "CWE-457: Use of Uninitialized Variable." https://cwe.mitre.org/data/definitions/457.html

  2. CERT C. "EXP33-C: Do not read uninitialized memory." https://wiki.sei.cmu.edu/confluence/display/c/