Elternklasse mit virtuellem Destruktor und Kindklasse ohne virtuellen Destruktor

Beschreibung

Elternklasse mit virtuellem Destruktor und Kindklasse ohne virtuellen Destruktor tritt auf, wenn eine Elternklasse ordnungsgemäß einen virtuellen Destruktor deklariert, aber eine abgeleitete (Kind-)Klasse diesen nicht mit einem eigenen virtuellen Destruktor überschreibt. In C++ und ähnlichen Sprachen wird beim Löschen von Objekten über einen Pointer auf die Basisklasse nur der Destruktor der Basisklasse aufgerufen, wenn die Destruktoren nicht in der gesamten Hierarchie ordnungsgemäß virtuell sind. Obwohl der virtuelle Destruktor der Elternklasse hilft, kann die Kindklasse Ressourcen haben, die Bereinigung erfordern, und inkonsistente Destruktor-Deklarationen können zu Verwirrung und Wartungsproblemen führen.

Risiko

Dieses Problem kann zu Speicherlecks (CWE-401) und Ressourcenlecks führen, wenn Destruktoren abgeleiteter Klassen nicht ordnungsgemäß aufgerufen werden. Obwohl dies hauptsächlich ein Zuverlässigkeitsproblem ist, wird es zu einem Sicherheitsproblem, wenn: (1) Angreifer den problematischen Codepfad wiederholt auslösen können, um Systemressourcen zu erschöpfen, (2) sensible Daten in Kindklassen-Membern nicht ordnungsgemäß gelöscht werden, oder (3) das Ressourcenleck Denial-of-Service verursacht.

Lösung

Stellen Sie sicher, dass alle Klassen in einer Vererbungshierarchie, die möglicherweise polymorph gelöscht werden, virtuelle Destruktoren haben. In C++11 und später verwenden Sie das override-Schlüsselwort, um die Beziehung explizit zu machen. Erwägen Sie die Verwendung des final-Schlüsselworts für Klassen, die nicht weiter abgeleitet werden sollen. Befolgen Sie die Fünfer-/Null-Regel in C++ für korrektes Ressourcenmanagement. Verwenden Sie Smart Pointer (unique_ptr, shared_ptr) anstelle von rohen Zeigern, um ordnungsgemäße Bereinigung sicherzustellen.

Häufige Auswirkungen

AuswirkungDetails
VerfügbarkeitBereich: Verfügbarkeit

DoS: Ressourcenverbrauch - Speicherlecks durch unsachgemäße Destruktion können Systemressourcen erschöpfen.
VertraulichkeitBereich: Vertraulichkeit

Speicheroffenlegung - Sensible Daten in Kindklassen-Membern werden möglicherweise nicht ordnungsgemäß gelöscht, wenn der Destruktor nicht aufgerufen wird.
IntegritätBereich: Integrität

Qualitätsverschlechterung - Ressourcenlecks reduzieren die Zuverlässigkeit und können zu unvorhersehbarem Verhalten führen.

Beispielcode

Anfälliger Code

// ANFÄLLIG: Kindklasse ohne virtuellen Destruktor
class Base {
public:
    virtual ~Base() {
        std::cout << "Base Destruktor aufgerufen" << std::endl;
    }

    virtual void doSomething() = 0;
};

class VulnerableChild : public Base {
private:
    char* sensitiveData;
    size_t dataSize;
    int* largeArray;

public:
    VulnerableChild(const char* data) {
        dataSize = strlen(data) + 1;
        sensitiveData = new char[dataSize];
        strcpy(sensitiveData, data);
        largeArray = new int[10000];
    }

    // ANFÄLLIG: Kein virtueller Destruktor deklariert
    ~VulnerableChild() {
        // Dies wird möglicherweise nicht aufgerufen beim Löschen über Base-Pointer!
        std::cout << "VulnerableChild Destruktor aufgerufen" << std::endl;

        memset(sensitiveData, 0, dataSize);
        delete[] sensitiveData;
        delete[] largeArray;
    }

    void doSomething() override {
        std::cout << "Verarbeite: " << sensitiveData << std::endl;
    }
};

// Verwendung, die das Problem auslöst
void vulnerableUsage() {
    Base* obj = new VulnerableChild("geheimes Passwort");
    obj->doSomething();

    delete obj;  // Nur Base-Destruktor aufgerufen!
    // VulnerableChild-Destruktor NICHT aufgerufen
    // Speicherleck: sensitiveData und largeArray nicht freigegeben
    // Sicherheitsproblem: Sensible Daten bleiben im Speicher
}

Korrigierter Code

// KORRIGIERT: Richtiger virtueller Destruktor in Kindklasse
#include <memory>
#include <cstring>

class Base {
public:
    virtual ~Base() {
        std::cout << "Base Destruktor aufgerufen" << std::endl;
    }

    virtual void doSomething() = 0;
};

class FixedChild : public Base {
private:
    char* sensitiveData;
    size_t dataSize;
    int* largeArray;

public:
    FixedChild(const char* data) {
        dataSize = strlen(data) + 1;
        sensitiveData = new char[dataSize];
        strcpy(sensitiveData, data);
        largeArray = new int[10000];
    }

    // KORRIGIERT: Virtueller Destruktor mit override-Schlüsselwort
    ~FixedChild() override {
        std::cout << "FixedChild Destruktor aufgerufen" << std::endl;

        // Sichere Speicherlöschung
        memset(sensitiveData, 0, dataSize);
        delete[] sensitiveData;
        delete[] largeArray;
    }

    void doSomething() override {
        std::cout << "Verarbeite: " << sensitiveData << std::endl;
    }
};

// BESSER: Smart Pointer verwenden (Null-Regel)
class BetterChild : public Base {
private:
    std::unique_ptr<char[]> sensitiveData;
    size_t dataSize;
    std::unique_ptr<int[]> largeArray;

public:
    BetterChild(const char* data) {
        dataSize = strlen(data) + 1;
        sensitiveData = std::make_unique<char[]>(dataSize);
        strcpy(sensitiveData.get(), data);
        largeArray = std::make_unique<int[]>(10000);
    }

    // KORRIGIERT: Virtueller Destruktor
    ~BetterChild() override {
        if (sensitiveData) {
            memset(sensitiveData.get(), 0, dataSize);
        }
    }

    void doSomething() override {
        std::cout << "Verarbeite: " << sensitiveData.get() << std::endl;
    }
};

// Korrekte Verwendung
void fixedUsage() {
    // Option 1: Roher Pointer mit explizitem delete
    Base* obj1 = new FixedChild("geheimes Passwort");
    obj1->doSomething();
    delete obj1;  // Beide Destruktoren korrekt aufgerufen

    // Option 2: Smart Pointer (bevorzugt)
    std::unique_ptr<Base> obj2 = std::make_unique<BetterChild>("geheim");
    obj2->doSomething();
    // Automatisch zerstört wenn außerhalb des Gültigkeitsbereichs
}

CVE-Beispiele

Diese CWE ist hauptsächlich ein Code-Qualitäts- und Zuverlässigkeitsproblem. Obwohl keine spezifischen CVEs diesem Muster direkt zugeschrieben werden, hat unsachgemäße Destruktor-Behandlung zu Speicherleck-Schwachstellen beigetragen, die für Denial-of-Service-Angriffe ausgenutzt wurden.


Verwandte CWEs

  • CWE-1076: Insufficient Adherence to Expected Conventions (Eltern)
  • CWE-401: Missing Release of Memory after Effective Lifetime (kann führen zu)
  • CWE-1006: Bad Coding Practices (Kategoriemitglied)

Referenzen

  1. MITRE Corporation. "CWE-1045: Parent Class with a Virtual Destructor and a Child Class without a Virtual Destructor." https://cwe.mitre.org/data/definitions/1045.html

  2. Stroustrup, Bjarne. "The C++ Programming Language."

  3. C++ Core Guidelines. "C.35: A base class destructor should be either public and virtual, or protected and non-virtual."